Как не платить 199 рублей/неделю за hh Pro, и при этом найти работу джуну без проблем и откликов — Java выручит

В современном мире поиск работы может быть сложной и утомительной задачей. Особенно это касается начинающих специалистов, которые только начинают свой путь в профессии. В условиях жёсткой конкуренции и большого количества предложений от работодателей важно не только найти подходящую вакансию, но и выделиться среди других кандидатов.

Да, примерно так и выглядит поиск работы у джуна :)

Да, примерно так и выглядит поиск работы у джуна :)

Именно поэтому я рад представить вам прикольного бота на Java, которая поможет вам в поиске работы джуну, не тратя при этом 199 рублей каждую неделю за hh Pro. Оно базируется на API самого хедхантера, поэтому всё легально, и не требует установки Google Chrome и Selenium на сервер.

Но прежде чем мы перейдём к самой статье, хотелось бы рассказать вам о том, как она была написана. Автор статьи — Junior Java-разработчик, который свою первую работу нашёл только с помощью «холодного найма» — отклики никак не помогали, а вот пассивный поиск реально помог мне устроиться в AISA, а сейчас я чуть ли не нашёл вторую работу таким же методом.

Стек технологий

Этот бот использует только Java 21, Spring Boot 3.4, и Spring Web с вырезанным и отключенным Tomcat для уменьшения объёма JAR-файла, а также для снижения потребляемого объёма ОЗУ. Итоговый JAR-файл весит 16,5 Мбайт, что очень круто для приложения на Spring так-то.

Почему Spring?

Я мог бы написать всё это на чистом Java, но Spring даёт множество преимуществ — удобный RestClient вместо стандартного API из Java 11, удобный шедуллер для запуска метода только в определённый промежуток времени, IoC и DI, удобный менеджмент конфигурациями приложения, и многое другое.

Практика

Начинается всё с класса HhApiUtils.java — там находится логика работы с HeadHunter API. Начало выглядит так:

@Value("${ru.gavrilovegor519.hh-autoupdate-resume.authToken}")
private String authToken;

@Value("${ru.gavrilovegor519.hh-autoupdate-resume.clientId}")
private String clientId;

@Value("${ru.gavrilovegor519.hh-autoupdate-resume.clientSecret}")
private String clientSecret;

Здесь мы берём параметры из application.properties — auth_token, client_id, и client_secret, которые требуются для HeadHunter API.

private final ObjectMapper objectMapper = new ObjectMapper();

private final RestClient restClient = RestClient.builder()
        .baseUrl("https://api.hh.ru")
        .defaultHeader("User-Agent", "hh-autoupdate-resume/1.0 (<м>)")
        .build();

Здесь мы создаём объект ObjectMapper’а, а также RestClient’а. User-Agent требуется обязательно при обращении к HeadHunter API — здесь указывается имя приложения, версия, а также почта разработчика для обращений со стороны HeadHunter.

В SendTelegramNotification аналогично — принцип тот же:

@Value("${ru.gavrilovegor519.hh-autoupdate-resume.telegram.botToken}")
private String botToken;

@Value("${ru.gavrilovegor519.hh-autoupdate-resume.telegram.chatId}")
private String chatId;

private final ObjectMapper objectMapper = new ObjectMapper();

private final RestClient restClient = RestClient.builder()
        .baseUrl("https://api.telegram.org")
        .build();

А вот так мы обращаемся к API:

public void updateResume(String resumeId, String accessToken) {
    restClient.post()
            .uri("/resume/{resumeId}/publish", resumeId)
            .header("Authorization", "Bearer " + accessToken)
            .accept(MediaType.APPLICATION_JSON)
            .exchange((request, response) -> {
                if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(204))) {
                    return objectMapper.readValue(response.getBody(), new TypeReference<>() {
                    });
                } else {
                    throw new HttpClientErrorException(response.getStatusCode(), new String(response.getBody().readAllBytes()));
                }
            });
}

Особое внимание на способ обращения к Telegram API — для легковесности приложения я решил использовать прямое обращение по HTTPS, без использования сторонних библиотек. Это выглядит также, как и с HH API:

public void send(String message) {
    restClient.get()
            .uri("/{botToken}/sendMessage?chat_id={chatId}&text={message}", "bot" + botToken, chatId, message)
            .accept(MediaType.APPLICATION_JSON)
            .exchange((request, response) -> {
                if (response.getStatusCode().isSameCodeAs(HttpStatusCode.valueOf(200))) {
                    return objectMapper.readValue(response.getBody(), new TypeReference<>() {
                    });
                } else {
                    throw new HttpClientErrorException(response.getStatusCode(), new String(response.getBody().readAllBytes()));
                }
            });
}

Таким образом приложение становится легче. Очевидно, что для отправки уведомлений этого очень даже достаточно.

А вот так выглядит часть класса AutoUpdateResume.java:

@Scheduled(fixedRate = 14400000)
public void updateResume() {
    if (accessToken != null && refreshToken != null) {
        try {
            updateResumeInternal();
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode() == HttpStatusCode.valueOf(403)) {
                updateTokens(false);
                updateResumeInternal();
            }
        }
    } else {
        updateTokens(true);
        updateResumeInternal();
    }
}

Принцип предельно прост — если токенов нету, приложение получает начальные токены. Если есть — приложение обновляет резюме, если ошибка — пробует обновить токены с помощью refresh_token, и пробует ещё раз.

А вот так выглядит ещё один метод:

private void updateResumeInternal() {
    try {
        hhApiUtils.updateResume(resumeId, accessToken);
        sendTelegramNotification.send("Резюме обновлено");
    } catch (Exception e) {
        sendTelegramNotification.send("Ошибка обновления резюме: " + e.getMessage());
        throw e;
    }
}

Если вкратце — он обновляет резюме как раз. Если всё нормально — приложение не выводит exception’ов. Если есть exception’ы — он их отправляет методу выше, и он, собственно, и вызывает процесс обновления резюме.

Заключение

Вот такой код у меня получился. Я хочу, чтобы этот проект помог мне и дальше находить интересную работу без лишних мучений. Я теперь понял, что пассивный поиск даже эффективнее, чем активный («горячий») поиск работы, и (самое главное!) не требует использования методов Антона Назарова.

GitHub проекта

Habrahabr.ru прочитано 8476 раз