感慨&概述

  • 在使用minio(容器部署方式)的过程中,由于使用容器时间和本地时间不同,然后寻求解决方法,花费了很多时间,然后才有以下的收获和整理。整个过程耗费不少时间。
  • 本文章对于bitnami/minio:latestminio/minio问题二:minio修改时间和本地查询结果不一致上均适用。在问题一:minio容器和本地容器时间不一致bitnami/minio:latest在部署时,使用TZ=Asia/Shanghai即可解决问题。

补充:MINIO_REGION和容器时间的关系

  • region描述的是服务器的物理位置,默认是us-east-1(美国东区1),也是亚马逊S3的默认区域。可以通过MINIO_REGION环境变量进行修改
  • 修改MINIO_REGION并不能解决,容器时间和本地时间问题,也不能解决文件最后修改时间在web控制台和java查询时间不一致的问题。这个可能是为了minio容器部署时,划分机器用的(如果错误,请大佬指导)
  • 当然,注意如果设置了MINIO_REGION请在java代码中配置同样的时区设置,否则导致代码运行失败,因为时区相差太大无法正常连接到minio
  • 当启动MinIO Docker容器时,可以通过设置环境变量MINIO_REGION来指定区域(如无需要,建议不要配置)
docker run -p 9000:9000 --name minio1 \
	-e "MINIO_ROOT_USER=minioadmin" \
	-e "MINIO_ROOT_PASSWORD=minioadmin" \
	-e "MINIO_REGION=cn-north-1" \
	-e "TZ=Asia/Shanghai" \
	minio/minio server /data
  • java minioclient配置
@Configuration
public class Config {
    //链式编程 构建MinioClient对象
    @Bean
    public MinioClient minioClient(){
        return MinioClient.builder()
                .region("cn-north-1") //注意一致
                .endpoint("http://192.168.1.18:9000")
                .credentials("minioadmin","minioadmin")
                .build();
    }
}

问题一:minio容器和本地容器时间不一致

问题说明

  • 使用的Minio容器版本为1Panel中应用商店提供的minio官方镜像版本minio/minio:RELEASE.2024-04-06T05-26-02Z
  • minio容器内查看时间,刚好和本机相差八小时
    bash-5.1# date 
    Sat May  4 15:58:36 CST 2024
    bash-5.1# exit
    exit
    [root@yang ~]# date
    2024年 05月 04日 星期六 23:58:43 CST
    

原因探究

  • 这里不再详述探究的过程,简略地给出研究思路、方法。
  • 研究思路:查看容器中使用的时区,将其修改为东八区。
  • 研究过程和结果:在该版本容器内查看没有常见的linux时区设置的目录和文件,不过通过复制本地的文件到,minio容器中重新设置时区,可以成功解决容器时区和本地时区不一致的问题。

解决方法

  1. 查看容器信息
    # 查看运行中minio容器的名称
    docker ps
    
    • 例如:运行中的MInio容器ID为 826ab07a0a42,名称为 minio
    [root@yang ~]# docker ps
    CONTAINER ID   IMAGE                                      COMMAND                   CREATED          STATUS          PORTS                                                                                  NAMES
    826ab07a0a42   minio/minio:RELEASE.2024-04-06T05-26-02Z   "/usr/bin/docker-ent…"   14 minutes ago   Up 14 minutes   0.0.0.0:9001->9000/tcp, :::9001->9000/tcp, 0.0.0.0:9000->9001/tcp, :::9000->9001/tcp   minio
    
  2. 进入容器创建时区文件目录
    # 进入容器 下面的两个命令任选其一即可
    # 命令一
    docker exec -it  docker_Minio_name bash # 上一步查询的信息 minio
    # 命令二
    docker exec -it  docker_Minio_ID bash # 上一步查询的信息 826ab07a0a42
    # 在容器内执行命令:创建时区文件目录
    mkdir -p  /usr/share/zoneinfo/Asia/
    # 推出容器命令
    exit
    
  3. 复制本地时区文件到容器
    # 复制本地的时区配置文件到容器中
    [root@localhost ~]# docker cp /usr/share/zoneinfo/Asia/Shanghai docker_Minio_name:/usr/share/zoneinfo/Asia/
    # 操作成功演示
    [root@yang ~]# docker cp /usr/share/zoneinfo/Asia/Shanghai minio:/usr/share/zoneinfo/Asia/
    Successfully copied 2.56kB to minio:/usr/share/zoneinfo/Asia/
    
  4. 再次进入容器内部,修改本地时间
    # 再次进入容器 下面的两个命令任选其一即可
    # 命令一
    docker exec -it  docker_Minio_name bash # 上一步查询的信息 minio
    # 命令二
    docker exec -it  docker_Minio_ID bash # 上一步查询的信息 826ab07a0a42
    
    # 定义本地的时区配置文件
    ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  
    # 演示
    bash-5.1# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    

