(二)Springboot + vue + 达梦数据库构建RBAC权限模型前后端分离脚手架保姆级教程(后端项目)

XX后台管理系统

Springboot + vue + dm8 的前后端分离项目,后端项目

https://spring.io
https://start.aliyun.com

1. 创建项目

初始化项目,导入坐标

web、lombok、devtools

<!-- web start -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web end -->

<!-- devtools start -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!-- devtools end -->

<!-- lombok start -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- lombok end -->

2.整合Swagger3

2.1 导入swagger3坐标

<swagger3.version>3.0.0</swagger3.version>

<!-- swagger3 start  -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>${swagger3.version}</version>
</dependency>
<!-- swagger3 end -->

2.2 编写swagger3配置类

package org.cn.common.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Configuration
@EnableOpenApi
@EnableWebMvc
public class Swagger3Config {

    /**
     * 创建API
     * http:localhost:9999/swagger-ui/index.html 原生地址
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.OAS_30).pathMapping("/")
                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
                /*.enable(enable)*/
                .apiInfo(apiInfo())
                // 设置哪些接口暴露给Swagger展示
                .select()
                // 扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // 扫描指定包中的swagger注解
                //.apis(RequestHandlerSelectors.basePackage("com.cn"))
                // 扫描所有 .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.regex("(?!/ApiError.*).*"))
                .paths(PathSelectors.any())
                .build()
                // 支持的通讯协议集合
                .protocols(newHashSet("https", "http"))
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());

    }

    /**
     * 支持的通讯协议集合
     *
     * @param type1
     * @param type2
     * @return
     */
    private Set<String> newHashSet(String type1, String type2) {
        Set<String> set = new HashSet<>();
        set.add(type1);
        set.add(type2);
        return set;
    }

    /**
     * 认证的安全上下文
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> securitySchemes = new ArrayList<>();
        securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
        return securitySchemes;
    }

    /**
     * 授权信息全局应用
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.any()).build());
        return securityContexts;
    }

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


    /**
     * 添加摘要信息
     * @return 返回ApiInfo对象
     */
    private ApiInfo apiInfo() {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // 设置标题
                .title("接口文档")
                // 服务条款
                .termsOfServiceUrl("NO terms of service")
                // 描述
                .description("权限模型管理系统-接口文档")
                // 作者信息
                .contact(new Contact("LM", "https://www.cnblogs.com/longronglang/", "lumin@gmail.com"))
                // 版本
                .version("版本号:V1.0")
                //协议
                .license("The Apache License")
                // 协议url
                .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html")
                .build();
    }
}

2.3 swagger3注解测试

新增测试类、测试方法等,加上Swagger3注解进行测试

http:localhost:9999/swagger-ui/index.html

@Api(tags = "用户管理")
@ApiOperation("用户登录")
http://localhost:9999/swagger-ui/index.html

3. 代码自动生成

3.1 引入MP&DB坐标

mybatis-plus-boot-starter、mybatis-plus-generator、freemarker、dameng

注意:mybatis-plus 坐标引入的是mybatis-plus启动坐标和代码生成坐标

<dm.version>8.1.2.192</dm.version>
<mybatis-plus-boot.version>3.5.3.1</mybatis-plus-boot.version>
<mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>

<!-- mybatis-plus-boot start -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus-boot.version}</version>
</dependency>
<!-- mybatis-plus-boot end -->

<!-- mybatis-plus-generator start -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- mybatis-plus-generator end -->

<!-- freemarker start -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- freemarker end -->

<!-- dameng start -->
<dependency>
    <groupId>com.dameng</groupId>
    <artifactId>DmJdbcDriver18</artifactId>
    <version>${dm.version}</version>
</dependency>
<!-- dameng end -->

3.2 代码生成工具类编写

3.2.1 代码生成工具类
package generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;

public class CodeGenerator {
    static String url ="jdbc:dm://LOCALHOST:5236";
    static String username ="SYSDBA";
    static String password ="SYSDBA001";
    static String moduleName ="system";
    static String javaLocation ="F:\\install\\workspace\\sty\\x-dm-server\\src\\main\\java";
    static String mapperLocation ="F:\\install\\workspace\\sty\\x-dm-server\\src\\main\\resources\\mapper\\";
    static String tables = "x_user,x_role,x_menu,x_user_role,x_role_menu";

    public static void main(String[] args) {

        FastAutoGenerator.create(url,username,password)
                .globalConfig(builder -> builder
                        .author("LuMin")
                        .enableSwagger() // 开启 swagger 模式
                        .fileOverride() // 覆盖已生成文件
                        .outputDir(javaLocation)
                        .commentDate("yyyy-MM-dd")
                )
                .packageConfig(builder -> builder
                        .parent("org.cn") // 设置父包名
                        .moduleName(moduleName) // 设置父包模块名
                        .entity("entity")
                        .mapper("mapper")
                        .service("service")
                        .serviceImpl("service.impl")
                        .pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation))
                )
                .strategyConfig(builder -> builder
                        .addInclude(tables) // 设置需要生成的表名
                        .addTablePrefix("x_") // 设置过滤表前缀
                        .entityBuilder()
                        .enableLombok()
                )
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

    public static class Test {
        static Connection con = null;
        static String cname = "dm.jdbc.driver.DmDriver";
        static String url = "jdbc:dm://192.168.106.137:30236";
        static String userid = "SYSDBA";
        static String pwd = "SYSDBA001";
        public static void main(String[] args) {
            try {
                Class.forName(cname);
                con = DriverManager.getConnection(url, userid, pwd);
                con.setAutoCommit(true);
                System.out.println("[SUCCESS]conn database");
            } catch (Exception e){
                System.out.println("[FAIL]conn database:" + e.getMessage());
            }
        }

        public void disConn(Connection con) throws SQLException {
            if (con != null) {
                con.close();
            }
        }
    }
}
3.2.2 启动异常解决
# 注意生成代码之后,直接启动项目会报错,需要对项目信息进行修改
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.cn.system.mapper.MenuMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
# ①修改启动类扫描的包
@SpringBootApplication(scanBasePackages = {"org.cn"})
@MapperScan(value = {"org.cn.*.mapper"})
# ②在Mapper接口上增加@Mapper类注解,启动还是会报错
initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuMapper' defined in file
# ③删除创建项目默认生成的demos包,重新启动

