Shiro的使用(六)—SpringBoot整合shiro和Swagger2实现前后端分离

通过扩展AccessControlFilter,HashedCredentialsMatcher完成了自定义身份校验器,访问控制过滤器等核心技术。
使用了全局业务异常,处理项目中可能出现的异常信息,并使用了枚举定义输出信息。
封装了通用的返回结果集,可在实际开发项目中,直接进行使用。

0、流程图、思路分析

在这里插入图片描述

思路

首先看图,先理解整合shiro之后如何实现前后端分离的项目

看完图我们会发现几个问题

  1. 为什么不使用shiro进行身份认证?

    1. 因为这是前后端分离项目,原本的html页面可能和后台并不在同一个项目中,使用shiro是访问不到html页面的
  2. 如何储存用户认证成功生成的 token ?

    1. token 可以储存在session、redis、数据库中
    2. 这里采用 储存在数据库中的方式,给数据中添加一个字段 token
    3. 再设置上 token 的过期时间,添加一个字段 expireDate
  3. 登录成功后,再次访问资源的时候,如何校验身份?

    1. 不能使用shiro原本的校验方式,原本校验是通过校验密码实现的,现在传递的验证方式只有token
    2. 所以我们需要重写 密码校验器的方法,实现我们自己的验证方式。

1、代码实现

1.1、导入依赖

