程序员

从零开始构建springboot 2.x Web项目【持续更新】

2018-12-04  本文已影响5人  谢随安

简介

文章内容介绍:基于SpringBoot 2.x 的demo,集成了 spring-boot-security、mybatis、druid、redis 等等

读者按需自取,还有很多未完成的,慢慢来

项目代码下载地址:https://github.com/ChaselX/spring-boot-2-demo

使用Maven构建项目

可以用IDEA集成好的Spring Initializr来创建一个SpringBoot项目。

IDEA创建SpringBoot项目 Group和Artifact根据你的项目随意命名

勾选上自己需要的依赖(不选也没关系,在Maven中手动加即可)

Spring Boot应用启动器

Spring Boot提供了很多应用启动器,分别用来支持不同的功能,因为Spring Boot的自动化配置特性,我们不需考虑项目依赖版本问题,使用Spring Boot的应用启动器,它能自动帮我们将相关的依赖全部导入到项目中。

这里介绍几个常见的应用启动器:

也可以在Spring官网 https://start.spring.io/ 构建项目,勾选上自己需要的依赖即可(之后在Maven里再加也可以)。

项目的创建成功以后的Maven如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version> 发布文章
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置系统基本参数

要访问mysql数据库,还需要配置一下系统变量。
默认的系统变量配置文件是项目当前文件夹的/src/main/resources下的application.properties

但是我更喜欢yml的风格,删掉这个文件,在相同的位置创建一个application.yml文件

# 指定端口号
server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password:
mybatis:
  mapper-locations: classpath*:mapper/*.xml  #注意:一定要对应mapper映射xml文件的所在路径
  type-aliases-package: com.example.demo.model.entity  # 注意:对应实体类的路径

编写控制层处理HTTP请求

/src/main/java/com/example/demo/controller下创建一个HelloController.java

package com.example.demo.controller;

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

/**
 * @author ChaselX
 * @date 2018/10/6 18:56
 */
@RestController
@RequestMapping("/")
public class HelloController {
    @GetMapping
    public String sayHello() {
        return "Hello SpringBoot!";
    }
}

运行DemoApplication.java,浏览器请求localhost:8080可以看到如下效果

localhost:8080

通过Mybatis操作数据库

根据之前的配置

mybatis:
  mapper-locations: classpath*:mapper/*.xml  # 注意:一定要对应mapper映射xml文件的所在路径
  type-aliases-package: com.example.demo.model.entity # 对应实体类的路径

/src/main/java/com/example/demo/model/entity下创建实体类SysUser.java

package com.example.demo.model.entity;

/**
 * @author ChaselX
 * @date 2018/10/7 17:42
 */
public class SysUser {

    private static final long serialVersionUID = 215517484123587L;

    /**
     * 主键id
     */
    private Long id;

    /**
     * 账号
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 姓名
     */
    private String name;

    /**
     * 电话号码
     */
    private String mobile;

    /**
     * 账号是否可用
     */
    private boolean enabled;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}

/src/main/java/com/example/demo/mapper下创建UserMapper.java

package com.example.demo.mapper;

import com.example.demo.model.entity.SysUser;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/10/7 17:53
 */
public interface UserMapper {
    @Insert("INSERT INTO user(username, password, name, mobile) VALUES(#{username}, #{password}, #{name}, #{mobile})")
//    返回插入记录的主键id
//    @SelectKey(statement = "select LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class)
    int add(SysUser user);

    @Select("SELECT * from user")
    List<SysUser> getAll();
}

DemoApplication.java上加一个@MapperScan("com.example.demo.mapper")注解,这个注解的作用是自动扫描com.example.demo.mapper包下的Mapper,实现并注入到Bean中。

package com.example.demo;

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

@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

编写对应的后端控制层与服务层业务逻辑代码,文件位置参考代码中的package

package com.example.demo.service;

import com.example.demo.model.entity.SysUser;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/10/8 8:59
 */
public interface UserService {
    List<User> getAll();

