云E办后端 后端接口地址

项目找maven

找到每个项目文件夹里的pom.xml右键选择 +add as maven project
邮箱密码:
zvpetxrypnaogiabbb

项目搭建

第一步:

创建一个springboot项目,将原有的src及其他目录删除,重新构建一个模块(maven modules, [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3IpJIy8-1661933983136)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220517120330748.png)]).

父模块pom.xml增加一个packing。删除原有的构建和依赖。

父模块pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wyc</groupId>
    <artifactId>my-endyeb</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>my-endyeb</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
</project>

第二步:

子模块选择quelistart,删除原有的bulid,和依赖,查看子工程有没有父工程坐标,没有就加入父工程项目坐标,让父子工程关联起来,子模块导入相关依赖,web,mysql,mybatis,lombok.将properties里面maven改为1.8,

子模块y-servicepom.xml
父工程坐标

    <parent>
        <artifactId>yeb</artifactId>
        <groupId>com.wyc</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

   <dependencies>
        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--        mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--        mybatisplus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
        <!--        mybatisplus代码生成器依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--        freemarker依赖,模板引擎-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <!-- swagger2 依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!-- Swagger第三方ui依赖 因为swagger2原生的ui有点丑,所以就用了第三方的一个ui-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

        <!-- swaggerUI界面切换第一步:添加 原生 swaggerUI 依赖 -->
        <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
        <!--        <dependency>-->
        <!--            <groupId>io.springfox</groupId>-->
        <!--            <artifactId>springfox-swagger-ui</artifactId>-->
        <!--            <version>2.7.0</version>-->
        <!--        </dependency>-->

        <!--Spring Security 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--JWT 依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

        <!-- google kaptcha依赖 -->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

        <!-- spring data redis 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- commons-pool2 对象池依赖 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--easy poi依赖-->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.1.3</version>
        </dependency>

        <!--rabbitmq 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!--websocket 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!-- 解决依赖导入不成功的问题:http://www.qishunwang.net/knowledge_show_129043.aspx -->
        <!--FastDFS依赖,用于头像的更新-->
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.29-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.5.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>5.6.3</version>
        </dependency>
        <!--        该依赖是针对日志的相关包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

    </dependencies>

第三步

将java和test文件里面的原有文件删掉,创建项目相关的包。配置启动类。在main下创建resource包,在resource报下创建config包,在config里面创建application.yaml配置文件。将所有可以改变的值进行相应的改变。

application.yaml配置文件内容
server:
  #端口
  port: 8081       //改变



spring:
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER
  thymeleaf:
    cache: false  #清除缓存
    prefix: classpath:/templates/
  #  mvc:
  #    static-path-pattern: /admin/**
  txcos:
    secretId: AKIDiEVYP1RZ1GDwTiv5tSnt2yrXx97rrkvs
    secretKey: wf9OPAnDUXieNq4xc5LTM3Xq05yd9QUN
    bucket: ap-shanghai
    bucketName: school-1251531734
    path: https://school-1251531734.cos.ap-shanghai.myqcloud.com
    prefix: exam
  servlet:
    multipart:
      max-file-size: 5MB # 单个文件的最大值
      max-request-size: 5MB # 上传文件总的最大值

      #数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root               //改变
    password: 123456             //改变
    url: jdbc:mysql://localhost:3306/yeb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai                   //改变
    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
      #连接超时时间,默认3000(30秒)
      connection-timeout: 30000
      #测试是否可用的查询语句
      connection-test-query: SELECT 1
  main:
    allow-circular-references: true
#  redis:
#    #数据库索引
#    database: 0
#    host: 127.0.0.1              //改变
#    port: 6379
#    password:                        //改变
#    jedis:
#      pool:
#        #最大连接数
#        max-active: 8
#        #最大阻塞等待时间(负数表示没限制)
#        max-wait: -1
#        #最大空闲
#        max-idle: 8
#        #最小空闲
#        min-idle: 0
#        #连接超时时间
#    timeout: 10000
#  rabbitmq:
#    addresses: 127.0.0.1                 //改变
#    port: 5672       
#    username: guest                     //改变
#    password: guest                     //改变
#    virtual-host: /    

# JWT配置

## JWT配置    //注解要是拿不到,就主要看配置文件命名是否一致
jwt:
  # JWT存储的请求头
  tokenHeader: Authorization        //改变
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer                //改变


#Mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/*Mapper.xml
  configuration:
    #自动驼峰命名
    map-underscore-to-camel-case: false

    ## mybatis sql  打印(方法接口所在的包,不是Mapper.xml所在的包)
    logging:
      level:
        com.wyc.mbg.mapper: debug          //改变
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

    spring:
      main:
        web-application-type: none

到此,项目构建基本完成。

项目启动

将test文件和java文件下的app文件删掉,编写主启动类。

编写主启动类

package com.wyc;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * @author superBrother
 * @create 2022/7/2
 * @Description
 */
@SpringBootApplication
@MapperScan("com.wyc.mbg.mapper")
@EnableScheduling
 swaggerUI界面切换第三步:在启动类加上 @EnableSwagger2 注解(前面两步修改后,能成功访问,这步就不需要做了)
@EnableSwagger2
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

使用代码生成器完成重复性的单表操作:可以直接在test目录下创建CodeGenerator类,进行代码自动生成.

建类CodeGenerator

CodeGenerator代码:将注释里面有改变的进行改变为自己项目相对应的内容。
package com.wyc;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
 * @author superBrother   //改变
 * @create 2021/11/20
 */
public class CodeGenerator {
    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    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");

        //需要修改模块的路径  yeb-server
        gc.setOutputDir(projectPath + "/yeb-server/src/main/java");  //改变
        //作者
        gc.setAuthor("superBrother");
        //打开输出目录
        gc.setOpen(false);
        //xml开启 BaseResultMap
        gc.setBaseResultMap(true);
        //xml 开启BaseColumnList
        gc.setBaseColumnList(true);
        // 实体属性 Swagger2 注解
        gc.setSwagger2(true);
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/yeb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia" +
                "/Shanghai");           //改变
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");      //改变
        dsc.setPassword("123456");    //改变
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.wyc.mbg")        //改变
                .setEntity("pojo")
                .setMapper("mapper")
                .setService("service")
                .setServiceImpl("service.impl")
                .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<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                //需要修改模块的路径  yeb-server       //改变
                return projectPath + "/yeb-server/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);
        //lombok模型
        strategy.setEntityLombokModel(true);
        //生成 @RestController 控制器
        strategy.setRestControllerStyle(true);
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        //生成表时,去掉表前缀
        strategy.setTablePrefix("t_");  //没有注释掉     改变
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
<!--        swagger第三方ui依赖-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>

登录

jwt token 工具类编写

第一步:先导入security依赖和jwt依赖

        <!--Spring Security 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--JWT 依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

第二步:在application.yml进行jwt的配置

## JWT配置
jwt:
  # JWT存储的请求头
  tokenHeader: Authorization
  # JWT 加解密使用的密钥
  secret: yeb-secret
  # JWT的超期限时间(60*60*24)
  expiration: 604800
  # JWT 负载中拿到开头
  tokenHead: Bearer

第三步:创建config包,然后创建security包,创建JwtTokenUtil实例类,编写jwtToken。

package com.xxxx.server.config.security.component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * Created by HMF on 2021/07/23 11:48
 */
@Component  // @Component注解使用在任意层中,交给IOC实例化
public class JwtTokenUtil {

    private static final String CLAIM_KEY_USERNAME = "sub"; // 用户名的key
    private static final String CLAIM_KEY_CREATED = "created"; // Jwt创建时间的key

    // 从配置文件application.yml中拿到JWT的配置内容,使用@Value注解绑定
    @Value("${jwt.secret}")
    private String secret; // JWT加解密使用的密钥

    @Value("${jwt.expiration}")
    private Long expiration; // JWT的超期限时间(60*60*24)


    /**
     * 【根据用户信息生成token:】
     * 用户信息根据Spring Security的UserDetails来获取 // 实际可以被调用的方法
     *
     * @param userDetails
     * @return
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        // 自定义申明的键值对
        // 主体,用户 {"sub": "Rose"},如 .setSubject("Rose")
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        // 创建日期 {"iat": "当前的时间"},如 .setIssuedAt(new Date())
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 【从Token中获取登录用户名】 / 实际可以被调用的方法
     *
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token) {
        // 先去从token中获取荷载,因为用户名放在了荷载里面
        String username;

        try {
            Claims claims = getClaimsFromToken(token); // 获取荷载,从token中,即解析token,通过 claims.getSubject() 获取用户名
            username = claims.getSubject();
        } catch (Exception e) {
            // 有异常时,用户名重新赋值 null
            username = null;
            e.printStackTrace();
        }

        return username;
    }

    /**
     * 【验证token是否有效:(需要判断两个方面)】 / 实际可以被调用的方法
     * 1.判断token是否过期
     * 2.判断token荷载里面的用户名和UserDetails里的用户名是否一致
     *
     * @param token
     * @param userDetails
     * @return
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 【判断token是否可以被刷新】  / 实际可以被调用的方法
     *
     * @param token
     * @return
     */
    public boolean canRefresh(String token) {
        // 如果过期就可以被刷新
        return !isTokenExpired(token);
    }

    /**
     * 【刷新token】 / 实际可以被调用的方法
     *
     * @param token
     * @return
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        // 重新设置token的创建时间为当前时间,从而重新生成token,达到刷新token的目的
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

     以下都是一些内部的私有方法 
    /**
     * 判断token是否失效过期
     *
     * @param token
     * @return
     */
    private boolean isTokenExpired(String token) {
        Date expiredData = getExpiredDateFromToken(token);
        // 直接使用 before() 方法,判断失效时间是否在当前时间的前面
        return expiredData.before(new Date());
    }

    /**
     * 从token中获取过期时间
     *
     * @param token
     * @return
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);  // 从token中获取荷载
        return claims.getExpiration(); // 从荷载中获取过期时间,然后return
    }

    /**
     * 从token中获取荷载,即 解析传入的 token
     *
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;

        try {
            // 解析 token,获取负载中声明的对象
            claims = Jwts.parser()
                    // 解析的秘钥 secret 即 yeb-secret
                    .setSigningKey(secret)
                    // 需要被解析的token
                    .parseClaimsJws(token)
                    // 解析之后的主体
                    .getBody();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }

    /**
     * 方法的重载:
     * 根据荷载构建生成JWT TOKEN
     *
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) { // 方法的重载
        return Jwts.builder()
                // 自定义申明,键值对的形式
                .setClaims(claims)
                // 设置 token 的失效时间是 24h
                .setExpiration(generateExpirationDate())
                // 签名算法 HS512 和 secret 盐 yeb-secret(在配置文件中配置了内容)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 生成token失效时间
     *
     * @return
     */
    private Date generateExpirationDate() {
        // 设置为 当前时间 + 设置的过期限时间 (24小时)
        return new Date(System.currentTimeMillis() + expiration);
    }

}

第四步:编写公共返回对象,在wyc/common包下建立RespBean,进行编写RespBean,还可以自定义其他类的编写。

package com.xxxx.server.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 公共返回对象
 * Created by HMF on 2021/07/23 15:07
 */

@Data  // getter和setter方法
@NoArgsConstructor   // 无参构造
@AllArgsConstructor  // 全参构造
public class RespBean {   /  公共返回对象   //

    private long 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);
    }
}

pageR

package com.wyc.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author superBrother
 * @create 2022/7/2
 * @Description
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageR {

    /**
     * 每页显示大小
     */
    private long  size;

    /**
     * 当前页码
     */
    private  long current;

    /**
     * 最大页数
     */
    private  long maxCurrent;

    /**
     * 数据总条数
     */
    private  long total;
}

R

package com.wyc.common;

import java.util.List;

/**
 *
 * @author superBrother
 * @date 2020/7/2
 * @ Description:
 */
public class R {
    //状态,成功:1,失败:0
    private int status;
    //消息
    private String msg;
    //数据
    private Object data;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public R() {
    }

    public R(int status, String msg) {
        this.status = status;
        this.msg = msg;
        this.data = null;
    }

    public R(int status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public R(ResultStatus resultStatus) {
        this.status = resultStatus.getStatus();
        this.msg = resultStatus.getMsg();
        this.data = null;
    }

    public R(ResultStatus resultStatus, Object data) {
        this.status = resultStatus.getStatus();
        this.msg = resultStatus.getMsg();
        this.data = data;
    }
    public R(ResultStatus resultStatus, String str) {
        this.status = resultStatus.getStatus();
        this.msg = resultStatus.getMsg() + ":[" + str +"]";
    }

    //具体成功信息,带数据(2个参数)
    public static R ok(ResultStatus ok, Object data) {
        //查询集合为空的统一提示处理
        if(data instanceof List){
            List list = (List)data;

        }
        return new R(ok, data);
    }
    //成功信息,不带数据。
    public static R ok() {
        return new R(ResultStatus.SUCCESS);
    }
    //具体成功信息,不带数据。
    public static R ok(ResultStatus ok) {
        return new R(ok);
    }
    //具体错误信息。
    public static R error(ResultStatus error) {
        return new R(error);
    }
    //具体错误信息(加附带信息)。
    public static R error(ResultStatus error, String str) {
        return new R(error,str);
    }

    /** 格式化占位符*/
    public static R format(ResultStatus rs, String ...value){
        StringBuffer sbMsg = new StringBuffer(rs.getMsg());
        int index = 0;
        for(int i=0,l=value.length;i<l;i++){
            String old = "{"+i+"}";
            index = sbMsg.indexOf(old,index);
            sbMsg = sbMsg.replace(index,index+old.length(),value[i]);
        }
        return new R(rs.getStatus(),sbMsg.toString());
    }
    /** 首部添加信息*/
    public static R appendBegin(R r, String addMsg){
        r.setMsg(addMsg+","+r.getMsg());
        return r;
    }
    /** 尾部添加信息*/
    public static R appendEnd(R r, String addMsg){
        r.setMsg(r.getMsg()+","+addMsg);
        return r;
    }

    @Override
    public String toString() {
        return "Result{" +
                "status=" + status +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

ResultStatus

package com.wyc.common;

/**
 * @author superBrother
 * @create 2022/7/2
 * @Description
 */
public enum ResultStatus {
    /**
     * 500:失败
     * 200:成功
     */
    /**
     * info
     */
    NOT_LOGIN(401,"未登录,请登录"),
    NOT_PERMISSION(403,"权限不足,请联系管理员"),
    NOT_ID(500,"未传入id"),
    ID_NOT_EXIST(500,"该id不存在"),
    MIN_NOT_GREATER_MAX(500,"最小值不能大于最大值"),
    NEED_ID(500,"请传入id"),
    USER_EXIST(500,"该用户已经存在"),
    USER_NOT_EXIST(500,"该用户不存在"),
    USER_IS_FORBIDDEN(500,"该用户已经被禁用了"),
    MOBILE_EXIST(500,"该电话已经存在"),
    EMAIL_EXIST(500,"该邮箱已经存在"),
    TEACHER_NOT_EXIST(500,"该用户不存在"),
    COURSE_NOT_EXIST(500,"该课程不存在"),
    SEMESTER_NOT_EXIST(500,"该学期不存在"),
    CLAZZ_ID_NOT_EXIST(500,"传入的班级id不存在"),
    FILE_IS_NULL(500,"传入的文件为空"),
    UPLOAD_ERROR(500,"上传失败"),

    /**
     * 成功信息
     */
    SUCCESS(200, "成功"),
    ADD_SUCCESS(200, "增加成功"),
    DELETE_SUCCESS(200, "删除成功"),
    UPDATE_SUCCESS(200, "更改成功"),
    SELECT_SUCCESS(200, "查询成功"),
    SINGUP_SUCCESS(200, "注册成功"),
    LOGIN_SUCCESS(200, "登录成功"),
    LOGOUT_SUCCESS(200, "退出成功"),
    GAIN_SUCCESS(200,"获取成功"),
    EMAIL_SEND_SUCCESS(200,"邮箱发送成功"),
    UPLOAD_SUCCESS(200,"上传文件成功"),


    /**
     * 失败
     */
    ERROR(500,"失败"),
    ADD_ERROR(500,"添加失败"),
    UPDATE_ERROR(500,"修改失败"),
    DELETE_ERROR(500,"删除失败"),
    SELECT_ERROR(500,"查询失败"),
    LOGIN_ERROR(500,"登录失败"),

    TYPE_ERROR(500,"类型转换错误"),
    ASSOCIATED_DATA_ERROR(500,"该数据有关联数据,操作失败!"),
    MYSQL_ERROR(500,"数据库异常,操作失败!"),
    BUSINESS_ERROR(500,"业务出现错误,请联系后端开发人员"),
    PASSWORD_ERROR(500,"密码错误,请重新输入"),
    EMAIL_FORMAT_ERROR(500,"邮箱格式错误"),
    EMAIL_SEND_ERROR(500,"邮箱发送失败"),
    ID_IS_NOT_TEACHER_ID(500,"传入的id不是老师id"),
    ;

    /**
     * 返回码
     */
    private int status;

    /**
     * 返回结果描述
     */
    private String msg;

    ResultStatus() {
    }

    ResultStatus(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
登录流程:

前端输入用户密码传给后端,后端判断用户名密码是否正确,如果用户名密码正确,就会生成一个jwt令牌,返回给前端。如果用户名和密码不正确,我们就让他重新输入。

前端拿到生成的jwt令牌之后,他就会把它放在请求头里面,后面每一次请求都会携带这个jwt令牌,后端写一个jwt令牌相关的拦截器,每次请求之前都走一下拦截器,判断一下jwt令牌是否存在,以及令牌是否正确,合法有效。如果不存在,或者不是合法有效的jwt令牌的话,就拦截掉了,不让他去访问我们其他的接口,如果它的jwt令牌判断出来是合法有效的,我们就允许访问我们其他的接口。

第五步:在Admin类中实现UserDetails.

Admin.java

package com.xxxx.server.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.xxxx.server.config.CustomAuthorityDeserializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author HMF
 * @since 2021-07-23
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_admin")
@ApiModel(value = "Admin对象", description = "")
public class Admin implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "手机号码")
    private String phone;

    @ApiModelProperty(value = "住宅电话")
    private String telephone;

    @ApiModelProperty(value = "联系地址")
    private String address;

    @ApiModelProperty(value = "是否启用")
    @Getter(AccessLevel.NONE)
    private Boolean enabled;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "用户头像")
    private String userFace;

    @ApiModelProperty(value = "备注")
    private String remark;

    @ApiModelProperty(value = "角色权限")
    @TableField(exist = false)
    private List<Role> roles;

    / 实现 UserDetails 接口需要重写的方法 /
    @Override
    @JsonDeserialize(using = CustomAuthorityDeserializer.class)  /// 使用自定义的反序列化
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = roles
                .stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

实现之后,将对应方法的返回值全部改为true.

创建param包,然后创建AdminLoginParams类,专门用来接受前端传过来的参数。(用户登录实体类)

AdminLoginParams.java

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 用户登录实体类:
 * (其实就是登录所需的参数不需要那么多,又另外搞个实体类)
 * Created by HMF on 2021/07/23 15:31
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)

@ApiModel(value = "AdminLogin对象", description = "")
public class AdminLoginParam {

    @ApiModelProperty(value = "用户名", required = true)
    private String username;

    @ApiModelProperty(value = "密码", required = true)
    private String password;

    @ApiModelProperty(value = "验证码", required = true)
    private String code;
}

在controller包下创建LoginController

1.首先注入service,返回公用的返回对象,RespBean。编写方法。

    @Autowired
    private IAdminService adminService;  // 注入service

    /**
     * 登录之后返回token
     *
     * @param adminLoginParam
     * @param request
     * @return
     */
    @ApiOperation(value = "登录之后返回token")
    @PostMapping("/login")
    public RespBean login(@RequestBody AdminLoginParam adminLoginParam, HttpServletRequest request) {
        return adminService.login(adminLoginParam.getUsername(), adminLoginParam.getPassword(), adminLoginParam.getCode(), request);
    }

2.编写service。

spring-security主要通过UserDetails中的loadUserByUsername来实现的。获取到UserDetails.

,所以先注入UserDetailsService.

密码:前端传的是明文密码,但数据库存的肯定是加密的密码,而Security提供了一个专门加密密码的工具,它就是PasswordEncoder.通过PasswordEncoder进行匹配,它第一个参数是我们前端传的密码,第二个是UserDetails自己获取的密码。

获取到UserDetails先进行判断一下,如果为空,或者密码不正确,就返回用户名和密码不正确。

再加一个判断,如何用户被禁用,提示联系管理员。

登录成功后,我们使用spring-security框架,在去获取相应的用户信息的时候,可以用spring-security提供的对象,如UserDetails,获取对象,就要把对象放在spring-security的全局中,下次对象就是我们登录的。

否则就登录成功。登录成功,根据UserDetails拿到令牌token。注入jwtTokenUtil.

通过JwtTokenUtil拿到token,创建tokenmap,将token和tokenHead添加到tokemap。

将tokenmap返回给前端,并给出提示信息。

    // 获取验证码,我们把生成的验证码存在了session中,直接获取就可以了
        String captcha = (String) request.getSession().getAttribute("captcha");
        System.out.println("================");
        System.out.println(captcha);
        // 先判断用户是否输入了验证码,然后判断前端输入的验证码与生成的存在session中的验证码是否一致
        if (StringUtils.isEmpty(code) || !captcha.equalsIgnoreCase(code)) {
            // 验证码错误时,直接返回携带错误消息
            return RespBean.error("验证码输入错误,请重新输入!");
        }

        // 登录
        // 通过Spring Security里面的UserDetailsService的loadUserByUsername()方法实现登录
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (null == userDetails || !passwordEncoder.matches(password, userDetails.getPassword())) {
            return RespBean.error("用户名或密码不正确!");
        }

        if (!userDetails.isEnabled()) {
            return RespBean.error("账号被禁用,请联系管理员!");
        }

        // 更新security登录用户对象
        UsernamePasswordAuthenticationToken authenticationToken = new
                UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        //【把登录的用户对象放到SpringSecurity全局中】
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // 如果前面的判断都通过了,就拿到一个令牌 token
        String token = jwtTokenUtil.generateToken(userDetails);

        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead); // 这个是给前端,让她们放到请求头中的

