# diboot 常见问题FAQ

# diboot支持Spring Boot版本情况

  • diboot 2.6.x 支持 Spring boot 2.7.x
  • diboot 2.5.x 支持 Spring boot 2.6.x
  • diboot 2.4.x 支持 Spring boot 2.6.x
  • diboot 2.3.x 支持 Spring boot 2.5.x

# IAM的后端代码在哪里?

IAM的controller等后端基础代码由devtools自动生成:

  • 配置好diboot组件依赖和devtools依赖
  • 启动项目,进入devtools的组件初始化页面,选择core及IAM等组件,执行初始化
  • devtools将生成IAM基础的代码到你配置的路径下

# 自定义jackson配置

diboot-core-starter中包含默认的HttpMessageConverters配置,启用jackson并做了初始化配置。 其中关键配置参数为:

@Bean
@ConditionalOnMissingBean
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper objectMapper = converter.getObjectMapper();
    ...
    return converter;
}
1
2
3
4
5
6
7
8

如果该配置无法满足您的开发场景,可以在Configuration文件中重新定义HttpMessageConverters:

@Bean
public HttpMessageConverters jacksonHttpMessageConverters() {
    ...
}
1
2
3
4

# 引入diboot-*-starter后,SQL分页查询出现重复的LIMIT

LIMIT ? LIMIT ? 
1

重复定义了分页导致的,diboot-core-starter默认预置了mybatis-plus的分页配置(使用mybatis-plus 3.4.x的MybatisPlusInterceptor最新配置方式)。 如果您依赖的是core-starter,则无需再次配置mybatis-plus的分页,将您自定义的mybatis-plus分页配置删掉即可。 如果需要添加其他Interceptor,则需要重新定义MybatisPlusInterceptor。 示例如下:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}
1
2
3
4
5
6

# 老项目中使用diboot的绑定能力

mybatis-plus老项目想要使用diboot的绑定能力或在无数据库连接配置文件的module下,使用diboot-core?

对于没有历史包袱的新项目,我们建议您使用diboot全套体系。 core内核组件有以下两个包:

  • diboot-core: 内核代码
  • diboot-core-starter: 依赖diboot-core,增加了自动配置及初始化字典表等功能(需要依赖数据库信息)。

对于mybatis-plus老项目中想要使用diboot的绑定能力 或 在无数据库连接配置文件的module下,使用内核组件,可以只依赖diboot-core,而不是diboot-core-starter。 步骤如下:

  1. 添加core依赖(非core-starter)
<dependency>
    <groupId>com.diboot</groupId>
    <artifactId>diboot-core</artifactId>
    <version>{latestVersion}</version>
</dependency>
1
2
3
4
5
  1. 如果只依赖core,你还需要将com.diboot.core加入包扫描并实现HttpMessageConverters和Mybatis-plus的分页配置:
