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

时间:2024-12-12 16:28:05

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.****.net/liuerchong/article/details/108606650

image-20241127102414528

image-20241127102437926

image-20241127101627304

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 测试用户登录接口

image-20241203155304154

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 前后端联通测试

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

image-20241204112925413

image-20241204113001551

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]

image-20241129151744591

pageNo、pageSize参数正确传入

image-20241129151849537

③前端获取数据进行展示

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; // 删除自增

image-20241204113041988

image-20241204113054765

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等接口测试工具,进行删除用户接口服务测试。

image-20241204114908961

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工具测试

image-20241205084752431

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()接口

image-20241205151911846

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()接口

image-20241205153335737

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()接口

image-20241205155215536

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()接口

image-20241206103904001

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()接口

image-20241206104130140

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()接口

image-20241206104214356

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()接口

image-20241206104303116

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()接口

image-20241206104420918

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()接口

image-20241206104500573

11 动态路由

11.1 菜单列表

11.1.1 自定义查询SQL

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

image-20241205174756775

<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<