Кроссплатформенное решение Youzan для печати розничных чеков

Архитектура внешний интерфейс Понравилось

Автор: Ван Цянь, Линь Хао (вяленая рыба)

1. Предпосылки

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

  • сторона спроса на печать

  • Получение бизнес-сценария

  • Чековый принтер Тип устройства

Болевые точки, которые у нас есть в прошлом:

  1. Каждый конец реализует набор процессов печати, и решения не являются унифицированными. В результате каждая модификация будет изменена с трех сторон, а iOS и Android должны полагаться на выпускную версию для выхода в сеть, что не является динамичным, а эффективность исследований и разработок относительно низка.
  2. Существует множество бизнес-сценариев печати чеков, каждый бизнес реализует свою инкапсуляцию шаблонов и логику печати, шаблоны и логика не унифицированы, а стоимость обслуживания высока.
  3. Адаптация множества небольших билетных устройств должна быть адаптирована для каждого конца.

Среди них основной болевой точкой является первая точка, многоконечная неоднородная проблема. Из-за несоответствия стоимость разработки и обслуживания выросла в геометрической прогрессии.

Ввиду вышеперечисленных болевых точек есть три основные проблемы, которые необходимо решить в техническом решении по печати небольших билетов:

  1. Программное обеспечение для розничной торговли на iOS, Android и веб-страницах — все они должны предоставлять возможность устанавливать и печатать стили чеков, чтобы снизить затраты на обслуживание и обновление кодов печати чеков.
  2. Как настроить содержимое квитанций, отображаемых в разных бизнес-сценариях: информация о квитанциях в разных бизнес-сценариях различается, например, квитанции о покупках и квитанции о возмещении.Стиль информации о продукте одинаков, но информация об оплате отличается. квитанция должна отображать информацию о платеже клиента, а квитанция о возврате должна отображать информацию о возврате продавца.
  3. Как более гибко адаптироваться к различным чековым принтерам, способ подключения делится на соединение Bluetooth и соединение WIFI, стиль бумаги делится на две ширины: 80 мм и 58 мм.

2. Общее решение

В ответ на три вышеупомянутые проблемы мы предлагаем кросс-платформенное решение, включающее клиентскую, мобильную и серверную части.

  • Диаграмма архитектуры

Ядром архитектуры является реализация кросс-платформенного сценария разбора квитанций через JS с преимуществом динамического обновления; реализация гибкой настройки содержимого квитанций с помощью редактируемого шаблона стиля, выдаваемого сервером; клиент для запуска JS-исполнитель для выполнения JS. Механизм сценариев билетов (далее именуемый как JS-движок) также отвечает за управление подключением устройства печати.

1, дизайн двигателя JS

Основная возможность JS-движка состоит в том, чтобы обрабатывать шаблон квитанции и бизнес-данные, интегрировать бизнес-данные в шаблон (те, которые не могут быть обработаны, передаются на мобильный терминал для обработки, например изображения), а затем преобразовывать интегрировать данные шаблона в инструкции по печати и вернуть их на мобильный терминал.

  • Общая схема обработки

  • структурный дизайн

* 小票格式中,打印机是一行一行的输出。那么基本输出布局单位,我们定义为 layout
* 默认一行有一个内容块,即一个 layout 里面有一个 content object
* 当一行有多列内容的时候,即一个 layout 里面包含 N 个 content object 。 各自内容块有 pagerWeight 代表每个内容的宽度占比
* 每一行的后面的是一个占位符,用数据模型的 key 做占位

Описание стиля макета чека:

Блок контента Блок контента:

Возможности, поддерживаемые различными типами контента:

  • Компилятор шаблонов

используется здесьHandleBars.jsБиблиотеки компилируются как шаблоны. Кроме того, в настоящее время предоставляется некоторая поддержка дополнительных мощностей.

Пользовательские способности:

  • Адаптация устройства принтера

Он в основном выполняет синтаксический анализ и адаптацию набора инструкций адаптации, а также выполняет синтаксический анализ различных инструкций в соответствии с подключением различных устройств. На данный момент адаптированные устройства: 365wifi, sunmi, sprt80, sprt58, wangpos, aclas, xprinter. Если подключено неподходящее устройство, будет выдана ошибка соответствующего синтаксического анализатора принтера.

  • Вызвать процесс разбора инструкции парсера соответствующего принтера

  • Проблемы совместимости

    • Обрезка бумаги: поддержка необходимости обрезки бумаги для внешнего ввода, чтобы предотвратить проблему повторной обрезки бумаги после добавления команды обрезки бумаги при отправке команды печати извне и добавления команды обрезки бумаги по умолчанию.
    • Многоразмерная печать на одной машине: есть принтер, который поддерживает два типа печати на бумаге (80 мм, 58 мм), тогда размер печати необходимо импортировать извне, и по умолчанию он равен 80 мм. Например, sunmiT1 поддерживает печать 80 мм и 58 мм, по умолчанию 80 мм.
  • Отказоустойчивость

    • Из-за определенных требований к формату для синтаксического анализа шаблона могут возникать ошибки синтаксического анализа, если в данных присутствуют некоторые специальные символы и символы переноса. Поэтому, когда JS передает данные, он выполняет фильтрацию для удаления или замены таких символов, как "\\", "\n", "\b"... для обеспечения печати.
    • Если в процессе синтаксического анализа возникнет ошибка, будет выдано исключение для перехвата мобильным терминалом.