3.3 application.yml配置

# server
server:
  port: 9999

# datasource
spring:
  datasource:
    url: jdbc:dm://LOCALHOST:5236/XGIS
    username: SYSDBA
    password: SYSDBA001
    driver-class-name: dm.jdbc.driver.DmDriver
  jpa:
    database-platform: org.hibernate.dialect.DMDialect
    hibernate:
      ddl-auto: update # 或者 create, create-drop, validate, none
    show-sql: true # 显示执行的 SQL 语句,便于调试
    properties:
      hibernate:
        format_sql: true # 格式化 SQL 语句输出
  redis:
    port: 6379
    password: Admin@123
    host: localhost

# mybatis-plus
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-not-delete-value: 0
      logic-delete-value: 1
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: org.cn

# logging
logging:
  charset:
    console: utf-8
  level:
    org.cn: debug

3.4 测试生成代码生成可用性

利用SpringBootJunit测试工具进行测试,在XDmServerApplicationTests类中新建testUserMapper()方法

package org.cn;

import org.cn.system.entity.User;
import org.cn.system.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;


@SpringBootTest
class XDmServerApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
    }

    @Test
    void testUserMapper () {
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }
}

foreach循环时没有打印出预期的内容(例如用户对象的某些属性),而是打印出了类似org.cn.system.entity.User@1ad1c363这样的内容,这通常意味着你没有正确覆写User类的toString方法。

org.cn.system.entity.User@76d0ecd7
org.cn.system.entity.User@57c69937
org.cn.system.entity.User@1ad1c363
org.cn.system.entity.User@446b64b3
org.cn.system.entity.User@35ac9ebd
org.cn.system.entity.User@56c0a61e

一是在User实体类中增加 toString()方法

二是在实体类上添加@Data/@NoArgsConstructor/@AllArgsConstructor注解

@Override
public String toString() {
    return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", email='" + email + '\'' +
            ", status='" + status + '\'' +
            ", phone=" + phone +
            ", avatar='" + avatar + '\'' +
            ", deleted=" + deleted +
            '}';
}

4. 统一结果返回封装

4.1 封装公共响应类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    /**
     * 返回编码
     */
    private Integer code;
    /**
     * 返回信息
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 返回成功
     * @return
     * @param <T>
     */
    public  static <T> Result<T> success(){
        return new Result<>(20000, "success",null);
    }

    public  static <T> Result<T> success(T data){
        return new Result<>(20000, "success",data);
    }
    public  static <T> Result<T> success(T data,String message){
        return new Result<>(20000, message,data);
    }

    public  static <T> Result<T> success(String message){
        return new Result<>(20000, message,null);
    }

    /**
     * 返回失败
     * @return
     * @param <T>
     */
    public  static <T> Result<T> fail(){
        return new Result<>(20001, "fail",null);
    }
    public  static <T> Result<T> fail(Integer code){
        return new Result<>(20001, "fail",null);
    }
    public  static <T> Result<T> fail(Integer code,String message){
        return new Result<>(20001, message,null);
    }
    public  static <T> Result<T> fail(String message){
        return new Result<>(20001, message,null);
    }

}

4.2 测试公共响应类

修改控制器,测试公共响应类是否返回自定义数据

4.2.1 修改注解
修改UserController控制器的@Controller为@RestController
4.2.2 新增方法测试

在控制类中注入IUserService接口服务类,新增getUserList()测试方法进行测试

@Resource
private IUserService userService;

@GetMapping("/getUserList")
public Result<List<User>> getUserList() {
    List<User> userList = userService.list();
    return Result.success(userList, "查询成功");
}

5.实体类Lombok改造

5.1 删除注解

@Getter/@Setter是mybatis-plus-generator生成实体时自动添加的注解

@Getter
@Setter

5.2 新增注解

@Data/@NoArgsConstructor/@AllArgsConstructor是lombok中的注解

@Data
@NoArgsConstructor
@AllArgsConstructor

6.JWT登录实现

6.1 JWT 整合

6.1.1 导入Jwt坐标
spring-security-core、jjwt

<fastjson.version>2.0.1</fastjson.version>

<!-- spring-security-core start -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
</dependency>
<!-- spring-security-core end -->

<!-- jjwt  start -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<!-- jjwt end -->
6.1.2 编写Jwt工具类

①在application.yml配置JWT令牌和有效期

# JWT 配置
jwt:
  secret-key: 123456
  token-validity-in-seconds: 86400000

②JwtUtil工具类

6.1.3 Jwt验证拦截器
6.1.3.1 定义Jwt拦截器
@Component
@Slf4j
public class JwtValidateInterceptor implements HandlerInterceptor {