        return RespBean.success("登录成功", tokenMap);
    }

退出登录:

正常的流程是登录之后,返回token,前端会把token放在请求头里面,访问任何接口,都会携带token令牌,我们的拦截器会判断这个令牌是否合法有效,如果合法有效,才能访问其他的接口,如果是非法的,就不让访问。我们和前端定义好,让前端直接调用这个退出登录的接口,我们后端给他返回状态码200,拿到这个状态码200之后,前端直接在请求头里面将token删除,这是用户在请求页面,调后端接口,就会被我们的拦截器拦截,不让访问,这样我们就完成了退出登录的处理,后端就不需要在做额外的处理,同样我们后端也可以直接做处理,这就需要前后端一起商量了。

    /**
     * 退出登录
     *
     * @return
     */
    @ApiOperation(value = "退出登录")
    @PostMapping("/logout")
    public RespBean logout() {
        return RespBean.success("注销成功!");
    }
}

获取当前用户信息 controller

    /**
     * 获取当前登录用户的信息
     *
     * @param principal
     * @return
     */
    @ApiOperation(value = "获取当前登录用户的信息")
    @GetMapping("/admin/info")
    //在运行过程中,Spring 会将 Username、Password、Authentication、Token 注入到 Principal 接口中,我们可以直接获取使用
    public Admin getAdminInfo(Principal principal) { // 因为我们把当前登录对象设置到了Spring Security的全局中,所以这里可以通过Principal直接获取
        if (null == principal) {
            return null;
        }
        String username = principal.getName();

        // 通过用户名username获取一个完整的用户对象
        Admin admin = adminService.getAdminByUserName(username);
        admin.setPassword(null); // 因为用户密码不能被返回,所以要设置为null
        admin.setRoles(adminService.getRoles(admin.getId()));
        return admin;
    }

service

    /**
     * 根据用户名username获取完整的用户对象 接口方法
     *
     * @param username
     * @return
     */
    Admin getAdminByUserName(String username);

impl

    /**
     * 根据用户名username获取完整的用户对象 实现方法
     *
     * @param username
     * @return
     */
    @Override
    public Admin getAdminByUserName(String username) {
        // 因为使用了MyBatisPlus,所以查询数据就变的更简单了
        //正常的话还有判断这个adminMapper是否为空,健壮性。
        return adminMapper.selectOne(new QueryWrapper<Admin>().eq("username", username).eq("enabled", true)); // 可以使用链式编程,一直...eq()方法
    }
配置spring-security。

在config/security新建SecurityConfig配置类

import com.wyc.mbg.pojo.Admin;
import com.wyc.mbg.service.IAdminRoleService;
import com.wyc.mbg.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * @author:superBrother
 * @create: 2022-07-02 19:16
 * @Description: Security配置类
 */
@Configuration // 配置类需要加的注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private IAdminService adminService;

    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

//    @Autowired
//    private CustomFilter customFilter;
//
//    @Autowired
//    private CustomUrlDecisionManager customUrlDecisionManager;


    @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",
//                "/swagger-ui.html",  // swaggerUI界面切换第二步:放行 原生swaggerUI的路径访问
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha",  // 验证码获取放行
                "/ws/**"    // websocket聊天放行,注意别忘了 /
        );
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 使用JWT,不需要csrf
        http.csrf().disable()
                // 基于token, 不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 所有请求都要求认证
                .anyRequest()
                .authenticated()
                // 动态权限配置
//                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
//                    @Override
//                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//                        object.setAccessDecisionManager(customUrlDecisionManager);
//                        object.setSecurityMetadataSource(customFilter);
//                        return object;
//                    }
//                })
                .and()
                // 禁用缓存
                .headers()
                .cacheControl();

        // 添加jwt,登录授权过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        // 添加自定义未授权和未登录结果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);

    }

    @Override
    @Bean   // @Bean注解,返回的对象交给IOC容器管理
    public UserDetailsService userDetailsService() {
        // 通过箭头函数重写 UserDetailsService 下的 loadUserByUsername()方法
        return username -> {  // 箭头代表将前面的参数username传递给后面的代码
            Admin admin = adminService.getAdminByUserName(username);
            if (null != admin) {
//                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin;
            }
            // 抛出SpringSecurity提供的异常 UsernameNotFoundException
            throw new UsernameNotFoundException("用户名或密码不正确!");
        };
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }
}

Jwt登录过滤器 JwtAuthencationTokenFilter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * JWT登录授权过滤器
 * <p>
 * Created by HMF on 2021/07/23 17:24
 */

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String authHeader = request.getHeader(tokenHeader);

        // 存在token
        if (null != authHeader && authHeader.startsWith(tokenHead)) {
            String authToken = authHeader.substring(tokenHead.length());
            String username = jwtTokenUtil.getUserNameFromToken(authToken);

            // token存在用户名但未登录
            if (null != username && null == SecurityContextHolder.getContext().getAuthentication()) {
                // 登录
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                // 验证token是否有效,重新设置用户对象
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authenticationToken = new
                            UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 重新设置到用户对象
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}


当未登录或者token失效时访问接口时,自定义的返回结果

RestAuthorizationEntryPoint.java

/**
 * 当未登录或者token失效时访问接口时,自定义的返回结果
 * Created by HMF on 2021/07/23 17:53
 */
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        RespBean bean = RespBean.error("尚未登录,请登录!");
        bean.setCode(401);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();
    }
}

当访问接口没有权限时,自定义返回结果

/**
 * 当访问接口没有权限时,自定义返回结果
 * Created by HMF on 2021/07/23 18:01
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        RespBean bean = RespBean.error("权限不足,请联系管理员!");
        bean.setCode(403);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();
    }
}
swagger配置 导入相关依赖,将改变的地方改变。
package com.wyc.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.*;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author:superBrother
 * @create: 2022-07-02 20:08
 * @Description: Swagger2配置类
 */
@Configuration
@EnableSwagger2
public class Swagger2Config {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.wyc.mbg.controller")) //改变
                .paths(PathSelectors.any())
                .build()
                //全局访问认证
                .securityContexts(securityContexts())
                .securitySchemes(securitySchemes());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("云E办接口文档")
                .description("云E办接口文档")
                .contact(new Contact("superBrother", "https://localhost:8081", "superbroo@qq.com")) //改变
                .version("1.0")
                .build();
    }

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

    private List<SecurityContext> securityContexts() {
        // 设置需要登录的认证路径
        List<SecurityContext> 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<SecurityReference> defaultAuth() {
        List<SecurityReference> 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;
    }
}

在controller编写测试接口
package com.xxxx.server.controller;

/**
 * Created by HMF on 2021/07/23 20:37
 */

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试接口用的controller
 */

@RestController
public class HelloController {

    @GetMapping("hello")
    public String hello() {
        return "hello!!!";
    }

    @GetMapping("/employee/basic/hello")
    public String hello2() {
        return "/employee/basic/hello";
    }

    @GetMapping("/employee/advanced/hello")
    public String hello3() {
        return "/employee/advanced/hello";
    }
}

启动发现被拦截,修改Security配置,放行这个接口。 SecurityConfig

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                "/login",
                "/logout",
                "/css/**",
                "/js/**",
                "/index.html",
                "favicon.ico",
                "/doc.html",
               //"/swagger-ui.html",  // swaggerUI界面切换第二步:放行 原生swaggerUI的路径访问
                "/webjars/**",
                "/swagger-resources/**",
                "/v2/api-docs/**",
                "/captcha",  // 验证码获取放行
                "/ws/**"    // websocket聊天放行,注意别忘了 /
        );
    }
登录验证码

使用谷歌的验证码

        <!-- google kaptcha依赖 -->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

编写验证码配置类

CaptchaConfig

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * 谷歌图形验证码配置类
 * <p>
 * Created by HMF on 2021/07/24 21:44
 */
@Configuration  // 配置了的@Configuration注解不要忘记了
public class CaptchaConfig {

    /**
     * 配置直接copy文档的
     * DefaultKaptcha 验证码生成器
     *
     * @return
     */
    @Bean   // @Bean注解,返回的对象交给IOC处理
    public DefaultKaptcha defaultKaptcha() {
        // 验证码生成器
        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;
    }
}

验证码接口 controller

package com.xxxx.server.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * 谷歌验证码接口Controller
 * <p>
 * Created by HMF on 2021/07/24 21:51
 */
@RestController
public class CaptchaController {

    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @ApiOperation(value = "验证码")
    @GetMapping(value = "/captcha", produces = "image/jpeg") //解决乱码
    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 ----------------------------
    }
}

并在SecurityConfig放行验证码接口。

token过期:JWT expired at 2022-07-04T07:40:55Z. Current time: 2022-07-04T08:41:37Z, a difference of 3642448 milliseconds. Allowed clock skew: 0 milliseconds.

1.清空浏览器

2.查看其他方法

https://blog.51cto.com/u_15127672/4137173

修改AdminLoginParam类,加入验证码

package com.wyc.param;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * @author:superBrother
 * @create: 2022-07-02 15:02
 * @Description: 用户登录实体类
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "AdminLogin对象",description = "")
public class AdminLoginParam {
    @ApiModelProperty(value = "用户名",required = true)
    private String username;
    @ApiModelProperty(value = "密码",required = true)
    private String password;
    @ApiModelProperty(value = "验证码",required = true)
    private String code;
}

去登录接口修改(LoginController)

     return adminService.login(adminLoginParam.getUsername(),adminLoginParam.getPassword(),adminLoginParam.getCode(),request);  //添加验证码

service

    /**
     * 登录之后返回token
     * @param username
     * @param password
     * @param code
     * @param request
     * @return
     */
    RespBean login(String username, String password, String code, HttpServletRequest request);

impl

    /**
     * 登录之后返回token
     * @param username
     * @param password
     * @param code
     * @param request
     * @return
     */
    @Override
    public RespBean login(String username, String password, String code, HttpServletRequest request) {
        String captcha =(String) request.getSession().getAttribute("captcha"); //拿到验证码进行匹配
        if (StringUtils.isEmpty(code)||!captcha.equalsIgnoreCase(code)){  //如果验证码错误,直接返回
            return RespBean.error("验证码输入错误,请重新输入!");
        }

到此,登录基本完成。

菜单功能

菜单可能会设计权限等等。

根据登录用户id查询对应菜单列表。

在pojo/menu中加入注解,代表数据库没有此字段,防止查询时出错。

    @ApiModelProperty(value = "子菜单")
    @TableField(exist = false)  // 因为数据库表没有这个字段,所以让程序识别这个字段不在数据库表中
    private List<Menu> children;

在controller层写接口

package com.xxxx.server.controller;


import com.xxxx.server.pojo.Menu;

import com.xxxx.server.service.IMenuService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author HMF
 * @since 2021-07-23
 */
@RestController
@RequestMapping("/system/config")     /菜单做了url
public class MenuController {

    @Autowired
    private IMenuService menuService;

    @ApiOperation(value = "通过用户id查询菜单列表")
    @GetMapping("/menu")
    public List<Menu> getMenusByAdminId() {        
        return menuService.getMenusByAdminId();
    }
}
//为什么通过用户id查询菜单列表,controller也没有传用户id?
    因为用户能正常登录之后,用户的相关信息都是后端直接获取的,而不需要前端传进来,因为前端传进来可能会出问题,可能或被别人篡改。
    如何从后端获取?
    SpringSecurity全局对象获取,只要登录了之后,就可以通过全局对象获取到用户id。

service

    /**
     * 通过用户id查询菜单列表 接口方法
     *
     * @return
     */
    List<Menu> getMenusByAdminId();

impl

    /**
     * 通过用户id查询菜单列表 实现方法
     *
     * @return
     */
    @Override
    public List<Menu> getMenusByAdminId() {
//        Integer adminId = ((Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        // 后期通过封装AdminUtils实现
        Integer adminId = AdminUtils.getCurrentAdmin().getId();
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        // 从redis获取菜单数据
        List<Menu> menus = (List<Menu>) valueOperations.get("menu_" + adminId);
        // 如果redis中为空,去数据库获取
        if (CollectionUtils.isEmpty(menus)) { // 如果没有
            menus = menuMapper.getMenusByAdminId(adminId);
            // 将数据设置到redis中
            valueOperations.set("menu_" + adminId, menus);
        }
        return menus;
    }

mapper

    /**
     * 通过用户id查询菜单列表
     *
     * @param id
     * @return
     */
    List<Menu> getMenusByAdminId(Integer id);

mapper.xml

根据用户信息拿到菜单列表
思路:根据什么在什么表中拿到什么?
根据   xxx			  在 xxx表xxx			      中拿到                     xxx  
根据   用户id        adminId     在  用户表admin_role              中拿到                     用户权限rid,
根据	用户权限      rid        在  用户权限表menu_role           中拿到                     用户菜单mid,
根据	用户菜单      MID        在  用户菜单表menu                中拿到                     用户菜单信息


主菜单信息通过子菜单parentId来拿,     m1.id = m2.parentId
子菜单通过菜单权限表的菜单rid来拿。    m2.id = mr.mid  mr.rid = ar.rid
菜单rid通过用户id来拿                   ar.adminId = 3


<!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.wyc.mbg.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>
将主菜单通用的查询映射结果复制到子菜单,然后将column全部改为xx2.
    <resultMap id="Menus" type="com.wyc.mbg.pojo.Menu" extends="BaseResultMap">
       <collection property="children" ofType="com.wyc.mbg.pojo.Menu">
           <id column="id2" property="id" />
           <result column="url2" property="url" />
           <result column="path2" property="path" />
           <result column="component2" property="component" />
           <result column="name2" property="name" />
           <result column="iconCls2" property="iconCls" />
           <result column="keepAlive2" property="keepAlive" />
           <result column="requireAuth2" property="requireAuth" />
           <result column="parentId2" property="parentId" />
           <result column="enabled2" property="enabled" />
       </collection>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, url, path, component, name, iconCls, keepAlive, requireAuth, parentId, enabled
    </sql>
将原来的resultType="com.wyc.mbg.pojo.Menu"换为:resultMap="Menus",因为多了一个子菜单children。
<!--    根据用户id查询菜单列表-->
    <select id="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.iconCls AS iconCls2,
            m2.keepAlive AS keepAlive2,
            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 = #{id}
            AND m2.enabled = TRUE
        ORDER BY
            m2.id
    </select>

由于菜单不会有太大变化,而且需要频繁的渲染,那么就将这个菜单放到redis里面。

加入redis

导入redis相关依赖

        <!-- spring data redis 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

在db配置文件里面配置redis

  redis:
    # 超时时间
    timeout: 10000ms
    # 服务器地址(学习时用的是虚拟机的ip地址)
    # host: 192.168.1.105
    host: 127.0.0.1
    # 服务器端口
    port: 6379
    # 选择的数据库
    database: 1
    # 密码(学习时,没有密码就不用配了)
#        password: 123456

    # 使用lettuce操作redis(还有Jedis也可以操作Redis)
    lettuce:
      pool:
        # 最大连接数,默认是8
        max-active: 1024
        # 最大连接阻塞等待时间,默认-1
        max-wait: 10000ms
        # 最大空闲连接,默认是8
        max-idle: 200
        # 最小空闲连接,默认是0
        min-idle: 5

编写redis配置类,序列化.

config/RedisConfig

package com.xxxx.server.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Created by HMF on 2021/07/28 21:52
 * <p>
 * Redis配置类,设置序列化
 */
@Configuration
public class RedisConfig {

    /**
     * 设置序列化
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        // String类型 key序列器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // String类型 value序列器
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        // Hash类型 key序列器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        // Hash类型 value序列器
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        // 把连接工厂放进去
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

序列化完成之后,去修改MenusServiceImpl.

//首先注入redisTemplate
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 通过用户id查询菜单列表
     * @return
     */
    @Override
    public List<Menu> getMenusByAdminId() {
        //通过全局对象,拿到adminId
       Integer adminId =((Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        //从redis获取菜单数据
        List<Menu> menus =(List<Menu>) valueOperations.get("menu_" + adminId);
        //如果为空,去数据库获取
        if(CollectionUtils.isEmpty(menus)){
            menus = menuMapper.getMenusByAdminId(adminId);
            //将数据设置到Redis中
            valueOperations.set("menu_"+adminId,menus);
        }
        return menus;
    }

如果对数据库的菜单进行修改,一定要清空redis数据库,不然查到的全是之前的数据。

用户权限

加入用户权限,让用户在自己的权限范围内访问自己的接口。

权限管理RBAC基本概念。

RBAC是基于角色的访问控制(Role-Based Access Control)在RBAC中,权限与角色相关联,用户通过扮演适当的角色从而得到这些角色的权限。这样管理层都是层级相互依赖的,权限赋予给角色,角色又赋予给用户,这样的权限设计很清楚,管理起来也很方便。

RBAC授权实际上是who,what,how三元组之间的关系,也就是who对what进行how的操作,简单说就是谁对什么资源做了怎样的操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ydN4wUIQ-1661933983138)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220704154631975.png)]

这里用户与角色实体对应的关系为多对多,角色与资源对应的关系同样为多对多关系,所以在实体设计上用户与角色间增加用户角色实体,将多对多的对应关系拆分为一对多,同理,角色与资源多对多对应关系拆分出中间实体对象权限实体。

基础的五表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w6pncLBM-1661933983138)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220630084233883.png)]

进行角色判断流程

首先是处于登录的状态之后,做相应的前提判断。没登录就没有所谓的角色,也就不需要判断。

1.根据登录之后,判断自己的角色。

2.根据请求的url,判断角色。假设我们访问一个url,我们会个根据url找到这个菜单id,根据mid在菜单权限表中判断这个id是否拥有访问这个url的权限。

根据访问url判断角色

修改pojo/menu

加入角色列表
    @ApiModelProperty(value = "角色列表")
    @TableField(exist = false)
    private List<Role> roles;

修改menuService

    /**
     * 根据角色获取菜单列表
     * @return
     */
    List<Menu> getMenusWithRole();

修改menuServiceImpl

    /**
     * 根据角色获取菜单列表
     * @return
     */
    @Override
    public List<Menu> getMenusWithRole() {
        return menuMapper.getMenusWithRole();

    }

menuMapper

    /**
     * 根据角色获取菜单列表
     * @return
     */
    List<Menu> getMenusWithRole();

menuMapper.xml

根据登录之后的adminId在admin_role表中拿到rid,
根据rid在role表中拿到role信息。