2. Служба управления шаблонами

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

  • Общая схема обработки

  • Пример хранения библиотеки базовых шаблонов небольших тикетов

shopId: идентификатор магазина

бизнес: деловая сторона

тип: тип содержимого печати

content: содержимое контента в макете

sortWeight: вес сортировки, используемый для вывода порядка макета шаблона.

  • Пример хранилища данных динамической подготовки

shopId: идентификатор магазина

бизнес: деловая сторона

тип: тип содержимого печати

params: содержимое, которое нужно заменить

  • Интерфейс возвращает интегрированный шаблон чека в формате json.
{
    "business": "shopping",
    "shopId": 111111,
    "id": 321,
    "version": 0,
    "layouts": [{
                "name": "LOGO",
                "content": "[{\"content\":\"http://www.test.com/test.jpg\",\"contentType\":\"image\",\"textAlign\":\"center\",\"width\":45}]"
                },{
                "name": "电话",
                "content": "[{\"content\":\"电话:{{mobile}}\",\"contentType\":\"text\",\"textAlign\":\"left\",\"fontSize\":\"default\",\"pagerWeight\":1}]"
                },...]
}

Соответствующий сервер динамических данных был интегрирован и заменен, а бизнес-данные, которые необходимо заменить, хранятся в шаблоне json, а затем заменяются механизмом JS после получения бизнес-данных. в json вышеhttp://www.test.com/test.jpgзаключается в динамической интеграции и замене данных,{{mobile}}Это бизнес-данные, которые необходимо заменить.

3. Мобильный терминал

Помимо динамической настройки шаблона на мобильном терминале, главное — это процесс печати. Мобильный терминал должен заботиться только о том, какие бизнес-квитанции необходимо распечатать, а затем перейти к серверной части, чтобы получить шаблон бизнес-квитанции и бизнес-данные, передать извлеченные данные в механизм JS для предварительной обработки и вернуть информацию об URL-адресе изображения, которая не может быть обработано в шаблоне, тогда мобильный терминал загружает картинку, выполняет бинарное преобразование, выводит шестнадцатеричную строку пикселя, заменяет url в исходном шаблоне и, наконец, передает тип подключенного принтера и обработанный шаблон в движок JS чтобы преобразовать команду печати и вернуть ее на печать принтера.

  • Динамическая конфигурация шаблона

Динамическая настройка содержимого чека, поддержка LOGO, данных магазина, настройка маркетинговой активности и т.д. Слева стиль превью на 80мм и 58мм. Благодаря динамической настройке шаблона можно обновить шаблон внутреннего интерфейса, а затем синхронно изменить содержимое печати в режиме реального времени. Динамическая настройка контента в программном обеспечении для интернет-магазинов такая же, как и на мобильных устройствах.

  • Печать бизнес-процесса

Бизнес-процессы, мобильный терминал передачи данных полностью, просто нужно выполнять дополнительную емкость, а также функции передачи, эффективное решение для изменений бизнес-данных, чтобы быть завершенной мобильной версией проблемы. Android и IOS унифицированный процесс.

3. Дизайн функций мобильного терминала

1. Динамический

Динамика является неотъемлемой частью этого решения. Обновление шаблонов бизнес-данных в режиме реального времени зависит от бэкенда, но доставка механизма парсинга JS зависит от мобильного терминала. Чтобы вовремя исправить обнаруженные проблемы JS или быстро адаптироваться новое оборудование и т.д. Схема обновления выглядит следующим образом:

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

2. JS-исполнитель