    @Resource
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //   String token = request.getHeader("Authorization");
        /*request.getHeader()方法与集成Swagger中ApiKey并无多大关系
        private List<SecurityScheme> securitySchemes() {
            List<SecurityScheme> securitySchemes = new ArrayList<>();
            securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
            return securitySchemes;
        }*/
        String token = request.getHeader("X-Token");
        log.debug(request.getRequestURI()+"需要验证"+token);
        if (token != null) {
            try {
                jwtUtil.parseToken(token);
                log.debug(request.getRequestURI()+"验证通过");
                return true;
            } catch (Exception e) {
                throw new Exception(e);
            }
        }
        log.debug(request.getRequestURI()+"验证失败,禁止访问");
        response.setContentType("application/json;charset=utf-8");
        Result<Object> fail = Result.fail(20003, "jwt验证失败,请重新登录");
        response.getWriter().write(JSON.toJSONString(fail));
        return false; //拦截
    }
}
6.1.3.2 注册Jwt拦截器
@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {

@Resource
private JwtValidateInterceptor jwtValidateInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor);
    registration.addPathPatterns("/**")
            .excludePathPatterns(
                    "/system/user/login",
                    "/system/user/info",
                    "/system/user/logout",
                    "/swagger-ui/**",
                    "/swagger-resources/**",
                    "/v3/**",
                    "/error",
                    "/webjars/**",
                    "/v2/**",
                    "/public/**",
                    "/static/**",
                    "*.html",
                    "doc.html",
                    "/favicon.ico"
            );
    }
}
6.1.3.3 Jwt拦截器测试

①在 XAdminServerApplicationTests 测试类中注入

@Autowired
private JwtUtil jwtUtil;
在 XAdminServerApplicationTests 测试

②测试Jwt创建

/**
 * Jwt创建
 */
@Test
void testCreateJwt(){
    User user = new User();
    user.setUsername("zhangsan");
    user.setPhone(18722820382L);
    String token = jwtUtil.createToken(user);
    System.out.println(token);
}

③测试Jwt解析

/**
 * Jwt解析
 */
@Test
void  testParseJwt(){
    String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJkZWRiNTRiMC1kOTllLTRhZGEtOGFjMi1iZTA2ODRlNzVhZmYiLCJzdWIiOiJ7XCJ0ZWxlcGhvbmVcIjoxODcyMjgyMDM4MixcInVzZXJuYW1lXCI6XCJ6aGFuZ3NhblwifSIsImlzcyI6InN5c3RlbSIsImlhdCI6MTcwNjExMzU3MCwiZXhwIjoxNzA2MTE1MzcwfQ.Pcj8kjcAC8FW-PER65wiYYJ1bSptOG8mViIHyeIY3EI";
    Claims claims = jwtUtil.parseToken(token);
    System.out.println("claims = " + claims);
}

6.2 登录逻辑

认证过程可以参考此篇博客,写得非常详细

https://blog.csdn.net/liuerchong/article/details/108606650

6.2.1 新增用户登录接口

在IUserService服务类中新增用户登录业务逻辑接口

 /**
 * 用户登录业务逻辑接口
 * @param user
 * @return
 */
Map<String, Object> login(User user);
6.2.2 实现用户登录接口

第一步根据用户名和密码查询用户信息;

第二步判断查询结果是否为空,为空抛出异常信息,不为空则将封装的登录信息传入JwtUtil中生成token

6.2.2.1 用户信息查询

根据前端传入User对象,LambdaQueryWrapper 构造wrapper查询对象参数,再将wrapper参数传入后端baseMapper查询接口,查询用户信息是否存在。

**** 复制代码
// 1. 根据用户名查询用户信息
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
User loginUser = this.baseMapper.selectOne(wrapper);
6.2.2.2 用户token生成

第一步:判断用户信息是否为空

判断用户信息不为空,使用spring-security-core中PasswordEncoder进行加密并使用matches方法匹配后端查询出来的密码是否一致,这里需要注意一下,安全考虑,密码在传输过程中需要进行置空,不要放到JWT生成token中,PasswordEncoder需要配置成一个公用组件,不然会报错。PasswordEncoder组件配置如下:

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

第二步:不为空,则创建token

①定义一个字符串token,用于接收jwtUtil中生成的token值(实质是JwtBuilder中将AES加密令牌密钥和过期时间压缩的字符串);

②将封装的loginUser登录信息传入JwtUtil工具类中createToken方法中生成token;

③定义一个Map集合,将jwtUtil生成的token存储在Map集合中,返回出去

// 2. 结果不为空,则生成 token
if (loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())) {
    String key = "user" + UUID.randomUUID();
    loginUser.setPassword(null);
    String token = jwtUtil.createToken(loginUser);
    Map<String,Object> data =  new HashMap<>();
    data.put("token",token);
    return data;
}

第三步:JwtUtil工具生成token步骤

①在application.yml配置JWT令牌和有效期

# JWT 配置
jwt:
  secret-key: 123456
  token-validity-in-seconds: 86400

②创建JWT工具类,通对令牌密钥进行Base64编码,再将编码后的令牌密码进行AES加密,并设置令牌密码过期时间,过期时间等于当前时间加上application.yml中设置的过期时间,最后将加密后的令牌密钥和过期时间压缩成紧凑字符串返回出去。

/*
 * @Description: JwtUtils工具类
 **/
@Component
public class JwtUtil {
    // 有效期
    @Value("${jwt.token-validity-in-seconds}")
    private Long JWT_EXPIRE;
    // 令牌密钥
    @Value("${jwt.secret-key}")
    private String JWT_KEY;
    public String createToken(Object data){
        // 当前时间
        Long currentTime = System.currentTimeMillis();
        // 过期时间
        Long expTime = currentTime+JWT_EXPIRE;
        //jwt
        JwtBuilder builder = Jwts.builder()
                .setId(UUID.randomUUID()+"")
                .setSubject(JSON.toJSONString(data))
                .setIssuer("acme") // 设置JWT的发布者信息
                .setIssuedAt(new Date(currentTime))
                // 签名方法
                .signWith(SignatureAlgorithm.HS256,encodeSecret(JWT_KEY))
                .setExpiration(new Date(expTime));
        return builder.compact();
    }

    private SecretKey encodeSecret(String key) {
        byte[] encode = Base64.getEncoder().encode(key.getBytes());
        SecretKeySpec aes = new SecretKeySpec(encode,0,encode.length,"AES");
        return aes;
    }

