Pull to refresh
0
0
Send message

На одном из сайтов сделал так:

<picture>
    <source srcset="/static/.../image.jpg.640x360q75.webp">
    <source srcset="/static/.../image.jpg.640x360q75.avif">
    <img itemprop="image" src="/static/.../image.jpg.c1280x720q75.jpg" alt="" loading="lazy">
</picture>

Пробовал менять местами элементы source webp и avif, но в папке с кэшем в основном сохраняются webp. AVIF тоже есть, но мало. Решил пока не заменять webp на avif.

Плюс я правильно понимаю, что программист будет вынужден реализовывать функционал для каждого шага?
Нет, необязательно. Достаточно реализовать прием команд и отправление ответов и переиспользовать этот функционал на каждом следующем шаге.

Ваш сценарий XML хорош, но для очень простой машины состояний. Очень скоро его перестанет хватать и придется дорабатывать. Этот XML можно легко заменить одним Java-классом, с enum-ом всех возможных состояний и методами для каждого состояния. Если пойти дальше, то каждый элемент enum может хранить в себе ссылку на соответствующий groovy-скрипт (либо на функцию внутри одного большого groovy-скрипта). Тогда Java-класс, получив очередную команду, вызывает groovy-скрипт, соответствующий текущему состоянию, который выполняет необходимые действия и, помимо всего прочего, переключает состояние на новое.

Посмотрите на этот BPMN-процесс (накидал за 5 минут на коленке)
image
Под капотом у него все тот же XML, вместо State-ов используются ServiceTask-и, Gateway-и и Event-ы. Вместо transition — incoming and outcoming SequenceFlow.
Task-и пишутся на Java, Groovy, JavaScript и всем том, что поддерживает Java Scripting API. Сам бизнес-процесс рисуется и тестируется в уютненькой среде.
Как вариант, реализация может быть основана на конечных автоматах, например BPM-системе. Бот получает команды и запускает соответствующие процессы, Service Task-и которых выполняют необходимые действия и отвечают пользователю. Один диалог (считай пользователь) = один или несколько, связанных с ним, экземпляров бизнес-процессов.

Клиент рисует бизнес-процесс или выбирает из готовых, а программист закладывает логику в Service Task-и.
Очень даже Spring- и Java- way. Я бы в дополнение к этому добавил еще новый Spring Scope для привязки бинов к пользователям и поддержки stateful диалогов.

SpringConfig.java
public class SpringConfig implements BeanFactoryPostProcessor {

    @Bean
    public UserScope userScope(ConfigurableListableBeanFactory beanFactory) {
        return new UserScope(beanFactory);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        final UserScope userScope = beanFactory.getBean(UserScope.class);
        beanFactory.registerScope(UserScope.SCOPE, userScope);
    }
}



UserScope.java
public class UserScope implements Scope {

    public static final String SCOPE = "user";
    private static final Logger logger = LoggerFactory.getLogger(UserScope.class);
    private static final ThreadLocal<User> USER = new ThreadLocal<>();

    private final Object lock = new Object();
    private final ConfigurableListableBeanFactory beanFactory;
    private final Cache<String, Map<String, Object>> conversations;

    public UserScope(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        // По истечению 1 часа пользовательские бины удаляются
        conversations = CacheBuilder.newBuilder()
                .expireAfterAccess(1, TimeUnit.HOURS)
                .removalListener(notification -> {
                    if (notification.wasEvicted()) {
                        Map<String, Object> userScope = (Map<String, Object>) notification.getValue();
                        userScope.values().forEach(this::removeBean);
                    }
                })
                .build();
    }

    public static User getUser() {
        return USER.get();
    }

    public static void setUser(User user) {
        USER.set(user);
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        final String userId = getConversationId();
        if (userId != null) {
            final String userName = MessageUtils.getSenderName(getUser());
            Map<String, Object> beans = conversations.getIfPresent(userId);
            if (beans == null) {
                synchronized (lock) {
                    beans = conversations.getIfPresent(userId);
                    if (beans == null) {
                        beans = new ConcurrentHashMap<>();
                        conversations.put(userId, beans);
                        logger.debug("Bean storage for user '{}' is initialized", userName);
                    }
                }
            }
            Object bean = beans.get(name);
            if (bean == null) {
                bean = objectFactory.getObject();
                beans.put(name, bean);
                logger.debug("Bean {} is created for user '{}'", bean, userName);
            }
            return bean;
        }
        //return null;
        throw new RuntimeException("There is no current user");
    }

    @Override
    public Object remove(String name) {
        final String userId = getConversationId();
        if (userId != null) {
            final Map<String, Object> userBeans = conversations.getIfPresent(userId);
            if (userBeans != null) {
                return userBeans.remove(name);
            }
        }
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {

    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        final User user = getUser();
        return user == null ? null : user.getId().toString();
    }

    public void removeConversation() {
        final String userId = getConversationId();
        if (userId != null) {
            final String userName = MessageUtils.getSenderName(getUser());
            final Map<String, Object> beans = conversations.getIfPresent(userId);
            if (beans != null && !beans.isEmpty()) {
                beans.values().forEach(this::removeBean);
                synchronized (lock) {
                    conversations.invalidate(userId);
                    logger.debug("Bean storage for user '{}' is invalidated", userName);
                }
            }
        }
    }

    private void removeBean(Object bean) {
        try {
            beanFactory.destroyBean(bean);
        } catch (Exception ex) {
            logger.error("An error has occurred during destroying bean {}", bean, ex);
        }
    }
}



Т.е. в бинах с аннотацией Scope(UserScope.SCOPE) можно смело хранить состояние диалога с одним, конкретным пользователем в течение небольшого времени
Со сверстниками на «ты», с людьми по-старше на «Вы». «Молодежь» прошу обращаться ко мне на «ты», иначе начинаю чувствовать себя старпёром.
Встречал нескольких людей, которые сразу начинали «тыкать». Поначалу это отталкивает, но такие люди, как правило, более открытые и ламповые. С такими всегда складываются хорошие отношения.

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Date of birth
Registered
Activity