    boolean addUser(SysUser user);
}
package com.example.demo.service.impl;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.entity.SysUser;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/10/8 8:59
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public List<SysUser> getAll() {
        return userMapper.getAll();
    }

    @Override
    public boolean addUser(SysUser user) {
        return userMapper.add(user) > 0;
    }
}
package com.example.demo.controller;

import com.example.demo.model.entity.SysUser;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * @author ChaselX
 * @date 2018/10/8 9:08
 */
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping
    public ResponseEntity getAllUsers() {
        return new ResponseEntity<>(userService.getAll(), HttpStatus.OK);
    }

    @PostMapping
    public ResponseEntity addUser(@RequestBody SysUser user) {
        if (userService.addUser(user)) {
            return ResponseEntity.ok().build();
        }
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("创建用户失败!");
    }
}

现在,可以运行项目利用postman对上面的功能接口进行测试了

redis配置与使用(非必须)

这里不讲redis的安装和启动,只讲项目如何使用redis

pom.xml中加入spring-boot-starter-data-redis依赖

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

对系统配置文件application.yml做如下配置

spring: 
  redis:
      host: 127.0.0.1
      port: 6379
      timeout: 2000ms
      database: 0
      password:
      lettuce:
        pool:
          max-active:  100 # 连接池最大连接数(使用负值表示没有限制)
          max-idle: 100 # 连接池中的最大空闲连接
          min-idle: 50 # 连接池中的最小空闲连接
          max-wait: 6000ms

由于使用的是SpringBoot 2.0推荐的lettuce连接池。SpringBoot 2.0需要手动构建LettuceConnectionFactory Bean

package com.example.demo.common.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

/**
 * @author ChaselX
 * @date 2018/9/4 10:02
 */
@Configuration
public class RedisConfig {
    @Autowired
    private RedisProperties redisProperties;

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisProperties.getHost(), redisProperties.getPort()));
    }
}
Redis哨兵主从模式

对系统配置文件application.yml做如下配置

  spring: 
    redis:
      timeout: 2000ms
      database: 1
      lettuce:
        pool:
          max-active:  100 # 连接池最大连接数(使用负值表示没有限制)
          max-idle: 100 # 连接池中的最大空闲连接
          min-idle: 50 # 连接池中的最小空闲连接
          max-wait: 6000ms
      sentinel:
        master: mymaster
        nodes: 10.1.58.117:27379,10.1.58.137:27379
      password: 

构建哨兵模式的连接工厂,修改LettuceConnectionFactory

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes()));
        redisSentinelConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        return new LettuceConnectionFactory(redisSentinelConfiguration);
    }
Redis集群

对系统配置文件application.yml做如下配置

spring: 
  redis:
    cluster:
      nodes: 
        - 192.168.1.111:7001
        - 192.168.1.112:7001
        - 192.168.1.110:7002
        - 192.168.1.110:7001
        - 192.168.1.111:7002
        - 192.168.1.112:7001
      password:
      lettuce:
        pool:
          max-active:  100 # 连接池最大连接数(使用负值表示没有限制)
          max-idle: 100 # 连接池中的最大空闲连接
          min-idle: 50 # 连接池中的最小空闲连接
          max-wait: 6000ms
      timeout: 2000ms

构建集群模式的连接工厂,修改LettuceConnectionFactory

    @Bean
    public LettuceConnectionFactory redisConnectionFactory(RedisClusterConfiguration redisClusterConfiguration) {
        return new LettuceConnectionFactory(redisClusterConfiguration);
    }

    @Bean
    public RedisClusterConfiguration redisClusterConfiguration() {
        RedisClusterConfiguration configuration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes());
        configuration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        return configuration;
    }
使用Redis

改造之前的HelloController对redis功能做简单的测试

package com.example.demo.controller;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author ChaselX
 * @date 2018/10/6 18:56
 */
@RestController
@RequestMapping("/")
@MapperScan("com.example.demo.mapper")
public class HelloController {
    @Autowired
    private RedisTemplate<String, String> stringStringRedisTemplate;