iOS использует инфраструктуру JavaScriptCore, а Android использует инфраструктуру J2V8. Введение конкретной инфраструктуры здесь не будет объясняться. Дизайн исполнителя JS включает в себя загрузку указанных файлов JS, вызов методов JS, получение атрибутов JS и захват исключений JS.

	/**
	 初始化 JSExecutor

	 @param fileName JS 文件名
	 @return JSExecutor
	 */
	- (instancetype)initWithScriptFile:(NSString *)fileName;

	/**
	 加载 JS 文件

	 @param fileName JS 文件名
	 */
	- (void)loadSriptFile:(NSString *)fileName;

	/**
	 执行 JS 方法

	 @param functionName 方法名
	 @param args 入参
	 @return 方法返回值
	 */
	- (JSValue *)runJSFunction:(NSString *)functionName args:(NSArray *)args;

	/**
	 获取 JS 属性

	 @param propertyName 属性名
	 @return 属性值
	 */
	- (JSValue *)getJSProperty:(NSString *)propertyName;

	/**
	 JS 异常捕获

	 @param handler 异常捕获回调
	 */
	- (void)catchExceptionWithHandler:(JSExceptionHandler)handler;

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

	- (void)loadSriptFile:(NSString *)fileName{
	    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	    if (paths.count > 0) {
	        NSString *docDir = [paths objectAtIndex:0];
	        NSString *docSourcePath = [docDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.js", fileName]];
	        NSFileManager *fm = [NSFileManager defaultManager];
	        if ([fm fileExistsAtPath:docSourcePath]) {
	            NSString *jsString = [NSString stringWithContentsOfFile:docSourcePath encoding:NSUTF8StringEncoding error:nil];
	            [self.content evaluateScript:jsString];
	            return;
	        }
	    }
	    NSString *sourcePath = [[YZCommonBundle bundle] pathForResource:fileName ofType:@"js"];
	    NSAssert(sourcePath, @"can't find jscript file");
	    NSString *jsString = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
	    [self.content evaluateScript:jsString];
	}

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

  • Файлы JS динамически распространяются для исправлений или обновлений оптимизации, поэтому, как правило, конфигурация распространения новой версии не существует.
  • Для поддержки отката версии JS

Функция захвата исключений JS выдает исключения бизнес-стороне, позволяя вызывающим сторонам независимо реализовывать логическую обработку.

3. Оптимизация кеша

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

блок-схема:

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

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

4. Обработка изображений

Поскольку JS-движок не может разобрать файл изображения, при наличии ссылки на изображение в исходном шаблоне все это обрабатывается мобильным терминалом, а затем заменяется. Обработка изображений в основном включает загрузку изображений, сжатие изображений, обработку двоичных изображений, сжатие пикселей изображения (требуется инструкциями по печати), преобразование каждого байта в шестнадцатеричный формат и объединение шестнадцатеричных строк.

  • скачать изображение

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

  • Сжатие изображения

В соответствии с шириной, требуемой шаблоном JS-движка (должна быть кратной 8, как объясняется ниже), выполните пропорциональное сжатие, конвертируйте в формат jpg и отфильтруйте альфа-канал.

  • Двоичная обработка изображений

Пройдитесь по каждому пикселю, возьмите значение RGB, а затем рассчитайте отношение среднего значения RGB к 255 и возьмите значение 0 или 255 в соответствии с соотношением. Невозможно использовать гистограмму для нахождения порога T из-за соображений производительности и времени.

  • сжатие пикселей

Из-за требований инструкций принтера необходимо сжимать ширину каждой точки после преобразования в двоичные значения, а также необходимо сжимать 8 байт до 1 байта, поэтому ширина должна быть кратна 8, когда изображение сжато, иначе распечатанное изображение будет смещено.

  • шестнадцатеричная строка

Поскольку принтер печатает шестнадцатеричную строку, каждый байт после обработки необходимо преобразовать в шестнадцатеричный символ, а затем преобразовать в строку.

5. Реализуйте многократную печать

Из-за потребностей бизнес-сценария необходимо автоматически распечатать несколько квитанций, поэтому разработана несколько логик печати. Так как каждая печать находится в асинхронном потоке, она не может быть напечатана напрямую в цикле, и здесь используется семафор.dispatch_semaphore_t, Создание и ожидание семафора в асинхронном потоке, каждый раз, когда печать завершается, сигнальный семафор в потоке обратного вызова завершается, и выполняется несколько распечаток, чтобы гарантировать, что каждая распечатка выполняется последовательно. Если во время печати возникает ошибка, последующая печать прерывается.

	dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        for (int i = 1; i <= printCount; i++) {
            if (stop) {
                break;
            }
            [self print:template andCompletionBlock:^(State state, NSString *errorStr) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (errorStr.length > 0 || i == printCount) {
                        if (completion) {
                            completion(state, errorStr);
                        }
                        stop = YES;
                    }
                    dispatch_semaphore_signal(semaphore);
                });
            }];
            dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 15*NSEC_PER_SEC));
        }
    });

4. Резюме и перспективы

Это решение было реализовано, и при использовании в розничных приложениях оно удовлетворяло большинству текущих бизнес-сценариев и потребностей. Последующие затраты на разработку и обслуживание также будут значительно снижены, что повысит эффективность исследований и разработок и упростит доступ к новым бизнес-поступлениям. С точки зрения использования клиентами пользовательский опыт мало чем отличается от предыдущего, в то же время при решении проблем, о которых сообщают клиенты, его также можно быстро модифицировать и выдавать в режиме реального времени. Тем не менее, есть еще некоторые недостатки.Например, функция печати изображений не может полностью удовлетворить идеальной печати всех изображений.Ведь обработка изображений учитывает опыт работы.Есть также шаблоны, которые могут увеличить номер версии в будущее, так что в шаблоне есть аномалия.Его также можно откатить или совместимую обработку и т. д. Кроме того, оптимизация кеша может дополнительно оптимизировать работу, например, добавление отправки шаблона, оптимизация локального кеша и т. д.

Ссылка на ссылку