    public Claims parseToken (String token) {
        Claims  body = Jwts.parser()
                .setSigningKey(encodeSecret(JWT_KEY))
                .parseClaimsJws(token)
                .getBody();
        return body;
    }

    public <T>  T parseToken (String token,Class<T> clazz) {
        Claims body = Jwts.parser()
                .setSigningKey(encodeSecret(JWT_KEY))
                .parseClaimsJws(token)
                .getBody();
        return JSON.parseObject(body.getSubject(),clazz);
    }

}
6.2.3 测试用户登录接口

7. 获取当前用户信息

7.1 新增获取用户信息接口

在IUserService新增获取当前用户信息业务逻辑接口

/**
 *  获取当前用户信息业务逻辑接口
 * @param token
 * @return
 */
Map<String, Object> getCurrentUserInfo(String token);

7.2 实现获取用户信息接口

在UserServiceImpl业务逻辑接口实现类中实现获取当前用户信息接口

@Override
public Map<String, Object> getCurrentUserInfo(String token) {
    // 1. 根据 token 获取用户信息
    User loginUser = null;
    try {
        loginUser = jwtUtil.parseToken(token,User.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (loginUser != null) {
        Map<String, Object> data = new HashMap<>();
        // 用户信息
        data.put("username", loginUser.getUsername());
        data.put("avatar", loginUser.getAvatar());

        // 角色

        // 权限列表

        // 返回用户信息数据
        return data;
    }
    return null;
}

7.3 调用获取用户信息接口

在UserController控制器新增获取用户信息方法,调用获取用户信息业务逻辑实现接口实现方法

/**
 * 获取当前用户信息
 * @param token
 * @return
 */
@ApiOperation("当前用户信息")
@GetMapping("/info")
public Result<Map<String,Object>> getCurrentUserInfo(@RequestParam("token") String token){
    // 根据 token 获取用户信息
    Map<String,Object> data =  userService.getCurrentUserInfo(token);
    if (data != null) {
        return Result.success(data);
    }
    return Result.fail(20003,"用户登录无效,请重新登录");
}

7.4 测试获取用户信息接口

8. 注销登录

8.1 新增注销登录接口

在IUserService新增用户登录业务逻辑接口

/**
 * 注销登录
 * @param token
 * @return
 */
@PostMapping("logout")
public Result<?> logout(@RequestHeader("token") String token){
    userService.logout(token);
    return Result.success();
}

8.2 实现注销登录接口

在UserServiceImpl业务逻辑接口实现类中实现注销登录接口

@Override
public void logout(String token) {
}

8.3 调用注销登录接口

在UserController控制器新增注销登录方法,调用注销登录业务逻辑实现接口实现方法

/**
 * 注销登录
 * @param token
 * @return
 */
@PostMapping("logout")
public Result<?> logout(@RequestHeader("token") String token){
    userService.logout(token);
    return Result.success();
}

9.前后端对接

解决前后端跨越问题

Access to XMLHttpRequest at 'http://localhost:9999/system/user/login' from origin 
'http://localhost:9528' has been blocked by CORS policy: Response to preflight request
doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

9.1 后端跨域拦截器配置

9.1.1 在application.yml配置
# CorsUrl
corsUrl:
  url: http://localhost:9528
9.1.2 跨域后端拦截器配置
package org.cn.common.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/*
 * @Description: 跨域后端拦截器配置类
 **/
@Configuration
public class CorsConfig {
    // 允许前端服务访问后端接口
    @Value("${corsUrl.url}")
    private String CorsUrl;

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration configuration = new CorsConfiguration();

        // 允许什么网址来异步访问
        configuration.addAllowedOrigin(CorsUrl);
        // 获取cookie
        configuration.setAllowCredentials(true);
        // 允许什么方法? POST、GET,此处为* 意味全部允许
        configuration.addAllowedMethod("*");
        // 允许所有的请求头
        configuration.addAllowedHeader("*");

        // 设置资源过滤器,过滤什么资源
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",configuration);

        return new CorsFilter(urlBasedCorsConfigurationSource);

    }
}
9.1.3 Jwt拦截器放行配置
package org.cn.common.config;


import org.cn.common.interceptor.JwtValidateInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {

    @Resource
    private JwtValidateInterceptor jwtValidateInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor);
        registration.addPathPatterns("/**")
                .excludePathPatterns(
                        "/system/user/login",
                        "/system/user/info",
                        "/system/user/logout",
                        "/swagger-ui/**",
                        "/swagger-resources/**",
                        "/v3/**",
                        "/error",
                        "/webjars/**",
                        "/v2/**",
                        "/public/**",
                        "/static/**",
                        "*.html",
                        "doc.html",
                        "/favicon.ico"
                );
        }
    }

9.2 前后端联通测试

前端登录,查看页面控制台是否还报跨越的问题

10.系统管理

10.1 分页插件

实现分页查询,需要Mybatis Plus中分页插件配置成分页拦截器组件

package org.cn.common.config;

import com.baomidou.mybatisplus.annotation.DbType;
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;

/*
 * @Description: MybatisPlusConfig分页拦截器
 **/
@Configuration
public class MybatisPlusPageConfig {
    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM));//如果配置多个插件,切记分页最后添加
        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
        return interceptor;
    }
}

10.2 用户管理

10.2.1 用户列表
10.2.1.1 处理前端请求

①在UserController中接收前端发送 /system/user/list 请求

/**
 * 分页查询
 * @param username
 * @param phone
 * @param pageNo
 * @param pageSize
 * @return
 */