    @GetMapping
    public String sayHello() {
        stringStringRedisTemplate.opsForValue().set("Say hello", "Hello SpringBoot From Redis!", 5, TimeUnit.SECONDS);
        return stringStringRedisTemplate.opsForValue().get("Say hello");
    }
}

运行项目

数据库创建用户及授权(非必须)

对于正式生产环境,你登录到数据库的往往不会是root用户,而是通过仅具有特定数据库权限的用户登录数据库。可以通过下面的SQL语句创建数据库用户。

insert into mysql.user(Host,User,Password) values("%","admin",password("admin123"));

GRANT ALL ON db_name.* TO admin@% identified by "admin123";

flush privileges;

数据库连接池druid配置(非必须)

Maven中加入druid依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

application.yml中加上如下配置

spring:
  datasource:
    druid:
      # 初始化大小,最小,最大
      initialSize: 5
      minIdle: 5
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,slf4j
      # 打开PSCache,并且指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxOpenPreparedStatements: 20
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      # yml方式配置servlet与filter
      stat-view-servlet:
        enabled: true
        # /druid登录账号
        login-username: admin 
        # /druid登录密码
        login-password: admin 
        reset-enable: false
      web-stat-filter:
        enabled: true
        exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico
        url-pattern: /*

配置好druid后访问 {url}/druid,由于配置了登录账号和密码,需要身份认证

认证成功后便可通过监控页面查看各项监控数据

基于Spring Security安全框架的的认证与验证

传统的登录是通过cookie-session方式实现登录认证,而在前后端分离的情况下,实现用户鉴权的更好的方式是使用JWT(Java Web Token)

引入Spring Security

pom.xml中加入Spring Security依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

在项目中加入WebSecurityConfig配置文件

package com.example.demo.common.config.security;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author ChaselX
 * @date 2018/11/28 16:18
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // http请求安全配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .antMatchers("/").permitAll()
                .anyRequest().authenticated() // 所有请求都需要权限验证
                .and()
                .logout().permitAll()
                .and()
                .formLogin();

    }

//    // 忽略web静态资源,若需要
//    @Override
//    public void configure(WebSecurity web) throws Exception {
//        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
//    }
}

修改一下之前的HelloController

package com.example.demo.controller;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author ChaselX
 * @date 2018/10/6 18:56
 */
@RestController
@RequestMapping("/")
@MapperScan("com.example.demo.mapper")
public class HelloController {
    @Autowired
    private RedisTemplate<String, String> stringStringRedisTemplate;

    @GetMapping
    public String mainPage() {
        return "Hello SpringBoot From \"/\"";
    }

    @GetMapping("/sayHello")
    public String sayHello() {
        stringStringRedisTemplate.opsForValue().set("demo:SayHello", "Hello SpringBoot From Redis!", 5, TimeUnit.SECONDS);
        return stringStringRedisTemplate.opsForValue().get("demo:SayHello");
    }
}

运行项目测试,访问系统首页

首页正常展示

访问localhost:8080/sayHello会跳转到http://localhost:8080/login登录页

Spring Security的安全策略已经生效,但是具体的登录功能还没有实现

基于Spring Security的登录功能实现

要实现基于Spring Security的登录功能,首先需要定义一个继承了Spring Security的UserDetailsService接口的接口,修改一下之前的UserService

package com.example.demo.service;

import com.example.demo.model.entity.SysUser;
import org.springframework.security.core.userdetails.UserDetailsService;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/10/8 8:59
 */
public interface UserService extends UserDetailsService {
    List<SysUser> getAll();

    boolean addUser(SysUser user);
}

再修改一下接口的实现类UserServiceImpl实现UserDetailsService接口的loadUserByUsername()方法

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }

因为要返回UserDetails对象,具体方法实现先放在一边。先看看如何使用这个方法进行用户认证。在SpringSecurityConfig加入以下代码

    @Autowired
    private UserDetailsService userServiceImpl; // 属性名为userServiceImpl对应实现类的名称

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceImpl);
    }
密码自定义加密验证