<!--    根据角色获取菜单列表-->
    <select id="getMenusWithRole" resultType="com.wyc.mbg.pojo.Menu">
       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

    </select>

权限控制

component/CustomFilter

package com.wyc.config.component;

import com.wyc.mbg.pojo.Menu;
import com.wyc.mbg.pojo.Role;
import com.wyc.mbg.service.IMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * 权限控制
 * @author:superBrother
 * @create: 2022-07-04 16:35
 * @Description: 根据请求url分析请求所需的角色
 */
@Component
public class CustomFilter implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private IMenuService menuService;
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //获取请求的url
        String requestUrl = ((FilterInvocation)object).getRequestUrl();
        List<Menu> menus = menuService.getMenusWithRole();
        for (Menu menu :menus){
            //判断请求url与菜单角色是否匹配
            if (antPathMatcher.match(menu.getUrl(),requestUrl)){
                String[] str = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                return SecurityConfig.createList(str);
            }
        }
        //没匹配的url默认登录即可访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

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

    @Override
    public boolean supports(Class<?> clazz) {
        return false;
    }
}

判断用户角色,判断登录的用户都有那些角色。

然后就可以根据请求的url角色和登录获取的角色进行比较,如果一直,用户就可以访问这些菜单资源。

第一步:修改pojo/Admin

    @ApiModelProperty(value = "角色")
    @TableField (exist = false)
    private List<Role> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getName()))
                .collect(Collectors.toList());
        return authorities;
    }

第二步:修改service

    /**
     * 根据用户id查询角色列表
     * @param adminId
     * @return
     */
    List<Role> getRoles(Integer adminId);

serviceimpl

    /**
     * 根据用户id查询角色列表
     * @param adminId
     * @return
     */
    @Override
    public List<Role> getRoles(Integer adminId) {
        return roleMapper.getRoles(adminId);
    }

roleMapper

    /**
     * 根据用户id查询角色列表
     * @param adminId
     * @return
     */
    List<Role> getRoles(Integer adminId);

roleMapper.xml

<!-- 根据用户id查询角色列表-->
    <select id="getRoles" resultType="com.wyc.mbg.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}
    </select>

修改登录方法,加上根据角色判断url方法,让每次登录完之后,就可以获取角色列表。

LoginController 修改这个获取当前用户信息,将role返回

    @ApiOperation(value = "获取当前登录用户信息")
    @GetMapping(value = "/admin/info")
    public Admin getAdminInfo(Principal principal){
        if (null==principal){
            return null;
        }
        String username = principal.getName();
        Admin admin = adminService.getAdminByUserName(username);
        admin.setPassword(null);
        //返回信息就会拿到角色
        admin.setRoles(adminService.getRoles(admin.getId()));  //修改
        return admin;
    }

修改登录,因为登录是通过userDetailsService.loadUserByUsername(username)方法,然后去SecurityConfig.

    @Override
    @Bean   // @Bean注解,返回的对象交给IOC容器管理
    public UserDetailsService userDetailsService() {
        // 通过箭头函数重写 UserDetailsService 下的 loadUserByUsername()方法
        return username -> {  // 箭头代表将前面的参数username传递给后面的代码
            Admin admin = adminService.getAdminByUserName(username);
            if (null != admin) {
                admin.setRoles(adminService.getRoles(admin.getId())); //修改
                return admin;
            }
//            return null;
             //抛出SpringSecurity提供的异常 UsernameNotFoundException
            throw new UsernameNotFoundException("用户名或密码不正确!");
        };
    }

创建component/CustomUrlDecisionManager

package com.wyc.config.component;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * 权限控制
 * @author:superBrother
 * @create: 2022-07-04 17:18
 * @Description: 判断用户角色
 */
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            // 当前url所需角色
            String needRole = configAttribute.getAttribute();
            // 判断角色是否登录即可访问的角色,此角色在 CustomFilter 中设置
            if ("ROLE_LOGIN".equals(needRole)) {  
                // 判断是否登录
                if (authentication instanceof AnonymousAuthenticationToken) { // 如果是AnonymousAuthenticationToken,表示还没有登录
                    throw new AccessDeniedException("尚未登录,请登录!");
                } else {
                    return;
                }
            }
            // 判断用户角色是否为url所需角色
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员!");
    }

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

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

修改SecurityConfig,加入动态权限配置

  先引入
    @Autowired
    private CustomFilter customFilter;

    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;


   @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 使用JWT,不需要csrf
        http.csrf().disable()
                // 基于token, 不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
//                .antMatchers("/login","/logout")
//                .permitAll()
                // 所有请求都要求认证
                .anyRequest()
                .authenticated()
                 //动态权限配置    添加
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilter);
                        return object;
                    }
                })

                .and()
                // 禁用缓存
                .headers()
                .cacheControl();

权限管理

1.首先根据请求的url分析url需要那些角色,

2.根据登录的用户分析判断这个用户拥有那些角色,然后进行匹配,匹配的上,就让访问,如果不匹配,就告诉权限不足,去联系管理员。

职位管理

在pojo/position加上时间格式注解@JsonFormat(。

@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
private LocalDateTime createDate;

controller

@RestController
@RequestMapping("/system/basic/pos")
public class PositionController {

    @Autowired
    private PositionService positionService;

    @ApiOperation(value = "获取所有职位信息")
    @GetMapping("/")
    public List<Position> getAllPosition() {
        return positionService.list();
    }

    @ApiOperation(value = "添加职位")
    @PostMapping("/")
    public RespBean addPosition(@RequestBody Position position) {
        position.setCreateDate(LocalDateTime.now());
        if (positionService.save(position)) {
            return RespBean.success("添加成功!");
        }
        return RespBean.error("添加失败!");
    }

    @ApiOperation(value = "更新职位信息")
    @PutMapping("/")
    public RespBean updatePosition(@RequestBody Position position) {
        position.setCreateDate(LocalDateTime.now());
        if (positionService.updateById(position)) {
            return RespBean.success("更新成功!");
        }
        return RespBean.error("更新失败!");
    }

    @ApiOperation(value = "删除单条职位信息")
    @DeleteMapping("/{id}")
    public RespBean deletePosition(@PathVariable Integer id) {
        if (positionService.removeById(id)) {
            return RespBean.success("删除成功!");
        }
        return RespBean.error("删除失败!");
    }

    @ApiOperation(value = "批量删除职位信息")
    @DeleteMapping("/")
    public RespBean deletePositionByIds(Integer[] ids) {
        if (positionService.removeByIds(Arrays.asList(ids))) {
            return RespBean.success("删除成功!");
        }
        return RespBean.error("删除失败!");
    }

权限组角色

需要注意的是:添加角色时,要给角色拼接ROLE_这是springSecurity的格式。

package com.wyc.mbg.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wyc.common.RespBean;
import com.wyc.mbg.pojo.MenuRole;
import com.wyc.mbg.pojo.Role;
import com.wyc.mbg.service.IMenuRoleService;
import com.wyc.mbg.service.IMenuService;
import com.wyc.mbg.service.IRoleService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author:superBrother
 * @create: 2022-07-05 07:01
 * @Description: 权限组
 */
@RestController
@RequestMapping("/system/basic/permiss")
public class PermissionController {


    @Autowired
    private IRoleService roleService;

    @Autowired
    private IMenuService menuService;

    @Autowired
    private IMenuRoleService menuRoleService;

    @ApiModelProperty(value = "获取所有角色")
    @GetMapping("/")
    public List<Role> getAllRoles(){
        return roleService.list();
    }
    @ApiModelProperty(value = "添加角色")
    @PostMapping("/role")
    public RespBean addRole(@RequestBody Role role){
        //用来判断传入的Name前面是否有ROLE,springSecurity定义的格式,如果没有,就加上这个格式。
        if (!role.getName().startsWith("ROLE_")){
            role.setName("ROLE_"+role.getName());
        }
        if (roleService.save(role)){
            return RespBean.success("添加成功!");
        }
        return RespBean.error("添加失败!");
    }
    @ApiModelProperty(value = "更新角色")
    @PutMapping("/")
    public RespBean updateRole(@RequestBody Role role){
        if (roleService.updateById(role)){
            return RespBean.success("更新成功!");
        }
        return RespBean.error("更新失败!");
    }
    @ApiModelProperty(value = "删除角色")
    @DeleteMapping("/role/{rid}")
    public RespBean deleteRoleById(@PathVariable Integer rid){
        if (roleService.removeById(rid)){
            return RespBean.success("删除成功!");
        }
        return RespBean.error("删除失败!");
    }
    @ApiModelProperty(value = "查询所有菜单")
    @GetMapping("menus")
    public RespBean getAllMenus(){
        return menuService.getAllMenus();
    }
    @ApiModelProperty(value = "根据角色id查询菜单id")
    @GetMapping("/mid/{rid}")
    public List<Integer> getMidByRid(@PathVariable Integer rid){
        return menuRoleService.list(new QueryWrapper<MenuRole>().eq("rid",rid))
                .stream()
                .map(MenuRole::getMid)
                .collect(Collectors.toList());
    }
    @ApiModelProperty(value = "更新角色菜单")
    @PutMapping()
    public RespBean updateRoleMenu(Integer rid,Integer[] mids){
        return menuRoleService.updateMenuRole(rid,mids);
    }
}


存储过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPcDRvoA-1661933983139)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220706095034320.png)]

存储过程的创建和调用

储存过程就是具有名字的一段代码,用来完成一个特定的功能。

创建的存储过程保存在数据库的数据字典中。

分页配置

common/MyBatisPlusConfig

package com.wyc.common;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author:superBrother
 * @create: 2022-07-07 08:10
 * @Description: 分页配置
 */
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor paginationInterceptor() {
        // 原理就是你查询的时候被这里拦截,然后加上 limit,实现分页查询
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOverflow(false);
        mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
        return mybatisPlusInterceptor;
    }
}

分页公共返回对象

​ common/RespBean

package com.wyc.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author:superBrother
 * @create: 2022-07-07 08:12
 * @Description: 分页公共返回对象
 */
@Data // Getter/Setter方法
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造
public class RespPageBean {

    /**
     * 总条数
     */
    private Long total;

    /**
     * 数据 List
     */
    private List<?> data;
}

时间转换

converter/DateConverter

package com.wyc.converter;

import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * @author:superBrother
 * @create: 2022-07-07 08:15
 * @Description: 日期转换
 */
