一、Java

1. Excel文件导出中文名称时被转义

1
2
String fileName = URLEncode.encode(name, "UTF-8");
response.setHeader("Content-disposition", "attachment;filename*=utf-8'zh_cn'" + fileName + ".xlsx");

文件的下载在响应之前要设置以 附件(attachment) 的形式,否则点击下载时会在浏览器打开。
Spring MVC 默认的上传文件大小(max-request-size)最大为10M,可以在配置文件中修改。

二、Spring

1. Spring Security 5.x 跨线程获取用户信息

使用 Spring Security 时,如果采用异步方法获取用户信息是获取不到的,它采用 ThreadLocal 存储,这样的话有些请求无法顺利执行,可以在 Spring Boot 启动类主方法添加:

1
2
3
4
5
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// 开启本地线程共享
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}

Spring Security 6.x+ 版本不推荐此方式,推荐传递 context 到子线程或者异步线程中再获取信息;

2. @RestControllerAdvice + swagger 导致的问题

如果在项目中实现 ResponseBodyAdvice 接口统一封装 controller 返回的接口,访问 swagger 地址时会出现弹窗 Unable to infer base url. This is common when using dynamic servlet registration or when the API is behind an API Gateway 的问题。

需要在实现 ResponseBodyAdvice 的类注解上额外加入 @RestControllerAdvice(basePackages = "xxx.xx.controller") 限定需要对返回结果进行封装的范围,这样的话就不会拦截 swagger 相关资源的地址,访问就正常了。

也可以下面这样修改(浪子没有测试,具体见:https://juejin.cn/post/6921700441038258189):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestControllerAdvice
// @RestControllerAdvice(basePackages = "com.example.controller") // 只对此包中的类进行结果封装
public class ResultResponseHandler implements ResponseBodyAdvice {

@Override
public boolean supports(MethodParameter returnType, Class converterT ype) {
// 过滤不需要封装的结果
if (returnType.getParameterType().isAssignableFrom(ResultResp.class)) {
return false;
}
return true;
}

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Json || body instanceof UiConfiguration ||
(body instanceof ArrayList && ((ArrayList) body).get(0) instanceof SwaggerResource)) {
return body;
}
return ResultResp.success(body);
}
}

3. 跨域

xxxController.java
1
2
3
4
5
// 注解用在 Controller 类中,该类所有方法允许其它域中进行访问
@CrossOrigin
public class xxxController {

}
CorsConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins("*");
config.setAllowedHeaders("*");
config.setAllowedMethods("*");
// 处理所有请求的跨域配置
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
WebMvcConfig.jav
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 跨域访问(CORS)
*
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(false)
// 所有头
.allowedHeaders("/**")
// 所有源
.allowedOrigins("/**")
// 所有方法
.allowedMethods("/**")
.maxAge(5000);
}
}

4. 在 Filter 中注入 Spring Bean

在 Spring Web 中,执行顺序是 context-param–>listener–>filter–>servlet,可以看到 servlet 是在最后的,所以在 Filter 中注入 Bean 的时候就会报 NPE,如果是 SpringBoot 项目,可以在 Filter 的实现类上使用 @Order 注解延迟加载,让 Bean 先行加载,这样就可以在 Filter 中注入了。

5. Spring AOP 和 Spring Security 6.X

1
Cannot invoke "org.apache.commons.logging.Log.isDebugEnabled()" because "this.logger" is null

背景:多模块项目,AOP 依赖在 common 模块,Security 在 system 模块,system 依赖于 common。使用 @Aspect 定义了切面和日志,但是定义切入点的时候是 execution(* com.xxx..*.*(..))com.xxx 项目是顶级包。

而 Security 中的 OncePerRequestFilter 继承的 GenericFilterBean#init() 中有如下代码:

1
2
3
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
}

在进行切入的时候,把这里的 logger 给覆盖了,导致一直拿不到值,项目启动就抛出了 NPE。

修改很简单,打印日志一般抓 controller 层,只需要把切入点定义改为:execution(* com.xxx.controller..*.*(..))。更加推荐的是把 AOP 依赖直接移入 system 中,相关的日志类也放到该模块,这里出现错误是因为之前的划分原本就不是很合理。

三、MyBatis/MyBatis Plus(MP)