结果验证

  1. 使用命令查看容器和本机时间是否一致
    # 验证解决结果
    bash-5.1# date 
    Sat May  4 23:58:36 CST 2024
    bash-5.1# exit
    exit
    [root@yang ~]# date
    2024年 05月 04日 星期六 23:58:43 CST
    

  • Minio上传文件测试时间是否生效
  • 在观察页面显示正常,但是遗憾的是,如果使用java minio客户端查询还是显示相差八小时。(这是第二个问题)
    Minio(官方docker版)容器部署时区问题研究记录-LMLPHP

问题二:minio修改时间和本地查询结果不一致

具体问题

  • minio在web管理显示的文件最后修改时间和本地一致,然后使用java代码查询出来的时间和本地时间差八个小时
    Minio(官方docker版)容器部署时区问题研究记录-LMLPHP
  • 查询代码和结果
//查询代码
@Test
void testObjectExists() throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
	StatObjectResponse response = minioClient.statObject(
			StatObjectArgs.builder()
					.bucket("test02")
					.object("image.jpg")
					.build()
	);
	System.out.println(response);
}		
//查询结果
ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T06:53:55Z, size=563102}

原因探究

  • MinIO存储文件的元数据时,通常会使用UTC(协调世界时间)来记录时间戳。这意味着无论实际的物理服务器位置如何,时间都会按照UTC来记录。但在Web管理界面中,MinIO会根据浏览器的时区设置自动调整显示时间,使其看起来与本地时间一致。
  • 当使用Java代码通过MinIO的API查询文件的最后修改时间时,如果没有正确处理时区,Java可能会默认将UTC时间作为本地时间(即不进行时区转换)来处理。导致看到的时间比实际的本地时间少8小时(中国大陆时间为UTC+8)。

解决办法

  • Java代码在处理时间时将UTC时间转换为本地时间(虽然麻烦,但只能这么解决问题)

时间转化工具类

  • 这里我给出一个已验证可用的工具类
import java.lang.annotation.Retention;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;

public class TimeConverter {

    // 使用自定义格式化模式
    private static final DateTimeFormatter isoFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .appendLiteral('T')
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .optionalStart()
            .appendOffsetId()
            .optionalEnd()
            .toFormatter();

    private static final DateTimeFormatter customFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .appendLiteral('T')
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .appendLiteral('Z') // 添加字面值 'Z' 表示时区偏移量
            .toFormatter();

    /**
     * 将UTC时间转换为指定时区的时间,格式保持ISO-8601 默认时区为东八区
     * @param utcTimeString UTC时间字符串(ISO-8601格式)
     * @return 指定时区的时间字符串(ISO-8601格式)
     */
    public static String convertUtcToLocal(String utcTimeString) {
        String zoneIdString="Asia/Shanghai";
        return convertUtcToLocal(utcTimeString,zoneIdString);
    }

    /**
     * 将UTC时间转换为指定时区的时间,格式保持ISO-8601
     * @param utcTimeString UTC时间字符串(ISO-8601格式)
     * @param zoneIdString 时区ID,如"Asia/Shanghai"
     * @return 指定时区的时间字符串(ISO-8601格式)
     */
    public static String convertUtcToLocal(String utcTimeString, String zoneIdString) {
        Instant utcTime = Instant.parse(utcTimeString);
        ZonedDateTime localTime = utcTime.atZone(ZoneId.of(zoneIdString));
        DateTimeFormatter formatter = customFormatter.withZone(ZoneId.of(zoneIdString));
        return formatter.format(localTime);
    }