`<dependencies>
        <!-- springboot对缓存的支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- ehcache缓存核心 -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.4</version>
        </dependency>
        <!-- shiro对ehcache缓存的支持 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>

        <!-- swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

        <!-- druid连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

        <!-- spring对shiro的支持 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </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>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

1.2、配置缓存文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
    <diskStore path="D:\mytemp"/>
    <cache name="users"
           timeToLiveSeconds="300"
           maxEntriesLocalHeap="1000"/>
    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
</ehcache>

1.3、配置application.properties

server.port=8080
spring.application.name=springboot
# mybatis配置
mybatis.type-aliases-package=com.fu.springboot.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
# druid连接池配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///demo
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

1.4、编写swaggerConfig

@SpringBootConfiguration
@EnableSwagger2
public class SwaggerConfig {
    private Logger logger = LoggerFactory.getLogger(SwaggerConfig.class);

    @Bean
    public Docket docket() {
        logger.info("Swagger2 ---> docket 执行了...");
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo()) // 生成接口文档的头部信息
                .select()           // 表示选择哪些路径和API生成文档,这里是所有
                .apis(RequestHandlerSelectors.basePackage("com.fu.springboot.controller"))  // 指定接口所在的包
                .paths(PathSelectors.any())     // 表示对所有的API进行监控
                .build();
    }
    /**
     * 接口文档的头部信息
     */
    private ApiInfo apiInfo() {
        logger.info("Swagger2 ---> apiInfo 执行了...");
        Contact contact = new Contact("孔明", "暂无url", "[email protected]");
        return new ApiInfoBuilder()
                .title("SpringBoot整合shiro+Swagger2实现前后端分离")
                .description("文档描述")
                .contact(contact)
                .version("v1.0")
                .build();
    }
}

1.5、编写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = 8142836626401616290L;

    private Integer id;
    private String name;
    private String password;
    private String token;
    private Date expireDate;
}

1.6、编写返回结果集

public interface ResponseCodeInterface {
    /**
     *  获取返回码
     */
    int getCode();

    /**
     *  获取返回的消息
     */
    String getMsg();
}
public enum  BaseResponseCode implements ResponseCodeInterface {
    SUCCESS(0, "操作成功"),
    SYSTEM_ERROR(500001, "系统错误"),
    METHOD_INVALIDATE(400001, "数据校验出错"),
    DATA_ERROR(400002, "传入数据异常"),
    TOKEN_NOT_NULL(401001, "用户token不存在,请重新登录"),
    TOKEN_ERROR(500002, "用户身份校验失败,请重新登录");

    private int code;
    private String msg;

    BaseResponseCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public int getCode() {
        return this.code;
    }

    @Override
    public String getMsg() {
        return this.msg;
    }
}
@Data
public class DataResult<T> {
    /**
     * 码值
     */
    private int code = 0;
    /**
     * 返回的错误信息
     */
    private String msg = "";
    /**
     *  返回的数据
     */
    private T data;

    // 封装构造器
    public DataResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public DataResult(int code, T data) {
        this.code = code;
        this.data = data;
    }

    public DataResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public DataResult(){
        this.code = BaseResponseCode.SUCCESS.getCode();
        this.msg = BaseResponseCode.SUCCESS.getMsg();
        this.data = null;
    }

    public DataResult(T data){
        this.code=BaseResponseCode.SUCCESS.getCode();
        this.msg=BaseResponseCode.SUCCESS.getMsg();
        this.data=data;
    }

    public DataResult(ResponseCodeInterface responseCodeInterface) {
        this.code = responseCodeInterface.getCode();
        this.msg = responseCodeInterface.getMsg();
    }

    public DataResult(ResponseCodeInterface responseCodeInterface, T data) {
        this.code = responseCodeInterface.getCode();
        this.msg = responseCodeInterface.getMsg();
        this.data = data;
    }

    // 不带数据的返回值信息
    public static <T>DataResult success() {
        return new DataResult();
    }
    // 带数据的返回值
    public static <T>DataResult success(T data) {
        return new <T>DataResult(data);
    }
    // 3个参数的返回值
    public static <T>DataResult getResult(int code, String msg, T data) {
        return new <T>DataResult(code, msg, data);
    }
    // 2个参数的返回值(码值,提示信息)
    public static <T>DataResult getResult(int code, String msg) {
        return new <T>DataResult(code, msg);
    }
    // 2个参数的返回值(码值,用户信息)
    public static <T>DataResult getResult(int code, T data) {
        return new <T>DataResult(code, data);
    }
    /**
     *  直接传递一个枚举类型
     */
    public static <T>DataResult getResult(BaseResponseCode baseResponseCode) {
        return new <T>DataResult(baseResponseCode);
    }
    public static <T>DataResult getResult(BaseResponseCode baseResponseCode, T data) {
        return new <T>DataResult(baseResponseCode, data);
    }
}

1.7、编写全局业务异常

public class BusinessException extends RuntimeException {
    private static final long serialVersionUID = 3618625760384608631L;

    private int messageCode;
    private String defaultMessage;

    public BusinessException(int messageCode,String defaultMessage){
        super(defaultMessage);
        this.messageCode=messageCode;
        this.defaultMessage=defaultMessage;
    }

    public String getDefaultMessage() {
        return defaultMessage;
    }

    public int getMessageCode() {
        return messageCode;
    }
}

1.8、编写mapper

public interface UserMapper {
    /**
     *  通过用户名查询用户
     */
    User getUserByName(String name);
    /**
     * 查询所有的用户
     */
    List<User> getUserAll();
    /**
     *  更新数据库用户的token
     */
    void updateUser(User user);
    /**
     *  查看token是否存在
     */
    User getUserByToken(String token);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fu.springboot.mapper.UserMapper">

    <!-- 通过用户名查询用户 -->
    <select id="getUserByName" resultType="user">
        select * from user where name = #{name}
    </select>

    <!-- 查询所有的用户 List<User> getUserAll(); -->
    <select id="getUserAll" resultType="user">
        select * from user
    </select>

    <!-- 更新数据库用户的token void updateToken(User user); -->
    <update id="updateUser" parameterType="user">
        update user set token = #{token}, expireDate = #{expireDate} where id = #{id}
    </update>

    <!-- 查看token是否存在  User getUserByToken(String token); -->
    <select id="getUserByToken" resultType="user">
        select * from user where token = #{token}
    </select>
</mapper>

1.9、编写service

public interface UserService {
    /**
     * 根据用户名获取用户信息
     */
    User getUserByName(String name);
    /**
     *  登录
     */
    User login(User user);
    /**
     * 更新用户信息
     */
    void updateUser(User user);
    /**
     * 查询所有的用户
     */
    List<User> getUserList()throws Exception;
    /**
     * 判定这个token是否存在
     */
    boolean tokenExistsOrNot(String token);
}
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserByName(String name) {
        return userMapper.getUserByName(name);
    }

    /**
     *  登录
     * 第一步:获取到前端传递过来的用户名
     * 第二步:通过用户名 获取用户对象
     * 第三步:校验
     * 第四步:生成token保存到数据库
     * 第五步:将token封装到返回数据里面给前端
     */
    @Override
    public User login(User user) {
        String name = user.getName();
        User userResult = this.getUserByName(name);
        if (null == userResult) {   // 说明用户名不对
            throw new BusinessException(400001, "用户名不正确");
        }
        if (!userResult.getPassword().equals(user.getPassword())) {
            throw new BusinessException(400002, "密码错误");
        }
        // 生成token,这里用UUID表示
        String token = UUID.randomUUID().toString().substring(0, 30);
        Date date = new Date();
        userResult.setToken(token);
        userResult.setExpireDate(date);
        // 更新数据库的数据
        this.updateUser(userResult);
        userResult.setPassword("");
        return userResult;
    }

    @Override
    public void updateUser(User user) {
        userMapper.updateUser(user);
    }
    @Override
    public List<User> getUserList() throws Exception {
        return userMapper.getUserAll();
    }
    @Override
    public boolean tokenExistsOrNot(String token) {
        try {
            User user = userMapper.getUserByToken(token);
            if (null != user) {
                return true;
            }
        } catch (Exception e) {
            return false;
        }
        return false;
    }
}

1.10、编写controller

@RestController
@Api(tags = {"用户接口"})
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("login")
    @ApiOperation(value = "用户登录的接口")
    public DataResult<User> login(@RequestBody User user) {
        User user1 = userService.login(user);
        DataResult<User> result = null;
        try {
            result = DataResult.success(user1);
        } catch (Exception e) {
            if (e instanceof BusinessException) {
                BusinessException err = (BusinessException) e;
                result = DataResult.getResult(err.getMessageCode(), err.getMessage());
            } else {
                result = DataResult.getResult(BaseResponseCode.SYSTEM_ERROR.getCode(), BaseResponseCode.SYSTEM_ERROR.getMsg());
            }
            return result;
        }
        return result;
    }

    @GetMapping("list")
    @ApiOperation("获取所有用户的信息")
    @ApiImplicitParam(paramType = "header", name = "token", value = "验证身份的token", required = true, dataType = "string")
    public Object getUserList() {
        DataResult<List<User>> result = null;
        try {
            List<User> list = userService.getUserList();
            result = DataResult.success(list);
        } catch (Exception e) {
            e.printStackTrace();
            result = DataResult.getResult(BaseResponseCode.SYSTEM_ERROR.getCode(), BaseResponseCode.SYSTEM_ERROR.getMsg());
        }
        return result;
    }
}

1.11、自定义token

public class CustomToken extends UsernamePasswordToken {
    private static final long serialVersionUID = 561721881796304836L;

    /**
     * 用户身份唯一的标识
     *     这个token是在认证通过之后,用户访问其他资源的时候,来进行身份识别的
     */
    private String token;

    /**
     *  只允许在 CustomToken构造的时候给token赋值
     */
    public CustomToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        // 在用户认证通过之后 再访问这个方法 默认返回的是 Realm校验的第一个参数
        // Realm校验我们是自己定义的,我们可以自己设置这个方法的返回值
        return this.token;
    }
}

1.12、编写UserRealm

public class UserRealm extends AuthorizingRealm {
    private Logger logger = LoggerFactory.getLogger(UserRealm.class);

    @Override
    public String getName() {
        return "UserRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 取出前端传递过来的token
        CustomToken customToken = (CustomToken) authenticationToken;
        String token = (String) customToken.getPrincipal();
        // 将前端传递过来的token封装到 SimpleAuthenticationInfo 对象中
        SimpleAuthenticationInfo simpleAuthorizationInfo = new SimpleAuthenticationInfo(token, token, getName());
        logger.info("UserRealm ---> 认证身份信息执行...");
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        logger.info("UserRealm ---> 授权方法执行...");
        return simpleAuthorizationInfo;
    }
}

1.13、自定义身份校验器

public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher {

    @Autowired
    private UserService userService;

    /**
     * @return 返回true代表校验成功,false代表失败
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        // 获取客户端传过来的token
        CustomToken customToken = (CustomToken) token;
        String tokenClient = (String) customToken.getPrincipal();
        // 获取从服务器获取的token(redis,数据库 或者 session)
        boolean b = false;
        try {
            b = userService.tokenExistsOrNot(tokenClient);
        } catch (Exception e) {
            throw new BusinessException(BaseResponseCode.TOKEN_NOT_NULL.getCode(), BaseResponseCode.TOKEN_NOT_NULL.getMsg());
        }
        // 判断token是否一致
        if (!b) {
            throw new BusinessException(BaseResponseCode.TOKEN_ERROR.getCode(), BaseResponseCode.TOKEN_ERROR.getMsg());
        }
        return true;
    }
}

1.14、自定义访问控制过滤器

public class CustomAccessControlFilter extends AccessControlFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 校验身份
        try {
            // 1. 获取token
            String token = request.getHeader(Constant.REQ_TOKEN);
            // 2. 判断 token 是否为空
            if (StringUtils.isEmpty(token)) { // 用户的身份是非法的
                throw new BusinessException(400004, "用户请求的token不能为空");
            } else {    // 用户已经登录,并获取到了token
                // 3. 封装token
                CustomToken customToken = new CustomToken(token);
                // 4. 把token交给shiro做认证,判断身份是否合法
                /*  这个方法,用户第一次访问请求(即 登录)的时候,并不会执行
                    只有在认证成功之后访问其他资源的时候,才会执行
                    作用是:校验用户身份,而不是登录
                 */
                getSubject(servletRequest, servletResponse).login(customToken);
                return true;
            }
        } catch (BusinessException e) {
            // 如果是这个异常:返回JSON告诉客户端,出现问题了
            resultResponse(e.getMessageCode(), e.getDefaultMessage(), servletResponse);
        } catch (AuthenticationException e) { // 校验未通过异常
            // e.getCause() :该方法返回的是当前异常的实例
            if (e.getCause() instanceof BusinessException) { // 表示返回的是自定义的异常
                // 将异常实例进行转换
                BusinessException err = (BusinessException) e.getCause();
                resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
            } else { // 说明是 shiro 抛出的异常
                resultResponse(400001, "用户身份校验失败", servletResponse);
            }
        } catch (AuthorizationException e) {    // 授权时出现异常
            if (e.getCause() instanceof BusinessException) {
                BusinessException err = (BusinessException) e.getCause();
                resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
            } else {
                resultResponse(403001, "用户没有访问权限", servletResponse);
            }
        } catch (Exception e) { // 捕获未考虑到的异常,比如系统异常
            if (e.getCause() instanceof BusinessException) {
                BusinessException err = (BusinessException) e.getCause();
                resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
            } else {
                resultResponse(500001, "服务器开小差了,系统出错", servletResponse);
            }
        }
        return false;
    }

    /**
     * 这个方法的主要功能就是告诉客户端 一些出错的信息
     */
    private void resultResponse(int messageCode, String defaultMessage, ServletResponse servletResponse) {
        // 构建返回的数据
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", messageCode);
        jsonObject.put("msg", defaultMessage);
        // 设置返回的数据类型
        /*  MediaType.APPLICATION_JSON_UTF8_VALUE ===>>> MediaType.APPLICATION_JSON_VALUE
            MediaType.APPLICATION_JSON_UTF8_VALUE 已被标记@Deprecated
            自Spring Framework 5.2起不推荐使用,而推荐使用{@link #APPLICATION_JSON_VALUE}
            由于主要的浏览器(例如Chrome)现在已符合规范并正确解释了UTF-8特殊字符 不需要{@code charset = UTF-8}参数。
         */
        servletResponse.setContentType(MediaType.APPLICATION_JSON.toString());
        // 获取输出流
        try {
            ServletOutputStream out = servletResponse.getOutputStream();
            // 将数据写出去
            out.write(jsonObject.toJSONString().getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.15、编写shiroConfig

@SpringBootConfiguration
public class ShiroConfig {
    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    // 自定义密码认证器
    @Bean
    public CustomHashedCredentialsMatcher customHashedCredentialsMatcher() {
        CustomHashedCredentialsMatcher hashedCredentialsMatcher = new CustomHashedCredentialsMatcher();
        logger.info("shiro ---> HashedCredentialsMatcher 执行了...");
        return hashedCredentialsMatcher;
    }
    // 用户Realm
    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(customHashedCredentialsMatcher());
        logger.info("shiro ---> UserRealm 执行了...");
        return userRealm;
    }
    // 安全管理器
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm());
        defaultWebSecurityManager.setCacheManager(ehCacheManager());
        logger.info("shiro ---> SecurityManager 执行了...");
        return defaultWebSecurityManager;
    }
    // shiro的过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 配置自定义的校验身份的过滤器
        LinkedHashMap<String, Filter> customAccessControlFilter = new LinkedHashMap<>();
        customAccessControlFilter.put("token", new CustomAccessControlFilter());
        shiroFilterFactoryBean.setFilters(customAccessControlFilter);
        // 配置拦截访问路径的过滤器
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("/user/login", "anon");
        map.put("/swagger/**","anon");
        map.put("/v2/api-docs","anon");
        map.put("/swagger-ui.html","anon");
        map.put("/swagger-resources/**","anon");
        map.put("/webjars/**","anon");
        map.put("/favicon.ico","anon");
        map.put("/captcha.jpg","anon");
        map.put("/csrf","anon");
        map.put("/**", "token,authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        logger.info("shiro ---> ShiroFilterFactoryBean 执行了...");
        return shiroFilterFactoryBean;
    }
    // 缓存管理器
    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        logger.info("shiro ---> EhCacheManager 执行了...");
        return ehCacheManager;
    }
    /**
     * 开启aop的注解的支持
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        attributeSourceAdvisor.setSecurityManager(securityManager);
        return attributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
}

1.16、编写AppConfig

@SpringBootApplication
@ComponentScan("com.fu.springboot")
@MapperScan("com.fu.springboot.mapper")
public class AppConfig {
}

1.17、测试

访问 http://127.0.0.1:8080/swagger-ui.html ,在页面进行接口测试即可

3、总结

  1. 首先理解文章开头的流程图,理解那几个问题,然后才能对项目有清晰的思路
  2. 重点是那两个过滤器(身份校验器,访问流程过滤器),还有返回结果集的封装,一定要理解清除,那是本项目的核心所在
  3. 本项目还有很多不足之处,可自行拓展
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章