@ApiOperation("分页查询")
@GetMapping("/list")
public Result<Map<String,Object>> getUserList(
        @RequestParam(value = "username",required = false) String username,
        @RequestParam(value = "phone",required = false) String phone,
        @RequestParam(value = "pageNo") Long pageNo,
        @RequestParam(value = "pageSize") Long pageSize){
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
    // 疑惑:为什么请求参数中phone改成Long就查不出数据,请求参数中的类型是String才能查出数据
    wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);
    wrapper.orderByDesc(User::getId);

    Page<User> page = new Page<>(pageNo, pageSize);
    userService.page(page,wrapper);

    Map<String,Object> data = new HashMap<>();
    data.put("total",page.getTotal());
    data.put("rows",page.getRecords());

    return Result.success(data);
}
10.2.1.2 测试接口服务

②使用Postman/Apifox/Swagger等接口测试工具,获取token,传入参数,进行获取用户列表接口服务测试

注意:特别注意,测试工具的使用中,参数传入异常,折磨了两个小时,pageNo、pageSize参数未传入

Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'pageNo' for method parameter type Long is not present]

pageNo、pageSize参数正确传入

③前端获取数据进行展示

10.2.2 新增用户
10.2.2.1 处理前端请求

①在UserController中注入PasswordEncoder并调用后端服务接口

@Resource
private PasswordEncoder passwordEncoder;

// 新增用户
@ApiOperation("添加用户")
@PostMapping
public Result<?> addUser(@RequestBody User user){
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    userService.addUser(user);
    return Result.success("添加成功");
}
10.2.1.2 新增服务接口

②在IUserService中新增用户接口

/**
 * 新增用户
 * @param user
 */
void addUser(User user);
10.2.1.3 实现接口服务

③在UserServiceImpl实现新增用户接口

/**
 * 新增用户
 * @param user
 */
@Override
@Transactional
public void addUser(User user) {
    // 写入用户表
    this.baseMapper.insert(user);
    // 写入用户角色表
}
10.2.1.4 测试接口服务

④使用Postman/Apifox/Swagger等接口测试工具,进行添加用户接口服务测试,出现了一个异常

SQL: INSERT INTO XGIS.X_USER  ( id, username, password, email, status, phone )  VALUES  ( ?, ?, ?, ?, ?, ? )
### Cause: dm.jdbc.driver.DMException: 仅当指定列列表,且SET IDENTITY_INSERT为ON时,才能对自增列赋值

SET IDENTITY_INSERT XGIS.X_USER ON; // 开启自增

SET IDENTITY_INSERT XGIS.X_USER OFF; // 关闭自增

ALTER TABLE XGIS.X_USER DROP IDENTITY; // 删除自增
10.2.3 修改用户
10.2.3.1 处理前端请求

在UserController控制器获取用户ID的方法

/**
 * 根据前端传入用户id查询到单条用户信息,用于回显修改弹出框
 * @param id
 * @return
 */
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id") Integer id){
   User user = userService.getById(id);
    return Result.success(user);
}

在UserController控制器添加修改用户方法

/**
 * 修改用户
 * @param user
 * @return
 */
@PutMapping
public Result<?> updateUser(@RequestBody User user){
    user.setPassword(null);
    userService.updateById(user);
    return Result.success("修改成功");
}
10.2.3.2 测试接口服务

使用Postman/Apifox/Swagger等接口测试工具,进行修改用户接口服务测试。

10.2.4 删除用户
10.2.4.1 处理前端请求

在UserController控制器添加删除用户方法

/**
 * 根据用户id删除用户
 * @param id
 * @return
 */
@DeleteMapping("/{id}")
public Result<User> deleteUserById(@PathVariable("id") Integer id){
    userService.removeById(id);
    return Result.success("删除成功");
}

在application.yml中配置逻辑删除

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-not-delete-value: 0
      logic-delete-value: 1
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: org.cn
10.2.4.2 测试接口服务

使用Postman/Apifox/Swagger等接口测试工具,进行删除用户接口服务测试。

10.3 角色管理

10.2.1 角色列表
10.2.1.1 处理前端请求

单表增删改查

package org.cn.system.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.cn.common.utils.Result;
import org.cn.system.entity.Role;
import org.cn.system.service.IRoleService;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 角色表 前端控制器
 * </p>
 *
 * @author LuMin
 * @since 2024-11-26
 */
@Api(tags = "角色管理")
@RestController
@RequestMapping("/system/role")
public class RoleController {

    @Resource
    private IRoleService roleService;
    /**
     * 角色列表
     * @param roleName
     * @param roleDesc
     * @param pageNo
     * @param pageSize
     * @return
     */
    @ApiOperation("角色列表")
    @GetMapping("/list")
    public Result<Map<String,Object>> getRoleList(
            @RequestParam(value = "roleName",required = false) String roleName,
            @RequestParam(value = "roleDesc",required = false) String roleDesc,
            @RequestParam(value = "pageNo") Long pageNo,
            @RequestParam(value = "pageSize") Long pageSize){
        LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StringUtils.hasLength(roleName),Role::getRoleName,roleName);
        wrapper.eq(StringUtils.hasLength(roleDesc),Role::getRoleDesc,roleDesc);
        wrapper.orderByDesc(Role::getID);

        Page<Role> page = new Page<>(pageNo, pageSize);
        roleService.page(page,wrapper);

        Map<String,Object> data = new HashMap<>();
        data.put("total",page.getTotal());
        data.put("rows",page.getRecords());