@Component
public class DateConverter implements Converter<String, LocalDate> {
    @Override
    public LocalDate convert(String source) {
        try { //防止出现问题,加异常  选中代码然后Ctrl + Alt + T
            return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

在employee实体类中,加上@JsonFormat(pattern = “yyyy-MM-dd”,timezone = “Asia/Shanghai”)注解。

员工操作

首先修改pojo类

package com.wyc.mbg.pojo;

import cn.afterturn.easypoi.excel.annotation.ExcelEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import java.time.LocalDate;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 * 
 * </p>
 *
 * @author superBrother
 * @since 2022-07-02
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_employee")
@ApiModel(value="Employee对象", description="")
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "员工编号")
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @ApiModelProperty(value = "员工姓名")
    private String name;

    @ApiModelProperty(value = "性别")
    private String gender;

    @ApiModelProperty(value = "出生日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate birthday;

    @ApiModelProperty(value = "身份证号")
    private String idCard;

    @ApiModelProperty(value = "婚姻状况")
    private String wedlock;

    @ApiModelProperty(value = "民族")
    private Integer nationId;

    @ApiModelProperty(value = "籍贯")
    private String nativePlace;

    @ApiModelProperty(value = "政治面貌")
    private Integer politicId;

    @ApiModelProperty(value = "邮箱")
    private String email;

    @ApiModelProperty(value = "电话号码")
    private String phone;

    @ApiModelProperty(value = "联系地址")
    private String address;

    @ApiModelProperty(value = "所属部门")
    private Integer departmentId;

    @ApiModelProperty(value = "职称ID")
    private Integer jobLevelId;

    @ApiModelProperty(value = "职位ID")
    private Integer posId;

    @ApiModelProperty(value = "聘用形式")
    private String engageForm;

    @ApiModelProperty(value = "最高学历")
    private String tiptopDegree;

    @ApiModelProperty(value = "所属专业")
    private String specialty;

    @ApiModelProperty(value = "毕业院校")
    private String school;

    @ApiModelProperty(value = "入职日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate beginDate;

    @ApiModelProperty(value = "在职状态")
    private String workState;

    @ApiModelProperty(value = "工号")
    private String workID;

    @ApiModelProperty(value = "合同期限")
    private Double contractTerm;

    @ApiModelProperty(value = "转正日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate conversionTime;

    @ApiModelProperty(value = "离职日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate notWorkDate;

    @ApiModelProperty(value = "合同起始日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate beginContract;

    @ApiModelProperty(value = "合同终止日期")
    @JsonFormat(pattern = "yyyy-MM-dd",timezone = "Asia/Shanghai")
    private LocalDate endContract;

    @ApiModelProperty(value = "工龄")
    private Integer workAge;

    @ApiModelProperty(value = "工资账套ID")
    private Integer salaryId;

     以下为外键字段 //
    @ApiModelProperty(value = "民族")
    @TableField(exist = false)  // 数据库中不存在需要加上这个注解
    @ExcelEntity(name = "民族")  
    private Nation nation;

    @ApiModelProperty(value = "政治面貌")
    @TableField(exist = false)
    @ExcelEntity(name = "政治面貌")  
    private PoliticsStatus politicsStatus;

    @ApiModelProperty(value = "部门")
    @TableField(exist = false)
    @ExcelEntity(name = "部门")  
    private Department department;

    @ApiModelProperty(value = "职称")
    @TableField(exist = false)
    @ExcelEntity(name = "职称")  
    private Joblevel joblevel;

    @ApiModelProperty(value = "职位")
    @TableField(exist = false)
    @ExcelEntity(name = "职位")  
    private Position position;

    @ApiModelProperty(value = "工资账套")
    @TableField(exist = false)
    private Salary salary;
}

查询controller

package com.wyc.mbg.controller;


import com.wyc.common.RespPageBean;
import com.wyc.mbg.pojo.Employee;
import com.wyc.mbg.service.IEmployeeService;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author superBrother
 * @since 2022-07-02
 */
@RestController
@RequestMapping("/employee/basic")
public class EmployeeController {

    @Autowired
    private IEmployeeService employeeService;

    @ApiModelProperty(value = "获取所有员工(分页)")
    @GetMapping("/")
    public RespPageBean getEmployee(@RequestParam(defaultValue = "1") Integer currentPage,
                                    @RequestParam(defaultValue = "10") Integer size,
                                    Employee employee,
                                    LocalDate[] beginDateScope){
        return employeeService.getEmployeeByPage(currentPage,size,employee,beginDateScope);
    }

}

impl

package com.wyc.mbg.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wyc.common.RespPageBean;
import com.wyc.mbg.pojo.Employee;
import com.wyc.mbg.mapper.EmployeeMapper;
import com.wyc.mbg.service.IEmployeeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author superBrother
 * @since 2022-07-02
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {


    @Autowired
    private EmployeeMapper employeeMapper;

    /**
     * 获取所有员工(分页)
     * @param currentPage
     * @param size
     * @param employee
     * @param beginDateScope
     * @return
     */
    @Override
    public RespPageBean getEmployeeByPage(Integer currentPage, Integer size, Employee employee, LocalDate[] beginDateScope) {
        //开启分页
        Page<Employee> page = new Page<>(currentPage,size);
        // IPage是国人开发的一个插件,用来做分页,可以点击去看源码(发现里面是中文的注释,因为是国人开发的)
        IPage<Employee> employeeByPage = employeeMapper.getEmployeeByPage(page, employee, beginDateScope);
        RespPageBean respPageBean = new RespPageBean(employeeByPage.getTotal(), employeeByPage.getRecords());
        return respPageBean;
    }
}

mapper

package com.wyc.mbg.mapper;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wyc.mbg.pojo.Employee;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.time.LocalDate;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author superBrother
 * @since 2022-07-02
 */
public interface EmployeeMapper extends BaseMapper<Employee> {

    /**
     * 获取所有员工(分页)
     * @param page
     * @param employee
     * @param beginDateScope
     */
    IPage<Employee> getEmployeeByPage(Page<Employee> page, @Param("employee") Employee employee, @Param("beginDateScope") LocalDate[] beginDateScope);
}

mapper.xml

    <!-- 获取所有员工(分页) resultMap -->
    <resultMap id="EmployeeInfo" type="com.wyc.mbg.pojo.Employee" extends="BaseResultMap">
        <!-- 这里是对象 -->
        <!-- 民族 -->
        <association property="nation" javaType="com.wyc.mbg.pojo.Nation">
            <id column="nid" property="id"/>
            <result column="nname" property="name"/>
        </association>
        <!-- 政治面貌 -->
        <association property="politicsStatus" javaType="com.wyc.mbg.pojo.PoliticsStatus">
            <id column="pid" property="id"/>
            <result column="pname" property="name"/>
        </association>
        <!-- 部门 -->
        <association property="department" javaType="com.wyc.mbg.pojo.Department">
            <id column="did" property="id"/>
            <result column="dname" property="name"/>
        </association>
        <!-- 职称 -->
        <association property="joblevel" javaType="com.wyc.mbg.pojo.Joblevel">
            <id column="jid" property="id"/>
            <result column="jname" property="name"/>
        </association>
        <!-- 职位 -->
        <association property="position" javaType="com.wyc.mbg.pojo.Position">
            <id column="posid" property="id"/>
            <result column="posname" property="name"/>
        </association>
    </resultMap>




<!--    获取所有员工(分页)-->
    <select id="getEmployeeByPage" resultMap="EmployeeInfo">
        SELECT
        e.*,
        n.id AS nid,
        n.`name` AS nname,
        p.id AS pid,
        p.`name` AS pname,
        d.id AS did,
        d.`name` AS dname,
        j.id AS jid,
        j.`name` AS jname,
        pos.id AS posid,
        pos.`name` AS posname
        FROM
        t_employee e,
        t_nation n,
        t_politics_status p,
        t_department d,
        t_joblevel j,
        t_position pos
        WHERE
        e.nationId = n.id
        AND e.politicId = p.id
        AND e.departmentId = d.id
        AND e.jobLevelId = j.id
        AND e.posId = pos.id
        <if test="null!=employee.name and ''!=employee.name">
            AND e.`name` LIKE CONCAT( '%', #{employee.name}, '%' )
        </if>
        <if test="null!=employee.politicId">
            AND e.politicId = #{employee.politicId}
        </if>
        <if test="null!=employee.nationId">
            AND e.nationId = #{employee.nationId}
        </if>
        <if test="null!=employee.jobLevelId">
            AND e.jobLevelId = #{employee.jobLevelId}
        </if>
        <if test="null!=employee.posId">
            AND e.posId = #{employee.posId}
        </if>
        <if test="null!=employee.engageForm and ''!=employee.engageForm">
            AND e.engageForm = #{employee.engageForm}
        </if>
        <if test="null!=employee.departmentId">
            AND e.departmentId = #{employee.departmentId}
        </if>
        <if test="null!=beginDateScope and 2==beginDateScope.length">
            AND e.beginDate BETWEEN #{beginDateScope[0]} AND #{beginDateScope[1]}
        </if>
        ORDER BY
        e.id
    </select>

数据的导入导出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SqHn3vK4-1661933983139)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220707105304376.png)]

导出

Controller

    @ApiOperation(value = "导出员工数据")
    @GetMapping(value = "/export", produces = "application/octet-stream")
    public void exportEmployee(HttpServletResponse response) { // 使用流的方式传出去
        List<Employee> list = employeeService.getEmployee(null); // 因为查询所有所以传入null
        // 导出数据,ExcelType.HSSF兼容性好,所以就用这个 /
        ExportParams params = new ExportParams("员工表", "员工表", ExcelType.HSSF); // 使用03版的Excel,兼容性好
        Workbook workbook = ExcelExportUtil.exportExcel(params, Employee.class, list);
        ServletOutputStream out = null;
        try {
            // 流形式
            response.setHeader("content-type", "application/octet-stream");
            // 防止中文乱码
            response.setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode("员工表.xls", "UTF-8"));
            out = response.getOutputStream();
            workbook.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

server

    /**
     * 查询员工
     * @param id
     * @return
     */
    List<Employee> getEmployee(Integer id);
}

impl

    /**
     * 查询员工
     * @param id
     * @return
     */
    @Override
    public List<Employee> getEmployee(Integer id) {
        return employeeMapper.getEmployee(id);
    }

mapper

    /**
     * 查询员工
     * @param id
     * @return
     */
    List<Employee> getEmployee(Integer id);

xml

<!--    查询员工-->
    <select id="getEmployee" resultMap="EmployeeInfo">
        SELECT
        e.*,
        n.id AS nid,
        n.`name` AS nname,
        p.id AS pid,
        p.`name` AS pname,
        d.id AS did,
        d.`name` AS dname,
        j.id AS jid,
        j.`name` AS jname,
        pos.id AS posid,
        pos.`name` AS posname
        FROM
        t_employee e,
        t_nation n,
        t_politics_status p,
        t_department d,
        t_joblevel j,
        t_position pos
        WHERE
        e.nationId = n.id
        AND e.politicId = p.id
        AND e.departmentId = d.id
        AND e.jobLevelId = j.id
        AND e.posId = pos.id
        <if test="null!=id">
            AND e.id = #{id}
        </if>
        ORDER BY
        e.id
    </select>
导入

Controller

    @ApiOperation(value = "导入员工数据")
    @PostMapping("/import")
    public RespBean importEmployee(MultipartFile file) { // 需要什么就在该方法的参数中加上去,如Request、Response等
        ImportParams params = new ImportParams();
        // 去掉标题行,就是从第二行开始
        params.setTitleRows(1);
        // 通过查询数据库获取到所有的民族,然后在下面匹配获取到Id,使用MyBatisPlus提供的list()方法获取表数据
        List<Nation> nationList = nationService.list();
        List<PoliticsStatus> politicsStatusList = politicsStatusService.list();
        List<Department> departmentList = departmentService.list();
        List<Joblevel> joblevelList = joblevelService.list();
        List<Position> positionList = positionService.list();
        try {
            List<Employee> list = ExcelImportUtil.importExcel(file.getInputStream(), Employee.class, params); // 获取Excel表格中的数据
            list.forEach(employee -> {  // lambda表达式
                // 重点是要通过导入的内容转换成Id
                /// new Nation(employee.getNation().getName()) --> 通过导入的数据生成一个Nation对象
                /// nationList.indexOf(new Nation(employee.getNation().getName())) --> 通过生成的Nation对象匹配到对应的下标
                /// nationList.get(nationList.indexOf(new Nation(employee.getNation().getName()))) --> 通过下标再获取到数据查出并匹配的Nation对象
                /// nationList.get(nationList.indexOf(new Nation(employee.getNation().getName()))).getId() --> 然后获取对应的Id
                // 民族id
                employee.setNationId(nationList.get(nationList.indexOf(new Nation(employee.getNation().getName()))).getId());
                // 政治面貌id
                employee.setPoliticId(politicsStatusList.get(politicsStatusList.indexOf(new PoliticsStatus(employee.getPoliticsStatus().getName()))).getId());
                // 部门id
                employee.setDepartmentId(departmentList.get(departmentList.indexOf(new Department(employee.getDepartment().getName()))).getId());
                // 职称id
                employee.setJobLevelId(joblevelList.get(joblevelList.indexOf(new Joblevel(employee.getJoblevel().getName()))).getId());
                // 职位id
                employee.setPosId(positionList.get(positionList.indexOf(new Position(employee.getPosition().getName()))).getId());
            });
            if (employeeService.saveBatch(list)) { // 插入到数据库中
                return RespBean.success("导入成功!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return RespBean.error("导入失败!");
    }

邮件发送

创建module。maven项目选择quelistart。

导入依赖

    <dependencies>
        <!--rabbitmq 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <!--mail 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

        <!--thymeleaf 依赖,用来专门写邮件的模板-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--server 依赖,*************这个依赖就有点意思了********-->
        <dependency>
            <groupId>com.wyc</groupId>
            <artifactId>yeb-server</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

配置文件

resources/config/application

server:
  # 端口
  port: 8082

spring:
  # 邮件配置163
  mail:
    # 服务器地址
    host: smtp.163.com
    # 协议
    protocol: smtp
    # 编码格式
    default-encoding: utf-8
    # 授权码
    password: WCXOTSJNYJKMTWHK
    # 发送者邮箱地址
    username: superbroo@163.com
    # 端口号
    port: 25


#spring:
#  mail:
#    username: superbroo@qq.com
#    password: wjvxdfodcvqcgbce
#    host: smtp.qq.com
#    port: 465
#    properties:
#      mail:
#        transport:
#          protocol: smtp
#        smtp:
#          socketFactory:
#            class: javax.net.ssl.SSLSocketFactory
#          port: ${spring.mail.port}
#          auth: true
#          starttls:
#            enable: true
#            required: true

  # rabbitmq配置
  rabbitmq:
    # 用户名
    username: guest
    # 密码
    password: guest
    # 服务器地址
    host: 120.26.222.131
    # host: 192.168.15.181
    # 端口
    port: 5672
    # 消息确认回调
    publisher-confirm-type: correlated
    # 消息失败回调
    publisher-returns: true

  # redis配置
  redis:
    # 超时时间
    timeout: 10000ms
    # 服务器地址(学习时用的是虚拟机的ip地址)
    host: 127.0.0.1
    # 服务器端口
    port: 6379
    # 选择的数据库
    database: 0
    # 密码(学习时,没有密码就不用配了)
    #    password: 123456

    # 使用lettuce操作redis(还有Jedis也可以操作Redis)
    lettuce:
      pool:
        # 最大连接数,默认是8
        max-active: 1024
        # 最大连接阻塞等待时间,默认-1
        max-wait: 10000ms
        # 最大空闲连接,默认是8
        max-idle: 200
        # 最小空闲连接,默认是0
        min-idle: 5

邮件模板

resource/templates/mail.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>入职欢迎邮件</title>
</head>

<body>
欢迎 <span th:text="${name}"></span> 加入 XXXX 大家庭,您的入职信息如下:

<table border="1">
    <tr>
        <td>姓名</td>
        <td th:text="${name}"></td>
    </tr>
    <tr>
        <td>职位</td>
        <td th:text="${posName}"></td>
    </tr>
    <tr>
        <td>职称</td>
        <td th:text="${joblevelName}"></td>
    </tr>
    <tr>
        <td>部门</td>
        <td th:text="${departmentName}"></td>
    </tr>
</table>
<p>我们公司的工作忠旨是严格,创新,诚信,您的加入将为我们带来新鲜的血液,带来创新的思维,以及为
    我们树立良好的公司形象!希望在以后的工作中我们能够齐心协力,与时俱进,团结协作!同时也祝您在本公
    司,工作愉快,实现自己的人生价值!希望在未来的日子里,携手共进!</p>
</body>
</html>

主启动类

mail/MailApplication

package com.wyc.mail;


import com.wyc.common.MailConstants;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;


/**
 * @author superBrother
 * @create 2022/7/2
 * @Description
 */
// 去掉数据源的配置,否则启动的时候该启动类会报没有数据源的错误
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MailApplication {
    public static void main(String[] args) {
        SpringApplication.run(MailApplication.class, args);
    }

    // 创建消息队列 名字是 mail.welcome
    @Bean
    public org.springframework.amqp.core.Queue queue() {
        return new Queue(MailConstants.MAIL_QUEUE_NAME); // 改为定义的常量
    }
}

消息接受者

mail/MailReceiver

package com.wyc.mail;

import com.rabbitmq.client.Channel;
import com.wyc.common.MailConstants;
import com.wyc.mbg.pojo.Employee;
//import com.wyc.mbg.pojo.MailConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Date;

/**
 * @author:superBrother
 * @create: 2022-07-07 20:46
 * @Description: 消息接受者
 */
@Component
public class MailReceiver {

    // 准备日志打印,使用slf4j日志打印,门面模式
    private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class);

    // 下面三个都是跟邮件相关的东西
    @Autowired
    private JavaMailSender javaMailSender;  // 注入邮件发送对象
    @Autowired
    private MailProperties mailProperties;  // 邮件的属性配置,在application.yml中
    @Autowired
    private TemplateEngine templateEngine; // thymeleaf模板引擎
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 因为使用了 rabbitTemplate.convertAndSend("mail.welcome", emp); 发送消息
     * 所以在这里就要监听他的RoutingKey: mail.welcome
     *
     * @param message
     */
    @RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME) // 改为定义的常量
    public void handler(Message message, Channel channel) {
        Employee employee = (Employee) message.getPayload();
        MessageHeaders headers = message.getHeaders();
        // 消息序号
        long tag = (long) headers.get(AmqpHeaders.DELIVERY_TAG);
        // 里面的参数是固定的
        String msgId = (String) headers.get("spring_returned_message_correlation");
        HashOperations hashOperations = redisTemplate.opsForHash();

        try {
            if (hashOperations.entries("mail_log").containsKey(msgId)) {
                LOGGER.error("消息已经被消费============>{}", msgId);
                /**
                 * 手动确认消息
                 * tag:消息序号
                 * multiple:是否确认多条
                 */
                channel.basicAck(tag, false);
                return;
            }

            MimeMessage msg = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);

            // 发件人
            helper.setFrom(mailProperties.getUsername());  // 获取在application.yml中配置邮件的的信息
            // 收件人
            helper.setTo(employee.getName());
            // 邮件主题
            helper.setSubject("入职欢迎邮件");
            // 发送日期
            helper.setSentDate(new Date());

            // 邮件内容
            Context context = new Context(); // thymeleaf 的Context
            context.setVariable("name", employee.getName());
            context.setVariable("posName", employee.getPosition().getName());
            context.setVariable("joblevelName", employee.getJoblevel().getName());
            context.setVariable("departmentName", employee.getDepartment().getName());


            String mail = templateEngine.process("mail", context); // 这里的mail要与mail.html对应
            // 设置邮件的发送模板
            helper.setText(mail, true); // 这里要加上true,不然显示的是html的源码

            // 发送邮件
            javaMailSender.send(msg);

            LOGGER.info("邮件发送成功!");
            // 将消息id存入redis
            hashOperations.put("mail_log", msgId, "OK");
            // 手动确认消息
            channel.basicAck(tag, false);

        } catch (Exception e) {
            /**
             * 手动确认消息
             * tag:消息序号
             * multiple:是否确认多条
             * requeue:是否要退回到队列
             */
            try {
                channel.basicNack(tag, false, true);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            LOGGER.error("邮件发送失败=======>{}", e.getMessage());
        }
    }
}

消息状态,常量

yeb-server/common/MailConstants

package com.wyc.common;

/**
 * @author:superBrother
 * @create: 2022-07-07 21:42
 * @Description: 消息状态
 */
public class MailConstants {

    // 消息投递中
    public static final Integer DELIVERING = 0;

    // 消息投递成功
    public static final Integer SUCCESS = 1;

    // 消息投递失败
    public static final Integer FAILURE = 2;

    // 最大重试次数
    public static final Integer MAX_TRY_COUNT = 3;

    // 消息超时时间
    public static final Integer MSG_TIMEOUT = 1;

    // 队列
    public static final String MAIL_QUEUE_NAME = "mail.queue";

    // 交换机
    public static final String MAIL_EXCHANGE_NAME = "mail.exchange";

    // 路由键
    public static final String MAIL_ROUTING_KEY_NAME = "mail.routing.key";
}

RabbitMQConfig配置类

package com.wyc.config;


import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.wyc.common.MailConstants;
import com.wyc.mbg.pojo.MailLog;
import com.wyc.mbg.service.IMailLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author:superBrother
 * @create: 2022-07-07 21:55
 * @Description: RabbitMQ配置类
 */
@Configuration
public class RabbitMQConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQConfig.class);

    @Autowired
    private CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    private IMailLogService mailLogService;

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
        /**
         * 消息确认回调,确认消息是否到达broker
         * data:消息唯一标识
         * ack:确认结果
         * cause:失败原因
         */
        rabbitTemplate.setConfirmCallback((data, ack, cause) -> {
            String msgId = data.getId();
            if (ack) {
                LOGGER.info("{}=======>消息发送成功", msgId);
                mailLogService.update(new UpdateWrapper<MailLog>().set("status", 1).eq("msgId", msgId));
            } else {
                LOGGER.error("{}=======>消息发送失败", msgId);
            }
        });

        /**
         * 消息失败回调,比如router不到queue时回调
         * msg:消息主题
         * repCode:响应码
         * repText:相应描述
         * exchange:交换机
         * routingkey:路由键
         */
        rabbitTemplate.setReturnCallback((msg, repCode, repText, exchange, routingkey) -> {
            LOGGER.error("{}=======>消息发送queue时失败", msg.getBody());
        });
        return rabbitTemplate;
    }

    // 队列
    @Bean
    public Queue queue() {
        return new Queue(MailConstants.MAIL_QUEUE_NAME);
    }

    // 交换机,路由模式
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(MailConstants.MAIL_EXCHANGE_NAME);
    }

    // 绑定
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(directExchange()).with(MailConstants.MAIL_ROUTING_KEY_NAME);
    }
}

定时任务,在主启动类开启定时任务,不能忘记。yeb-server

@SpringBootApplication
@MapperScan("com.wyc.mbg.mapper")
@EnableScheduling //开启定时任务
 swaggerUI界面切换第三步:在启动类加上 @EnableSwagger2 注解(前面两步修改后,能成功访问,这步就不需要做了)
@EnableSwagger2
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }
}

yeb-server/wyc/task/MailTask

package com.wyc.task;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.wyc.common.MailConstants;
import com.wyc.mbg.pojo.Employee;
import com.wyc.mbg.pojo.MailLog;
import com.wyc.mbg.service.IEmployeeService;
import com.wyc.mbg.service.IMailLogService;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author:superBrother
 * @create: 2022-07-08 08:06
 * @Description: 邮件发送定时任务
 */
@Component
public class MailTask {

    @Autowired
    private IMailLogService mailLogService;
    @Autowired
    private IEmployeeService employeeService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 邮件发送定时任务
     *
     * @Scheduled(cron = "0/10 * * * * ?") 表示 10秒执行一次
     */
    @Scheduled(cron = "0/10 * * * * ?")
    public void mailTask() {
        List<MailLog> list = mailLogService.list(new QueryWrapper<MailLog>().eq("status", 0).lt("tryTime", LocalDateTime.now()));
        list.forEach(mailLog -> {
            // 如果重试次数超过3次,更新状态为投递失败,不再重试
            if (3 <= mailLog.getCount()) {
                mailLogService.update(new UpdateWrapper<MailLog>().set("status", 2).eq("msgId", mailLog.getMsgId()));
            }
            mailLogService.update(new UpdateWrapper<MailLog>().set("count", mailLog.getCount() + 1).set("updateTime", LocalDateTime.now()).set("tryTime", LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT)).eq("msgId", mailLog.getMsgId()));
            Employee emp = employeeService.getEmployee(mailLog.getEid()).get(0);
            // 发送消息
            rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME, MailConstants.MAIL_ROUTING_KEY_NAME, emp, new CorrelationData(mailLog.getMsgId()));
        });
    }
}

在线聊天

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gLE31cb-1661933983140)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220708092159006.png)]

在yeb-server引入依赖

        <!--websocket 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

配置websocker

config/WebSocketConfig

package com.wyc.config;

import com.wyc.config.component.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @author:superBrother
 * @create: 2022-07-08 09:27
 * @Description: WebSocket配置类
 */
@Configuration  // 配置类必须要加上的注解
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    // 获取application.yml文件中配置的值
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 添加这个 Endpoint,这样在网页可以通过 websocket 连接上服务
     * 也就是我们配置 websocket 的服务地址,并且可以指定是否使用 socketJS,
     * 前端一般会使用 socketJS 去连接我们的服务
     *
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /**
         * 1.将 /ws/ep 路径注册为 stomp 的端点,用户连接了这个端点就可以进行 websocket 通讯,支持 socketJS
         * 2.setAllowedOrigins("*"):允许跨域
         * 3.withSockJS(): 支持 socketJS 访问
         */
        registry.addEndpoint("/ws/ep").setAllowedOrigins("*").withSockJS();
    }

    /**
     * 输入通道参数配置,使用了JWT令牌就需要在这里配置websocket
     *
     * @param registration
     */
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() { // 类似内部类
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                // 判断是否为连接,如果是,需要获取token,并且设置用户对象
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    // Auth-Token参数是前端发给我们的
                    String token = accessor.getFirstNativeHeader("Auth-Token");
                    if (!StringUtils.isEmpty(token)) {
                        String authToken = token.substring(tokenHead.length());
                        String username = jwtTokenUtil.getUserNameFromToken(authToken);

                        // token中存在用户名
                        if (!StringUtils.isEmpty(username)) {
                            // 登录
                            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                            // 验证token是否有效,重新设置用户对象
                            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                                UsernamePasswordAuthenticationToken authenticationToken =
                                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                                // 这里别忘了设置
                                accessor.setUser(authenticationToken);
                            }
                        }
                    }
                }
                return message;
            }
        });
    }

    /**
     * 配置消息代理
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 配置代理域,可以配置多个,配置代理目的地前缀为 /queue,可以在配置域上向客户端推送消息
        registry.enableSimpleBroker("/queue");
    }
}

websocker Controller

package com.wyc.mbg.controller;

import com.wyc.common.ChatMsg;
import com.wyc.mbg.pojo.Admin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

/**
 * @author:superBrother
 * @create: 2022-07-08 09:40
 * @Description: websocket
 */
@Controller  // 这里使用普通的@Controller而不是Restful风格的@RestController
public class WsController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/ws/chat")   // 这里的请求地址也跟其他的不一样,使用的是 @MessageMapping()
    public void handleMsg(Authentication authentication, ChatMsg chatMsg) {
        Admin admin = (Admin) authentication.getPrincipal();
        chatMsg.setFrom(admin.getUsername());
        chatMsg.setFormNickName(admin.getName());
        chatMsg.setDate(LocalDateTime.now());
        // 发送消息
        simpMessagingTemplate.convertAndSendToUser(chatMsg.getTo(), "/queue/chat", chatMsg);
    }
}

消息类

package com.wyc.common;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.time.LocalDateTime;

/**
 * @author:superBrother
 * @create: 2022-07-08 09:37
 * @Description: 消息
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ChatMsg {

    private String from;
    private String to;
    private String content;
    private LocalDateTime date;
    private String formNickName;

}

在线聊天 controller

package com.wyc.mbg.controller;

import com.wyc.mbg.pojo.Admin;
import com.wyc.mbg.service.IAdminService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @author:superBrother
 * @create: 2022-07-08 09:45
 * @Description: 在线聊天
 */
@RestController
@RequestMapping("/chat")
public class ChatController {

    @Autowired
    private IAdminService adminService;

    @ApiOperation(value = "获取所有操作员")
    @GetMapping("/admin")
    public List<Admin> getAllAdmins(String keywords) {
        return adminService.getAllAdmins(keywords);
    }
}

个人中心

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8wU3LOgK-1661933983140)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220708100009783.png)]

更新当前用户信息
Controller
package com.wyc.mbg.controller;

import com.wyc.common.RespBean;
import com.wyc.mbg.pojo.Admin;
import com.wyc.mbg.service.IAdminService;
import com.wyc.util.FastDFSUtils;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;

/**
 * @author:superBrother
 * @create: 2022-07-08 10:02
 * @Description: 个人中心
 */
@RestController
public class AdminInfoController {

    @Autowired
    private IAdminService adminService;

    @ApiOperation(value = "更新当前用户信息")
    @PutMapping("/admin/info")
    public RespBean updateAdmin(@RequestBody Admin admin, Authentication authentication){
        // 使用MyBatisPlus提供的updateById()方法进行更新
        if (adminService.updateById(admin)) {
            // 每次更新之后需要重新设置 SpringSecurity的  Authentication 对象
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(admin, null, authentication.getAuthorities()));
            return RespBean.success("更新成功!");
        }
        return RespBean.error("更新失败!");
    }

    @ApiOperation(value = "更新用户密码")
    @PutMapping("/admin/pass")
    public RespBean updateAdminPassword(@RequestBody Map<String, Object> info) {
        String oldPass = (String) info.get("oldPass");  // 旧密码
        String pass = (String) info.get("pass");  // 新密码
        Integer adminId = (Integer) info.get("adminId");  // 用户Id,用于指定用户的更新
        return adminService.updateAdminPassword(oldPass, pass, adminId);
    }

    @ApiOperation(value = "更新用户头像")
    @PostMapping("/admin/userface")
    public RespBean updateAdminUserFace(MultipartFile file, Integer id, Authentication authentication) {
        // 使用fdfs的工具类上传文件
        String[] filePath = FastDFSUtils.upload(file);
        // 获取上传文件的路径
        String url = FastDFSUtils.getTrackerUrl() + filePath[0] + "/" + filePath[1];
        return adminService.updateAdminUserFace(url, id, authentication);
    }
}