用户的密码在数据库中通常是以密文的形式存储的,为此需要实现一个密码的自定义验证,指定Spring Security使用什么加密规则对密码进行验证,创建一个beanbCryptPasswordEncoder

package com.example.demo.common.config.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author ChaselX
 * @date 2018/11/28 19:21
 */
@Configuration
public class BaseConfig {
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

修改WebSecurityConfig的代码指定passwordEncoder

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceImpl).passwordEncoder(bCryptPasswordEncoder);
    }

由于使用了密码加密验证,需要修改一下添加用户那里的逻辑,在插入数据库之前先对密码做BCrypt加密

package com.example.demo.service.impl;

import com.example.demo.mapper.UserMapper;
import com.example.demo.model.entity.SysUser;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

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

/**
 * @author ChaselX
 * @date 2018/10/8 8:59
 */
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }

    @Override
    public List<SysUser> getAll() {
        return userMapper.getAll();
    }

    @Override
    public boolean addUser(SysUser sysUser) {
        sysUser.setPassword(encoder.encode(sysUser.getPassword()));
        return userMapper.add(sysUser) > 0;
    }
}

这样便实现了密码的自定义验证

基于RBAC的用户、角色、权限

虽然实现了加密验证,但是却没有定义权限验证相关的用户、角色、权限

要使用Spring Security安全框架,由于UserDetailsService.loadUserByUsername()返回的是一个UserDetails类型的对象。UserDetails接口中最重要的是getAuthorities()方法,用户所具有的所有权限都定义在里面。因此需要做些处理,从数据库获取系统用户,并根据相关的角色权限来构造UserDetailsauthorities属性,为此首先需要定义好系统的用户、角色、权限实体表与它们之间的关系表。用户实体类已经定义好,还有角色、权限、用户角色关系以及角色权限关系未定义。

package com.example.demo.model.entity;

import java.util.Date;

/**
 * 角色实体类
 *
 * @author ChaselX
 * @date 2018/12/1 16:05
 */
public class Role {
    /**
     * 角色的authority前缀
     */
    public static final String PREFIX = "ROLE_";

    /**
     * 主键id
     */
    private Long id;

    /**
     * 角色代号
     */
    private String code;

    /**
     * 角色名
     */
    private String name;

    /**
     * 备注
     */
    private String remark;

    private Long operator;

    private Date operateTime;

    // 省略get/set方法代码
}
package com.example.demo.model.entity;

import java.util.Date;

/**
 * 权限实体类
 * 
 * @author ChaselX
 * @date 2018/12/1 16:00
 */
public class Permission {
    /**
     * 主键id
     */
    private Long id;

    /**
     * 权限编码
     */
    private String code;

    /**
     * 权限名称
     */
    private String name;

    /**
     * 操作人
     */
    private String operator;

    /**
     * 操作时间
     */
    private Date operateTime;

    // 省略get/set方法代码
}

package com.example.demo.model.entity;

import java.util.Date;

/**
 * 用户-角色关系表
 *
 * @author ChaselX
 * @date 2018/12/1 16:17
 */
public class UserRole {
    private Long id;

    private Long userId;

    private Long roleId;

    private Long operator;

    private Date operateTime;

    // 省略get/set方法代码
}
package com.example.demo.model.entity;

import java.util.Date;

/**
 * 角色-权限关系表
 *
 * @author ChaselX
 * @date 2018/12/1 16:25
 */
public class RolePermission {
    private Long id;

    private Long roleId;

    private Long permissionId;

    private Long operator;

    private Date operateTime;

    // 省略get/set方法代码
}

数据库相关建表这边就不赘述了,按照基本的主键id自增,参数驼峰命名法转下划线命名法即可,若有需要日后再补充。

为了减少数据库的访问次数,一次性将用户相关的信息(角色、权限)查询出来,封装一个SysUserVO

package com.example.demo.model.vo;

import com.example.demo.model.entity.Permission;
import com.example.demo.model.entity.Role;
import com.example.demo.model.entity.SysUser;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/12/1 16:51
 */
public class SysUserVO extends SysUser {
    private List<Role> roles;