        return Result.success(data);
    }

    /**
     * 添加角色
     * @param role
     * @return
     */
    @ApiOperation("添加角色")
    @PostMapping("/add")
    public Result<?> addRole(@RequestBody Role role){
        roleService.save(role);
        return Result.success("添加成功");
    }

    /**
     * 根据页面点解获取角色id查询到单条角色信息,用于回显修改弹出框
     * @param id
     * @return
     */
    @ApiOperation("单个角色")
    @GetMapping("/{id}")
    public Result<Role> getRoleById(@PathVariable("id") Integer id){
        Role role = roleService.getById(id);
        return Result.success(role);
    }

    /**
     * 修改角色
     * @param role
     * @return
     */
    @ApiOperation("修改角色")
    @PutMapping("/update")
    public Result<?> updateRole(@RequestBody Role role){
        roleService.updateById(role);
        return Result.success("修改成功");
    }

    /**
     * 根据角色id删除角色信息
     * @param id
     * @return
     */
    @ApiOperation("删除角色")
    @DeleteMapping("/{id}")
    public Result<Role> deleteRoleById(@PathVariable("id") Integer id){
        roleService.removeById(id);
        return Result.success("删除成功");
    }
}
10.2.1.2 测试接口服务

Postman工具测试

10.4 角色菜单

10.4.1 菜单列表
10.4.1.1 处理前端请求

①在Menu实体中加上构建菜单属性

@ApiModelProperty("子菜单")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private List<Menu> children;

@TableField(exist = false)
@ApiModelProperty("构建meta对象")
private Map<String,Object> meta;

public Map<String, Object> getMeta() {
    meta = new HashMap<>();
    meta.put("title",title);
    meta.put("icon",icon);
    return meta;
}

②在MenuController控制器种添加拦截请求

**注意:**将@Controller 改为@RestController,否则会报异常

**异常:**javax.servlet.ServletException: Could not resolve view with name 'system/menu/list' in servlet with name 'dispatcherServlet

@Resource
private IMenuService menuService;

@ApiOperation("菜单列表")
@GetMapping("/list")
public Result<List<Menu>> getMenuList () {
    List<Menu> menuList  = menuService.getMenuList();
    return Result.success(menuList);
}

③在IMenuService服务类种构建getMenuList()接口

/**
 * 菜单列表
 * @return
 */
List<Menu> getMenuList();

④在MenuServiceImpl实现类中实现getMenuList()接口

@Override
public List<Menu> getMenuList() {
    // 一级菜单
    LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Menu::getParentId,0);
    List<Menu> menuList = this.list(wrapper);

    // 填充子菜单
    setMenuChildren(menuList);
    return menuList;
}

private void setMenuChildren(List<Menu> menuList) {
    if (menuList != null) {
        for (Menu menu : menuList) {
            LambdaQueryWrapper<Menu> subWrapper = new LambdaQueryWrapper<>();
            subWrapper.eq(Menu::getParentId,menu.getId());
            List<Menu> subMenuList = this.list(subWrapper);
            menu.setChildren(subMenuList);
            // 递归
            setMenuChildren(subMenuList);
        }
    }
}
10.4.1.2 测试接口服务

⑤在Postman中测试getMenuList()接口

10.4.2 单条角色菜单
10.4.2.1 处理前端请求

①在Role实体中加入菜单属性

@ApiModelProperty("菜单列表")
@TableField(exist = false)
private List<Integer> menuIdList;

②在RoleController控制器中改造获取单个角色的方法

/**
 * 根据角色id查询某个角色所拥有的菜单权限
 * @param id
 * @return
 */
@ApiOperation("单条角色菜单")
@GetMapping("/{id}")
public Result<Role> getRoleById(@PathVariable("id") Integer id){
    Role role = roleService.getRoleById(id);
    return Result.success(role);
}

③在IRoleService服务类中构建getRoleById()接口

/**
 * 根据角色id查询某个角色所拥有的菜单权限
 * @param id
 * @return
 */
Role getRoleById(Integer id);

④在RoleServiceImpl实现类中实现getRoleById()接口

@Override
public Role getRoleById(Integer id) {
    Role role = this.baseMapper.selectById(id);
    List<Integer> menuIdList = roleMenuMapper.getMenuIdListByRoleId(id);
    role.setMenuIdList(menuIdList);
    return role;
}

⑤在RoleMenuMapper中构建查询数据库的getMenuIdListByRoleId()接口

/**
 * 根据角色菜单关联表,关联查询出某个角色拥有多少菜单权限
 * @param id
 * @return
 */
List<Integer> getMenuIdListByRoleId(Integer id);

⑥在RoleMenuMapper.xml构建自定义查询菜单SQL

<select id="getMenuIdListByRoleId" parameterType="Integer" resultType="Integer">
    SELECT XRM.MENU_ID
    FROM XGIS.X_ROLE_MENU AS XRM,
         XGIS.X_MENU AS XM
    WHERE XRM.MENU_ID = XM.ID
      AND XM.IS_LEAF = 'Y'
      AND XRM.ROLE_ID = #{ID}
</select>
10.4.2.2 测试接口服务

⑦在Postman中测试getMenuList()接口

10.4.2 添加角色菜单
10.4.2.1 处理前端请求

①在RoleController控制器中添加addRole()方法

/**
 * 添加角色菜单
 * @param role
 * @return
 */
@ApiOperation("添加角色菜单")
@PostMapping("/add")
public Result<?> addRole(@RequestBody Role role){
    roleService.addRole(role);
    return Result.success("添加成功");
}

②在IRoleService服务类中添加addRole()服务接口

/**
 * 添加角色菜单
 * @param role
 */
void addRole(Role role);

③在IRoleServiceImpl中实现addRole()服务接口

**注意:**使用roleMenuMapper.insert()时,需要在RoleMenu实体中添加构造方法,否则会报错

@Override
@Transactional
public void addRole(Role role) {
    //  写入角色表
    this.baseMapper.insert(role);

    // 写入菜单菜单关联表
    if (null != role.getMenuIdList()) {
        for (Integer menuId : role.getMenuIdList()){
            roleMenuMapper.insert(new RoleMenu(null,role.getId(),menuId));
        }
    }
}

④在ReloMenu实体中添加构造方法