impl
package com.wyc.mbg.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wyc.common.RespBean;
import com.wyc.config.component.JwtTokenUtil;
import com.wyc.mbg.mapper.AdminRoleMapper;
import com.wyc.mbg.mapper.RoleMapper;
import com.wyc.mbg.pojo.Admin;
import com.wyc.mbg.mapper.AdminMapper;
import com.wyc.mbg.pojo.AdminRole;
import com.wyc.mbg.pojo.Role;
import com.wyc.mbg.service.IAdminRoleService;
import com.wyc.mbg.service.IAdminService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wyc.util.AdminUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author superBrother
 * @since 2022-07-02
 */
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {

    @Resource
    private AdminMapper adminMapper;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private AdminRoleMapper adminRoleMapper;




    /**
     * 登录之后返回token
     * @param username
     * @param password
     * @param code
     * @param request
     * @return
     */
    @Override
    public RespBean login(String username, String password, String code, HttpServletRequest request) {
        String captcha =(String) request.getSession().getAttribute("captcha"); //拿到验证码进行匹配
        if (StringUtils.isEmpty(code)||!captcha.equalsIgnoreCase(code)){  //如果验证码错误,直接返回
            return RespBean.error("验证码输入错误,请重新输入!");
        }
        //登录
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        //判断如果userDetails为空,或者前端的密码和userDetails加密后的密码匹配不成功,则返回错误。
        if (null == userDetails ||!passwordEncoder.matches(password,userDetails.getPassword())){
            return RespBean.error("用户名或密码不正确!");
        }
        //判断账号是否被禁用
        if(!userDetails.isEnabled()){
            return RespBean.error("账号被禁用,请联系管理员!");
        }
        //更新security登录用户对象用户登录之后,将用户信息放到springSecurity全局中
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //生成token
        //判断都通过,通过userDetails拿到令牌
        String token = jwtTokenUtil.generateToken(userDetails);
        //返回一个map,里面包含请求头,和这个token,并将提示信息和状态码也一起返回
        HashMap<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token",token);
        tokenMap.put("tokenHead",tokenHead);   //tokenHead通过@value注解来拿
        return RespBean.success("登录成功",tokenMap);
    }

    /**
     * 根据用户名获取用户信息
     * @param username
     * @return
     */
    @Override
    public Admin getAdminByUserName(String username) {
        return adminMapper.selectOne(new QueryWrapper<Admin>().eq("username",username).eq("enabled",true));
    }

    /**
     * 根据用户id查询角色列表
     * @param adminId
     * @return
     */
    @Override
    public List<Role> getRoles(Integer adminId) {
        return roleMapper.getRoles(adminId);
    }

    /**
     * 获取所有操作员
     * @param keywords
     * @return
     */
    @Override
    public List<Admin> getAllAdmins(String keywords) {
        return adminMapper.getAllAdmins(AdminUtils.getCurrentAdmin().getId(),keywords);
    }

    /**
     * 更新操作员角色
     * @param adminId
     * @param rids
     * @return
     */
    @Override
    @Transactional //因为这里涉及到了数据库的两个操作,所以加事务注解安全
    public RespBean updateAdminRole(Integer adminId, Integer[] rids) {
        //在更新之前需要进行删除
        adminRoleMapper.delete(new QueryWrapper<AdminRole>().eq("adminId",adminId));
        Integer result = adminRoleMapper.addAdminRole(adminId,rids);
        //如果受影响的行数是一样的,就更新成功。
        if(rids.length == result){
            return RespBean.success("更新成功!");
        }
        return RespBean.error("更新失败!");

    }

    /**
     * 更新用户密码
     * @param oldPass
     * @param pass
     * @param adminId
     * @return
     */
    @Override
    public RespBean updateAdminPassword(String oldPass, String pass, Integer adminId) {
        // 直接通过MyBatisPlus提供的方法selectById(),通过adminId获取用户信息对象
        Admin admin = adminMapper.selectById(adminId);
        // 前端传入的明文密码加密后,与数据库加密的密码比较
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        // 判断旧密码是否正确
        if (encoder.matches(oldPass, admin.getPassword())) {
            admin.setPassword(encoder.encode(pass));
            int result = adminMapper.updateById(admin);
            if (1 == result) {
                return RespBean.success("更新成功!");
            }
        }
        return RespBean.error("更新失败!");
    }

    /**
     * 更新用户头像
     * @param url
     * @param id
     * @param authentication
     * @return
     */
    @Override
    public RespBean updateAdminUserFace(String url, Integer id, Authentication authentication) {
        // 根据Id拿到Admin对象,直接使用MyBatisPlus提供的selectById()方法
        Admin admin = adminMapper.selectById(id);
        admin.setUserFace(url);
        int result = adminMapper.updateById(admin); // 更新头像
        if (1 == result) {
            // 还需要做一个SpringSecurity全局对象的更新,比如更新登录的用户对象
            Admin principal = (Admin) authentication.getPrincipal();
            principal.setUserFace(url);
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(admin, null, authentication.getAuthorities()));
            return RespBean.success("更新成功!", url);
        }
        return RespBean.error("更新失败!");
    }
}

更新密码 Controller
    @ApiOperation(value = "更新用户密码")
    @PutMapping("/admin/pass")
    public RespBean updateAdminPassword(@RequestBody Map<String, Object> info) {
        String oldPass = (String) info.get("oldPass");  // 旧密码
        String pass = (String) info.get("pass");  // 新密码
        Integer adminId = (Integer) info.get("adminId");  // 用户Id,用于指定用户的更新
        return adminService.updateAdminPassword(oldPass, pass, adminId);
    }

impl

    /**
     * 更新用户密码
     * @param oldPass
     * @param pass
     * @param adminId
     * @return
     */
    @Override
    public RespBean updateAdminPassword(String oldPass, String pass, Integer adminId) {
        // 直接通过MyBatisPlus提供的方法selectById(),通过adminId获取用户信息对象
        Admin admin = adminMapper.selectById(adminId);
        // 前端传入的明文密码加密后,与数据库加密的密码比较
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        // 判断旧密码是否正确
        if (encoder.matches(oldPass, admin.getPassword())) {
            admin.setPassword(encoder.encode(pass));
            int result = adminMapper.updateById(admin);
            if (1 == result) {
                return RespBean.success("更新成功!");
            }
        }
        return RespBean.error("更新失败!");
    }
自定义json反序列化
package com.wyc.config;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * @author:superBrother
 * @create: 2022-07-08 10:15
 * @Description: 自定义Authority解析器
 */
public class CustomAuthorityDeserializer extends JsonDeserializer {

    /**
     * 自定义Json序列化--hmf
     *
     * @param p
     * @param ctxt
     * @return
     * @throws IOException
     * @throws JsonProcessingException
     */
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) p.getCodec();
        JsonNode jsonNode = mapper.readTree(p);
        List<GrantedAuthority> grantedAuthorities = new LinkedList<>();
        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()) {
            JsonNode next = elements.next();
            JsonNode authority = next.get("authority");
            grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
        }
        return grantedAuthorities;
    }
}

更新头像

引入FastDFS依赖

        <!-- 解决依赖导入不成功的问题:http://www.qishunwang.net/knowledge_show_129043.aspx -->
        <!--FastDFS依赖,用于头像的更新-->
        <dependency>
            <groupId>org.csource</groupId>
            <artifactId>fastdfs-client-java</artifactId>
            <version>1.29-SNAPSHOT</version>
        </dependency>

FastDFS配置

/config/fdfs_client.conf

#连接超时
connect_timeout = 2
#网络超时
network_timeout = 30
#编码格式
charset = UTF-8
#tracker端口号
http.tracker_http_port = 8080
#防盗链功能
http.anti_steal_token = no
#秘钥
http.secret_key = FastDFS1234567890
#tracker ip:端口号,【因为可以是分布式的,所以这里可以配置多个】
tracker_server = 192.168.1.105:22122
#连接池配置
connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000

FastDFS配置类

package com.wyc.util;

import org.csource.fastdfs.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author:superBrother
 * @create: 2022-07-08 10:32
 * @Description: FastDFS工具类
 */
public class FastDFSUtils {

    private static Logger logger = LoggerFactory.getLogger(FastDFSUtils.class);

    /**
     * 初始化客户端
     * ClientGlobal.init 读取配置文件,并初始化对应的属性
     */
    static {
        try {
            String filePath = new ClassPathResource("fdfs_client.conf").getFile().getAbsolutePath();
            ClientGlobal.init(filePath);
        } catch (Exception e) {
            logger.error("初始化FastDFS失败", e.getMessage());
        }
    }

    /**
     * 上传文件
     *
     * @param file
     * @return
     */
    public static String[] upload(MultipartFile file) {
        String name = file.getOriginalFilename();
        logger.info("文件名:", name);
        StorageClient storageClient = null;
        String[] uploadResults = null;
        try {
            //获取storage客户端
            storageClient = getStorageClient();
            //上传
            uploadResults = storageClient.upload_file(file.getBytes(), name.substring(name.lastIndexOf(".") + 1),
                    null);
        } catch (Exception e) {
            logger.error("上传文件失败", e.getMessage());
        }
        if (null == uploadResults) {
            logger.error("上传失败", storageClient.getErrorCode());
        }
        return uploadResults;
    }

    /**
     * 获取文件信息
     *
     * @param groupName
     * @param remoteFileName
     * @return
     */
    public static FileInfo getFileInfo(String groupName, String remoteFileName) {
        StorageClient storageClient = null;
        try {
            storageClient = getStorageClient();
            return storageClient.get_file_info(groupName, remoteFileName);
        } catch (Exception e) {
            logger.error("文件信息获取失败", e.getMessage());
        }
        return null;
    }

    /**
     * 下载文件
     *
     * @param groupName
     * @param remoteFileName
     * @return
     */
    public static InputStream downFile(String groupName, String remoteFileName) {
        StorageClient storageClient = null;
        try {
            storageClient = getStorageClient();
            byte[] fileByte = storageClient.download_file(groupName, remoteFileName);
            InputStream inputStream = new ByteArrayInputStream(fileByte);
            return inputStream;
        } catch (Exception e) {
            logger.error("文件下载失败", e.getMessage());
        }
        return null;
    }

    /**
     * 删除文件
     *
     * @param groupName
     * @param remoteFileName
     */
    public static void deleteFile(String groupName, String remoteFileName) {
        StorageClient storageClient = null;
        try {
            storageClient = getStorageClient();
            storageClient.delete_file(groupName, remoteFileName);
        } catch (Exception e) {
            logger.error("文件删除失败", e.getMessage());
        }
    }


    /**
     * 生成storage客户端
     *
     * @return
     * @throws IOException
     */
    private static StorageClient getStorageClient() throws IOException {
        TrackerServer trackerServer = getTrackerServer();
        StorageClient storageClient = new StorageClient(trackerServer, null);
        return storageClient;
    }


    /**
     * 生成tracker服务器
     *
     * @return
     * @throws IOException
     */
    private static TrackerServer getTrackerServer() throws IOException {
        TrackerClient trackerClient = new TrackerClient();
        TrackerServer trackerServer = trackerClient.getTrackerServer();
        return trackerServer;
    }


    /**
     * 获取文件路径
     *
     * @return
     */
    public static String getTrackerUrl() {
        TrackerClient trackerClient = new TrackerClient();
        TrackerServer trackerServer = null;
        StorageServer storeStorage = null;
        try {
            trackerServer = trackerClient.getTrackerServer();
            storeStorage = trackerClient.getStoreStorage(trackerServer);
        } catch (Exception e) {
            logger.error("文件路径获取失败", e.getMessage());
        }
        // 这里的端口号 8888 也需要根据你的具体配置来写
        return "http://" + storeStorage.getInetSocketAddress().getHostString() + ":8888/";
    }

}

更新头像Controller
    @ApiOperation(value = "更新用户头像")
    @PostMapping("/admin/userface")
    public RespBean updateAdminUserFace(MultipartFile file, Integer id, Authentication authentication) {
        // 使用fdfs的工具类上传文件
        String[] filePath = FastDFSUtils.upload(file);
        // 获取上传文件的路径
        String url = FastDFSUtils.getTrackerUrl() + filePath[0] + "/" + filePath[1];
        return adminService.updateAdminUserFace(url, id, authentication);
    }
更新头像impl
    /**
     * 更新用户头像
     * @param url
     * @param id
     * @param authentication
     * @return
     */
    @Override
    public RespBean updateAdminUserFace(String url, Integer id, Authentication authentication) {
        // 根据Id拿到Admin对象,直接使用MyBatisPlus提供的selectById()方法
        Admin admin = adminMapper.selectById(id);
        admin.setUserFace(url);
        int result = adminMapper.updateById(admin); // 更新头像
        if (1 == result) {
            // 还需要做一个SpringSecurity全局对象的更新,比如更新登录的用户对象
            Admin principal = (Admin) authentication.getPrincipal();
            principal.setUserFace(url);
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(admin, null, authentication.getAuthorities()));
            return RespBean.success("更新成功!", url);
        }
        return RespBean.error("更新失败!");
    }

查询网址ip:http://tool.chinaz.com/dns/

云E办前端

前期项目搭建。

创建utils,编写api.js文件。

下载相应的插件

package.json

{
  "name": "yeb",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
  "dependencies": {
    "axios": "^0.24.0",
    "core-js": "^3.6.5",
    "element-ui": "^2.15.6",
    "vue": "^2.6.11",
    "vue-form": "^4.10.3",
    "vue-router": "^3.2.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "vue-template-compiler": "^2.6.11"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}
import axios from "axios";
import { Message } from "element-ui";
// import { config } from "vue/types/umd";
import router from '../router';

//请求拦截器
axios.interceptors.request.use(config => {
        //如果存在token,请求携带这个token
        if (window.sessionStorage.getItem('tokenStr')) { //先判断是否有token,请求有没有拿到token
            config.headers['Authorization'] = window.sessionStorage.getItem('tokenStr'); //将存储的token赋值给config.headers的key['Authorization'] 
        }
        return config;

    }, error => {
        console.log(error);

    })
    //响应拦截器
axios.interceptors.response.use(success => {
    //业务逻辑错误,已经成功调到后端接口
    if (success.status && success.status == 200) {
        if (success.data.code == 500 || success.data.code == 401 || success.data.code == 403) { //code是这几种,代表逻辑错误
            Message.error({ message: success.data.message }); //message:键    success.data.message:值,后端接口返回的信息
            return;
        }
        if (success.data.message) { //请求成功,但是要判断接口请求成功是否有返回提示信息,如果有就将提示信息返回到message对象中
            Message.success({ message: success.data.message });
        }
    }
    return success.data; //将拿到的数据返回出去。
}, 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(location, '/'); //如果是未登录,就跳转到登录页面去登录
    } else {
        if (error.response.data.message) { //判断有没有错误响应的信息
            Message.error({ message: error.response.data.message }); //有响应的信息赋值出去
        } else {
            Message.error({ message: '未知错误!' }); //错误信息都没有,直接报错
        }
    }
    return;
})

let base = ''; //前置路径,一般大项目会加
//传送json格式的post请求
export const postRequest = (url, params) => {
        return axios({
            method: 'post',
            url: `${base}${url}`,
            data: params
        })
    }
    //传送json格式的put请求
export const putRequest = (url, params) => {
        return axios({
            method: 'put',
            url: `${base}${url}`,
            data: params
        })
    }
    //传送json格式的get请求
export const getRequest = (url, params) => {
        return axios({
            method: 'get',
            url: `${base}${url}`,
            data: params
        })
    }
    //传送json格式的delete请求
export const deleteRequest = (url, params) => {
    return axios({
        method: 'delete',
        url: `${base}${url}`,
        data: params
    })
}
创建vue.config.js解决跨域,代理问题
let proxyObj = {}

proxyObj['/'] = {
    //websocket 普通接口代理
    ws: false,
    //目标地址
    target: 'http://localhost:8081/',
    //发送请求头host会被设置target
    changeOrigin: true,
    //不重写请求地址
    pathReWrite: {
        '^/': '/'
    }
}


module.exports = {
    devServer: {
        host: 'localhost',
        port: 8080,
        proxy: proxyObj
    }
}
main.js

全局的引用,如,将api的所有请求全局引入,然后使用,其他页面直接this.就可直接使用,不需要每个页面引入。

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import VueForm from 'vue-form'
import { postRequest } from "./utils/api"
import { putRequest } from "./utils/api"
import { getRequest } from "./utils/api"
import { deleteRequest } from "./utils/api"
Vue.config.productionTip = false
Vue.use(ElementUI);
Vue.use(VueForm);
//插件形式使用请求
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;



new Vue({
    router,
    render: h => h(App)
}).$mount('#app')

router/index.js

编写页面请求路径

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Home from '../views/Home.vue'
import Test1 from '../views/test1.vue'
import Test2 from '../views/test2.vue'
Vue.use(VueRouter)

const routes = [{
        path: '/',
        name: 'Login',
        component: Login
    },
    {
        path: '/Home',
        name: 'Home',
        component: Home,
        //子路由,在home页面显示
        children: [{
                path: '/Test1',
                name: 'Test1',
                component: Test1
            },
            {
                path: '/Test2',
                name: 'Test2',
                component: Test2
            },
        ]
    },
]

const router = new VueRouter({
    routes
})

export default router

view文件夹下页面

登录login.vue
<template>
  <div>
    <el-form ref="loginForm"     
    v-loading="loading"
    element-loading-text="正在登录..."
    element-loading-spinner="el-icon-loading"
    element-loading-background="rgba(0,0,0,0.8)"
    :rules="rules" :model="loginForm" 
    label-width="80px" 
    class="loginContainer">
           <h2 class="logintitle">系统登录</h2>
           <el-form-item prop="username">
             <el-input type="text" auto-complete="false" v-model="loginForm.username" placeholder="请输入用户名"></el-input>
           </el-form-item>
            <el-form-item prop="password">
             <el-input type="password" auto-complete="false" v-model="loginForm.password" placeholder="请输入密码"></el-input>
           </el-form-item>
            <el-form-item prop="code">
             <el-input type="text" auto-complete="false" v-model="loginForm.code" placeholder="点击图片,更换验证码" style="width:200px;margin-right:5px"></el-input>
             <img :src="captchaUrl" @click="updateCaptcha">
           </el-form-item>
             <el-checkbox v-model="checked" class="loginRemember">记住我</el-checkbox>
             <el-button type="primary" style="width:100%" @click="submitLogin">登录</el-button>
    </el-form>
  </div>
</template>
<script>
export default {
  name: "Login",
  data() {
    return {
      captchaUrl:'/captcha?time='+new Date(),
      loginForm:{
        username:'admin',
        password:'123456',
        code:''
      },
      loading:false,  //false,没有加载,ture,正在加载中
      checked:true,
      rules:{
        username:[{required:true, message:'请输入用户名',trigger:'blur'}],
        password:[{required:true, message:'请输入用户名',trigger:'blur'}],
        code:[{required:true, message:'请输入用户名',trigger:'blur'}],
      },
    };
  },
  methods: {
    updateCaptcha(){
    this.captchaUrl ='/captcha?time='+new Date();   //确保验证码随时刷新
    console.log("------------");
    console.log(this.captchaUrl);
    },
    submitLogin(){
        this.$refs.loginForm.validate((valid) => {
          if (valid) {
            this.loading = true;  //登录时正在加载
            //this.postRequser通过this全局调用请求,在main.js全局引用api请求。
            this.postRequest('/login',this.loginForm).then(resp=>{
              console.log(resp);
                if(resp){
                  this.loading = false; //登录成功取消加载
                  const tokenStr = resp.obj.tokenHead+resp.obj.token;  //拿到请求token
                  //存储用户token到sessionStorage.
                  window.sessionStorage.setItem('tokenStr',tokenStr);
                  //跳转页面
                  this.$router.replace('/home')  //replace 回退按钮回不去之前的页面,要是push就可以回去。
                }else{
                  this.loading = false; //登录成功取消加载 登录失败也要取消加载  //自己写的else
                }
             })
          } else {
            this.$message.error('请输入所有信息');
            return false;
          }
        });
    }  
  }
};
</script>

