目录:
(可以按w
快捷键切换大纲视图)
记录下用Java开发项目中遇到的一些问题:
@Pattern注解
对于http request body中的字段需要做正则校验,不需要条件判断的一般性正则校验,可以用@Pattern,而不必在方法中写正则校验,简化开发。
为了校验必须采用”sftp://“开头,写成了下面的形式
@Pattern(regexp = “^sftp://“)
但是字段内容是sftp://10.10.10.10:33022/ftp_test
时报错
查了下@Pattern注解自动会加上^和&。正确的写法是@Pattern(regexp = "^sftp://.*$")
或者 @Pattern(regexp = "sftp://.*")
于是又完善了正则表达式写成下面的形式:
/**
* ftpPath
*/
@JsonProperty("ftpServer")
@Pattern(regexp = "sftp://.+[:\\d+]?/.*", message = "ftpPath字段需要符合 \"sftp //\" 语法格式, \n" +
"例如sftp://10.10.10.10:33022/ftp_test\n" +
"10.10.10.10 是ip地址或者hostname,33022是sftp的端口,/ftp_test是sftp的目录(sftp的根目录是当前用户的home目录)")
private String ftpPath;
/**
* ftpUser
*/
@JsonProperty("ftpUser")
private String ftpUser;
/**
* ftpPasswd
*/
@JsonProperty("ftpPasswd")
private String ftpPasswd;
要同时开启https和https服务
单独开启http服务,springboot的配置文件写
#http端口号.
server.port: 33021
单独开启https服务,用keytool做一个证书,springboot的配置文件这样写:
#https端口号.
server.port: 33021
#https配置
#证书的路径.
server.ssl.key-store: classpath:keystore.p12
#server.ssl.key-store: file:/Users/hanwei/xxx/yyy/api/src/main/resources/keystore.p12
#证书密码,请修改为您自己证书的密码.
server.ssl.key-store-password: 123456
#秘钥库类型
server.ssl.keyStoreType: PKCS12
#证书别名
server.ssl.keyAlias: tomcat
要同时开启http服务和https服务,springboot的配置文件这样写:
#https端口号.
server.port: 33021
# http 端口
http.port: 33011
#https配置
#证书的路径.
server.ssl.key-store: classpath:keystore.p12
#server.ssl.key-store: file:/Users/hanwei/xxx/yyy/api/src/main/resources/keystore.p12
#证书密码,请修改为您自己证书的密码.
server.ssl.key-store-password: 123456
#秘钥库类型
server.ssl.keyStoreType: PKCS12
并创建下面的java文件:
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SSLConfig {
@Value("${http.port}")
private Integer port;
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(port);
tomcat.addAdditionalTomcatConnectors(connector);
return tomcat;
}
}
想让http response body json 的null字段显示成空字符串””
//demo
@RequestMapping("/map")
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<>(3);
User user = new User(1, "xxx", null);
map.put("作者信息", user);
map.put("博客地址", "http://www.yyy.com");
map.put("CSDN地址", null);
return map;
}
调用接口显示:{“作者信息”:{“id”:1,”username”:”xxx”,”password”:null},”CSDN地址”:null,”博客地址”:”http://www.yyy.com"}
创建下面的java文件:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.io.IOException;
@Configuration
public class JacksonConfig {
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
objectMapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false);
objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString("");
}
});
return objectMapper;
}
@Bean
public Module customStringDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, CustomStringDeserializer.instance);
return module;
}
}
调用接口显示:{“作者信息”:{“id”:1,”username”:”xxx”,”password”:””},”CSDN地址”:””,”博客地址”:”http://www.yyy.com"}
想让http response body json 的null字段不显示出来
可以利用@JsonInclude(JsonInclude.Include.NON_NULL)
注解,例如:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@NoArgsConstructor
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ListPoolTenantsResponse {
/**
* tenantId
*/
@JsonProperty("tenantId")
private String tenantId;
/**
* poolTenants
*/
@JsonProperty("poolTenants")
private List<PoolTenantsDTO> poolTenants;
/**
* PoolTenantsDTO
*/
@NoArgsConstructor
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class PoolTenantsDTO {
/**
* id
*/
@JsonProperty("id")
private String id;
/**
* name
*/
@JsonProperty("name")
private String name;
/**
* poolId
*/
@JsonProperty("poolId")
private String poolId;
/**
* poolName
*/
@JsonProperty("poolName")
private String poolName;
/**
* poolType
*/
@JsonProperty("poolType")
private Integer poolType;
/**
* description
*/
@JsonProperty("description")
private String description;
/**
* status
*/
@JsonProperty("status")
private String status;
/**
* quotaSpec
*/
@JsonProperty("quotaSpec")
private QuotaSpecDTO quotaSpec;
/**
* QuotaSpecDTO
*/
@NoArgsConstructor
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class QuotaSpecDTO {
/**
* cpu
*/
@JsonProperty("cpu")
private Integer cpu;
/**
* memory
*/
@JsonProperty("memory")
private Integer memory;
}
}
}
Mybatis-Plus作为ORM时,想将数据库中的某个字段更新为null
默认情况下,是不能将字段更新为null的,即使更新为null,查询数据库发现字段还是原来的字段并没有更新,是因为mybatis-plus FieldStrategy 有三种策略:
IGNORED:0 忽略
NOT_NULL:1 非 NULL,默认策略
NOT_EMPTY:2 非空
而默认更新策略是NOT_NULL:非 NULL;即通过接口更新数据时数据为NULL值时将不更新进数据库。
Mybatis-Plus这种默认的策略,对更新操作提供极大的便利,例如http request更新请求时只更新json body中的字段,而body中没有的字段不会更新,这也符合一般的需求。若将body中没有的字段也更新为null,有点奇怪。一般的需求都是为null的字段保持原样。
若某个字段有更新为null的需求,可以通过注解@TableField(updateStrategy = FieldStrategy.IGNORED)
实现。
例如:
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
@Data
@TableName(value = "image", autoResultMap = true)
public class Image {
// private String id;
@TableId("name")
private String name;
private String description;
private boolean publicView; //和Harbor一致
private String type;
private String registry; //镜像仓库地址,支持多镜像仓库
private String repository; //"project/repository"的形式,和Harbor一致
@TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.IGNORED)
private JSONObject lables; //"lables": {"gpu": "yes"}
}
json数据类型如何在Java实体字段和数据库字段的映射
json数据类型和Java实体类的映射是很常见,json嵌套json也可以通过在Java实体类再写个嵌套的内部类。内部的json数据类型对应实体内部类,但是现在的需求是要同数据库的某个json类型字段要关联起来。可以通过下面的方式。
例如:
CREATE TABLE image
(
#id int AUTO_INCREMENT comment 'id',
-- id varchar(80),
name varchar(80),
description varchar(500),
public_view tinyint(1) comment '0:非公开;1:公开',
type varchar(80),
registry varchar(80),
repository varchar(80),
lables JSON,
primary key (name)
);
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
@Data
@TableName(value = "image", autoResultMap = true)
public class Image {
// private String id;
@TableId("name")
private String name;
private String description;
private boolean publicView; //和Harbor一致
private String type;
private String registry; //记录仓库地址,支持多镜像仓库
private String repository; //"project/repository"的形式,和Harbor一致
@TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.IGNORED)
private JSONObject lables; //"lables": {"gpu": "yes"}
}
typeHandler: 字段类型处理器,用于 JavaType 与 JdbcType 之间的转换。
若不按上面处理会报下面的错误:
com.mysql.cj.jdbc.exceptions.MysqlDataTruncation: Data truncation: Cannot create a JSON value from a string with CHARACTER SET 'binary'."
http response body字段按需显示时间格式
response body字段前加上注解:
@JsonFormat(pattern=”yyyy-MM-dd HH:mm:ss”,timezone=”GMT+8”) 显示东八区的时间
@JsonFormat(pattern = “yyyy-MM-dd’T’HH:mm:ss.SSS’Z’”) 显示UTC的时间,精确到毫秒,例如:2017-06-01T00:00:00.000Z
同步服务器间的数据
可以利用com.jcraft.jsch.ChannelSftp的put方法,再写个定时更新任务
/**
* 定时更新任务
*/
@Async("taskScheduler")
@Scheduled(cron = "0/15 * * * * ?")
public void update() {
也可以利用org.apache.camel同步
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class FtpRouteBuilder extends RouteBuilder {
@Override
public void configure() throws Exception {
from("file:/tmp/ftp_test?recursive=true&noop=true").to("sftp://10.10.10.10:33022/ftp_test?username=root&password=123456@&binary=true");
}
}
修改日志的打包周期
同时修改按单位时间内和按大小打包(以修改SystemLog为例)
<appender name="FILE-SFTP-SystemLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件输出的文件名 -->
<File>${FILE_PATH_SFTP_SystemLog}</File>
<!--滚动日志 基于时间和文件大小-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动日志文件保存格式 -->
<FileNamePattern>${FILE_PATH_SFTP_SystemLog}_%d{"yyyy-MM-dd_HH", UTC}_0.zip</FileNamePattern>
<MaxFileSize>100MB</MaxFileSize>
<totalSizeCap>1GB</totalSizeCap>
<MaxHistory>2</MaxHistory>
</rollingPolicy>
<!-- 按临界值过滤日志:低于INFO以下级别被抛弃 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>
{
"time": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
"level": "%level",
"thread": "%thread",
"class": "%logger{40}",
"message": "%message"
}%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<rollingPolicy ... /rollingPolicy>
几个参数用于配置日志文件分割方式:
<FileNamePattern>
中的yyyy-MM-dd_HH-mm
表示以小时为周期分割文件,yyyy-MM-dd_HH
表示以小时为周期分割文件,yyyy-MM-dd
表示以天为单位分割文件,yyyy-MM
表示按月,yyyy
表示按年。<MaxFileSize>
表示打包的单个文件大小不超过这个值。<totalSizeCap>
限制日志打包的容量大小。<MaxHistory>
限制历史打包文件的数量。
修改Logback在小时单位内按分钟打包(以修改SystemLog为例)(支持以下时间间隔:1,2,5,10,15,20,30分钟)
<appender name="FILE-SFTP-SystemLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件输出的文件名 -->
<File>${FILE_PATH_SFTP_SystemLog}</File>
<!--滚动日志 基于时间和文件大小-->
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 滚动日志文件保存格式 -->
<!-- <FileNamePattern>${FILE_PATH_SFTP_OperationLog}_%d{yyyy-MM-dd-HH-mm}_%i.zip</FileNamePattern>-->
<FileNamePattern>${FILE_PATH_SFTP_SystemLog}_%d{"yyyy-MM-dd_HH-mm", UTC}_0.zip</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="xxx.yyy.api.config.MyTimeBasedFileNamingAndTriggeringPolicy">
<multiple>5</multiple>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- <MaxFileSize>100MB</MaxFileSize>-->
<!-- <totalSizeCap>1GB</totalSizeCap>-->
<MaxHistory>90</MaxHistory>
</rollingPolicy>
<!-- 按临界值过滤日志:低于INFO以下级别被抛弃 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>
{
"time": "%d{yyyy-MM-dd HH:mm:ss.SSS}",
"level": "%level",
"thread": "%thread",
"class": "%logger{40}",
"message": "%message"
}%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<rollingPolicy ... /rollingPolicy>
几个参数用于配置日志文件分割方式:
<FileNamePattern>
中的时间格式部分只可固定为yyyy-MM-dd_HH-mm
。<multiple>
表示打包间隔分钟数。支持以下时间间隔:1,2,5,10,12,15,20,30 分钟。<MaxHistory>
限制历史打包文件的时间。就是说历史打包文件的数量为<MaxHistory>
除以<multiple>
。
MyTimeBasedFileNamingAndTriggeringPolicy.java文件如下:
package xxx.yyy.api.config;
import ch.qos.logback.core.joran.spi.NoAutoStart;
import ch.qos.logback.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy;
@NoAutoStart
public class MyTimeBasedFileNamingAndTriggeringPolicy<E> extends DefaultTimeBasedFileNamingAndTriggeringPolicy<E> {
//这个用来指定时间间隔
private Integer multiple = 1;
@Override
protected void computeNextCheck() {
nextCheck = rc.getEndOfNextNthPeriod(dateInCurrentPeriod, multiple).getTime();
}
public Integer getMultiple() {
return multiple;
}
public void setMultiple(Integer multiple) {
if (multiple > 1) {
this.multiple = multiple;
}
}
}
转载请注明来源,欢迎指出任何有错误或不够清晰的表达。可以邮件至 backendcloud@gmail.com