Pull to refresh

Сертификат для Джонни

Reading time6 min
Views6.8K
Original author: Шум-гам

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

Краткое содержание первой серии

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

-Джонни, сгенерю-ка я тебе QR-код, - подумал я и сгенерил.

Как действует QR-код

Чтобы понять сценарий верификации кода, пришлось отправиться в местную Плазу (привет Виктору Левашову) и предъявить охраннику свой QR-сертификат. Откуда у меня сертификат? А я вакцинирован Спутником V. И вот что я обнаружил

  • охранник включил телефон

  • охранник включил камеру на телефоне

  • навел камеру на мой QR-код

  • нажал на всплывающий поп-ап

  • Проходи!

На экране телефона охранника высветилась информация обо мне в стыдливо прикрытом виде

Sic! В QR-коде просто зашифрован линк-ссылка на сайт Госуслуг. В чьей-то голове сформулировалось техническое задание о разработке приложения под iOS. Голова оказалась моя. И заметьте, информация на экране приложения не является персональной. Очевидно, что аббревиатура ФИО и 3 последние цифры паспорта никак не идентифицируют человека. По этим данным даже пол гражданина (гражданки?) не определить.

Техническое задание

  • Надо сделать сайт-пародию (дюжина строк кода на PHP) который отображает информацию из строки url-запроса типа http://govuslugi.her/vam_qr.php?string=BBB_15031964_22345

  • Надо сделать iOS приложение (пару дюжин строк на swift), которое будет генерить QR-код и показывать его на экране iPhone.

Серверная часть она же Web

Вообще, публикация планировалась как учебник по SwiftUI+MVVM+Reactive, но для общего понимания задачи приведу прототип серверной части.

Я сделал веб-страничку на PHP, она будет отображать входящую информацию, записанную в параметре name нашего url-запроса. Типа www.fig.vam/vsem.php?name=jonny_depp

Я извиняюсь, последний раз я кодировал на пе-хе-пе в 2005 году, поэтому прошу прощения за олд-стайл. Слава Джобсу, в PHP не надо проверять строку на её существование и на наличие в ней символов в нужных местах, код никогда не ломается и это прекрасно. Ой, тухлые помидоры!

if (isset($_GET['name'])) $name=($_GET['name']); else $name="Xerr";
list($fio, $birth, $pass) = explode("_", $name);
$user = m_substr($fio, 0, 1, 'UTF-8')."***** ".m_substr($fio, 1, 1, 'UTF-8')."*****  ".mb_substr($fio, 2, 1, 'UTF-8')."********"; 
$passport = "Паспорт: ".substr($pass, 0, 2)."*****".substr($pass, 2, 3);    
$birthday = "Дата рождения: ".$birth;

Как видите, мы получили 3 нужные переменные, которые будем отображать на веб-страничке. П-последний раз я кодировал на Х-Т-М-Л в 1997 году, поэтому извините за олд-олд-стайл из прошлого тысячелетия.

echo '
<div class="top"><strong>
<span style="font-size:140%;color:#FF0000">QR</span>
<span style="font-size:140%;color:#6666CC">УСЛУГИ</span></strong>
<span style="color:#ffffff">BASHNI.ORG</span>
<span style="font-size:140%;color:#000000">RUS</span></div>
<div class="relative">СЕРТИФИКАТ О ГЕНЕРАЦИИ<br>QR-КОДА-19
<div class="absolute">Действителен</div>
<br><p>№ QR9290499831</p></div><div class="info"><br>'.$user.'</div>
<div class="info">'.$port.'</div>
<div class="info">Дата рождения: '.$birth.'</div>
<div class="close">Закрыть</div>';

Стили я опустил, их можно посмотреть на гитхабе. Они тупые и загадочные, как <див>ы из древних сказок Востока.

SwiftUI+MVVM

Переходим к приложению под iOS. Прости, google. Выбираем паттерн проектирования MVVM. Разумеется, для нашей маленькой задачи он избыточен, но потренироваться всегда полезно. Вдруг интервью на сеньора?? Я готов, если что, звоните от $4000. Телефон зашит в статье.

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

struct UserModel {
    var user: User

		mutating func updateUser(name:String, birth:String, pass:String) {
        let d = "_"
        let temp = name+d+birth+d+pass
        self.user = User(isValid: true, string: temp )
    }

		mutating func cleanUser() {
        self.user = User(isValid: false, string: "")
    }

    struct User {
        var isValid:Bool
        var string:String
        func genUrl()->String {
            return urlPath+self.string
        }
    }
}

Полный текст файла ModelView.swift можно посмотреть на гитхабе. Вторая часть нашего паттерна - файл ContentView.swift. Он рисует в одном окне InputView ввод строки и кнопки, а во втором окне QRView изображение QR-кода. Этот SwiftUI стиль и он очень информативен - не нужны XIB-ы, верстка идет прямо в коде в джава-стиле.

import SwiftUI

struct ContentView: View {
    @ObservedObject var model: ModelView
    var body: some View {
        if model.isValid() {
            QRView(model: model)
        } else {
            InputView(model: model)
        }
    }
}