<style >
.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;
}
.logintitle{
  margin:0px auto 40 auto;
  text-align: center;
}
.loginRemember{
  text-align: left;
  margin:0px 0px 15px 0px;
}
.el-form-item__content{
display: flex;
align-content: center;  
}
</style>
主页面Home.vue

安装vuex,保存数据

在src目录下新建store文件,编辑index.js,并在main.js引入,在new Vue中添加store.

store/index.js

import Vue from "vue";
import Vuex from 'vuex'


Vue.use(Vuex);


export default new Vuex.Store({
    state: {
        routes: []
    },

    //同步,改变state
    mutations: {
        initRoutes(state, data) {
            state.routes = data;
        }
    },
    //异步
    actions: {}
})

封装菜单请求工具类

在utils/menus.js编写
import { getRequest } from "./api";

export const initMenu = (router, store) => { //初始化菜单
    if (store.state.routes.length > 0) { //判断菜单是否存在,如果存在就返回。
        return;
    }


    getRequest('/system/config/menu').then(data => {
        if (data) { //判断是否拿到数据,
            let fmtRoutes = formatRoutes(data); //如果存在,格式化router.
            router.addRoutes(fmtRoutes); //将格式和的路由添加到路由里面
            //将数据存入vuex
            store.commit('initRoutes', fmtRoutes);
        }
    })
}


export const formatRoutes = (routes) => {
    let fmtRoutes = []; //格式化路由的数组。
    routes.forEach(router => {
        let {
            path,
            component,
            name,
            iconCls,
            children,
        } = router;
        if (children && children instanceof Array) { //判断存在children,并且是一个数组。
            //递归
            children = formatRoutes(children);
        }
        let fmRouter = {
            path: path, //路径
            name: name, //名称
            iconCls: iconCls, //图标
            children: children,
            component(resolve) {
                require(['../views/' + component + '.vue'], resolve);
            }
        }
        fmtRoutes.push(fmRouter) //将格式化的fmRouter放到fmtRoutes中
    });
    return fmtRoutes;
}
解决用户在页面刷新,导致vuex数据丢失

在main.js编写全局路由守卫

//全局路由前置守卫
router.beforeEach((to, from, next) => { //to跳转的路由;from:当前路由;next:调用方法,resolve钩子,实现真正的跳转
    if (window.sessionStorage.getItem('tokenStr')) { //判断sessionStorage是否有tokenStr,有说明用户已经登录
        initMenu(router, store); //登录的话,初始化菜单。
        next();
    } else {
        if (to.path == '/') {
            //判断是否去的登录页, 如果是就next
            next();
        }
    }
})

在home主页面通过计算属性在vuex拿到routes

  computed:{
    routes(){
      return this.$store.state.routes();
    }
  },
      

直接in routes就可以了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jh50uYa3-1661933983141)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606200433515.png)]

vuex报错,版本过高,不匹配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0dikzJjs-1661933983141)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606201426521.png)]

解决办法:

先卸载Vue4.xx

npm uninstall vuex --save

在安装3.xx

npm install vuex@3 --save
安装图标
cnpm install font-awesome

在main.js导入图标

import 'font-awesome/css/font-awesome.css'

在public/index.html修改样式,将margin 和padding设置为0.去掉默认边框

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MEez9Fiw-1661933983142)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606211816398.png)]

用户信息渲染

main.js继续判断用户信息是否存在,不存在进行请求,然后在主页面Home,拿到用户信息,进行头像渲染。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRNBg5IT-1661933983142)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606215552861.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qiiSz4qu-1661933983142)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606215504013.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3ZQOTT2-1661933983143)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220606215525943.png)]

退出登录

在el-dropdown组件里面 添加@command=“commandHandler”事件,在el-dropdown-item 组件绑定

command=“logout”。

在methods里面编写js,进行注销操作

    commandHandler(command) {
      if (command == "logout") {        //判断传进的command是什么,如果是logout就进行退出操作。
        this.$confirm("此操作将注销登录, 是否继续?", "提示", {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        })
          .then(() => {
            //注销登录
            this.postRequest("logout");
            //清空token,用户信息,菜单
            window.sessionStorage.removeItem("tokenStr");
            window.sessionStorage.removeItem("user");
            this.$router.commit('initRoutes',[])
            //跳转登录页
            this.$router.replace("/");
          })
          .catch(() => {
            this.$message({
              type: "info",
              message: "已取消操作",
            });
          });
      }
    },
首页面包屑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xAg8tJvm-1661933983143)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607120505141.png)]

解决用户直接复制页面地址,出现空白页的问题,

如果用户没有登录,让他先去登录页面,进行登录之后,直接跳转,刚刚访问的路径页面。

main.js全局路由守卫

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lero4341-1661933983143)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607150749063.png)]

login登录页。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z2Jeha9E-1661933983144)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607150619209.png)]

删除index.js Home路径,登录跳转home页面时报错

vue-router.esm.js?8c4f:2065 Uncaught (in promise) Error: Navigation cancelled from “/” to “/home” with a new navigation.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5oIf8lG-1661933983144)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607153120535.png)]

解决办法:


选项卡和面包屑太近解决办法:

在home.vue里面的router-view 设置样式,margin-top:10px

在components建文件夹,sys/basic/ 然后创建对应的页面,作为组件,只需要在SysBasic页面引入组件,并且注册组件,然后使用组件即可。

引入组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFsFHr97-1661933983144)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607165730333.png)]

注册组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjmCNAGa-1661933983145)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607165829572.png)]

使用组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wirejs0F-1661933983145)(C:\Users\x大大怪\AppData\Roaming\Typora\typora-user-images\image-20220607165623980.png)]

键盘enter回车事件: @keydown.enter.native=“addPosition” el-input组件生效

解决表单未确定修改,或者直接赋值表单同步覆盖问题

使用拷贝

Object.assign(this.updatePos,data); 

posMana.vue