@ComponentScan(basePackages={"com.diboot.core"})
@MapperScan(basePackages = {"com.diboot.core.mapper"})
1
2
  1. 如果只依赖core,且需要使用@BindDict字典绑定,需实现DictionaryServiceExtProvider接口。 (使用diboot-core-starter可以自动创建dictionary表,或者可以下载SQL (opens new window)手动建表。

# 启动报错:找不到mapper中的自定义接口

diboot-devtools默认不指定mapper.xml路径时,mapper.xml文件会生成到mapper同路径下便于维护。 此时需要修改pom配置,让编译包含xml、dtd类型文件。

  • Maven配置:
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.dtd</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
        </resource>
    </resources>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • Gradle配置:
sourceSets {
    main {
        resources {
            srcDirs "src/main/java"
            include '**/*.xml'
            include '**/*.dtd'
            include '**/*.class'
        }
        resources {
            srcDirs "src/main/resources"
            include '**'
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 如何构建树形结构?

树形结构对象约定:要有 parentId属性(根节点为0) 和 List children 属性,便于自动构建。

  • 先把需要构建树形结构的节点全部查出来,如:
List<Menu> menus = menuService.getEntityList(wrapper);
1
  • 调用BeanUtils.buildTree构建树形结构
// 如果children属性在VO中,可以调用BeanUtils.convertList转换后再构建
menus = BeanUtils.buildTree(menus);
1
2

返回第一级子节点集合。

# 查询Date类型日期范围,如何自动绑定?

使用Comparison.GE,Comparison.LT进行绑定,避免数据库转型。

/**
 * 创建时间-起始
 */
@BindQuery(comparison = Comparison.GE, field = "createTime")
private Date createTimeBegin;

/**
 * 创建时间-截止(截止时间<=当天23:59:59是不精确的,应该是<第二天)
 */
@BindQuery(comparison = Comparison.LT, field = "createTime")
private Date createTimeEnd;

public TodoRemider setCreateTimeEnd(Date createTimeEnd) {
    this.createTimeEnd = D.nextDay(createTimeEnd);
    return this;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 查询Date类型日期时间字段 = 某天,如何自动绑定?

建议逻辑: datetime_field >= beginDate AND datetime_field < (beginDate+1) 。 无函数处理,不涉及数据库类型转换。示例:

/**
 * 创建时间-起始
 */
@BindQuery(comparison = Comparison.GE, field = "createTime")
private Date createTime;
/**
 * 创建时间-截止
 */
@BindQuery(comparison = Comparison.LT, field = "createTime")
private Date createTimeEnd;

public Date getCreateTimeEnd() {
    return D.nextDay(createTime);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 如何自动填充创建人、创建时间、更新时间等字段

  • 创建时间、更新时间首选采用数据库填充方式实现
  • 如需代码自动填充的字段,可通过Mybatis-plus的MetaObjectHandler自动填充, 具体请参考mybatis-plus文档 (opens new window)。 示例: 注解标记填充字段:
class MyEntity {
    @TableField(fill = FieldFill.INSERT)
    private Long createBy;
    ...
}
1
2
3
4
5

实现填充Handler:

@Component
public class AutoFillMetaObjectHandler implements MetaObjectHandler{
		
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, Cons.FieldName.createBy.name(), IamSecurityUtils::getCurrentUserId, Long.class);
        // this.strictInsertFill(metaObject, Cons.FieldName.createTime.name(), LocalDateTime::now, LocalDateTime.class);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, Cons.FieldName.updateBy.name(), IamSecurityUtils::getCurrentUserId, Long.class);
        // this.strictUpdateFill(metaObject, Cons.FieldName.updateTime.name(), LocalDateTime::now, LocalDateTime.class);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 如何配置swagger接口文档(以SpringDoc实现为例)

因springfox更新缓慢,无法执行spring boot 2.6版本,diboot推荐使用SpringDoc替代springfox。

步骤1. pom中引入SpringDoc依赖

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.6.2</version>
</dependency>
1
2
3
4
5

步骤2. 添加配置类

// 示例配置类
@Configuration
public class OpenApiConfig {
    @Bean
    public OpenAPI initOpenAPI() {
        return new OpenAPI().info(
            new Info().title("My Project API").description("OpenAPI").version("v1.0")
        );
    }
}
1
2
3
4
5
6
7
8
9
10

步骤1&2为swagger的正常配置,如果引入了diboot IAM组件,需要添加以下配置使swagger相关url可以匿名访问。

步骤3. 设置swagger相关的匿名url配置,使swagger不被拦截, 如下:

#swagger 3.x版本参考配置
diboot.iam.anon-urls=/swagger**/**,/v3/**
1
2

另外,如果启用了diboot devtools,可以配置devtools生成代码启用swagger注解。

diboot.devtools.enable-swagger=true
1

附: swagger访问入口地址:

# 如何启用多租户

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    // 建议严格遵循以下顺序添加拦截器
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 租户拦截器(可选)
    interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
        @Override
        public Expression getTenantId() {
            return new LongValue(0);
        }

        // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
        @Override
        public boolean ignoreTable(String tableName) {
            return "iam_account".equalsIgnoreCase(tableName) || S.startsWithIgnoreCase(tableName, "diboot_");
        }
    }));
    // 数据权限拦截器(可选)
    interceptor.addInnerInterceptor(new DataAccessControlInteceptor());
    // 分页拦截器,需保证 add PaginationInnerInterceptor 在最后
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    return interceptor;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 页面后台报如下错误:

Unable to correctly extract the Initialization Vector or ciphertext 异常信息:

o.a.shiro.mgt.DefaultSecurityManager: Delegate RememberMeManager instance of type [org.apache.shiro.web.mgt.CookieRememberMeManager] threw an exception during getRememberedPrincipals().
org.apache.shiro.crypto.CryptoException: Unable to correctly extract the Initialization Vector or ciphertext.
at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:378) ~[shiro-core-1.8.0.jar:1.8.0]
...
...
Caused by: java.lang.ArrayIndexOutOfBoundsException: null
at java.lang.System.arraycopy(Native Method) ~[na:1.8.0_221]
at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:370) ~[shiro-core-1.8.0.jar:1.8.0]
... 44 common frames omitted
1
2
3
4
5
6
7
8
9

问题原因: shiro的rememberMe策略与cookie相关问题引起。

解决方案: 直接清空浏览器的cookie的缓存,建议清空所有的cookie,问题可以得到解决。极有可能是当前浏览器在其他的网站中有过rememberMe的记录导致后台的shiro会用cookie去反序列化导致。

# Java 实现跨域方式

# 全局跨域

// 定义跨域filter
public class CustomCorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;

        // 告诉浏览器允许所有的域访问
        // 注意 * 不能满足带有cookie的访问,Origin 必须是全匹配
        // resp.addHeader("Access-Control-Allow-Origin", "*");
        // 解决办法通过获取Origin请求头来动态设置
        String origin = request.getHeader("Origin");
        if (StringUtils.hasText(origin)) {
            resp.addHeader("Access-Control-Allow-Origin", "*");
        }
        // 允许带有cookie访问
        resp.addHeader("Access-Control-Allow-Credentials", "true");

        // 告诉浏览器允许跨域访问的方法
        resp.addHeader("Access-Control-Allow-Methods", "*");
        // 设置支持所有的自定义请求头
        String headers = request.getHeader("Access-Control-Request-Headers");
        if (StringUtils.hasText(headers)) {
            resp.addHeader("Access-Control-Allow-Headers", headers);
        }
        // 告诉浏览器缓存OPTIONS预检请求1小时,避免非简单请求每次发送预检请求,提升性能
        resp.addHeader("Access-Control-Max-Age", "3600");
        chain.doFilter(request, resp);
    }
}

// 初始化filter
@Bean
public FilterRegistrationBean customCorsFilter(){
    FilterRegistrationBean registrationBean = new FilterRegistrationBean(new CustomCorsFilter());
    //过滤所有路径
    registrationBean.addUrlPatterns("/*");
    registrationBean.setOrder(1);
    return registrationBean;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 局部跨域

  • 在控制器(类上)上使用注解 @CrossOrigin,表示该类的所有方法允许跨域。
@RestController
@CrossOrigin("*")
public class VerificationController {

}
1
2
3
4
5
  • 在方法上使用注解 @CrossOrigin
@PostMapping("/check/phone")
@CrossOrigin("*")
public boolean checkPhoneNumber(@RequestBody @ApiParam VerificationPojo verification) throws BusinessException {
    return false;
}
1
2
3
4
5