public RoleMenu(Object o, Integer roleId, Integer menuId) {
    this.roleId = roleId;
    this.menuId = menuId;
}
10.4.2.2 测试接口服务

⑦在Postman中测试addRole()接口

10.4.3 修改角色菜单
10.4.3.1 处理前端请求

①在RoleController控制器中添加updateRole()方法

/**
 * 修改角色菜单:逻辑(根据角色Id把原先角色信息删除掉,再重新新增角色信息即可)
 * @param role
 * @return
 */
@ApiOperation("修改角色菜单")
@PutMapping("/update")
public Result<?> updateRole(@RequestBody Role role){
    roleService.updateRole(role);
    return Result.success("修改成功");
}

②在IRoleService服务类中添加 updateRole()服务接口

/**
 * 修改角色菜单
 * @param role
 */
void updateRole(Role role);

③在IRoleServiceImpl中实现addRole()服务接口

**注意:**使用roleMenuMapper.insert()时,需要在RoleMenu实体中添加构造方法,否则会报错

@Override
@Transactional
public void updateRole(Role role) {
    // 修改角色表
    this.baseMapper.updateById(role);
    // 删除原有权限
    LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(RoleMenu::getRoleId,role.getId());
    roleMenuMapper.delete(wrapper);
    // 新增权限
    if (null != role.getMenuIdList()) {
        for (Integer menuId : role.getMenuIdList()){
            roleMenuMapper.insert(new RoleMenu(null,role.getId(),menuId));
        }
    }
}

④在ReloMenu实体中添加构造方法

public RoleMenu(Object o, Integer roleId, Integer menuId) {
    this.roleId = roleId;
    this.menuId = menuId;
}
10.4.3.2 测试接口服务

⑦在Postman中测试updateRole()接口

10.4.4 删除角色菜单
10.4.4.1 处理前端请求

①在RoleController控制器中添加deleteRoleById()方法

/**
 * 删除角色菜单
 * @param roleId
 * @return
 */
@DeleteMapping("/{roleId}")
public Result<Role> deleteRoleById(@PathVariable("roleId") Integer roleId){
    roleService.deleteRoleById(roleId);
    return Result.success("删除成功");
}

②在IRoleService服务类中添加 deleteRoleById()服务接口

/**
 * 删除角色菜单
 * @param roleId
 */
void deleteRoleById(Integer roleId);

③在IRoleServiceImpl中实现deleteRoleById()服务接口

@Override
@Transactional
public void deleteRoleById(Integer id) {
    // 删除角色
    this.baseMapper.deleteById(id);
    // 删除权限
    LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(RoleMenu::getRoleId,id);
    roleMenuMapper.delete(wrapper);

}
10.4.4.2 测试接口服务

⑦在Postman中测试deleteRoleById()接口

10.5 用户角色

10.5.1 单条角色菜单
10.5.2.1 处理前端请求

①在User实体中加入角色属性

/**
 * 角色列表
 */
@TableField(exist = false)
private List<Integer> roleIdList;

②在UserController控制器中改造获取单个用户的方法

/**
 * 根据前端传入用户id查询到单条用户和角色信息,用于回显修改弹出框
 * @param id
 * @return
 */
@ApiOperation("单条用户角色")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id") Integer id){
    User user = userService.getUserById(id);
    return Result.success(user);
}

③在IUserService服务类中构建getRoleById()接口

/**
 * 获取单条用户角色
 * @param id
 * @return
 */
User getUserById(Integer id);

④在UserServiceImpl实现类中实现getUserById()接口

/**
 * 单条用户角色
 * @param id
 * @return
 */
@Override
public User getUserById(Integer id) {
    User user = this.baseMapper.selectById(id);
    LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(UserRole::getUserId,id);
    List<UserRole> userRoleList = userRoleMapper.selectList(wrapper);

    List<Integer> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());

    user.setRoleIdList(roleIdList);
    return user;
}
10.5.2.2 测试接口服务

⑤在Postman中测试getMenuList()接口

10.5.2 添加用户角色
10.5.2.1 处理前端请求

①在UserController控制器中添加addUser()方法

/**
 * 添加用户角色
 * @param user
 * @return
 */
@ApiOperation("添加用户角色")
@PostMapping("/add")
public Result<?> addUser(@RequestBody User user){
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    userService.addUser(user);
    return Result.success("添加成功");
}

②在IUserService服务类中添加addUser()服务接口

/**
 * 添加用户角色
 * @param user
 */
void addUser(User user);

③在IUsererviceImpl中实现addUser()服务接口

**注意:**使用userRoleMapper.insert()时,需要在UserRole实体中添加构造方法,否则会报错

/**
 * 添加用户角色
 * @param user
 */
@Override
@Transactional
public void addUser(User user) {
    // 写入用户表
    this.baseMapper.insert(user);
    // 写入用户角色表
    List<Integer> roleIdList = user.getRoleIdList();
    if (roleIdList != null) {
        for (Integer roleId : roleIdList){
            userRoleMapper.insert(new UserRole(null,user.getId(),roleId));
        }
    }
}

④在UserRole实体中添加构造方法

public UserRole(Object o, Integer id, Integer roleId) {
    this.userId = id;
    this.roleId = roleId;
}
10.4.5.2 测试接口服务

⑦在Postman中测试addUser()接口

10.5.3 修改用户角色
10.5.3.1 处理前端请求

①在UserController控制器中添加updateUser()方法

/**
 * 修改用户角色
 * @param user
 * @return
 */
@ApiOperation("修改用户角色")
@PutMapping("/update")
public Result<?> updateUser(@RequestBody User user){
    user.setPassword(null);
    userService.updateUser(user);
    return Result.success("修改成功");
}

②在IUserService服务类中添加updateUser()服务接口

/**
 * 修改用户角色
 * @param user
 */
void updateUser(User user);