Result Maps collection does not contain value for ……

在 SQL 文件中使用了 resultType="java.util.map",并且不止一处使用,那么凡是使用 resultType 或者 resultMap 这种属性的标签,不能有属性指向错误,否则就会报错: “Result Maps collection does not contain value for ……”

Parameter index out of range (2 > number of parameters, which is 1).

不能在 mybatis 的 sql.xml 中的 sql 标签(<select>、<update>、<delete>等)中注释 sql 语句,如果注释的语句中 带有参数,那么就会报这个错误。如果有必须注释的语句,把相关语句复制一份放在 mybatis 标签外面使用<!-- 注释 sql 内容 --> 注释。如果可以,还是把 SQL 语句存储在外部的 txt 等文本文件里。

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

有这个错误的话就是 mapper 接口和对应的 xml 文件没有绑定,或者绑定了,但是 xml 文件不是在 resources/mapper 路径下。需要在项目的 pom.xml 中添加以下内容:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 把 xml 文件一起打包 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

1. $ And #

这个我想很多人都知道使用哪个比较好了,说说问题。具体语句省略,请注意循环内的条件。

1
2
3
4
5
6
7
8
9
10
11
12
select * from user u
left join address a on u.address = a.site
where a.longitude = #{longitude} and a.latitude = #{latitude}
<if test="mallId != null and mallId != ''">
and u.mallId = #{mallId}
</if>
<if test="propertyType != null and propertyType.size() > 0">
and u.property_type in
<foreach collection="propertyType" index="index" item="type" open="(" separator="," close=")">
${type}
</foreach>
</if>

上面的语句在循环中使用了 ${},我把它修改成了 #{},但是输入的筛选的条件是不会生效的,继而没有返回数据。

#{}不生效

之后看了看它传的参数,发现接口接收的字符串,然后在业务层转为字符串集合,并且为每个元素加入单引号后传入 SQL 的。

1
2
3
4
5
6
7
8
9
10
11
12
private static List<String> getParamList(String propertyType) {
List<String> paramList = new ArrayList();
if (!StringUtils.isEmpty(propertyType.trim())) {
String[] split = propertyType.split(","):
forint i = θ;i< split.length; i++){
split[i] = "'" + split[i] + "'";
paramlist.add(split[i]);
}
}
log.debug("propertyType list : {}", paramlist);
return paramlist;
}

于是猜想可能就是这个原因导致查询条件不生效,于是把这段代码改成了下面的样子:

1
2
3
4
5
6
7
private static List<String> getParamlist(String str) {
List<String> result = null;
if (!StringUtils.isEmpty(str.trim())) {
result = Arrays.stream(str.split(",")).map(String::trim).collect(Collectors.toList(O);
}
return result;
}

这段代码没有给分割后的字符加入英文单引号,map 方法对每一个元素都去掉前后的空格(请结合自身业务场景确定参数是否可包含空格,浪子这里不需要),然后再次测试该接口,perfect!it is working now!

之后使用修改后的字符转集合方法代码再次使用 ${} 去测试,发现 SQL 语句查询又不生效了。。。

于是浪子明白了:使用 ${} 时,参数为 List 类型需要我们为每个元素手动加入单引号,使用单引号包裹才会生效;而使用 #{} 时,List 参数类型则不需要我们手动加入英文单引号,直接传入 List 即可。

2. 接口传参报错

错误信息:

1
No primary or default constructor found for interface java.util.List]

后端使用 List<String> 或者数组接参数收时,前端传入数组接收不到;但是可以传字符串,每个元素可以使用英文 , 分割。

3. MP插入或更新null值

当使用 Mybatis Plus 时,更新数据为 null 值时,即使数据库的字段设置为可以为 null,但是更新或插入时还是数据更新失败。这个就涉及到字段验证策略了。MP 官网也给出了 解决办法

4. mapper.xml

mapper.xml 中编写 SQL 语句时有部分字符需要转义才可以被框架正确的解析:

转义字符 含义 说明
&gt; > greater than
&lt; < less than
&amp; &

也可以使用 <![CDATA[]]> 来声明语句,被这个标记所包含的内容将表示为纯文本。


本站由 江湖浪子 使用 Stellar 1.28.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。