Довелось на одном ecommerce проекте (всем привет!) слегка приложить руку к подбору условий доставки для клиентского заказа.
Чтобы "у ей унутре" - зажужжало, посчитало, подобрало, да на блюдечке принесло варианты доставки, подходящие под весь заказ (адрес/массу/состав/стоимость/etc).
Первое чувство -
Развращённый Озоном, но соблазнённый кэшбэком и останками Икеи — пошёл на
Один заказ, один получатель, один Маркет! Четыре позиции — три разных места доставки. Два разных способа оплаты. (Предположение - медицинские салфетки нельзя оплатить дистанционно).
Он просто жалобно пишет что-то вроде "нет подходящего пункта выдачи" — и надо пойти в каждую позицию, и руками на карте выбрать пункт — который, конечно, где-то есть всё же.
Тут я понял, что же такое "Ужасный пользовательский опыт" 😱 И немедленно проснулся!
@panykey | Искусство реализации идей
Please open Telegram to view this post
VIEW IN TELEGRAM
TLS подключение к RabbitMQ
▪️ На входе: сертификат клиентского узла -- контейнер
▪️ Открытый серверный сертификат шлюза Rabbit: выкачивается автоматически (можно подложить вручную)
▪️ Используемая версия java поддерживает форматы хранилищ и JKS, и PKCS12
▪️ Требуется: создать и положить сертификаты в Key Store (сертификаты приложения "клиентские") и Trust Store (доверенные "серверные" сертификаты) [Oracle | Baeldung]
Памятка в форме "полу-скрипта", чтобы избавиться от ручного копипаста команд по очереди:
Создаст хранилища:
Поместить в контейнер (если нужно) -- в
Включить в самом приложении (тут Spring Boot application.yml properties):
Нужно было одновременное подключение к нескольким шлюзам.
Spring MultiRabbit Library -- чтобы вручную
В документации RabbitMQ описано подключение "голого" java client.
Глубокий окиян теории:
▫️Java Secure Socket Extension (JSSE) Reference Guide
▫️The Transport Layer Security (TLS) Protocol (RFC)
#java #rabbitmq #ssl
@panykey | Искусство реализации идей
▪️ На входе: сертификат клиентского узла -- контейнер
client-cert.pfx, корневой сертификат для него root-cert.crt▪️ Открытый серверный сертификат шлюза Rabbit: выкачивается автоматически (можно подложить вручную)
▪️ Используемая версия java поддерживает форматы хранилищ и JKS, и PKCS12
▪️ Требуется: создать и положить сертификаты в Key Store (сертификаты приложения "клиентские") и Trust Store (доверенные "серверные" сертификаты) [Oracle | Baeldung]
Памятка в форме "полу-скрипта", чтобы избавиться от ручного копипаста команд по очереди:
export CLIENT_APP_CRT_NAME=client-cert;
# Пароль, заданный при выпуске клиентского сертификата
# (используется в кач-ве пароля для хранилищ)
export CRT_STORAGES_PASSWORD='changeit';
export RABBIT_GATEWAY_CRT_NAME=rabbit-gw-dev;
export RABBIT_GATEWAY_URL=rabbit-gw-dev.url;
export RABBIT_GATEWAY_PORT=1234;
export ROOT_CRT_NAME=root-cert;
# Извлечь ключ pem и сертификат crt из pfx
openssl pkcs12 -in "${CLIENT_APP_CRT_NAME}".pfx -passin pass:"${CRT_STORAGES_PASSWORD}" -nocerts -nodes -out "${CLIENT_APP_CRT_NAME}".pem;
openssl pkcs12 -in "${CLIENT_APP_CRT_NAME}".pfx -passin pass:"${CRT_STORAGES_PASSWORD}" -clcerts -nokeys -out "${CLIENT_APP_CRT_NAME}".crt;
# Key Store (сертификат приложения)
openssl pkcs12 -export -in "${CLIENT_APP_CRT_NAME}".crt -inkey "${CLIENT_APP_CRT_NAME}".pem \
-out "${RABBIT_GATEWAY_CRT_NAME}"_key_store.p12 -name "${RABBIT_GATEWAY_CRT_NAME}" -passout pass:"${CRT_STORAGES_PASSWORD}" \
-CAfile "${ROOT_CRT_NAME}".crt -caname root;
# Trust Store (сертификат шлюза)
keytool -import -alias "${RABBIT_GATEWAY_URL}"_client -file "${CLIENT_APP_CRT_NAME}".crt -keystore "${RABBIT_GATEWAY_CRT_NAME}"_trust_store.jks \
-noprompt -deststorepass "${CRT_STORAGES_PASSWORD}";
openssl s_client -showcerts -connect "${RABBIT_GATEWAY_URL}":"${RABBIT_GATEWAY_PORT}" -prexit 2>/dev/null | sed -n -e '/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p' \
| tee "${RABBIT_GATEWAY_URL}".cert \
&& keytool -import -alias "${RABBIT_GATEWAY_URL}"_server -file "${RABBIT_GATEWAY_URL}".cert -keystore "${RABBIT_GATEWAY_CRT_NAME}"_trust_store.jks \
-noprompt -deststorepass "${CRT_STORAGES_PASSWORD}";
unset CLIENT_APP_CRT_NAME;
unset CRT_STORAGES_PASSWORD;
unset RABBIT_GATEWAY_CRT_NAME;
unset RABBIT_GATEWAY_URL;
unset RABBIT_GATEWAY_PORT;
unset ROOT_CRT_NAME;
Создаст хранилища:
rabbit-gw-dev_key_store.p12 и rabbit-gw-dev_trust_store.jksПоместить в контейнер (если нужно) -- в
Dockerfile:ADD app-repository/keystore/rabbit-gw-dev_key_store.p12 /app-in-container/keystore/rabbit-gw-dev_key_store.p12
ADD app-repository/keystore/rabbit-gw-dev_trust_store.jks /app-in-container/keystore/rabbit-gw-dev_trust_store.jks
ENV SSL_KEYSTORE=/app-in-container/keystore/
Включить в самом приложении (тут Spring Boot application.yml properties):
spring:
rabbitmq:
# <-- host, port, username, password
ssl:
enabled: true
keyStore: file:${SSL_KEYSTORE}${RABBIT_SSL_KEY_STORE}
key-store-password: ${RABBIT_SSL_KEY_STORE_PASSWORD}
trust-store: file:${SSL_KEYSTORE}${RABBIT_SSL_TRUST_KEY_STORE}
trust-store-password: ${RABBIT_SSL_TRUST_KEY_STORE_PASSWORD}
multirabbitmq:
enabled: true
connections:
reserve-gateway:
# <-- host, port, username, password
ssl:
enabled: ${spring.rabbitmq.ssl.enabled}
keyStore: ${spring.rabbitmq.ssl.keyStore}
key-store-password: ${spring.rabbitmq.ssl.key-store-password}
trust-store: ${spring.rabbitmq.ssl.trust-store}
trust-store-password: ${spring.rabbitmq.ssl.trust-store-password}
Нужно было одновременное подключение к нескольким шлюзам.
Spring MultiRabbit Library -- чтобы вручную
СontainerFactory/RabbitTemplate для слушателей не создавать/настраивать (пример с Testcontainers есть по ссылке в этой записи).В документации RabbitMQ описано подключение "голого" java client.
Глубокий окиян теории:
▫️Java Secure Socket Extension (JSSE) Reference Guide
▫️The Transport Layer Security (TLS) Protocol (RFC)
#java #rabbitmq #ssl
@panykey | Искусство реализации идей
Advent of Code 2023: Day 1: Trebuchet?!
Снова AoC — и снова начат с опозданием. Дуплет! Основная идея прежняя – решать в
Обвязка для загрузки условий задачи – та же, что для AoC-2022. Тег #adventofcode_2023, или страница на сайте.
Для первой части загадки получилось достаточно простое и короткое решение, но для второй оно практически никак не подошло.
Часть вторая и универсальное решение
Совсем бесполезным решение для первой части назвать нельзя – легло в основу универсального.
Жаль, что — для экономии буковок — не удалось отыскать где-нибудь в недрах стандартной библиотеки (
Загадка первого дня была не слишком сложной, но поначалу пошла туговато на фоне повседневного “перекладывания жысонов“.
Тем полезней переключаться! Участвуй!
#adventofcode #adventofcode_2023
@panykey | Искусство реализации идей
Снова AoC — и снова начат с опозданием. Дуплет! Основная идея прежняя – решать в
jshell, пока это не станет слишком многословным. Экономия на буковках, в общем.Обвязка для загрузки условий задачи – та же, что для AoC-2022. Тег #adventofcode_2023, или страница на сайте.
Для первой части загадки получилось достаточно простое и короткое решение, но для второй оно практически никак не подошло.
Часть вторая и универсальное решение
Совсем бесполезным решение для первой части назвать нельзя – легло в основу универсального.
static void day1(String puzzleInputUri) throws IOException, InterruptedException {
Map<String, String> digits = Map.of(
"1", "one",
"2", "two",
"3", "three",
"4", "four",
"5", "five",
"6", "six",
"7", "seven",
"8", "eight",
"9", "nine"
);
TreeMap<Integer, String> firstLast = new TreeMap<>();
var result = client.send(request.uri((URI.create(puzzleInputUri))).build(), HttpResponse.BodyHandlers.ofLines()).body()
.map(string -> {
firstLast.clear();
digits.forEach((digit, numeric) -> {
firstLast.put(string.indexOf(digit), digit);
firstLast.put(string.lastIndexOf(digit), digit);
firstLast.put(string.indexOf(numeric), digit);
firstLast.put(string.lastIndexOf(numeric), digit);
}
);
firstLast.remove(-1); // not matched substrings index
return firstLast.firstEntry().getValue() + firstLast.lastEntry().getValue();
})
.mapToInt(Integer::parseInt)
.sum();
System.out.println(result);
}Жаль, что — для экономии буковок — не удалось отыскать где-нибудь в недрах стандартной библиотеки (
Locale etc) готовых привязок цифр к числительным. Хотя и ожидаемо.Загадка первого дня была не слишком сложной, но поначалу пошла туговато на фоне повседневного “перекладывания жысонов“.
Тем полезней переключаться! Участвуй!
#adventofcode #adventofcode_2023
@panykey | Искусство реализации идей
Advent of Code 2023: Day 2: Cube Conundrum
Страшный, длинный, мучительный парсинг ввода…
Хотелось бы, конечно, иметь возможность сделать по-перловому, что-то типа:
Немного почистить после, или сразу докрутить регулярку, чтобы не захватывала разделители. Сложить в хэш. И закончить кучей циклов в итоге 🙁
С другой стороны – и возня с
Вид этой портянки мне не особо мил, особенно
А часики тикают, задачи требуют решений - и пора двигаться дальше 🚀
#adventofcode #adventofcode_2023
@panykey | dimio.org
Страшный, длинный, мучительный парсинг ввода…
Хотелось бы, конечно, иметь возможность сделать по-перловому, что-то типа:
echo -e "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green\nGame 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue" | perl -wlne '@matches = $_ =~ /(?:\G(?!\A)|^Game) (\S+)/g; print join("=", @matches)'Немного почистить после, или сразу докрутить регулярку, чтобы не захватывала разделители. Сложить в хэш. И закончить кучей циклов в итоге 🙁
С другой стороны – и возня с
java Matcher не вдохновляла, и описывать структуру в виде POJO - для чисто скриптового решения – зачем.split – дёшево и сердито, fastpath бонусом. К счастью – ввод был одинаков для обеих частей загадки.// Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
static void day2(String puzzleInputUri) throws IOException, InterruptedException {
Map<String, Integer> bagOfCubes = Map.of("red", 12, "green", 13, "blue", 14);
var games = client.send(request.uri((URI.create(puzzleInputUri))).build(),
HttpResponse.BodyHandlers.ofLines()).body()
.map(game -> game.split(":", 2))
.map(idAndResults -> Map.entry(
idAndResults[0].split(" ", 2)[1], // game id
idAndResults[1] // results string
))
.map(idAndResults -> Map.entry(idAndResults.getKey(), idAndResults.getValue().split(";")))
.map(idAndResults -> {
List<Map<String, Integer>> parsedResults = new ArrayList<>();
for (String s : idAndResults.getValue()) {
Map<String, Integer> cubesCount = new HashMap<>();
for (String s1 : s.split(",")) {
String[] cubeCount = s1.trim().split(" ", 2);
cubesCount.put(cubeCount[1].trim(), Integer.parseInt(cubeCount[0].trim()));
}
parsedResults.add(cubesCount);
}
return Map.entry(idAndResults.getKey(), parsedResults);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var answer1 = games.entrySet().stream()
.filter(idAndResults -> idAndResults.getValue().stream()
.flatMap(it -> it.entrySet().stream())
.noneMatch(result -> result.getValue() > bagOfCubes.get(result.getKey()))
)
.mapToInt(idAndResults -> Integer.parseInt(idAndResults.getKey()))
.sum();
var answer2 = games.entrySet().stream()
.map(idAndResults -> Map.entry(
idAndResults.getKey(),
idAndResults.getValue().stream()
.reduce((m1, m2) -> {
Map<String, Integer> minRequired = new HashMap<>();
bagOfCubes.keySet().forEach(cube -> minRequired.put(cube,
Math.max(m1.getOrDefault(cube, 0),
m2.getOrDefault(cube, 0))
));
return minRequired;
}).orElseThrow()
)
)
.mapToInt(idAndMinRequired -> idAndMinRequired.getValue().values().stream()
.reduce((v1, v2) -> v1 * v2).orElseThrow() // v != 0 ? v : 1
)
.sum();
System.out.println(answer1);
System.out.println(answer2);
}
Вид этой портянки мне не особо мил, особенно
for внутри (mapMulti взамен?). Но и как-то значимо "облагородить" её не удалось с ходу - получалась куча менее понятных map, происходящее в которых сложно осознать уже через пару десятков минут.А часики тикают, задачи требуют решений - и пора двигаться дальше 🚀
#adventofcode #adventofcode_2023
@panykey | dimio.org
Advent of Code 2023: Day 3: Gear Ratios
В начале была лень… Стойкое нежелание возиться с границами массивов. Оно, и только оно толкнуло меня на скользкую дорожку замены матрицы примитивов на
Следующим шагом на пути окунания в пучины многословия – стало создание контейнеров для упаковки данных со “схемы двигателя”.
У меня было три штуки рекордов, несколько методов с регулярно вычисляющей одно и то же логикой внутри них и россыпь операций по упаковке простого содержимого в сложные формы. Не то, чтобы всё это было нужно в решении задачи, но раз начал коллекционировать объекты, то иди в своём увлечении до конца.
Это очень опасное предприятие закончилось благополучно! Длинная подготовка вылилась в достаточно короткие решения. Пришло удивительное, вселенское ощущение правильности всего того, что я делал.
#adventofcode #adventofcode_2023
@panykey | dimio.org
В начале была лень… Стойкое нежелание возиться с границами массивов. Оно, и только оно толкнуло меня на скользкую дорожку замены матрицы примитивов на
List<List<>>.Следующим шагом на пути окунания в пучины многословия – стало создание контейнеров для упаковки данных со “схемы двигателя”.
У меня было три штуки рекордов, несколько методов с регулярно вычисляющей одно и то же логикой внутри них и россыпь операций по упаковке простого содержимого в сложные формы. Не то, чтобы всё это было нужно в решении задачи, но раз начал коллекционировать объекты, то иди в своём увлечении до конца.
static void day3(String puzzleInputUri) throws IOException, InterruptedException {
Integer emptyCell = (int) '.';
Integer gearCell = (int) '*';
List<List<Integer>> scheme = client.send(request.uri((URI.create(puzzleInputUri))).build(),
HttpResponse.BodyHandlers.ofLines()).body()
.map(line -> line.chars().boxed().collect(Collectors.toCollection(ArrayList::new)))
.peek(chars -> chars.addFirst(emptyCell))
.peek(chars -> chars.addLast(emptyCell))
.collect(Collectors.toCollection(ArrayList::new));
scheme.addFirst(Collections.nCopies(scheme.getFirst().size(), emptyCell));
scheme.addLast(Collections.nCopies(scheme.getFirst().size(), emptyCell));
List<Part> parts = new ArrayList<>();
List<Cell> gears = new ArrayList<>();
List<Cell> partDigit = new ArrayList<>();
boolean prevIsDigit = false;
for (int row = 1; row < scheme.size() - 1; row++) {
for (int column = 1; column < scheme.get(row).size() - 1; column++) {
Integer cellValue = scheme.get(row).get(column);
if (Character.isDigit(cellValue)) {
partDigit.add(new Cell(new Coord(row, column), cellValue));
prevIsDigit = true;
} else if (prevIsDigit) {
parts.add(new Part(partDigit));
partDigit.clear();
prevIsDigit = false;
}
if (gearCell.equals(cellValue)) {
gears.add(new Cell(new Coord(row, column), cellValue));
}
}
}
var validParts = parts.stream()
.filter(part -> part.border().stream().anyMatch(
c -> !emptyCell.equals(scheme.get(c.row()).get(c.column()))
)).toList();
var answer1 = validParts.stream()
.mapToInt(part -> part.value())
.sum();
System.out.println(answer1);
var answer2 = gears.stream()
.map(gear -> Map.entry(gear,
validParts.stream().filter(part -> part.border().contains(gear.coord())).toList()
))
.filter(gearParts -> gearParts.getValue().size() == 2)
.map(Map.Entry::getValue)
.mapToInt(gearParts -> gearParts.getFirst().value() * gearParts.getLast().value())
.sum();
System.out.println(answer2);
}Это очень опасное предприятие закончилось благополучно! Длинная подготовка вылилась в достаточно короткие решения. Пришло удивительное, вселенское ощущение правильности всего того, что я делал.
#adventofcode #adventofcode_2023
@panykey | dimio.org
Advent of Code 2023: Day 4: Scratchcards
Про задачу четвертого дня сказать особенно нечего. По условию – проще третей. По парсингу ввода – проще второй. Решается быстро.
Сразу прикинул, что с использованием примитивов или оборачиванием в типы из стандартной библиотеки – будет портяночно. Поэтому – отдельный класс с логикой:
И короткое решение с его помощью:
#adventofcode #adventofcode_2023
@panykey | dimio.org
Про задачу четвертого дня сказать особенно нечего. По условию – проще третей. По парсингу ввода – проще второй. Решается быстро.
Сразу прикинул, что с использованием примитивов или оборачиванием в типы из стандартной библиотеки – будет портяночно. Поэтому – отдельный класс с логикой:
public class Card {
private static final Predicate<String> EMPTY = s -> " ".equals(s) || "".equals(s);
Integer cardN, winCnt;
Set<Integer> win, have;
List<Card> nextCards = new ArrayList<>(); // added for part 2
public Card(String cardN, String[] win, String[] have) {
this.cardN = Integer.parseInt(cardN.replaceAll("^Card\\s+", ""));
this.win = parseNums(win);
this.have = parseNums(have);
this.winCnt = (int) this.have.stream().filter(h -> this.win.contains(h)).count();
}
void addNext(List<Card> nextCards) { // added for part 2
this.nextCards.addAll(nextCards);
}
Stream<Card> flat() { // added for part 2
return Stream.concat(Stream.of(this),
this.nextCards.stream().flatMap(Card::flat));
}
private static Set<Integer> parseNums(String[] nums) {
return Arrays.stream(nums).filter(not(EMPTY)).map(Integer::parseInt)
.collect(Collectors.toSet());
}
};И короткое решение с его помощью:
static void day4(String puzzleInputUri) throws IOException, InterruptedException {
var cards = client.send(request.uri((URI.create(puzzleInputUri))).build(),
HttpResponse.BodyHandlers.ofLines()).body()
.map(it -> it.split(":", 2))
.map(it -> Map.entry(it[0], it[1].split("\\|", 2)))
.map(it -> new Card(it.getKey(), it.getValue()[0].split(" "),
it.getValue()[1].split(" ")))
.toList();
var answer1 = cards.stream()
.filter(card -> card.winCnt > 0)
.mapToInt(card -> card.winCnt)
.mapToDouble(win -> Math.pow(2, (win - 1)))
.reduce(Double::sum).orElseThrow();
System.out.println(answer1); // 20117
var answer2 = IntStream.iterate(cards.size() - 1, i -> i >= 0, i -> i - 1)
.mapToObj(cards::get)
.peek(card -> card.addNext(cards.subList(card.cardN, card.cardN + card.winCnt)))
.flatMap(card -> card.flat())
.count();
System.out.println(answer2); // 13768818
}#adventofcode #adventofcode_2023
@panykey | dimio.org
List split
Долго валялось в закладках на SO - и пригодилось, наконец. Обычно - или уже была "в коробке" Guava с Lists.partition(), или - Apache Commons и его ListUtils.partition().
Деление на подмножества (каплю доработано относительно исходника на SO):
Несколько тестов тут.
#java
@panykey | dimio.org
Долго валялось в закладках на SO - и пригодилось, наконец. Обычно - или уже была "в коробке" Guava с Lists.partition(), или - Apache Commons и его ListUtils.partition().
Деление на подмножества (каплю доработано относительно исходника на SO):
// https://stackoverflow.com/a/30072617
public static <T> Stream<List<T>> listPartition(@NotNull List<T> source, @Positive int length) {
requireNonNull(source, "Source list must not be null");
if (length <= 0) {
throw new IllegalArgumentException("Partitions length must be greater than zero");
}
int size = source.size();
if (size == 0) {
return Stream.empty();
}
if (size <= length) {
return Stream.of(source);
}
int fullChunks = (size - 1) / length;
return IntStream.range(0, fullChunks + 1)
.mapToObj(n -> source.subList(n * length, n == fullChunks ? size : (n + 1) * length));
}
Несколько тестов тут.
#java
@panykey | dimio.org
Друзья, хочу всех вас искренне поздравить с наступающими праздниками! 🥳
Здоровья и счастья, успехов, и всего наилучшего!🎆
Здоровья и счастья, успехов, и всего наилучшего!
Please open Telegram to view this post
VIEW IN TELEGRAM
🎉1
Хабр
Разбираемся с MavenGate, новой атакой на цепочку поставок для Java и Android-приложений
Всем привет! Сегодня с вами Юрий Шабалин, генеральный директор «Стингрей Технолоджиз», и я хотел бы разобрать в этой статье новый тип атаки на цепочку поставок под названием «MavenGate». А что в ней,...
Забавная история описана на Хабре.
Стоит сказать, что за прошедшую неделю многие из свободных ранее доменов были куплены. И далеко не факт, что приобрели их владельцы библиотек. К примеру, домен ini4j.org куплен 23 января, а домен jdesktop.org зарегистрирован 19 января. Кто и для чего покупает домены, пока непонятно. Это могут быть как владельцы библиотек, так и злоумышленники, которые нацелены на реализацию данной атаки.
——
Таким образом, мы понимаем, что безумно важно правильно указывать порядок репозиториев и конкретные версии, с которыми работает приложение, чтобы избежать «сюрпризов» с репозиторием, из которого будет загружена библиотека.
Если по поводу использования фиксированных версий зависимостей — неожиданностей нет, то вот про порядок определения репозиториев - впервые читаю. "Автоматически" (читай - копипастно) везде стоит первым central. Но это не было осознанным решением 😞
Ну и дополнительный аргумент в пользу применения "корпоративного" BOM для всех проектов.
#java
@panykey | dimio.org
Стоит сказать, что за прошедшую неделю многие из свободных ранее доменов были куплены. И далеко не факт, что приобрели их владельцы библиотек. К примеру, домен ini4j.org куплен 23 января, а домен jdesktop.org зарегистрирован 19 января. Кто и для чего покупает домены, пока непонятно. Это могут быть как владельцы библиотек, так и злоумышленники, которые нацелены на реализацию данной атаки.
——
Таким образом, мы понимаем, что безумно важно правильно указывать порядок репозиториев и конкретные версии, с которыми работает приложение, чтобы избежать «сюрпризов» с репозиторием, из которого будет загружена библиотека.
Если по поводу использования фиксированных версий зависимостей — неожиданностей нет, то вот про порядок определения репозиториев - впервые читаю. "Автоматически" (читай - копипастно) везде стоит первым central. Но это не было осознанным решением 😞
Ну и дополнительный аргумент в пользу применения "корпоративного" BOM для всех проектов.
#java
@panykey | dimio.org
Платон - мне друг?
Почти два месяца в Тиньке! Конечно, не было времени что-то писать :) Резюмируя: - в продуктовой конторе интересно, нравится, процессы нацелены на результат, а не на "работу ради работы". Оперативно решают и общие, и узкие технические вопросы. На всех этапах собеседования был диалог
двух заинтересованных сторон. Пока все нравится!
Из мелких недочетов: здесь тоже в ходу AnyConnect.
В целом - всё приятно. Так что вброшу рефералку на вакансии🐣
Толков - го в Тиньков🫡
Upd: тут можно полезного поискать, я для этапа сисдиза с успехом воспользовался: https://github.com/Tinkoff/career/tree/main/interview
@panykey | Искусство реализации идей
Почти два месяца в Тиньке! Конечно, не было времени что-то писать :) Резюмируя: - в продуктовой конторе интересно, нравится, процессы нацелены на результат, а не на "работу ради работы". Оперативно решают и общие, и узкие технические вопросы. На всех этапах собеседования был диалог
двух заинтересованных сторон. Пока все нравится!
Из мелких недочетов: здесь тоже в ходу AnyConnect.
В целом - всё приятно. Так что вброшу рефералку на вакансии
Толков - го в Тиньков
Upd: тут можно полезного поискать, я для этапа сисдиза с успехом воспользовался: https://github.com/Tinkoff/career/tree/main/interview
@panykey | Искусство реализации идей
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Forwarded from GlyshAlkash
Открываем настройки демона докера
И добавляем рабочее зеркало, например gcr.io
И рестарт
У вас также может быть заблокирован gcr, если вы подключаетесь по IPv6.
Еще парочка зеркал:
– https://docker.mirrors.ustc.edu.cn
– https://registry.docker-cn.com
– https://cr.yandex/mirror/ (нельзя добавить в daemon.json; не все образы)
Полюбился сообществу, прокси до docker-hub
- https://huecker.io
Также, вы можете реализовать собственный docker-registry proxy на Sonatype Nexus
- https://help.sonatype.com/en/proxy-repository-for-docker.html
Непроверенные, но возможные зеркала
На свой страх и риск
- https://mirror.gcr.io
- https://ghcr.io
- https://mcr.microsoft.com
- https://public.ecr.aws
- https://daocloud.io
- https://registry.docker-cn.com
- https://c.163.com
- https://er-central-1.mirror.aliunc.com
- https://docker.mirrors.ustc.edu.cn
Добавление всех в одну строку
На свой страх, риск и ментальное здоровье
Второй вариант добавления зеркала:
nano /etc/docker/daemon.json
{
"registry-mirrors": ["https://mirror.gcr.io"]
}service docker restart
У вас также может быть заблокирован gcr, если вы подключаетесь по IPv6.
Еще парочка зеркал:
– https://docker.mirrors.ustc.edu.cn
– https://registry.docker-cn.com
– https://cr.yandex/mirror/ (нельзя добавить в daemon.json; не все образы)
Полюбился сообществу, прокси до docker-hub
- https://huecker.io
Также, вы можете реализовать собственный docker-registry proxy на Sonatype Nexus
- https://help.sonatype.com/en/proxy-repository-for-docker.html
Непроверенные, но возможные зеркала
На свой страх и риск
- https://mirror.gcr.io
- https://ghcr.io
- https://mcr.microsoft.com
- https://public.ecr.aws
- https://daocloud.io
- https://registry.docker-cn.com
- https://c.163.com
- https://er-central-1.mirror.aliunc.com
- https://docker.mirrors.ustc.edu.cn
Добавление всех в одну строку
На свой страх, риск и ментальное здоровье
{
"registry-mirrors" : ["https://huecker.io", "https://yandex.cr/mirror/", "https://mirror.gcr.io", "https://ghcr.io", "https://gcr.io", "https://mcr.microsoft.com", "https://public.ecr.aws", "https://daocloud.io", "https://registry.docker-cn.com", "https://c.163.com", "https://eu-central-1.mirror.aliyuncs.com", "https://docker.mirrors.ustc.edu.cn"]
}Второй вариант добавления зеркала:
nano /etc/sysconfig/docker
OPTIONS='--selinux-enabled --log-driver=journald --registry-mirror=<MIRROR>'
Магия финтеха - эта жёлтая пневматическая сосиска проводила Zumba-групповуху 😁 И научила отлично делать ХОБА! 🐈
Пятнично по мотивам минувшего корпоратива :)
#TSummerPartySpb #repose
@panykey | Искусство реализации идей
Пятнично по мотивам минувшего корпоратива :)
#TSummerPartySpb #repose
@panykey | Искусство реализации идей
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
План -- закон!
Выбирал тут планировщик для периодических заданий по обслуживанию приложения.
С Quartz что-то все устали возиться, ленились отдельную схему под его таблички выделять и кончили тем,
что возжелалисвежих проблем нового, неизведанного.
И, в том числе:
* Поддержки работы со множеством узлов в кластере;
* Поддержки персистентности заданий;
* Простоты использования;
* Простоты развертывания.
Конечно - open source с подходящей лицензией. И
Первым в гонку влезает Quartz - он здесь ужé и у́же применяться не желает!
Вторым идёт истиннобезликий средний JobRunr - откуда-то вынырнул и смылся куда-то.
А против всего недостаточно хорошего у нас, как пелось, есть третий путь:
молодёжный и свежий, только из-под клавиатуры -- db-scheduler!
(Если не заглядывать в
Таблички все же любят? Кто нет - можно без любви, она суха и беспристрастна при любом отношении (но ориентацию предпочитает альбомную):
Quartz
По Кварцу понятно - он остановился в развитии (Достиг нирваны? Ждёт тепловой смерти вселенной?).
Делает много табличек - делает больно глазкам (да-да, можно и так исправить, но лень же). В поставке нет ретраев.
Создание заданий смотрится очень энтерпрайзно, если вы в Индии - хватайте Кварц!
JobRunr
У JobRunr есть UI-панелька, Healthcheck из коробки, API-шка для работы с задачами, поддержка NoSQL хранилищ - и совсем нет совести!
Хочет много денег за такие пустячки, как: автоматический рестарт собственного служебного сервера при падениях; использование открытой Spring-транзакции в заданиях; интервал опроса задач менее 5 секунд. Всё это решается усердной ручной работой, но - без приятных ощущений.
А ещё у него тяжелые запросы во вьюшке для дашборда и построения метрик. И умирание сервера вместе с liveness пробами при потере коннекта к БД, после чего отстреливается под. И название - из семи символов! И...Ладно, не буду придираться.
db-scheduler
О, от db-scheduler веет ветром перемен! Самим свои существованием он, как будто, подбивает впасть
в грех починки исправного!😳 Соблазняет, как в истории про змею и яблоко.
Ну и кое-что полезное имеет и умеет тоже: мало (очень) сторонних зависимостей, код понятный и простой,
Healthcheck в поставке, метрики - тоже, заявляет высокую производительность, умеет создавать задания в одном сервисе и выполнять в другом, создает впечатление просты перехода на него.
При этом - из коробки в нём нет истории выполнений задания (есть плагин) и UI-панели (тоже плагин).
И id-шник заданию при запуске надо вручную создавать. Не выглядит особо опасным занятием, если честно.
Что делать?
Простыня выше ⬆️ совершенно точно не является индивидуальной инвестиционной рекомендацией, если только вы не инвестируете в таблицы БД и строчки в редакторе (снова поприветствуем Индию!). Никакой другой рекомендацией
она не является тоже☝
Личный выбор каждого -TITS or GTFO , а подбор планировщика - не так уж, в сущности, важен 😎
#db #scheduler
@panykey | Искусство реализации идей
Выбирал тут планировщик для периодических заданий по обслуживанию приложения.
С Quartz что-то все устали возиться, ленились отдельную схему под его таблички выделять и кончили тем,
что возжелали
И, в том числе:
* Поддержки работы со множеством узлов в кластере;
* Поддержки персистентности заданий;
* Простоты использования;
* Простоты развертывания.
Конечно - open source с подходящей лицензией. И
spring-boot-starter в комплекте - для максимального удовольствия.Первым в гонку влезает Quartz - он здесь ужé и у́же применяться не желает!
Вторым идёт истинно
А против всего недостаточно хорошего у нас, как пелось, есть третий путь:
молодёжный и свежий, только из-под клавиатуры -- db-scheduler!
(Если не заглядывать в
git log, а мы и не будем).Таблички все же любят? Кто нет - можно без любви, она суха и беспристрастна при любом отношении (но ориентацию предпочитает альбомную):
| Критерий | Quartz | JobRunr | db-scheduler |
|:-----------------------|:------------:|:------------:|:------------:|
| Cluster-friendly | + | + | + |
| Персистентные задачи | + | + | + |
| История запусков | + | + | +/- |
| Простота использования | +/- | +/- | + |
| Простота развертывания | +/- | + | + |
| Служебных таблиц в БД | 11 (-) | 5 (+/-) | 1 (+) |
| Ретраи | Вручную (-) | Поставка (+) | Поставка (+) |
| Звёзд в GitHub | 6,2k | 2,3k | 1,2k |
| Дата последнего релиза | Окт 2019 (-) | Июл 2024 (+) | Июл 2024 (+) |
| Лицензия | Apache 2.0 | LGPL v3 | Apache 2.0 |
Quartz
По Кварцу понятно - он остановился в развитии (Достиг нирваны? Ждёт тепловой смерти вселенной?).
Делает много табличек - делает больно глазкам (да-да, можно и так исправить, но лень же). В поставке нет ретраев.
Создание заданий смотрится очень энтерпрайзно, если вы в Индии - хватайте Кварц!
JobRunr
У JobRunr есть UI-панелька, Healthcheck из коробки, API-шка для работы с задачами, поддержка NoSQL хранилищ - и совсем нет совести!
Нет и не было... 🧘♀️
Хочет много денег за такие пустячки, как: автоматический рестарт собственного служебного сервера при падениях; использование открытой Spring-транзакции в заданиях; интервал опроса задач менее 5 секунд. Всё это решается усердной ручной работой, но - без приятных ощущений.
А ещё у него тяжелые запросы во вьюшке для дашборда и построения метрик. И умирание сервера вместе с liveness пробами при потере коннекта к БД, после чего отстреливается под. И название - из семи символов! И...
db-scheduler
О, от db-scheduler веет ветром перемен! Самим свои существованием он, как будто, подбивает впасть
в грех починки исправного!
Ну и кое-что полезное имеет и умеет тоже: мало (очень) сторонних зависимостей, код понятный и простой,
Healthcheck в поставке, метрики - тоже, заявляет высокую производительность, умеет создавать задания в одном сервисе и выполнять в другом, создает впечатление просты перехода на него.
При этом - из коробки в нём нет истории выполнений задания (есть плагин) и UI-панели (тоже плагин).
И id-шник заданию при запуске надо вручную создавать. Не выглядит особо опасным занятием, если честно.
Что делать?
Простыня выше ⬆️ совершенно точно не является индивидуальной инвестиционной рекомендацией, если только вы не инвестируете в таблицы БД и строчки в редакторе (снова поприветствуем Индию!). Никакой другой рекомендацией
она не является тоже
Личный выбор каждого -
#db #scheduler
@panykey | Искусство реализации идей
Please open Telegram to view this post
VIEW IN TELEGRAM
Увлёкся азартной игрой с государством корпорацией 🤑
Успел выиграть каплю знания —
И получается очень удобно, когда словарный запас иссяк:
Легче писать и проще читать, чем комбинацию привычного
Затем — немного "угадайки" (гостя можно было тоже отфильтровать), попыток на которую остаётся достаточно:
Базу существительных где-то в интернетах стянул давно, импорт был инсертами под
Пришлось её чистить, чтобы в👇
#sql #db
@panykey | Искусство реализации идей
Успел выиграть каплю знания —
PostgreSQL умеет нативно оперировать множествами, в т.ч. — символов.И получается очень удобно, когда словарный запас иссяк:
select word
from public.nouns
where
char_length(word) = 5
and string_to_array(word, null) @> '{с,о}'
and not string_to_array(word, null) && '{а,к,ц,и,я}'
and word like '____ь'
Легче писать и проще читать, чем комбинацию привычного
like с кучей отдельных сравнений типаand strpos(word, 'а') = 0
and strpos(word, 'к') = 0
and strpos(word, 'ц') = 0
-- ...
and strpos(word, 'о') > 0
Затем — немного "угадайки" (гостя можно было тоже отфильтровать), попыток на которую остаётся достаточно:
word |
-----+
гость|
осень|
особь|
осыпь|
смоль|
Базу существительных где-то в интернетах стянул давно, импорт был инсертами под
MySQL и с каким-то мусором.Пришлось её чистить, чтобы в
PG присунуть. Цепляю в комментарий, если кому понадобится #sql #db
@panykey | Искусство реализации идей
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Путеводитель по базам данных (Владимир Комаров)
Отличное дополнение к PostgreSQL изнутри — ещё одна годная и бесплатная книга по СУБД на сайте PostgresPro — Путеводитель по базам данных.
Хороший учебник/справочник по базе баз 😜 с примерами на актуальных системах. И, сверх того, ещё и с указанием конкретных вендорских названий для реализаций тех или иных аспектов СУБД.
Есть отличный предметный указатель, с которого можно и начать - по нему хорошо прослеживаются релевантные темы и термины, т.е. можно прям "укрупнённо" быстро охватить некую предметную область.
Нет заныривания в глубины глубин и погружения в детали, не влияющие на практическое использование, но уж по приведённым примерам - будет дано и сравнение, и описание альтернатив.
#sql
@panykey
Отличное дополнение к PostgreSQL изнутри — ещё одна годная и бесплатная книга по СУБД на сайте PostgresPro — Путеводитель по базам данных.
Хороший учебник/справочник по базе баз 😜 с примерами на актуальных системах. И, сверх того, ещё и с указанием конкретных вендорских названий для реализаций тех или иных аспектов СУБД.
Есть отличный предметный указатель, с которого можно и начать - по нему хорошо прослеживаются релевантные темы и термины, т.е. можно прям "укрупнённо" быстро охватить некую предметную область.
Нет заныривания в глубины глубин и погружения в детали, не влияющие на практическое использование, но уж по приведённым примерам - будет дано и сравнение, и описание альтернатив.
Книга рассказывает об архитектурных принципах, на которых базируются все современные системы управления базами данных, а также об алгоритмах и структурах данных, которые в них используются. Особое внимание уделено сравнению реализаций одних и тех же подходов в близких по функциональности платформах.
#sql
@panykey
Итоги года - вспомнить всё
Не всё же поквартально подытоживать.
Мои итоги просты и понятны, год показал - чуть лучше, чем "на отлично 💯", я:
▪️ пою, и ещё лучше - когда молчу😭
▪️ плаваю: вниз, вбок - ок, прямо - не ок 📐
▪️ пляшу😕 — просто потенциал "дедушки Запорожца" роскошного олдтаймера 🧐 ещё не полностью раскрыт
И пара менее значительных вещей 🤭, речь о которых пойдёт ниже.
Перешёл в ТБанк, где участвую в создании и запуске прикольного продукта практически с нуля. Это забавно - не так давно поймали незапланированное НТ на
Успел провести пяток интервью, из них одно успешное. И проникся смыслом наличия тех самых "тупых шаблонных вопросов, которые все знают и от которых все страдают". А за их границы и выходить-то почти не приходится - таков печальный итог 🤦🏼♂️🤷🏼♂️
Ещё здесь узнал, что тру-DBA читают🤔 — и такой фразы достаточно для успешного прохождения DBA-собеса! (Но это не точно).
А, да - курил лет двадцать, и лет двадцать же не летал самолётами (совпадение?). По-кайфу катнул на мотике по горам в Дагестане 🏍 Лишился зуба глупости - тоже не повседневная рутина (к счастью!). Были и иные утраты.
В целом - жить можно🧘♀️ 🏄♂️ 🤯 😁
И всех с наступающим Новым годом!🌟 🌟 Достижения квартальных целей! 🤦♂️
@panykey | dimio.org
Не всё же поквартально подытоживать.
Мои итоги просты и понятны, год показал - чуть лучше, чем "на отлично 💯", я:
▪️ пою, и ещё лучше - когда молчу
▪️ плаваю: вниз, вбок - ок, прямо - не ок 📐
▪️ пляшу
И пара менее значительных вещей 🤭, речь о которых пойдёт ниже.
Перешёл в ТБанк, где участвую в создании и запуске прикольного продукта практически с нуля. Это забавно - не так давно поймали незапланированное НТ на
2,50К req/s, доблестно залогировав каждый из них! И запарно - даже не было времени в очередной #adventofcode поиграть.Успел провести пяток интервью, из них одно успешное. И проникся смыслом наличия тех самых "тупых шаблонных вопросов, которые все знают и от которых все страдают". А за их границы и выходить-то почти не приходится - таков печальный итог 🤦🏼♂️🤷🏼♂️
Ещё здесь узнал, что тру-DBA читают
select * — примерно как "селектить ежика" А, да - курил лет двадцать, и лет двадцать же не летал самолётами (совпадение?). По-кайфу катнул на мотике по горам в Дагестане 🏍 Лишился зуба глупости - тоже не повседневная рутина (к счастью!). Были и иные утраты.
В целом - жить можно
И всех с наступающим Новым годом!
@panykey | dimio.org
Please open Telegram to view this post
VIEW IN TELEGRAM
👏3
AI курильщика в завязке 😮💨
Утёнок Му запустил свой агрегатор доступа к LLM: https://duck.ai
Менее красноглазый💻 , чем какой-нибудь jan - интерфейс здорового человека, дающий, кстати, и http апишку к запущенным локально моделям. Ну так и ресурсов локальных не требует.
И явно менее агрегатор курильщика, чем куча расплодившихся проксей к OpenAI и подобным. Claude весьма неплоха - теперь можно без приседаний с доступом её покрутить.
Слава роботам!🖕 (на всякий случай)
#chatgpt #AI
@panykey | dimio.org
Утёнок Му запустил свой агрегатор доступа к LLM: https://duck.ai
Менее красноглазый
И явно менее агрегатор курильщика, чем куча расплодившихся проксей к OpenAI и подобным. Claude весьма неплоха - теперь можно без приседаний с доступом её покрутить.
Слава роботам!
#chatgpt #AI
@panykey | dimio.org
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Лень нарезать, да и необрезанные интересней кмк