③在IUsererviceImpl中实现updateUser()服务接口

**注意:**使用userRoleMapper.insert()时,需要在UserRole实体中添加构造方法,否则会报错

/**
 * 修改用户角色
 * @param user
 */
@Override
@Transactional
public void updateUser(User user) {
    // 更新用户表
    this.baseMapper.updateById(user);
    // 删除原有角色
    LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(UserRole::getUserId,user.getId());
    userRoleMapper.delete(wrapper);
    // 设置新的角色
    List<Integer> roleIdList = user.getRoleIdList();
    if (roleIdList != null) {
        for (Integer roleId : roleIdList){
            userRoleMapper.insert(new UserRole(null,user.getId(),roleId));
        }
    }
}

④在UserRole实体中添加构造方法

public UserRole(Object o, Integer id, Integer roleId) {
    this.userId = id;
    this.roleId = roleId;
}
10.4.3.2 测试接口服务

⑦在Postman中测试updateUser()接口

10.5.4 删除用户角色
10.5.4.1 处理前端请求

①在UserController控制器中添加deleteUserById()方法

/**
 * 删除用户角色
 * @param id
 * @return
 */
@ApiOperation("删除用户角色")
@DeleteMapping("/{id}")
public Result<User> deleteUserById(@PathVariable("id") Integer id){
    userService.deleteUserById(id);
    return Result.success("删除成功");
}

②在IUserService服务类中添加deleteUserById()服务接口

/**
 * 删除用户角色
 * @param id
 */
void deleteUserById(Integer id);

③在IUsererviceImpl中实现deleteUserById()服务接口

/**
 * 删除用户角色
 * @param id
 */
@Override
public void deleteUserById(Integer id) {
    // 删除用户
    this.baseMapper.deleteById(id);
    // 删除角色
    LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(UserRole::getUserId,id);
    userRoleMapper.delete(wrapper);
}
10.4.3.2 测试接口服务

⑦在Postman中测试deleteById()接口

11 动态路由

11.1 菜单列表

11.1.1 自定义查询SQL

在 MenuMapper.xml 中菜单列表关联查询SQL

<select id="getMenuListByUserId" resultType="org.cn.system.entity.Menu">
    SELECT DISTINCT XM.*
    FROM XGIS.X_USER_ROLE AS XUR,
         XGIS.X_ROLE_MENU AS XRM,
         XGIS.X_MENU AS XM,
         WHERE XM.ID = XRM.MENU_ID
   AND XRM.ROLE_ID = XUR.ROLE_ID
   AND XUR.USER_ID = #{userId}
   AND XM.PARENT_ID = #{parentId}
</select>
11.2 构建菜单列表Mapper接口

在 MenuMapper 中构建获取菜单列表接口:getMenuListByUserId()

/**
 * 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
 * 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
 * @param userId
 * @param parentId
 * @return
 */
public List<Menu> getMenuListByUserId(@Param("userId") Integer userId, @Param("parentId") Integer parentId);
11.3 构建菜单列表Service接口

在 IMenuService 中构建获取菜单列表服务接口:getMenuListByUserId()

/**
 * 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
 * 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
 * @param userId
 * @return
 */
List<Menu> getMenuListByUserId(Integer userId);
11.4 实现菜单列表Service接口

在 MenuServiceImpl 中实现获取菜单列表服务接口:getMenuListByUserId()

/**
 * 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
 * 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
 * @param userId
 * @return
 */
@Override
public List<Menu> getMenuListByUserId(Integer userId) {
    // 一级菜单
    List<Menu> menuList = this.baseMapper.getMenuListByUserId(userId, 0);
    // 子菜单
    setMenuListByUserId(userId, menuList);
    return menuList;
}

private void setMenuListByUserId(Integer userId, List<Menu> menuList) {
    if (menuList != null){
        for (Menu menu : menuList) {
            List<Menu> subMenuList = this.baseMapper.getMenuListByUserId(userId,menu.getId());
            menu.setChildren(subMenuList);
            // 递归
            setMenuListByUserId(userId,subMenuList);
        }
    }
}

11.2 角色列表

11.2.1 自定义查询SQL

在 UserMapper.xml 中菜单列表关联查询SQL

<select id="getRoleNameByUserId" resultType="java.lang.String">
    SELECT XR.ROLE_DESC
    FROM XGIS.X_USER_ROLE AS XUR,
         XGIS.X_ROLE AS XR
    WHERE XUR.ROLE_ID = XR.ID
      AND XUR.USER_ID = #{userId}
</select>
11.2.2 构建角色名称Mapper接口

在 UserMapper 中构建获取菜单列表接口:getRoleNameByUserId()

/**
 * 根据用户ID获取角色名称
 * @param id
 * @return
 */
List<String> getRoleNameByUserId(Integer id);
11.3 获取所有角色信息列表
/**
 * 角色列表
 * @return
 */
@ApiOperation("角色列表")
@GetMapping("/all")
public Result<List<Role>> getAllRoleList(){
    List<Role> roleList = roleService.list();
    return Result.success(roleList);
}
11.4 测试用户角色接口服务

在UserController控制器中测试获取当前用户信息接口是否能够获取到用户对应的角色信息、菜单信息

调用顺序:getCurrentUserInfo() --->getRoleNameByUserId()--->getMenuListByUserId()

/**
 * 获取当前用户信息:包括用户所有角色信息,用户所有菜单信息
 * @param token
 * @return
 */
@ApiOperation("当前用户信息")
@GetMapping("/info")
public Result<Map<String,Object>> getCurrentUserInfo(@RequestParam("token") String token){
    // 根据 token 获取用户信息
    Map<String,Object> data =  userService.getCurrentUserInfo(token);
    if (data != null) {
        return Result.success(data);
    }
    return Result.fail(20003,"用户登录无效,请重新登录");
}