You've successfully subscribed to Заметки Разработчиков
Great! Next, complete checkout for full access to Заметки Разработчиков
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.

Заметки Разработчиков

Сериализация и Десериализация даты в Jackson

Чаще всего по работе я сталкиваюсь с проблемой десериализации и сериализации даты. Многие разработчики отступают от стандартного формата времени yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ и изобретают свои форматы.

К сожалению, в Jackson не заложены все возможные форматы даты, поэтому необходимо наиписать свой десериализатор.

Десериализация

В данном случае это преобразование json формата в Java объект. Необходимо расширить абстрактный класс StdDeserializer.

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class CustomDeserializer extends StdDeserializer<LocalDateTime> {

    protected CustomDeserializer() {
        this(null);
    }

    protected CustomDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
        String value = jsonParser.getText();
        if (!"".equals(value)) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
            return LocalDateTime.parse(value, formatter);
        }
        return null;
    }

}

После этого необходимо над полем поставить аннотацию @JsonDeserialize c указанием нашего кастомного десериализатора.

public class Foo {

    // ... ... ... ... ...

    @JsonDeserialize(using = CustomDeserializer.class)
    private LocalDateTime date;

    // ... ... ... ... ...

}

Сериализация

В данном случае это преобразование Java объекта в json формат. Для сериализации необходимо расширить класс StdSerializer.

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class CustomSerializer extends StdSerializer<LocalDateTime> {

    protected CustomSerializer(Class<LocalDateTime> t) {
        super(t);
    }

    protected CustomSerializer() {
        this(null);
    }

    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss");
        gen.writeString(formatter.format(value));
    }

}

Для работы над полем поставить аннотацию @JsonSerialize

public class Foo {

    // ... ... ... ... ...

    @JsonSerialize(using = LocalDateTimestampSerializer.class)
    private LocalDateTime date;

    // ... ... ... ... ...

}

Игнорирование ошибок сертификата OkHttp3

Так лучше не делать, но иногда нужно проигнорировать ошибки связанные с сертификатом сайта.
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * @author upagge 03.02.2021
 */
@Slf4j
@UtilityClass
public class OkHttpUtil {

    private static OkHttpClient client = new OkHttpClient.Builder().build();

    public static OkHttpClient getClient() {
        return client;
    }

    public static void init(boolean ignoreCertificate) {

        OkHttpClient.Builder builder = new OkHttpClient.Builder();

        log.info("Initialising httpUtil with default configuration");
        if (ignoreCertificate) {
            builder = configureToIgnoreCertificate(builder);
        }

        //Other application specific configuration

        client = builder.build();
    }

    //Setting testMode configuration. If set as testMode, the connection will skip certification check
    private static OkHttpClient.Builder configureToIgnoreCertificate(OkHttpClient.Builder builder) {
        log.warn("Ignore Ssl Certificate");
        try {

            // Create a trust manager that does not validate certificate chains
            final TrustManager[] trustAllCerts = new TrustManager[]{
                    new X509TrustManager() {
                        @Override
                        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                        }

                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return new java.security.cert.X509Certificate[]{};
                        }
                    }
            };

            final SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
            builder.hostnameVerifier((hostname, session) -> true);
        } catch (Exception e) {
            log.warn("Exception while configuring IgnoreSslCertificate" + e, e);
        }
        return builder;
    }

}

Как это использовать?

Перед получением OkHttpClient необходимо инициализировать настройки игнорирования сертификатов. Для этого вызываем метод:

OkHttpUtil.init(true);

После этого можете получить OkHttpClient:

OkHttpUtil.getClient();

Или вот так:

public class HttpParse {

    static {
        OkHttpUtil.init(true);
    }

    private static final OkHttpClient client = OkHttpUtil.getClient();

    // ... ... ... ... ...

}

Создание linux сервиса для приложения Spring Boot

Для запуска jar файла в linux в виде сервиса, необходимо создать файл конфигурации.

sudo nano /etc/systemd/system/app_name_service.service

В этот файл вставляем примерно следующее. Не забудьте заменить в выделенных строках app_path, app_name.