struct InputView: View {
    var model: ModelView
    @State private var name: String = ""
    @State private var birth: String = ""
    @State private var passport: String = ""
    
    var body: some View {
        VStack {
            Spacer()
            TextField("3 буквы ФИО", text: $name)
            TextField("ДР 15.03.1964", text: $birth)
            TextField("2+3 цифры паспорта", text: $passport)
            Spacer()
            HStack {
                Button("Сделай QR", action: {
                    self.model.tapQRButton(name, birth, passport)
                })
                Spacer()
                Button("Очистить", action: {
                    name=""
                    birth = ""
                    passport = ""
                    self.model.tapClean()
                })
            }.padding()
            Spacer()
        }
        .padding()
        .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}


struct QRView: View {
    var model: ModelView
    @State private var isSharePresented: Bool = false
    var body: some View {
        VStack {
            if let image = model.generateQRCode() {
                Text("QR код").bold()
                Text("успешно сформирован")
                Spacer()
                Image(uiImage: image).resizable()
                    .frame(width: 200, height: 200)
            } else {
                Text("QR код").bold().foregroundColor(.red)
                Text("не сформирован").foregroundColor(.red)
                Spacer()
                Image(systemName: "qrcode").resizable()
                    .frame(width: 75, height: 75)
            }
            Spacer()
            Button("Назад", action: {           self.model.tapClean()
            } ).padding()
        }.padding()
        Spacer()
    }
}
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView(model: ModelView())
        }
    }

И, наконец, последний акт Мерлезонского балета - это файл ModelView.swift, который одним глазом глядит на ContentView и получает от него сообщения о нажатии на кнопку, а другим глазом он глядит на UserModel, куда он складывает полученную от ContentView информацию и забирает строку, которую превращает в QR-картинку и возвращает в ContentView.

//
//  ModelView.swift
//

import UIKit

class ModelView: ObservableObject {
    @Published private var model:UserModel = UserModel()

    func isValid()->Bool {
        return model.user.isValid
    }

    func tapQRButton(_ name:String,_ birth:String,_ pass:String) {
        model.updateUser(name: name, birth: birth, pass: pass)
    }
 
    func tapClean() {
        model.cleanUser()
    }
    
    func generateQRCode() -> UIImage? {
        var uiImage: UIImage?
//        let string = model.user.genUrl()
        let string = "https://github.com/PapaBubaDiop/qrushka"

        if let data = string.data(using: String.Encoding.utf8) {
            if let filter = CIFilter(name: "CIQRCodeGenerator",
                                     parameters: ["inputMessage": data,
                                                  "inputCorrectionLevel": "L"]) {
                if let outputImage = filter.outputImage,
                   let cgImage = CIContext().createCGImage(outputImage,
                                                           from: outputImage.extent) {
                    let size = CGSize(width: outputImage.extent.width * 5.0,
                                      height: outputImage.extent.height * 5.0)
                    UIGraphicsBeginImageContext(size)
                    if let context = UIGraphicsGetCurrentContext() {
                        context.interpolationQuality = .none
                        context.draw(cgImage,
                                     in: CGRect(origin: .zero,
                                                size: size))
                        uiImage = UIGraphicsGetImageFromCurrentImageContext()
                    }
                    UIGraphicsEndImageContext()
                }
            }
        }
        return uiImage
    }
}

ModelView объявлена внутри ContentView реактивной

@ObservedObject var model: ModelView 

и это значит, что графические окна перерисовываются каждый раз когда меняется содержимое нашей модели. Еще раз сценарий нашего приложения

  • вводим текст и нажимаем кнопку в ContentView

  • ModelView получает этот текст и обновляет UserModel и значит меняется сам

  • UserModel формирует из текста url виде строки

  • раз UserModel изменился, то и ModelView изменился, значит ContentView перерисовывается в соотвествии с новыми данными, которые появились в ModelView

Ура, проект готов и компилируется. No Errors No Warnings.

Запускаем приложение в симуляторе

И получаем стартовый экран. Заполняем поля в соответсвии с документом Джонни.

Нажимаем кнопку Сделай QR и получаем QR-картинку, в которой скрыт линк на наш сайт, где уже лежит php-скрипт, описанный выше. Сформированные картинки, граждане, настоящие!

Вы можете навести свой девайс на картинку и протестировать ее. Мою протестировал тот же охранник из Плазы. Welcome to Russia, Джонни!

Внимание! Никогда так не делайте! Не используйте прототип в незаконных целях! Потому что скоро это будет весить от 3-х до 5-ти.

Заключение

Вся эта история, разумеется, вымышлена от начала и до конца, но код, товарищи, настоящий! Ссылка на гитхаб прилагается.

Задачка

QR-код формируется разной зернистости в зависимости от длины строки. Мое приложение зашифровало в коды число 0 и число 1. Угадайте, где какой QR-код для 0 и 1?

Tags:
Hubs:
Total votes 50: ↑27 and ↓23+4
Comments41

Articles