    private List<Permission> permissions;

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

UserMapper添加一个查询用户详细信息的方法getDetailsByUsername

package com.example.demo.mapper;

import com.example.demo.model.entity.SysUser;
import com.example.demo.model.vo.SysUserVO;
import org.apache.ibatis.annotations.*;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/10/7 17:53
 */
public interface UserMapper {
    @Insert("INSERT INTO user(username, password, name, mobile) VALUES(#{username}, #{password}, #{name}, #{mobile})")
//    返回插入记录的主键id
//    @SelectKey(statement = "select LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class)
    int add(SysUser sysUser);

    @Select("SELECT * from user")
    List<SysUser> getAll();

    @Select("SELECT * FROM user WHERE username = #{username}")
    @Results({
            @Result(property = "roles", column = "user_id", many = @Many(select = "com.example.demo.mapper.RoleMapper.getRolesByUserId")),
            @Result(property = "permissions", column = "user_id", many = @Many(select = "com.example.demo.mapper.PermissionMapper.getPermissionsByUserId"))
    })
    SysUserVO getDetailsByUsername(String username);
}

注意,由于SysUserVO的两个字段roles与permissions是集合类型的,所以用到了@Results@Result@Many注解,更全面的说明可参考mybatis官方文档(需翻墙)

@Many注解的select属性表明select引用的来源分别为com.example.demo.mapper.RoleMapper下的getRolesByUserId方法与com.example.demo.mapper.PermissionMapper下的getPermissionsByUserId方法

package com.example.demo.mapper;

import com.example.demo.model.entity.Role;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/12/4 14:36
 */
public interface RoleMapper {
    @Select("select r.id, r.code, r.name, r.remark from role r where r.id in (select ur.role_id from user_role ur where ur.user_id = #{userId})")
    List<Role> getRolesByUserId(Long userId);
}
package com.example.demo.mapper;

import com.example.demo.model.entity.Permission;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author ChaselX
 * @date 2018/12/4 14:58
 */
public interface PermissionMapper {
    @Select("SELECT p.id, p.code, p.name FROM permission p WHERE p.id IN (" +
            "SELECT rp.permission_id FROM role_permission rp WHERE rp.role_id in(" +
            "SELECT ur.role_id FROM user_role ur WHERE ur.user_id = #{userId}))")
    List<Permission> getPermissionsByUserId(Long userId);
}

这些都完成了以后就可以动手实现前面放置在一边的UserServiceImplloadUserByUsername方法了

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserVO userVO = userMapper.getDetailsByUsername(username);
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : userVO.getRoles()
        ) {
            authorities.add(new SimpleGrantedAuthority(Role.PREFIX + role.getCode()));
        }
        for (Permission permission : userVO.getPermissions()
        ) {
            authorities.add(new SimpleGrantedAuthority(permission.getCode()));
        }
        return new User(userVO.getUsername(), userVO.getPassword(), authorities);
    }

现在登录功能已经实现,可以运行项目进行登录功能测试了(tips:在登录之前需要先在用户表中加入使用BCrypt加密的用户记录),项目启动后访问http://localhost:8080/login会跳转到登录界面

输入用户名和密码,登录成功后会返回系统首页


未完待续 未完待续 未完待续 未完待续 未完待续 未完待续 未完待续 未完待续 未完待续

aes加密传输登录密码

在非https的情况下,若无特殊处理,用户的登录密码会以明文的方式传输给后端。因此需要对用户密码进行加密传输,保证请求报文即使被截取,也不会泄露用户的密码。前后端加解密流程如下(图片引用):

调用接口获取动态加密秘钥

在客户端向后端post登录信息之前,先调用接口获取动态加密秘钥,前端生成随机秘钥,后端会把缓存放进redis里,为了安全性考虑,缓存的有效期设置为5s

客户端收到动态加密秘钥后,通过秘钥对密码做AES加密,将登陆信息通过POST请求发送给后端

jwt动态刷新

数据库分页查询

往项目的pom.xml里加入

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>latest version</version>
</dependency>
上一篇下一篇

猜你喜欢

热点阅读