    /**
     * 将本地时间转换为UTC时间
     * @param localTimeString 本地时间字符串(ISO-8601格式)
     * @param zoneIdString 时区ID,如"Asia/Shanghai"
     * @return UTC时间字符串(ISO-8601格式)
     */
    public static String convertLocalToUtc(String localTimeString) {
        String zoneIdString="Asia/Shanghai";
        return convertLocalToUtc(localTimeString,zoneIdString);
    }
    /**
     * 将本地时间转换为UTC时间
     * @param localTimeString 本地时间字符串(ISO-8601格式)
     * @param zoneIdString 时区ID,如"Asia/Shanghai"
     * @return UTC时间字符串(ISO-8601格式)
     */
    public static String convertLocalToUtc(String localTimeString, String zoneIdString) {
        ZonedDateTime localTime = ZonedDateTime.parse(localTimeString, customFormatter.withZone(ZoneId.of(zoneIdString)));
        Instant utcTime = localTime.toInstant();
        return isoFormatter.format(utcTime.atZone(ZoneId.of("UTC")));
    }

    // 测试主方法
    public static void main(String[] args) {
        String utcTime = "2024-05-07T04:20:00Z";
        System.out.println("原UTC时间: " + utcTime);
        System.out.println("转换为北京时间: " + convertUtcToLocal(utcTime));
        
        String shanghaiTime = "2024-05-20T20:20:00Z";
        System.out.println("从北京时间转换回UTC时间: " + convertLocalToUtc(shanghaiTime));
    }
}

  • 测试结果
    UTC时间: 2024-05-07T04:20:00Z
    转换为北京时间: 2024-05-07T12:20:00Z
    从北京时间转换回UTC时间: 2024-05-20T12:20:00Z
    

调用测试和验证

  • 测试类代码
    @SpringBootTest
    class MinioApplicationTests {
       @Resource
    	private MinioClient minioClient;
        /**
    	 * 判断文件是否存在
    	 */
    	@Test
    	void testObjectExists() throws Exception {
    		StatObjectResponse response = minioClient.statObject(
    				StatObjectArgs.builder()
    						.bucket("test02")
    						.object("image.jpg")
    						.build()
    		);
    		System.out.println("修改前文件信息:"+response);
    
    		String localLastModifiedTime = TimeConverter.convertUtcToLocal(response.lastModified().toString());
    		ZonedDateTime localLastModified = ZonedDateTime.parse(localLastModifiedTime);
    		//通过反射 修改文件信息 不影响minio存储的文件信息
    		try {
    			Field lastModifiedField = response.getClass().getDeclaredField("lastModified");
    			lastModifiedField.setAccessible(true);
    			lastModifiedField.set(response, localLastModified);
    		} catch (NoSuchFieldException | IllegalAccessException e) {
    			e.printStackTrace();
    		}
    		System.out.println("修改后文件信息:"+response);
    	}
    }
    
  • 测试结果
    修改前文件信息:ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T06:53:55Z, size=563102}
    修改后文件信息:ObjectStat{bucket=test02, object=image.jpg, last-modified=2024-05-04T14:53:55Z, size=563102}
    

Minio(官方docker版)容器部署时区问题研究记录-LMLPHP

上传文件说明

  • 在查询文件信息时,需要将文件修改时间进行本地化转化,但是在上传文件的时候,我没找到直接设置文件修改时间的配置。所以,上传文件的时候,正常上传就行。
/**
 * 上传文件
 */
@Test
void testObjectUpload() throws Exception {
	ObjectWriteResponse response = minioClient.uploadObject(
			UploadObjectArgs.builder()
					.bucket("test02")
					.object("image3.jpg")
					.filename("src/main/resources/picture/image.jpg")
					.build()
	);
	System.out.println(response);
}
  • 上传成功后,在web控制台正常显示本地时间。查询时,使用工具类就可以了!
    Minio(官方docker版)容器部署时区问题研究记录-LMLPHP
05-07 21:49