<template>
  <div id="posMana">
    <div>
      <el-input
        size="small"
        class="addPosInput"
        placeholder="添加职位"
        suffix-icon="el-icon-plus"
        @keydown.enter.native="addPosition"
        v-model="pos.name"
      >
      </el-input>
      <el-button
        size="small"
        icon="el-icon-plus"
        type="primary"
        @click="addPosition"
        >添加</el-button
      >
    </div>
    <div class="posManaMain">
      <el-table
        size="small"
        stripe
        border
        :data="positions"
        @selection-change="handleSelectionChange"
        style="width: 100%"
      >
        <el-table-column type="selection" width="55"> </el-table-column>
        <el-table-column prop="id" label="编号" width="180"> </el-table-column>
        <el-table-column prop="name" label="职位" width="180">
        </el-table-column>
        <el-table-column prop="createDate" label="创建时间"> </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <el-button
              size="mini"
              @click="showEditView(scope.$index, scope.row)"
              >编辑</el-button
            >
            <el-button
              size="mini"
              type="danger"
              @click="handleDelete(scope.$index, scope.row)"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-button
      size="samll"
      style="margin-top: 8px"
      type="danger"
      :disabled="multipleSelection.length==0"
      @click="deleteMany"
    >批量删除</el-button>
    <el-dialog title="编辑职位" :visible.sync="dialogVisible" width="35%">
      <div>
        <el-tag>职位名称</el-tag>
        <el-input
          v-model="updatePos.name"
          size="small"
          class="updatePosInput"
        ></el-input>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button size="small" @click="dialogVisible = false">取 消</el-button>
        <el-button size="small" type="primary" @click="doUpdate"
          >确 定</el-button
        >
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "posMana",
  data() {
    return {
      pos: {
        name: "",
      },
      positions: [],
      dialogVisible: false,
      updatePos: {
        name: "",
      },
      multipleSelection: []
    };
  },
  mounted() {
    this.initPositions();
  },
  methods: {
    deleteMany() {
      this.$confirm(
        "此操作将永久删除[" + this.multipleSelection.length + "]条职位, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          let ids = '?';
          this.multipleSelection.forEach(item=>{
            ids+= 'ids='+item.id+ '&';
          })
          this.deleteRequest("/system/basic/pos/" + ids).then((resp) => {
            if (resp) {
              this.initPositions();
            }
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    handleSelectionChange(val){
      console.log(val);
      this.multipleSelection = val;
    },
    doUpdate() {
      this.putRequest("/system/basic/pos/", this.updatePos).then((resp) => {
        if (resp) {
          this.initPositions();
          this.dialogVisible = false;
        }
      });
    },
    addPosition() {
      if (this.pos.name) {
        this.postRequest("/system/basic/pos/", this.pos).then((resp) => {
          if (resp) {
            this.initPositions();
            this.pos.name = ""; //添加完将输入框内容变为空。
          }
        });
      } else {
        this.$message.error("职位名称不能为空!");
      }
    },
    showEditView(index, data) {
      // this.updatePos = data;  //直接赋值会表单会同步覆盖
      Object.assign(this.updatePos, data); //使用拷贝
      this.updatePos.createDate = ""; //创建时间为空,不然后端错误
      this.dialogVisible = true;
    },
    handleDelete(index, data) {
      //data对应每一条json的数据
      this.$confirm(
        "此操作将永久删除[" + data.name + "]职位, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          this.deleteRequest("/system/basic/pos/" + data.id).then((resp) => {
            if (resp) {
              this.initPositions();
            }
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    initPositions() {
      this.getRequest("/system/basic/pos/").then((resp) => {
        if (resp) console.log(resp);
        this.positions = resp;
      });
    },
  },
};
</script>

<style  scoped>
.addPosInput {
  width: 200px;
  margin-right: 10px;
}
.posManaMain {
  margin-top: 10px;
}
.updatePosInput {
  width: 200px;
  margin-left: 10px;
}
</style>
批量删除

在el-table绑定@selection-change="handleSelectionChange"方法,在return定义multipleSelection: [],在methods编写handleSelectionChange方法的事件,

    handleSelectionChange(val){
      console.log(val);     //拿到选择行的所有信息。
      this.multipleSelection = val;
    },

批量删除html

    <el-button
      size="samll"
      style="margin-top: 8px"
      type="danger"
      :disabled="multipleSelection.length==0"  当未选择时,禁用批量删除。
      @click="deleteMany"  绑定批量删除方法
    >批量删除</el-button>

批量删除方法事件

    deleteMany() {
      this.$confirm(
        "此操作将永久删除[" + this.multipleSelection.length + "]条职位, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          let ids = '?'; //?传参
          this.multipleSelection.forEach(item=>{
            ids+= 'ids='+item.id+ '&'; //拼接
          })
          this.deleteRequest("/system/basic/pos/" + ids).then((resp) => {
            if (resp) {
              this.initPositions();
            }
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

权限组页面设计

<template>
  <div id="PermissMana">
    <div class="permissManaTool">
      <el-input size="small" placeholder="请输入角色英文名" v-model="role.name">
        <template slot="prepend">ROLE_</template>
      </el-input>
      <el-input
        size="small"
        v-model="role.nameZh"
        placeholder="请输入角色中文名"

      ></el-input>
      <el-button size="small" type="primary" icon="el=icon-plus" @click="doAddRole"
        >添加角色</el-button
      >
    </div>
    <div class="permissManaMain">
      <el-collapse v-model="activeName" accordion @change="change">
        <el-collapse-item
          :title="r.nameZh"
          :name="r.id"
          v-for="(r, index) in roles"
          :key="index"
        >
          <el-card class="box-card">
            <div slot="header" class="clearfix">
              <span>可访问资源</span>
              <el-button
                style="float: right; padding: 3px 0; color: #ff0000"
                type="text"
                icon="el-icon-delete"
                @click="doDeleteRole(r)"
              ></el-button>
            </div>
            <div>
              <!-- default-checked-keys:默认选中的节点。
              需要注意的是,此时必须设置node-key,其值为节点数据中的一个字段名,该字段在整棵树中是唯一的,id就是唯一的,满足-->
              <el-tree
                 show-checkbox
                :data="allMenus"
                :props="defaultProps"
                ref="tree"
                :default-checked-keys="selectedMenus"
                node-key="id" 
              ></el-tree>
              <div style="display:flex;justify-content:flex-end">
                <el-button size="mini" @click="cancelUpdate">取消修改</el-button>
                <el-button size="mini" type="primary" @click="doUpdate(r.id,index)">确认修改</el-button>
              </div>
            </div>
          </el-card>
        </el-collapse-item>
      </el-collapse>
    </div>
  </div>
</template>

<script>
export default {
  name: "PermissMana",
  data() {
    return {
      role: {
        name: '',
        nameZh: '',
      },
      roles: [],
      allMenus:[],
      defaultProps: {
        children: "children", //allMenus对应的属性,子菜单
        label: "name",      //allMenus对应的属性菜单的组件名
      },
      selectedMenus:[],
      activeName: -1  //默认-1,是关闭折叠板。

    };
  },
  mounted() {
    this.initRoles();
  },
  methods: {
    doDeleteRole(role){
      this.$confirm(
        "此操作将永久删除[" + role.nameZh + "]角色, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          this.deleteRequest("/system/basic/permiss/role/" + role.id).then(
            (resp) => {
              if (resp) {
                this.initRoles();
              }
            }
          );
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    cancelUpdate(){
      this.activeName = -1;
    },
    doUpdate(rid,index){
      let tree = this.$refs.tree[index];
      let selectedKeys = tree.getCheckedKeys(true);  //true,就不会打印根节点,只会打印叶子节点,getCheckedKeys拿到选到的节点。
      console.log(selectedKeys);
      let url = '/system/basic/permiss/?rid=' + rid;
      selectedKeys.forEach(key =>{
        url += '&mids=' + key;
      });
      this.putRequest(url).then(resp=>{
        if(resp){
          this.initRoles();
          this.activeName = -1;  //关闭折叠面板。
        }
      })
    },
    doAddRole(){
      if(this.role.name && this.role.nameZh){
        console.log("0000000000");
          this.postRequest('/system/basic/permiss/role',this.role).then(resp=>{
            if(resp){
              this.initRoles();
              this.role.name = '';
              this.role.nameZh = '';
            }
          })
      }else{
        // this.message.error('所有字段不能为空!');
      }
    },
    cancelUpdate(){
      this.activeName = -1;
    },
    change(rid){  //change传的值是el-collapse-item组件的name属性,name绑定的是r.id,所以拿到的是id值
      if(rid){  //判断如果拿到id就刷新菜单,根据id值来加载获取的菜单
         this.initAllMenus();
         this.initSelectedMenus(rid);
      }
    },
    initSelectedMenus(rid){
      this.getRequest('/system/basic/permiss/mid/'+rid).then(resp=>{
        if(resp){
          this.selectedMenus = resp;
        }
      })
    },
    initAllMenus(){
      this.getRequest('/system/basic/permiss/menus').then(resp=>{
        if(resp){
          this.allMenus = resp;
        }
      })
    },
    initRoles() {
      this.getRequest("/system/basic/permiss/").then((resp) => {
        if (resp) {
          this.roles = resp;
        }
      });
    },
  },
};
</script>

<style scoped>
.permissManaTool {
  display: flex;
  justify-content: flex-start;
}
.permissManaTool .el-input {
  width: 350px;
  margin-right: 8px;
}
.permissManaMain {
  margin-top: 10px;
  width: 700px;
}
</style>
部门管理
<template>
  <div style="width: 500px">
    <el-input
      placeholder="请输入部门名称进行搜索..."
      prefix-icon="el-icon-search"
      v-model="filterText"
    >
    </el-input>

    <el-tree
      style="margin-top: 10px"
      :data="deps"
      :props="defaultProps"
      :filter-node-method="filterNode"
      :expand-on-click-node="false"
      ref="tree"
    >
      <span
        class="custom-tree-node"
        slot-scope="{ node, data }"
        style="display: flex; justify-content: space-between; width: 100%"
      >
        <span>{{ data.name }}</span>
        <span>
          <el-button
            class="depBtn"
            type="primary"
            size="mini"
            @click="() => showAddDep(data)"
          >
            添加部门
          </el-button>
          <el-button
            class="depBtn"
            type="danger"
            size="mini"
            @click="() => deleteDep(data)"
          >
            删除部门
          </el-button>
        </span>
      </span>
    </el-tree>
    <el-dialog title="添加部门" :visible.sync="dialogVisible" width="30%">
      <div>
        <table>
          <tr>
            <td>
              <el-tag>上级部门</el-tag>
            </td>
            <td>{{ pname }}</td>
          </tr>
          <tr>
            <td>
              <el-tag>部门名称</el-tag>
            </td>
            <td>
              <el-input
                v-model="dep.name"
                placeholder="请输入部门名称..."
              ></el-input>
            </td>
          </tr>
        </table>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="doAddDep">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "DepMana",
  data() {
    return {
      filterText: "",
      deps: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
      dialogVisible: false,
      dep: {
        name: "",
        parentId: -1,
      },
      pname: "",
    };
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val);
    },
  },
  mounted() {
    this.initDeps();
  },
  methods: {
    initDep() {
      this.dep = {
        name: "",
        parentId: -1,
      };
      this.pname = "";
    },
    addDep2Deps(deps, dep) {
      for (let i = 0; i < deps.length; i++) {
        let d = deps[i]; //父部门
        if (d.id == dep.parentId) {
          d.children = d.children.concat(dep);
          if(d.children.length > 0){
            d.isParent = true;
          }
          return;
        } else {
          this.addDep2Deps(d.children, dep);
        }
      }
    },
    doAddDep() {
      this.postRequest("/system/basic/department/", this.dep).then((resp) => {
        if (resp) {
          this.addDep2Deps(this.deps, resp.obj);
          this.dialogVisible = false;
          this.initDep();
        }
      });
    },
    showAddDep(data) {
      console.log(data);
      this.dep.parentId = data.id;
      this.pname = data.name;
      this.dialogVisible = true;
    },
    removeDepFromDeps(p,deps,id){
      for(let i = 0; i < deps.length; i++){
        let d = deps[i];
        if(d.id == id){
          deps.splice(i, 1);
          if(deps.length==0){
            p.isParent = false;
          }
          return;
        }else{
          this.removeDepFromDeps(d,d.children,id);
        }
      }

    },
    deleteDep(data) {
      console.log(data);
      if (data.isParent) {
        this.$message.error("父部门删除失败!");
      } else {
        this.$confirm(
          "此操作将永久删除[" + data.name + "]部门, 是否继续?",
          "提示",
          {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning",
          }
        )
          .then(() => {
            this.deleteRequest("/system/basic/department/" + data.id).then(
              (resp) => {
                if (resp) {
                  this.removeDepFromDeps(null,this.deps, data.id);
                }
              }
            );
          })
          .catch(() => {
            this.$message({
              type: "info",
              message: "已取消删除",
            });
          });
      }
    },
    initDeps() {
      this.getRequest("system/basic/department/").then((resp) => {
        if (resp) {
          this.deps = resp;
        }
      });
    },
    filterNode(value, data) {
      if (!value) return true;
      return data.name.indexOf(value) !== -1;
    },
  },
};
</script>

<style scoped>
.depBtn {
  padding: 2px;
}
</style>

操作员

问题:绑定enter事件没反应

操作员管理

<template>
  <div>
    <div style="display: flex; justify-content: center; margin-top: 10px">
      <el-input
        v-model="keywords"
        placeholder="通过用户名搜索用户..."
        prefix-icon="el-icon-search"
        style="width: 400px; margin-right: 10px"
      ></el-input>
      <el-button
        type="primary"
        icon="el-icon-search"
        @click="doSearch"
        @keyup.enter.native="doSearch"
        >搜索</el-button
      >
    </div>
    <div class="admin-container">
      <el-card class="admin-card" v-for="(admin, index) in admins" :Key="index">
        <div slot="header" class="clearfix">
          <span>{{ admin.name }}</span>
          <el-button
            style="float: right; padding: 3px 0; color: red"
            type="text"
            icon="el-icon-delete"
            @click="deleteAdmin(admin)"
          ></el-button>
        </div>
        <div>
          <div class="img-container">
            <img
              :src="admin.userFace"
              :alt="admin.name"
              :title="admin.name"
              class="userface-img"
            />
          </div>
        </div>
        <div class="userinfo-container">
          <div>用户名:{{ admin.name }}</div>
          <div>手机号码:{{ admin.phone }}</div>
          <div>电话号码:{{ admin.telephone }}</div>
          <div>地址:{{ admin.address }}</div>
          <div>
            用户状态:
            <el-switch
              v-model="admin.enabled"
              active-color="#13ce66"
              inactive-color="#ff4949"
              @change="enabledChange(admin)"
              active-text="启用"
              inactive-text="禁用"
            ></el-switch>
          </div>
          <div>
            用户角色:
            <el-tag
              style="margin-right: 6px"
              type="success"
              v-for="(role, indexj) in admin.roles"
              :key="indexj"
              >{{ role.nameZh }}</el-tag
            >
            <el-popover
              placement="right"
              title="角色列表"
              width="200"
              @show="showPop(admin)"
              @hide="hidePop(admin)"
            >
              <el-select v-model="selectedRoles" multiple placeholder="请选择">
                <el-option
                  v-for="(r,index) in allRoles"
                  :key="index"
                  :label="r.nameZh"
                  :value="r.id"
                >
                </el-option>
              </el-select>
              <el-button
                slot="reference"
                type="text"
                icon="el-icon-more"
              ></el-button>
            </el-popover>
          </div>
          <div>备注:{{ admin.remark }}</div>
        </div>
      </el-card>
    </div>
  </div>
</template>
  
<script>
export default {
  name: "SysAdmin",
  data() {
    return {
      admins: [],
      keywords: "",
      allRoles:[],
      selectedRoles:[]
    };
  },
  mounted() {
    this.initAdmins();
  },
  methods: {
    hidePop(admin){
      let roles = [];
      Object.assign(roles,admin.roles);
      let flag = false;
      if(roles.length!=this.selectedRoles.length){
        flag = true;
      }else {
        for(let i =0; i<roles.length;i++){
          let role = roles[i];
          for(let j =0; j < this.selectedRoles.length; j++){
            let sr = this.selectedRoles[j];
            if(role.id ==sr){
              roles.splice(i,1);
              i--;
              break;
            }
          }
        }
        if(roles.length !=0){
          flag = true;
        }
      }
      if(flag){
      let url = '/system/admin/role?adminId=' + admin.id;
      this.selectedRoles.forEach(sr => {
        url += '&rids=' +sr;
      });
      this.putRequest(url).then(resp =>{
        if(resp){
          this.initAdmins();
        }
      })
      }
    },
    showPop(admin){
      this.initAllRoles();
      let roles = admin.roles;
      this.selectedRoles = [];
      roles.forEach(r => {
        this.selectedRoles.push(r.id);
      })
    },
    initAllRoles(){
      this.getRequest('/system/admin/roles').then(resp=>{
        if(resp){
          this.allRoles = resp
        }
      })
    },
    enabledChange(admin) {
      this.putRequest("system/admin/", admin).then((resp) => {
        if (resp) {
          this.initAdmins();
        }
      });
    },
    deleteAdmin(admin) {
      this.$confirm(
        "此操作将永久删除[" + admin.name + "]操作员, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          this.deleteRequest("/system/admin/" + admin.id).then((resp) => {
            if (resp) {
              this.initAdmins();
            }
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    doSearch() {
      this.initAdmins();
    },
    initAdmins() {
      this.getRequest("/system/admin/?keywords=" + this.keywords).then(
        (resp) => {
          if (resp) {
            this.admins = resp;
          }
        }
      );
    },
  },
};
</script>

<style scoped>
.admin-container {
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
  margin-top: 10px;
}
.admin-card {
  width: 450px;
  margin-bottom: 20px;
}
.userface-img {
  width: 80px;
  height: 80px;
  border-radius: 80px;
}
.img-container {
  width: 100%;
  display: flex;
  justify-content: center;
}
.userinfo-container {
  font-size: 12px;
  color: rgb(241, 136, 37);
}
</style>

员工资料

遇到问题:

1.Cannot read properties of undefined (reading ‘getItem’)"

window大写了

2.“SyntaxError: Unexpected token u in JSON at position 0”

判空写法错误

错误写法: if (!window.sessionStorage.getItem(“nations”))

正确写法: if (window.sessionStorage.getItem(“nations”)==null)

员工资料页面
<template>
  <div>
    <div>
      <div style="display: flex; justify-content: space-between">
        <div>
          <el-input
            style="width: 300px; margin-right: 10px"
            prefix-icon="el-icon-search"
            v-model="empName"
            @keydown.enter.native="initEmps"
            clearable
            @clear="initEmps"
            :disabled="showAdvanceSearchVisible"
            placeholder="请输入员工名进行搜索..."
          ></el-input>
          <el-button type="primary" icon="el-icon-search" @click="initEmps" :disabled="showAdvanceSearchVisible"
            >搜索</el-button
          >
          <el-button type="primary" @click="showAdvanceSearchVisible = !showAdvanceSearchVisible">
            <i :class="showAdvanceSearchVisible?'fa fa-angle-double-up':'fa fa-angle-double-down'" aria-hidden="true">高级搜索</i>
          </el-button>
        </div>
        <div>
          <el-upload
            style="display: inline-flex; margin-right: 8px"
            :headers="headers"
            :show-file-list="false"
            :before-upload="beforeUpload"
            :on-success="onSuccess"
            :on-error="onError"
            :disabled="importDataDisabled"
            action="/employee/basic/import"
          >
            <el-button
              type="success"
              :icon="importDataBtnIcon"
              :disabled="importDataDisabled"
            >
              {{ importDataBtnText }}
            </el-button>
          </el-upload>

          <el-button @click="exportData" type="success" icon="el-icon-download">
            导出数据
          </el-button>
          <el-button type="primary" icon="el-icon-plus" @click="showAddEmpView"
            >添加员工</el-button
          >
        </div>
      </div>
    </div>
    <transition name="slide-fade">
    <div
    v-show="showAdvanceSearchVisible"
      style="
        border: 1px solid #409eff;
        border-radius: 5px;
        box-sizing: border-box;
        padding: 5px;
        margin: 10px 0px;
      "
    >
      <el-row>
        <el-col :span="5">
          政治面貌
          <el-select
            v-model="searchValue.politicId"
            size="mini"
            style="width: 130px"
            placeholder="政治面貌"
          >
            <el-option
              v-for="item in politicsstatus"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
        </el-col>
        <el-col :span="4">
          民族:
          <el-select
            v-model="searchValue.nationId"
            size="mini"
            style="width: 130px"
            placeholder="民族"
          >
            <el-option
              v-for="item in nations"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
        </el-col>
        <el-col :span="4">
          职位:
          <el-select
            v-model="searchValue.posId"
            size="mini"
            style="width: 130px"
            placeholder="职位"
          >
            <el-option
              v-for="item in positions"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
        </el-col>
        <el-col :span="4">
          职称:
          <el-select
            v-model="searchValue.jobLevelId"
            size="mini"
            style="width: 130px"
            placeholder="职称"
          >
            <el-option
              v-for="item in joblevels"
              :key="item.id"
              :label="item.name"
              :value="item.id"
            >
            </el-option>
          </el-select>
        </el-col>
        <el-col :span="7">
          聘用形式:
          <el-radio-group v-model="searchValue.engageForm" style="margin-top: 5px">
            <el-radio label="劳动合同">劳动合同</el-radio>
            <el-radio label="劳务合同">劳务合同</el-radio>
          </el-radio-group>
        </el-col>
      </el-row>
      <el-row style="margin-top: 10px">
        <el-col :span="5">
          所属部门
          <el-popover
            placement="bottom"
            title="请选择部门"
            width="150px"
            trigger="manual"
            v-model="visible2"
          >
            <el-tree
              :data="allDeps"
              :props="defaultProps"
              default-expand-all
              @node-click="searchHandleNodeClick"
            ></el-tree>
            <div
              slot="reference"
              style="
                width: 130px;
                display: inline-flex;
                border: 1px solid #dedede;
                height: 24px;
                border-radius: 3px;
                cursor: pointer;
                align-items: center;
                font-size: 13px;
                padding-left: 8px;
                box-sizing: border-box;
              "
              @click="showDepView2"
            >
              {{ inputDepName }}
            </div>
          </el-popover>
        </el-col>
        <el-col :span="10">
          入职日期:
          <el-date-picker
            v-model="searchValue.beginDateScope"
            size="mini"
            type="daterange"
            value-format="yyyy-MM-dd"
            unlink-panels
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          >
          </el-date-picker>
        </el-col>
        <el-col :span="5" :offset="4">
          <el-button size="mini">取消</el-button>
          <el-button @click="initEmps('advanced')" size="mini" icon="el-icon-search" type="primary"
            >搜索</el-button
          >
        </el-col>
      </el-row>
    </div>
    </transition>


    <div style="margin-top: 10px">
      <el-table
        :data="emps"
        stripe
        border
        v-loading="loading"
        element-loading-text="拼命加载中"
        element-loading-spinner="el-icon-loading"
        element-loading-background="rgba(0, 0, 0, 0.8)"
        style="width: 100%"
      >
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="name" label="姓名" width="80" fixed>
        </el-table-column>
        <el-table-column prop="workID" label="工号" align="left" width="90">
        </el-table-column>
        <el-table-column prop="gender" label="性别" align="left" width="50">
        </el-table-column>
        <el-table-column
          prop="birthday"
          label="出生日期"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="idCard"
          label="身份证号码"
          align="left"
          width="160"
        >
        </el-table-column>
        <el-table-column
          prop="wedlock"
          label="婚姻状况"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="nation.name"
          label="民族"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="nativePlace"
          label="籍贯"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="politicsStatus.name"
          label="政治面貌"
          align="left"
          width="100"
        >
        </el-table-column>
        <el-table-column prop="email" label="电子邮件" align="left" width="190">
        </el-table-column>
        <el-table-column prop="phone" label="电话号码" align="left" width="110">
        </el-table-column>
        <el-table-column
          prop="address"
          label="联系地址"
          align="left"
          width="270"
        >
        </el-table-column>
        <el-table-column
          prop="department.name"
          label="所属部门"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="joblevel.name"
          label="职称"
          align="left"
          width="120"
        >
        </el-table-column>
        <el-table-column
          prop="position.name"
          label="职位"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="engageForm"
          label="聘用形式"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="tiptopDegree"
          label="最高学历"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="school"
          label="毕业院校"
          align="left"
          width="150"
        >
        </el-table-column>
        <el-table-column prop="specialty" label="专业" align="left" width="140">
        </el-table-column>
        <el-table-column
          prop="workState"
          label="在职状态"
          align="left"
          width="70"
        >
        </el-table-column>
        <el-table-column
          prop="beginDate"
          label="入职日期"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="conversionTime"
          label="转正日期"
          align="left"
          width="90"
        >
        </el-table-column>
        <el-table-column
          prop="endContract"
          label="合同截止日期"
          align="left"
          width="110"
        >
        </el-table-column>
        <el-table-column label="合同期限" align="left" width="90">
          <template slot-scope="scope">
            <el-tag>{{ scope.row.contractTerm }}</el-tag
            >年
          </template>
        </el-table-column>
        <el-table-column label="操作" fixed="right" width="200">
          <template slot-scope="scope">
            <el-button
              @click="showEditEmpView(scope.row)"
              style="padding: 3px"
              size="mini"
              >编辑</el-button
            >
            <el-button style="padding: 3px" size="mini" type="primary"
              >查看高级资料</el-button
            >
            <el-button
              @click="deleteEmp(scope.row)"
              style="padding: 3px"
              size="mini"
              type="danger"
              >删除</el-button
            >
          </template>
        </el-table-column>
      </el-table>
      <div style="display: flex; justify-content: flex-end">
        <el-pagination
          background
          @current-change="currentChange"
          @size-change="sizeChange"
          layout="sizes,prev, pager, next,jumper,->,total"
          :total="total"
        >
        </el-pagination>
      </div>
    </div>
    <el-dialog :title="title" :visible.sync="dialogVisible" width="80%">
      <div>
        <el-form ref="empForm" :model="emp" :rules="rules">
          <el-row>
            <el-col :span="6">
              <el-form-item label="姓名:" prop="name">
                <el-input
                  size="mini"
                  prefix-icon="el-icon-edit"
                  style="width: 150px"
                  v-model="emp.name"
                  placeholder="请输入员工姓名"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item label="性别:" prop="gender">
                <el-radio-group v-model="emp.gender" style="margin-top: 10px">
                  <el-radio label="男">男</el-radio>
                  <el-radio label="女">女</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="出生日期:" prop="birthday">
                <el-date-picker
                  v-model="emp.birthday"
                  type="date"
                  size="mini"
                  style="width: 160px"
                  value-format="yyyy-MM-dd"
                  placeholder="选择日期"
                >
                </el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="7">
              <el-form-item label="政治面貌:" prop="politicId">
                <el-select
                  v-model="emp.politicId"
                  size="mini"
                  style="width: 200px"
                  placeholder="政治面貌"
                >
                  <el-option
                    v-for="item in politicsstatus"
                    :key="item.id"
                    :label="item.name"
                    :value="item.id"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <el-form-item label="民族:" prop="nationId">
                <el-select
                  v-model="emp.nationId"
                  size="mini"
                  style="width: 150px"
                  placeholder="民族"
                >
                  <el-option
                    v-for="item in nations"
                    :key="item.id"
                    :label="item.name"
                    :value="item.id"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item label="籍贯:" prop="nativePlace">
                <el-input
                  v-model="emp.nativePlace"
                  placeholder="请输入籍贯"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 150px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="电子邮箱:" prop="email">
                <el-input
                  v-model="emp.email"
                  placeholder="请输入电子邮箱"
                  prefix-icon="el-icon-message"
                  size="mini"
                  style="width: 160px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="7">
              <el-form-item label="联系地址:" prop="address">
                <el-input
                  v-model="emp.address"
                  placeholder="请输入联系地址"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 200px"
                ></el-input>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <el-form-item label="职位:" prop="posId">
                <el-select
                  v-model="emp.posId"
                  size="mini"
                  style="width: 150px"
                  placeholder="职位"
                >
                  <el-option
                    v-for="item in positions"
                    :key="item.id"
                    :label="item.name"
                    :value="item.id"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item label="职称:" prop="jobLevelId">
                <el-select
                  v-model="emp.jobLevelId"
                  size="mini"
                  style="width: 150px"
                  placeholder="职称"
                >
                  <el-option
                    v-for="item in joblevels"
                    :key="item.id"
                    :label="item.name"
                    :value="item.id"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="所属部门:" prop="departmentId">
                <el-popover
                  placement="bottom"
                  title="请选择部门"
                  width="200"
                  trigger="manual"
                  v-model="visible"
                >
                  <el-tree
                    :data="allDeps"
                    :props="defaultProps"
                    default-expand-all
                    @node-click="handleNodeClick"
                  ></el-tree>
                  <div
                    slot="reference"
                    style="
                      width: 160px;
                      display: inline-flex;
                      border: 1px solid #dedede;
                      height: 24px;
                      border-radius: 3px;
                      cursor: pointer;
                      align-items: center;
                      font-size: 13px;
                      padding-left: 8px;
                      box-sizing: border-box;
                    "
                    @click="showDepView"
                  >
                    {{ inputDepName }}
                  </div>
                </el-popover>
              </el-form-item>
            </el-col>
            <el-col :span="7">
              <el-form-item label="电话号码:" prop="phone">
                <el-input
                  v-model="emp.phone"
                  placeholder="电话号码"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 200px"
                ></el-input>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <el-form-item label="工号:" prop="workID">
                <el-input
                  v-model="emp.workID"
                  placeholder="请输入工号"
                  prefix-icon="el-icon-edit"
                  disabled
                  size="mini"
                  style="width: 150px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item label="学历:" prop="tiptopDegree">
                <el-select
                  v-model="emp.tiptopDegree"
                  size="mini"
                  style="width: 150px"
                  placeholder="学历"
                >
                  <el-option
                    v-for="item in tiptopDegrees"
                    :key="item"
                    :label="item"
                    :value="item"
                  >
                  </el-option>
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="毕业学校:" prop="school">
                <el-input
                  v-model="emp.school"
                  placeholder="请输入毕业学校"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 160px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="7">
              <el-form-item label="专业名称:" prop="specialty">
                <el-input
                  v-model="emp.specialty"
                  placeholder="请输入专业名称"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 200px"
                ></el-input>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <el-form-item label="入职日期:" prop="beginDate">
                <el-date-picker
                  v-model="emp.beginDate"
                  type="date"
                  size="mini"
                  style="width: 120px"
                  value-format="yyyy-MM-dd"
                  placeholder="入职日期"
                >
                </el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="5">
              <el-form-item label="转正日期:" prop="conversionTime">
                <el-date-picker
                  v-model="emp.conversionTime"
                  type="date"
                  size="mini"
                  style="width: 122px"
                  value-format="yyyy-MM-dd"
                  placeholder="转正日期"
                >
                </el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="合同起始日期:" prop="beginContract">
                <el-date-picker
                  v-model="emp.beginContract"
                  type="date"
                  size="mini"
                  style="width: 133px"
                  value-format="yyyy-MM-dd"
                  placeholder="合同起始日期"
                >
                </el-date-picker>
              </el-form-item>
            </el-col>
            <el-col :span="7">
              <el-form-item label="合同截止日期:" prop="endContract">
                <el-date-picker
                  v-model="emp.endContract"
                  type="date"
                  size="mini"
                  style="width: 170px"
                  value-format="yyyy-MM-dd"
                  placeholder="合同截止日期"
                >
                </el-date-picker>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="6">
              <el-form-item label="身份证号码:" prop="idCard">
                <el-input
                  v-model="emp.idCard"
                  placeholder="请输入身份证号码"
                  prefix-icon="el-icon-edit"
                  size="mini"
                  style="width: 180px"
                ></el-input>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="聘用形式:" prop="engageForm">
                <el-radio-group
                  v-model="emp.engageForm"
                  style="margin-top: 10px"
                >
                  <el-radio label="劳动合同">劳动合同</el-radio>
                  <el-radio label="劳务合同">劳务合同</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="婚姻状况:" prop="wedlock">
                <el-radio-group v-model="emp.wedlock" style="margin-top: 10px">
                  <el-radio label="已婚">已婚</el-radio>
                  <el-radio label="未婚">未婚</el-radio>
                  <el-radio label="离异">离异</el-radio>
                </el-radio-group>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="doAddEmp">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "EmpBasic",
  data() {
    return {
      searchValue:{
        politicId:null,
        nationId:null,
        posId:null,
        jobLevelId:null,
        engageForm:'',
        departmentId:null,
        beginDateScope:null
      },
      showAdvanceSearchVisible:false,
      headers: {
        Authorization: window.sessionStorage.getItem("tokenStr"),
      },
      importDataDisabled: false,
      importDataBtnText: "导入数据",
      importDataBtnIcon: "el-icon-upload2",
      title: "",
      defaultProps: {
        children: "children",
        label: "name",
      },
      //部门回显数据
      inputDepName: "",
      allDeps: [],
      visible: false,
      visible2:false,
      emps: [],
      loading: false,
      total: 0,
      currentPage: 1,
      size: 10,
      empName: "",
      dialogVisible: false,
      nations: [],
      joblevels: [],
      politicsstatus: [],
      positions: [],
      tiptopDegrees: [
        "博士",
        "硕士",
        "本科",
        "大专",
        "高中",
        "初中",
        "小学",
        "其他",
      ],
      emp: {
        id: null,
        name: "",
        gender: "",
        birthday: "",
        idCard: "",
        wedlock: "",
        nationId: null,
        nativePlace: "",
        politicId: null,
        email: "",
        phone: "",
        address: "",
        departmentId: null,
        jobLevelId: null,
        posId: null,
        engageForm: "",
        tiptopDegree: "",
        specialty: "",
        school: "",
        beginDate: "",
        workState: "在职",
        workID: "",
        contractTerm: null,
        conversionTime: "",
        notWorkDate: null,
        beginContract: "",
        endContract: "",
        workAge: null,
        salaryId: null,
      },
      rules: {
        name: [{ required: true, message: "请输入员工姓名", trigger: "blur" }],
        gender: [
          { required: true, message: "请选择员工性别", trigger: "blur" },
        ],
        birthday: [
          { required: true, message: "请输入出生日期", trigger: "blur" },
        ],
        idCard: [
          { required: true, message: "请输入身份证号码", trigger: "blur" },
          {
            patten:
              /(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/,
            message: "身份证号码不正确",
            trigger: "blur",
          },
        ],
        wedlock: [
          { required: true, message: "请选择婚姻状况", trigger: "blur" },
        ],
        nationId: [{ required: true, message: "请输入民族", trigger: "blur" }],
        nativePlace: [
          { required: true, message: "请输入籍贯", trigger: "blur" },
        ],
        politicId: [
          { required: true, message: "请输入政治面貌", trigger: "blur" },
        ],
        email: [
          { required: true, message: "请输入邮箱地址", trigger: "blur" },
          { type: "email", message: "邮箱地址格式不正确", tigger: "blur" },
        ],
        phone: [{ required: true, message: "请输入电话号码", trigger: "blur" }],
        address: [
          { required: true, message: "请输入员工地址", trigger: "blur" },
        ],
        departmentId: [
          { required: true, message: "请输入部门名称", trigger: "blur" },
        ],
        jobLevelId: [
          { required: true, message: "请输入职称", trigger: "blur" },
        ],
        posId: [{ required: true, message: "请输入职位", trigger: "blur" }],
        engageForm: [
          { required: true, message: "请选择聘用形式", trigger: "blur" },
        ],
        tiptopDegree: [
          { required: true, message: "请输入学历", trigger: "blur" },
        ],
        specialty: [{ required: true, message: "请输入专业", trigger: "blur" }],
        school: [
          { required: true, message: "请输入毕业院校", trigger: "blur" },
        ],
        beginDate: [
          { required: true, message: "请输入入职日期", trigger: "blur" },
        ],
        workState: [
          { required: true, message: "请输入工作状态", trigger: "blur" },
        ],
        workID: [{ required: true, message: "请输入工号", trigger: "blur" }],
        contractTerm: [
          { required: true, message: "请输入合同期限", trigger: "blur" },
        ],
        conversionTime: [
          { required: true, message: "请输入转正日期", trigger: "blur" },
        ],
        notWorkDate: [
          { required: true, message: "请输入离职日期", trigger: "blur" },
        ],
        beginContract: [
          { required: true, message: "请输入合同起始日期", trigger: "blur" },
        ],
        endContract: [
          { required: true, message: "请输入合同结束日期", trigger: "blur" },
        ],
        workAge: [{ required: true, message: "请输入工龄", trigger: "blur" }],
      },
    };
  },
  mounted() {
    //为了让搜索时的职位下拉框有数据
    this.initPositions();
    this.initEmps();
    this.initData();
  },
  methods: {
    onSuccess() {
      this.importDataBtnIcon = "el-icon-upload2";
      this.importDataBtnText = "导入数据";
      this.importDataDisabled = true;
      this.initEmps();
    },
    onError() {
      this.importDataBtnIcon = "el-icon-upload2";
      this.importDataBtnText = "导入数据";
      this.importDataDisabled = true;
    },
    beforeUpload() {
      this.importDataBtnIcon = "el-icon-loading";
      this.importDataBtnText = "正在导入";
      //在导入数据的时候,禁用导入,不可再次点击导入文件。
      this.importDataDisabled = true;
    },
    exportData() {
      this.downloadRequest("/employee/basic/export");
    },
    showEditEmpView(data) {
      this.title = "编辑员工信息";
      this.emp = data;
      //将自定义的数据赋值上去
      this.inputDepName = data.department.name;
      console.log(data.department);
      //处理职位显示问题,职位没有存在session,在添加窗口时,掉接口,这里同样要处理
      this.initPositions();
      this.dialogVisible = true;
    },
    deleteEmp(data) {
      this.$confirm("此操作将永久删除" + data.name + ", 是否继续?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          this.deleteRequest("/employee/basic/" + data.id).then((resp) => {
            if (resp) {
              this.initEmps();
            }
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    doAddEmp() {
      //判断id是否为空,添加员工id为null,编辑是有id的。
      if (this.emp.id) {
        this.$refs["empForm"].validate((valid) => {
          if (valid) {
            this.putRequest("/employee/basic/", this.emp).then((resp) => {
              if (resp) {
                this.dialogVisible = false;
                this.initEmps();
              }
            });
          } else {
            this.$message.error("添加失败!");
          }
        });
      } else {
        this.$refs["empForm"].validate((valid) => {
          if (valid) {
            this.postRequest("/employee/basic/", this.emp).then((resp) => {
              if (resp) {
                this.dialogVisible = false;
                this.initEmps();
              }
            });
          } else {
            this.$message.error("添加失败!");
          }
        });
      }
    },
    searchHandleNodeClick(data){
      this.inputDepName = data.name;
      this.searchValue.departmentId = data.id;
      this.visible2 = !this.visible2;
    },
    handleNodeClick(data) {
      this.inputDepName = data.name;
      this.emp.departmentId = data.id;
      this.visible = !this.visible;
    },
    showDepView2(){
      this.visible2 = !this.visible2;
    },
    showDepView() {
      this.visible = !this.visible;
    },
    //展示对话框的时候调用
    getMaxWorkID() {
      this.getRequest("/employee/basic/maxWorkID").then((resp) => {
        if (resp) {
          this.emp.workID = resp.obj;
        }
      });
    },
    //对于会变动的数据,在打开对话框中调用
    initPositions() {
      this.getRequest("/employee/basic/positions").then((resp) => {
        if (resp) this.positions = resp;
      });
    },
    //对于不怎么会变动的数据放在sessionStorage里面
    initData() {
      //判断有没有在sessionStorage里面,如果在直接查,没有就调用接口去查
      if (window.sessionStorage.getItem("nations") == null) {
        this.getRequest("/employee/basic/nations").then((resp) => {
          if (resp) {
            this.nations = resp;
            window.sessionStorage.setItem("nations", JSON.stringify(resp));
          }
        });
      } else {
        this.nations = JSON.parse(window.sessionStorage.getItem("nations"));
      }
      if (window.sessionStorage.getItem("joblevels") == null) {
        this.getRequest("/employee/basic/joblevels").then((resp) => {
          if (resp) {
            this.joblevels = resp;
            window.sessionStorage.setItem("joblevels", JSON.stringify(resp));
          }
        });
      } else {
        this.joblevels = JSON.parse(window.sessionStorage.getItem("joblevels"));
      }
      if (window.sessionStorage.getItem("politicsstatus") == null) {
        this.getRequest("/employee/basic/politicsstatus").then((resp) => {
          if (resp) {
            this.politicsstatus = resp;
            window.sessionStorage.setItem(
              "politicsstatus",
              JSON.stringify(resp)
            );
          }
        });
      } else {
        this.politicsstatus = JSON.parse(
          window.sessionStorage.getItem("politicsstatus")
        );
      }
      if (window.sessionStorage.getItem("allDeps") == null) {
        this.getRequest("/employee/basic/deps").then((resp) => {
          if (resp) {
            this.allDeps = resp;
            window.sessionStorage.setItem("allDeps", JSON.stringify(resp));
          }
        });
      } else {
        this.allDeps = JSON.parse(window.sessionStorage.getItem("allDeps"));
      }
    },
    sizeChange(size) {
      this.size = size;
      this.initEmps();
    },
    currentChange(currentPage) {
      this.currentPage = currentPage;
      this.initEmps();
    },
    showAddEmpView() {
      this.title = "添加员工";
      //将添加完之后的菜单数据清空
      this.emp = {
        id: null,
        name: "",
        gender: "",
        birthday: "",
        idCard: "",
        wedlock: "",
        nationId: null,
        nativePlace: "",
        politicId: null,
        email: "",
        phone: "",
        address: "",
        departmentId: null,
        jobLevelId: null,
        posId: null,
        engageForm: "",
        tiptopDegree: "",
        specialty: "",
        school: "",
        beginDate: "",
        workState: "在职",
        workID: "",
        contractTerm: null,
        conversionTime: "",
        notWorkDate: null,
        beginContract: "",
        endContract: "",
        workAge: null,
        salaryId: null,
      };
      this.inputDepName = "";
      this.getMaxWorkID();
      this.initPositions();
      this.dialogVisible = true;
    },
    initEmps(type) {
      this.loading = true;
      let url = '/employee/basic/?currentPage=' + this.currentPage + '&size=' +this.size;
      if(type && type == 'advanced'){
        if(this.searchValue.politicId){
          url += '&politicId=' + this.searchValue.politicId;
        }
        if(this.searchValue.nationId){
          url += '&nationId=' + this.searchValue.nationId;
        }
        if(this.searchValue.posId){
          url += '&posId=' + this.searchValue.posId;
        }
        if(this.searchValue.jobLevelId){
          url += '&jobLevelId=' + this.searchValue.jobLevelId;
        }
        if(this.searchValue.engageForm){
          url += '&engageForm=' + this.searchValue.engageForm;
        }
        if(this.searchValue.departmentId){
          url += '&departmentId=' + this.searchValue.departmentId;
        }
        if(this.searchValue.beginDateScope){
          url += '&beginDateScope=' + this.searchValue.beginDateScope;
        }
      }else{
        url+= '&name=' + this.empName;
      }
      this.getRequest(url).then((resp) => {
        this.loading = false;
        if (resp) {
          this.emps = resp.data;
          this.total = resp.total;
        }
      });
    },
  },
};
</script>

<style>
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
  transition: all .8s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
  transform: translateX(10px);
  opacity: 0;
}
</style>

导出文件

安装插件:cnpm install js-file-download –save

工资账套
<template>
  <div id="name">
    <div style="display: flex; justify-content: space-between">
      <el-button type="primary" icon="el-icon-plus" @click="showAddSalaryView">添加工资账套</el-button>
      <el-button type="success" icon="el-icon-refresh" @click="initSalaries"></el-button>
    </div>
    <div style="margin-top: 10px">
      <el-table :data="salaries" border stripe>
        <el-table-column type="selection" width="40px"></el-table-column>
        <el-table-column
          label="账套名称"
          width="120px"
          prop="name"
        ></el-table-column>
        <el-table-column
          label="基本工资"
          width="70px"
          prop="basicSalary"
        ></el-table-column>
        <el-table-column
          label="交通补助"
          width="70px"
          prop="trafficSalary"
        ></el-table-column>
        <el-table-column
          label="午餐补助"
          width="70px"
          prop="lunchSalary"
        ></el-table-column>
        <el-table-column
          label="奖金"
          width="70px"
          prop="bonus"
        ></el-table-column>
        <el-table-column
          label="启用时间"
          width="100px"
          prop="createDate"
        ></el-table-column>
        <el-table-column label="养老金" align="center">
          <el-table-column
            label="比率"
            prop="pensionPer"
            width="70"
          ></el-table-column>
          <el-table-column
            label="基数"
            prop="pensionBase"
            width="70"
          ></el-table-column>
        </el-table-column>
        <el-table-column label="医疗保险" align="center">
          <el-table-column
            label="比率"
            prop="medicalPer"
            width="70"
          ></el-table-column>
          <el-table-column
            label="基数"
            prop="medicalBase"
            width="70"
          ></el-table-column>
        </el-table-column>
        <el-table-column label="公积金" align="center">
          <el-table-column
            label="比率"
            prop="accumulationFundPer"
            width="70"
          ></el-table-column>
          <el-table-column
            label="基数"
            prop="accumulationFundBase"
            width="70"
          ></el-table-column>
        </el-table-column>
        <el-table-column label="操作" fixed="right">
          <template slot-scope="scope">
          <el-button type="primary" @click="showEditSalaryView(scope.row)">编辑</el-button>
          <el-button type="danger" @click="deleteSalary(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <el-dialog
      :title="dialogTitle"
      :visible.sync="dialogVisible"
      width="50%"
    >
      <div style="display:flex;justify-content:space-around;align-items:center">
  <el-steps direction="vertical" :active="activeItemIndex">
    <el-step :title="itemName" v-for="(itemName,index) in salaryItemName" :key="index"></el-step>
  </el-steps>
  <el-input v-model="salary[title]" :placeholder="'请输入'+ salaryItemName[index]+'...'" v-for="(value,title,index) in salary" :key="index" v-show="activeItemIndex==index" style="width:200px"></el-input>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="preStep">{{activeItemIndex==10?'取消':'上一步'}}</el-button>
        <el-button type="primary" @click="nextStep"
          >{{activeItemIndex==10?'完成':'下一步'}}</el-button
        >
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "SalSob",

  data() {
    return {
      dialogTitle:'添加工资账套',
      dialogVisible:false,
      salaries: [],
      activeItemIndex:0,
      salaryItemName:[
        '账套名称','基本工资','交通补助','午餐补助','奖金','养老金比率','养老金基数','医疗保险比率','医疗保险基数','公积金比率','公积金基数'
      ],
      salary:{
        name:'',
        basicSalary:0,
        trafficSalary:0,
        lunchSalary:0,
        bonus:0,
        pensionPer:0,
        pensionBase:0,
        medicalPer:0,
        medicalBase:0,
        accumulationFundPer:0,
        accumulationFundBase:0,
      }
    };
  },
  mounted() {
    this.initSalaries();
  },
  methods: {
    showEditSalaryView(data){
      console.log("kkkk");
      this.dialogTitle = '编辑工资账套';
      this.activeItemIndex = 0;
      this.salary.id = data.id;
      this.salary.name = data.name; 
      this.salary.basicSalary = data.basicSalary; 
      this.salary.trafficSalary = data.trafficSalary; 
      this.salary.lunchSalary = data.lunchSalary; 
      this.salary.bonus = data.bonus; 
      this.salary.pensionPer = data.pensionPer; 
      this.salary.pensionBase = data.pensionBase; 
      this.salary.medicalPer = data.medicalPer; 
      this.salary.medicalBase = data.medicalBase; 
      this.salary.accumulationFundPer = data.accumulationFundPer; 
      this.salary.accumulationFundBase = data.accumulationFundBase; 
      this.dialogVisible = true;
    },
    deleteSalary(data){
            this.$confirm(
        "此操作将永久删除[" + data.name + "]工资账套, 是否继续?",
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      )
        .then(() => {
          this.deleteRequest("/salary/sob/" + data.id).then(
            (resp) => {
              if (resp) {
                this.initSalaries();
              }
            }
          );
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },
    preStep(){
      if(this.activeItemIndex == 0){
        return;
      }else if(this.activeItemIndex ==10){
        this.dialogVisible = false;
        return;
      }
      this.activeItemIndex--;
    },
    nextStep(){
      if(this.activeItemIndex == 10){
        if(this.salary.id){
              this.putRequest('/salary/sob/',this.salary).then(resp=>{
                if(resp){
                  this.initSalaries();
                  this.dialogVisible = false;
                }
              })
        }else{
        this.postRequest('/salary/sob/',this.salary).then(resp=>{
          if(resp){
            this.initSalaries();
            this.dialogVisible = false;
          }
        })
        }
        return;
      }
      this.activeItemIndex++;
    },
    showAddSalaryView(){
      this.dialogTitle = '添加工资账套';
      //初始化数据
      this.salary={
        name:'',
        basicSalary:0,
        trafficSalary:0,
        lunchSalary:0,
        bonus:0,
        pensionPer:0,
        pensionBase:0,
        medicalPer:0,
        medicalBase:0,
        accumulationFundPer:0,
        accumulationFundBase:0,
      };
      //让步骤条一开始就从零开始
      this.activeItemIndex = 0;
       this.dialogVisible = true;
    },
    initSalaries() {
      this.getRequest("/salary/sob/").then((resp) => {
        if (resp) {
          this.salaries = resp;
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped></style>

工资账套管理

<template>
  <div id="name">
    <el-table
    :data="emps"
    size="mini"
    border
    stripe
    >
    <el-table-column type="selection" align="left" width="55px">
    </el-table-column>    
    <el-table-column label="姓名" prop="name" align="left" fixed width="120px">
    </el-table-column>  
    <el-table-column label="工号" prop="workID" align="left" width="120px">
    </el-table-column>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    " width="180"></el-table-column>
    <el-table-column label="邮箱地址" prop="workID" align="left" width="200px">
    </el-table-column> 
    <el-table-column label="电话号码" prop="workID" align="left" width="120px">
    </el-table-column> 
    <el-table-column label="所属部门" prop="department.name" align="left" width="120px">
    </el-table-column> 
    <el-table-column label="工资账套" align="center">
      <template slot-scope="scope">
        <el-tooltip placement="right" v-if="scope.row.salary">
       <div slot="content">
            <table>
              <tr>
                <td>基本工资</td>
                <td>{{scope.row.salary.basicSalary}}</td>
              </tr>
              <tr>
                <td>交通补助</td>
                <td>{{scope.row.salary.trafficSalary}}</td>
              </tr>
              <tr>
                <td>午餐补助</td>
                <td>{{scope.row.salary.lunchSalary}}</td>
              </tr>
              <tr>
                <td>奖金</td>
                <td>{{scope.row.salary.bonus}}</td>
              </tr>
              <tr>
                <td>养老金比率</td>
                <td>{{scope.row.salary.pensionPer}}</td>
              </tr>
              <tr>
                <td>养老金基数</td>
                <td>{{scope.row.salary.pensionBase}}</td>
              </tr>
              <tr>
                <td>医疗保险比率</td>
                <td>{{scope.row.salary.medicalPer}}</td>
              </tr>
              <tr>
                <td>医疗保险基数</td>
                <td>{{scope.row.salary.medicalBase}}</td>
              </tr>
              <tr>
                <td>公积金比率</td>
                <td>{{scope.row.salary.accumulationFundPer}}</td>
              </tr>
              <tr>
                <td>公积金基数</td>
                <td>{{scope.row.salary.accumulationFundBase}}</td>
              </tr>
            </table>
       </div>
        <el-tag>{{scope.row.salary.name}}</el-tag>
       </el-tooltip>
        <el-tag v-else>暂未设置</el-tag>
      </template>
    </el-table-column> 
    <el-table-column label="操作" align="centern">
       <template slot-scope="scope">
           <el-popover
              placement="left"
              title="编辑工资账套"
              @show="showPop(scope.row.salary)"
              @hide="hidePop(scope.row)"
              width="200"
              trigger="click">
              <div>
                <el-select v-model="currentSalary" size="mini" placeholder="请选择">
                  <el-option
                     v-for="item in salaries"
                     :key="item.id"
                     :label="item.name"
                     :value="item.id">
                  </el-option>
                </el-select>
              </div>
        <el-button slot="reference" type="danger">修改工资账套</el-button>
  </el-popover>
       </template>
    </el-table-column> 
    <el-table-column label="工号" prop="workID" align="left" width="120px">
    </el-table-column> 
    <el-table-column label="工号" prop="workID" align="left" width="120px">
    </el-table-column> 
    </el-table>
    <div style="display:flex;justify-content:flex-end;margin-top:5px">
      <el-pagination
      background
      @current-change="currentChange"
      @size-change="sizeChange"
      layout="sizes,prev,pager,next,jumper,->,total,slot"
       :total="total" 
      >
      </el-pagination>
    </div>
  </div>
</template>

<script>
export default {
  name: "SalSobCfg",
  data() {
    return {
      currentSalary:null,
      salaries:[],
      emps:[],
      currentPage:1,
      size:10,
      total:0
    };
  },
  mounted(){
    this.initEmps();
    this.initSalaries();
  },
  methods: {
    hidePop(data){
      if(this.currentSalary &&this.currentSalary!=data.id){
       this.putRequest('/salary/sobcfg/?eid=' + data.id +'&sid=' +this.currentSalary).then(resp=>{
         if(resp){
           this.initEmps();
         }
       })
      }
    },
    showPop(data){
      if(data){
        this.currentSalary = data.id;
      }else{
        this.currentSalary = null;
      }
    },
    initSalaries(){
      this.getRequest('/salary/sobcfg/salaries').then(resp=>{
        if(resp){
          this.salaries = resp;
        }
      })
    },
    currentChange(page){
      this.currentPage = page;
      this.initEmps();
    },
    sizeChange(size){
      this.size = size;
      this.initEmps();
    },
    initEmps(){
       this.getRequest('/salary/sobcfg/?currentPage=' + this.currentPage + '&size=' +this.size).then(resp=>{
         if(resp){
           this.emps = resp.data;
           this.total = resp.total;
         }
       })
    }
  }
};
</script>

<style lang="scss" scoped>
</style>

在线聊天


Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