[Unit]
Description=App Description
After=network.target

[Service]
Type=simple
SyslogIdentifier=appdescription
WorkingDirectory=/app_path
PIDFile=/app_path/app_name.pid
ExecStart=/bin/sh -c "exec /usr/bin/java -jar app_name.jar & echo $! > /app_path/app_name.pid"
ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID

[Service]
Type=forking
PIDFile=/app_path/app_name.pid

[Install]
WantedBy=default.target

Для автоматического запуска приложение после перезагрузки сервиса, используйте следующую команду.

systemctl enable app_service

Чтобы убрать приложения из автозагрузки:

systemctl disable app_service

Парсинг URL c помощью регулярки

// Split URL into protocol, domain, port and URI
Pattern pattern = Pattern.compile("(https?://)([^:^/]*)(:\\d*)?(.*)?");
Matcher matcher = pattern.matcher(url);

matcher.find();

String protocol = matcher.group(1);
String domain   = matcher.group(2);
String port     = matcher.group(3);
String uri      = matcher.group(4);

Разделение URL-адреса на протокол, домен, порт и URI с помощью регулярных выражений.

Оптимизация изображений

Размер изображений составляет существенную часть от размера страницы. Вот уже второй день вожусь с оптимизацией изображений в блоге. До этого я использовал скрипт, который позволяет сжать jpg и png без потерь.

Теперь решил пойти еще дальше и использовать Webp формат.

WebP - это формат сжатия изображений, разработанный компанией Google. Он использует современный алгоритм сжатия, который обеспечивает более эффективное сжатие, чем форматы JPEG и PNG, что может привести к быстрее загрузке веб-страниц и экономии интернет-трафика.

В итоге пришел к такому кейсу:

  1. Прогоняю скриптом сжатия без потерь.
  2. Прогоняю скриптом для преобразования в webp. Сохраняю все изображения в отдельную папку.
  3. Nginx пытается сначала найти webp изображение, если не получается, то берет сжатый png или jpg

А вот и сам скрипт для преобразования в webp:

#!/bin/bash
file=webp.flag

if [ -f "$file" ]; then
  option="-newer $file"
fi

find ./struchkov.dev/www/images/ -type f -iregex '.*\.\(jpg\|jpeg\|png\)' $option -exec sh -c '
  webp_file="${1/\/images\//\/images\/webp\/}"
  webp_dir="$(dirname "$webp_file")"
  mkdir -p "$webp_dir"
  cwebp -mt -af -progress -m 6 -q 85 -pass 10 "$1" -o "${webp_file%.*}.webp"
' _ {} \;

touch "$file"
echo "$(date)" > "$file"

Также как и в первом скрипте, я использую файл-флаг, чтобы не преобразовывать файлы, которые уже преобразовывались.

Далее я беру папку /struchkov.dev/www/images/, в которой лежат исходные изображения и создаю новую папку /struchkov.dev/www/images/webp. Внутри папки webp структура папок и файлов такая же, как и в папке images. Только формат у файлов .webp.

За преобразование отвечает команда cwebp. Эта утилита сжимает изображения, но с потерями в качестве. Я подобрал оптимальные параметры сжатия, чтобы потеря качества была не заметна, но при этом изображения занимали меньше места.

cwebp -mt -af -progress -m 6 -q 85 -pass 10 "$1" -o "${webp_file%.*}.webp"

Подробнее об этих и других параметрах можно почитать в документации.

Nginx

Теперь сделаем так, чтобы при запросе jpg/png изображения отдавался аналогичный webp файл. Для этого в конфигурацию nginx добавляем:

location ~* ^(/blog/ru/content/images/)(.+)\.(png|jpe?g)$ {	
    expires max;
    alias /var/struchkov.dev/ghost/www/images;
    set $webp_image_subdir "/webp/";
    set $basename $2;
    try_files $webp_image_subdir$basename$webp_suffix $uri;
}

try_files сначала попытается достать webp изображение, если его нет, то возьмет сжатый оригинал формата jpg/png.