Топ вопросов, которые нужно обсудить с партнером по бизнесу (чек-лист)

Один из способов сделать партнерские отношения нестабильными, потерять время и деньги — это не составить партнерское соглашение или по-простому договор между партнерами-основателями.

В теории все всё знают, но когда дело доходит до практики, оказывается, что важные вопросы, которые в будущем могут стоить жизнедеятельности компании, остаются в стороне. Мы создали чек-лист основных вопросов, которые должны обговорить партнеры, чтобы избежать закрытия компании через год. Обсуждать правила совместного бизнеса нужно «на берегу», но если вы этого еще не сделали — самое время оградить себя от будущих проблем. И чтобы не получилось как в том анекдоте…

… мы написали ТОП 6 юридических советов, которые нужно учитывать перед составлением партнерского соглашения.

Рубрика юридические советы

  1. Законодательство не требует обязательного заключения партнерского соглашения. Но в отличие от устава и учредительного договора, партнерское соглашение — это конфиденциальный договор между партнерами. В нем вы можете урегулировать любые положения, которые касаются деятельности вашего бизнеса с партнером и не раскрывать их посторонним лицам. Процесс изменения договоренностей по партнерскому соглашению не требует регистрации и внесение изменений в устав.
  2. В течение одного года до Верховного Суда Украины дошло 496 дел, возникающих из корпоративных правоотношений, поэтому важно договориться «на берегу» перед началом ведения бизнеса.
  3. Часто вместо того, чтобы заключить договор и урегулировать отношения, стороны говорят «мы доверяем друг другу». Эти партнерские отношения очень похожи на отношения в браке, когда супруги сначала доверяют друг другу, а потом делят имущество в суде. Когда возникает спор между основателями (например после смерти одного основателя наследники желают продать бизнес, а второй основатель хочет его и дальше развивать), на суды уходят годы, а деятельность бизнеса фактически заблокирована.
  4. Не стоит делать бизнес 50/50, поскольку в случае конфликта или возникновения спорных вопросов вы окажетесь в тупиковой ситуации и деятельность вашего бизнеса опять же будет заблокирована.
  5. Предусмотрите в партнерском соглашении разрешение «тупиковых» (deadlock) ситуаций (ситуация, когда ни у одного из акционеров не хватает голосов для принятия решения по ключевому вопросу деятельности компании).
  6. Если вам неудобно друг другу задавать вопросы и обсуждать условия партнерского соглашения, наймите юриста, который сделает это за вас и поможет правильно оформить договор.

Чек-лист вопросов для партнеров

Миссии и цели стартапа

  • Зачем вам партнерское соглашение?
  • Что было до того, как вы решили его составить?
  • Что может измениться после его составления?
  • Как часто мы будем пересматривать партнерское соглашение?
  • Что является нашим бизнесом?
  • Какую основную ценность мы несем?
  • На чем мы фокусируемся?
  • Чего мы хотим добиться?
  • Для чего это каждому из нас?
  • Какие проблемы нам необходимо решить?
  • Что является критерием достижения цели?
  • Будем ли мы покупать другие бизнесы?
  • Готовы ли мы присоединиться к более крупному бизнесу?

Капитал компании и взносы

  • Кто вносит первоначальный капитал, предполагаются ли дополнительные взносы?
  • Что именно считается вкладом (денежные средства, имущество, объекты интеллектуальной собственности, оказанные услуги и пр.)?
  • Что будет если партнер не справляется со своими индивидуальными обязанностями по вкладу (не все деньги/не передал интеллектуальную собственность)?

Управление

  • Кто управляет операционной деятельностью компании, кто будет директором?
  • Какие полномочия у директора? Есть ли ограничения полномочий директора?
  • Какой порядок назначения / отстранения директора, или формирования / роспуска совета директоров? Какой порядок смены директора?
  • Какие ограничения по сумме сделок с третьими или аффилированными лицами, которые может заключать директор без согласия других основателей?
  • Какие коллегиальные полномочия у основателей, и какие полномочия предоставляются директору единолично? Кто будет нести ответственность за принятие решений директором?
  • Какая ответственность за превышения полномочий директором? Кто будет нести ответственность за принятия незаконного решения?

Распределение прибыли

  • В каких долях основатели будут владеть компанией?
  • Как будет происходить распределение прибыли компании между основателями?
  • Как принимается решение о распределении прибыли?
  • Будет ли действовать мораторий на распределение прибыли? Как долго?
  • Какая часть прибыли направляется на дальнейшее развитие бизнеса?

Собрания партнеров-основателей

  • Какая регулярность проведения собраний и кто может инициировать собрания?
  • Какой кворум для общих собраний?
  • Как решаются вопросы, которые отнесены к компетенции собрания?

Работа партнеров в компании

  • Распределение сфер ответственности, кто за какое направление отвечает?
  • Допускается работа еще где-то на стороне или фриланс?
  • Будут ли KPI для партнеров работающих на компанию?
  • Что если партнер не выполняет/выполняет ненадлежащим образом принятые на себя обязательства в своей сфере ответственности?

Конфиденциальность и конкуренция

  • Какая информация строго конфиденциальна, а какая может быть разглашена?
  • Как решаются вопросы непереманивании и неконкуренции?

Привлечение инвестиций в будущем

  • Какой порядок финансирования проекта компании?
  • Какой порядок прекращения финансирования проекта?

Акции компании

  • Разрешен ли выпуск новых акций? Если да, то для каких целей и в каком порядке?
  • Какие есть классы акций? Чем классы различаются? Для каких случаев предназначен тот или иной класс?
  • Какие ограничения вы хотите наложить на передачу акций?
  • Будет ли действовать мораторий на передачу акций? Может ли акционер продавать акции третьим лицам?
  • Как защищены права миноритариев?

Разрешение споров

  • Будут ли использоваться какие-либо механизмы для разрешения “тупиковых” ситуаций (deadlock), например: Russian roulette, Texas shoot-out, Mexican shoot-out (Dutch auction)?

ссылка на оригинал статьи https://habr.com/ru/company/icon_partners/blog/544138/

Что нужно помнить при переводе сотрудников на удаленку?

Кейс

Компания, которая 99% работы проводит в офисе. Для коммуникации используют живое общение и электронную почту. С учетом последних событий и тенденций планируют перевести своих сотрудников на удаленку. Что делать, о чем подумать и как не проиграть?

Важно сразу понимать: любой интеллектуальный труд можно перевести на удаленку без потери эффективности (а можно даже и выиграть).

Итак, 4 блока перевода из офиса на дом: 

1. IT

  • Установить на компьютеры сотрудников софт для удаленного подключения 

Что рекомендую использовать: Teamviewer. 

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

  • Установите VPN или аналог. (VPN)

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

Оборудование 

  • Купите/предоставьте/компенсируйте сотрудникам качественные и недорогие гарнитуры (наушники+микрофон) и проверьте их работоспособность.  

Что рекомендую использовать: Plantronics BlackWire C3220-C

Зачем? У сотрудника должен быть качественный звук на всех встречах. 

  • Купите/предоставьте/компенсируйте веб-камеру (если нет в ноутбуке).

Что рекомендую использовать: Любая недорогая камера 

Зачем? У сотрудника должно быть видео на всех встречах и это обязательно.

Интернет

  • Выпустите приказ о необходимости выходить в интернет со стационарной домашней линии (в том числе через домашний Wi-Fi) и контролируйте это. 

Что рекомендую использовать:  Подключать интернет через кабель

Зачем? Мобильная связь или интернет на даче или в кафе не позволят проводить встречи качественно. Ничто так не раздражает, как обрыв связи во время встречи.

Чат для всей компании

  • Используйте корпоративные мессенджеры. Регламентируйте общение, включая скорость ответа.

Что рекомендую использовать: Slack, Microsoft Teams, для небольших команд подходит Discord

Зачем? Обычные мессенджеры (например, Telegram) не предназначены для рабочих целей и плохо подходят для средних и больших команд. Во-первых это небезопасно: права на созданную группу принадлежат отдельному сотруднику, который может покинуть организацию. Во-вторых, идет смешение рабочих чатов с личными. В корпоративных чатах даже есть возможность интеграции с Active Directory.

Видеозвонки

  • Предоставьте сотрудникам минимум 2–3 варианта софта для видеозвонков. Можно использовать бесплатные версии. Что рекомендую использовать: Zoom, Microsoft Teams,

Важно: Во время звонков видео обязательно.

  • Учите сотрудников демонстрировать экран.

  • Договоренности о звонках делают через чат и заносят в календарь.

  • Важные звонки ставьте на запись.

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

Календарь 

  • Приучайте сотрудников пользоваться календарем и создавайте регламент. Все встречи следует высылать заранее для подтверждения всеми участникам. Если встречи нет в календаре — значит встречи не существует.

Что рекомендую использовать: Google Suite, Office 365

Зачем? Если нет встречи в календаре, то шанс ее пропустить крайне велик. На удаленке не получится подойти к сотруднику и напомнить про встречу.

Облако с документами

  • Приучайте сотрудников пользоваться облаком. Никаких папок на личных устройствах! 

Что рекомендую использовать: Google Диск, Яндекс Диск, Dropbox

Зачем? Для порядка в документах и быстрого доступа к ним у всей команды.

  • Таск-трекер и сервисы совместной работы

Что рекомендую использовать: Битрикс24, Asana, Trello

Зачем? Все сотрудники знают о своих задачах и дедлайнах, а вы можете ими управлять

Кадровый ЭДО

  • Подумайте сразу, как подписывать с сотрудниками кадровые документы и получать от них заявления. Курьеров отправлять или копить кипу бумаг — неудобно! 

Что рекомендую использовать: HR-Link

Зачем? С каждым сотрудником в год подписывают около 30 кадровых документов. Делать это по старинке: вызывать в отдел кадров или отдавать руководителю документ — не вариант!

2. Организационные моменты

  • Составьте список тех, кого хотите перевести на удаленку.

Зачем? Это основа, вы должны понимать кого отправляете, а кого нет. .

  • Разбейте на группы и составьте график перевода.

Зачем? Делать все сразу рискованно, так как можно запутаться и упустить важные моменты.

  • Проведите серию вебинаров для сотрудников (а в случае крупных компаний для ключевых сотрудников) Расскажите максимально подробно “правила игры”: что им нужно сделать, какие документы подписать, какие регламенты работы, как взаимодействовать между собой и важно, что начните с плюсов такой работы для самих сотрудников. 

Зачем? Если сотрудник не знает, что нужно делать, то он не будет выполнять свою работу. Убедитесь, что все ознакомились со своей должностной инструкцией и обязанностями. Все директивы сотрудники воспринимают “в штыки” Делайте акценты на плюсах для них. Обучите сотрудников работе с софтом на удаленке.

  • Научите сотрудников использовать необходимые программы и электронную подпись для кадровых документов

Зачем? Сотрудникам не придется самостоятельно разбираться в новых технологиях, что сэкономит время и повысит эффективность.

3. Юридические особенности

  • Подготовьте или внесите изменения и ознакомьте всех сотрудников с ЛНА, который:

  • Регулирует порядок дистанционной (удаленной) работы;

  • Позволяет переводить работников по инициативе работодателя на дистанционную работу;

  • Легализует и регламентирует обмен электронными документами с использованием электронной подписи.

  • Определяет режим работы дистанционщика;

  • Регламентирует режим рабочего времени;

  • Упорядочивает продолжительность и периодичность дистанционки, если она временная;

  • Регламентирует Порядок предоставления отпусков;

  • Уточняют порядок вызова в офис для временных дистанционщиков;

  • Определяет порядок выхода в офис по своему желанию;

  • Уточняют порядок и размер возмещения расходов

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

А если не зафиксировать время работы сотрудника, то может возникнуть следующая ситуация: руководитель звонит работнику в 15:00, а тот не берет трубку и потом говорит, что работал до 14:59. В этом случае ваши претензии к нему будут безосновательны.

  • Включите в ПВТР основные положения, регулирующие дистанционную (удаленную) работу.

Зачем? Наличие основных положений в ПВТР позволит не допустить рисков манипуляций и избежать снижения качества труда.

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

Зачем? Это позволит снизить риск при проверке ГИТ и при обращениях работников в суд.

  • Заключите с сотрудниками соглашения об ЭДО.

Зачем? Бумажный документооборот на дистанционке крайне сложный 

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

Зачем? Для использования системы кадрового электронного документооборота

ВАЖНО: 

Документы компании — это взаимосвязанная система, так что нужно проверить, как минимум, следующие ЛНА на актуальность и противоречия:

Правила внутреннего трудового распорядка — виды дистанционки, правила приема/увольнения и режим работы

Положение об оплате труда работников, Положение о премировании и материальном стимулировании работников — размер и сроки компенсации за использование своего компьютера или другого оборудования для работы

Положение об аттестации — как проводить аттестацию и оценку дистанционщиков

Положение об обработке персональных данных работников — как работники будут передавать документы с персональными данными и где кадровый сотрудник будет их хранить

Регламенты работы отделов — как дистанционные работники будут взаимодействовать, выполнять задания и отчитываться 

4. Психологические факторы

Для правильного выполнения функций планирования, организации, мотивации, контроля и координации принимайте и дополняйте следующие принципы:  

  1. Установите часы присутствия на работе (онлайн).

Они должны быть одинаковыми для всех сотрудников компании. В рабочие часы сотрудники должны быть доступны. Удаленка ≠ каждый работает, как и когда хочет.

  1. Не контролируйте время онлайн, а отслеживайте выполнение задач. Если вы будете контролировать присутствие, единственное, что сотрудники будут делать, так это быть в сети и создавать видимость работы.

  2. Установите регламент ответа в чатах и не требуйте мгновенного ответа (это главный враг продуктивности!)

  3. Решайте в чате только несложные вопросы. Основные же решайте на видеозвонках с последующими обязательными “минутками” встречи в чате. 

  4. Попросите сотрудников использовать в чате только реальные имена и фото (как бы это ни было банально). Чат может и должен стать заменой публичного справочника сотрудников.

  5. Используйте календарь для назначения встреч (всем сотрудникам должны быть доступны календари остальных).

  6. Проводите регулярные общие видеозвонки с командой и встречи 1-1 с ключевыми сотрудниками. Не реже раза в неделю!

  7. Введите «тихие» часы. Для максимальной продуктивности у сотрудников должен быть баланс между работой и личной жизнью.

  8. Подумайте над программой wellbeing. Постоянное сидение на одном месте может вызвать проблемы со здоровьем.

  9. Создайте курс «Как организовать удаленную работу дома», где подробно расскажете о тайм-менеджменте и прочих правилах и принципах. Дома рамки между отдыхом и рабочим временем размываются. В результате, накапливается усталость, которая может привести к психологическому выгоранию.

Принципы организации рабочего места сотрудника:

  1. Отдельная комната с хорошим интернетом.

  2. Десктоп, ноутбук, гарнитура, камера. 

ВАЖНО!

  1. Никаких личных дел во время работы. 

  2. Подумайте как помочь сотруднику “продать” семье почему его нельзя беспокоить в рабочии часы

Учтите, что на первых порах у сотрудников будет много вопросов, поэтому заведите канал помощи в чате.

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

А если у вас появились вопросы, возражения или пожелания, то буду рад вашим обращениям:

Махлин Дмитрий. +79110001778 Телеграм: @MakhlinD

Facebook: facebook.com/MakhlinD, https://www.instagram.com/makhlin.d/

ссылка на оригинал статьи https://habr.com/ru/post/544610/

Фото из Android смартфона в Qt Widgets

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

Описание проблемы

Если вы пишете кросс-платформенное приложение, то для получения изображения из камеры для ПК можно воспользоваться классом QCamera, пример для работы с которым описан в документации Qt.

В соответствии с указанным примером мы добавляем в .pro файл

QT += multimedia multimediawidgets

Далее создаём виджет в своей программе, отображающий изображение из веб-камеры и сохраняющий его в QPixmap или QImage для дальнейшего использования.

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

  1. использовать QML и написать свой Qt Quick-элемент, выполняющий эту функцию, затем состыковать его остальной частью приложения на Qt Widgets, С++;

  2. использовать приложение по-умолчанию Android-смартфона для получения фотографии, затем обработать её в своём приложении.

Рассмотрим первый вариант

Если вы С++ программист Qt Widgets, то очередное эпизодическое углубление в QML займёт у вас время, добавим к этому время на написание Qt Quick-элемента, стыковки этого элемента с С++ кодом, отладки написанного кода. Если вы не профессионал в QML получается долго и сложно.

Рассмотрим второй вариант

В Android-смартфоне уже есть приложение по-умолчанию, прекрасно выполняющее нужную функцию, нужно им просто воспользоваться, применив Java-вызовы (JNI — Java Native Interface) из С++ кода при помощи QtAndroid. Выглядит проще. Полностью работающего кода в интернете я не нашёл, и, изучив опыт других, опираясь на документацию разработчка на Android написал собственный.

Как это сделать

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

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

Чтобы разобраться самостоятельно, как оно работает, начнём с простой задачи, не требующей обращения к файлам и множества Java-вызовов — получению миниатюры.

Получение миниатюры

Начнём с .pro файла.

Он должен содержать следующие строки для поддержки Android.

android {     QT       +=androidextras }

Для получения результата нам понадобится класс, унаследованный от QAndroidActivityResultReceiver. Если требуется, чтобы объект нашего класса высылал изображение при помощи сигнала, то он также должен быть унаследован от любого класса Qt, имеющего базовый класс QObject.

Заголовочный файл (.h) класса имеет вид:

#ifndef CAMSHOT_H #define CAMSHOT_H #include <QObject> #include <QString> #include <cstring> #include <QImage> #include <QDebug> #include <QtAndroid> #include <QAndroidActivityResultReceiver> #include <QAndroidParcel> class CamShot : public QObject, public QAndroidActivityResultReceiver {     Q_OBJECT public:     CamShot(QObject *parent = nullptr):QObject(parent),QAndroidActivityResultReceiver(){}          static const int RESULT_OK = -1;      static const int REQUEST_IMAGE_CAPTURE = 1;     static const int REQUEST_TAKE_PHOTO = REQUEST_IMAGE_CAPTURE;     void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data)  override;     static QImage camThumbnailToQImage(const QAndroidJniObject &data); public slots:     void aMakeShot(); signals:     void createNew(const QImage &img); };  #endif // CAMSHOT_H

Заголовочный файл (.cpp) класса имеет вид:

QImage CamShot::camThumbnailToQImage(const QAndroidJniObject &data){     QAndroidJniObject bundle = data.callObjectMethod("getExtras","()Landroid/os/Bundle;");     qDebug()<<"bundle.isValid() "<<bundle.isValid()<<bundle.toString();     QAndroidJniObject bundleKey = QAndroidJniObject::fromString("data");     const QAndroidJniObject aBitmap (data.callObjectMethod("getParcelableExtra", "(Ljava/lang/String;)Landroid/os/Parcelable;", bundleKey.object<jstring>()));     qDebug()<<"aBitmap.isValid() "<<aBitmap.isValid()<<aBitmap.toString();     jint aBitmapWidth = aBitmap.callMethod<jint>("getWidth");     jint aBitmapHeight = aBitmap.callMethod<jint>("getHeight");     QAndroidJniEnvironment env;     const int32_t aBitmapPixelsCount = aBitmapWidth * aBitmapHeight;     jintArray pixels = env->NewIntArray(aBitmapPixelsCount);     jint aBitmapOffset = 0;     jint aBitmapStride = aBitmapWidth;     jint aBitmapX = 0;     jint aBitmapY = 0;     aBitmap.callMethod<void>("getPixels","([IIIIIII)V", pixels, aBitmapOffset, aBitmapStride, aBitmapX, aBitmapY, aBitmapWidth, aBitmapHeight);     jint *pPixels = env->GetIntArrayElements(pixels, nullptr);     QImage img(aBitmapWidth, aBitmapHeight, QImage::Format_ARGB32);     int lineSzB = aBitmapWidth * sizeof(jint);     for (int i = 0; i < aBitmapHeight; ++i){         uchar *pDst = img.scanLine(i);         const uchar *pSrc = reinterpret_cast<const uchar*>(pPixels + aBitmapWidth * i + aBitmapWidth);         memcpy(pDst, pSrc, lineSzB);     }     env->DeleteLocalRef(pixels); //env->ReleaseIntArrayElements(pixels, pPixels, 0); отвязывает указатель на данные массива от массива, а надо удалить сам массив, поэтому DeleteLocalRef.      return img; } void CamShot::aMakeShot() {     QAndroidJniObject action = QandroidJniObject::fromString("android.media.action.IMAGE_CAPTURE");     //Если необходимо указать Java-класс (не аргумент функции), то указывается полное имя класса (точки-разделители заменяются на "/"), например  "android/content/Intent", "java/lang/String".     //Если аргумент функции Java-объект, то писать имя класса начиная с "L" и ";" в конце, например "Landroid/content/Intent ;", "Ljava/lang/String;".     //Если примитивный тип или массив, то указываются соответствующие символы без разделителей, например "V" (void) или "[IIIIIII" (массив jint, и 6 jint за ним)     //Символы, соответствия примитивны типам:     QAndroidJniObject intent=QAndroidJniObject("android/content/Intent","(Ljava/lang/String;)V", action.object<jstring>());     QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this); } void CamShot::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data){     if ( receiverRequestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK )     {         const QImage thumbnail (camThumbnailToQImage(data));         if (!thumbnail.isNull())             emit createNew(thumbnail);     } }

Разберём приведённый код

Краткие правила указания аргументов в JNI-вызовах

  1. если необходимо указать имя Java-класса (не в качестве аргумента Java-функции), то указывается полное имя класса (точки-разделители заменяются на «/«), например «android/content/Intent«, «java/lang/String«;

  2. если аргумент функции Java-объект, то писать имя его класса начиная с «L» и «;» в конце, например «Landroid/content/Intent;«, «Ljava/lang/String;«;

  3. если примитивный тип или массив, то указываются соответствующие сигнатуры (символы без разделителей), например «V» (void), «I» (jint) или «[IIIIIII» (массив jint, и 6 jint за ним);

  4. сигнатуры примитивных типов:

    C/C++

    JNI

    Java

    Signature

    uint8_t/unsigned char

    jboolean

    bool

    Z

    int8_t/char/signed char

    jbyte

    byte

    B

    uint16_t/unsigned short

    jchar

    char

    C

    int16_t/short

    jshort

    short

    S

    int32_t/int/(long)

    jint

    int

    I

    int64_t/(long)/long long

    jlong

    long

    J

    float

    jfloat

    float

    F

    double

    jdouble

    double

    D

    void

    void

    V

  5. сигнатуры массивов:

    JNI

    Java

    Signature

    jbooleanArray

    bool[]

    [Z

    jbyteArray

    byte[]

    [B

    jcharArray

    char[]

    [C

    jshortArray

    short[]

    [S

    jintArray

    int[]

    [I

    jlongArray

    long[]

    [L

    jfloatArray

    float[]

    [F

    jdoubleArray

    double[]

    [D

    jarray

    type[]

    [Lfully/qualified/type/name;

    jarray

    String[]

    [Ljava/lang/String;

    Чтобы получить доступ к элементам массива, необходимо использовать JNI-методы объекта класса QAndroidJniEnvironment, например такие как: NewIntArray, GetIntArrayElements, DeleteLocalRef GetArrayLength,GetObjectArrayElement, SetObjectArrayElement, и т.д.

Подробнее можно прочитать в презентации (pdf) Practical Qt on Android JNI — qtcon.

В заголовочном файле class CamShot содержит:

  1. значения констант, взятых их документации разработчка Android (так код короче и меньше Java-вызовов);

  2. переопределение абстрактного метода void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override; в который будет передаваться Java-объект класса Intent с миниатюрой изображения;

  3. статический метод
    static QImage camThumbnailToQImage(const QAndroidJniObject &data);
    извлекающий из Java-объекта класса Intent Java-объект класса Bitmap, копирующий пиксели в массив пкселей (32-битных значений) и построчно копирующий эти пиксели в QImage;

  4. общедоступный слот
    void aMakeShot();
    вызывающий операцию по фотографированию изображения и получению его миниатюры;

  5. сигнал
    void createNew(const QImage &img);
    высылающий полученную миниатюру потребителю.

В методе void aMakeShot() создаётся Java-объект Intent в который передаётся строка со значением, указывающим, что необходимо сделать — произвести захват изображения. После этого сформированное действие (Intent) отправляется на исполнение (Activity).

В процессе выполнения действия будет запущено приложение по-умолчанию для фотографирования. Как только фотография будет сделана и подтверждена пользователем, будет произведён вызов виртуального метода handleActivityResult, в котором осуществляется проверка: является ли выполненное действие запрошенным и успешно выполненным. Если да, то вызовем статический метод camThumbnailToQImage получения изображения QImage из Java-объекта класса Bitmap и при успешном результате отправим полученное изображение потребителю сигналом Qt.

Рассмотрим статический метод
static QImage camThumbnailToQImage(const QAndroidJniObject &data) override;

Интересующее нас изображение передаётся в блоке дополнительных данных Java-объекта класса Intent и является Java-объектом класса Bundle, чтобы его получить нужно воспользоваться методом объекта Intent:
Bundle getExtras()

В Bundle хранятся ассоциативные пары <ключ-строка>:<значение>. В статье получить фотографии на Android указан ключ, по которому располагается миниатюра. Это строка «data».

Получим Java-объект класса Bitmap по ключу, воспользовавшись методом объекта Intent:
T getParcelableExtra (String name)

Cобрать из Bitmap QImage сразу не получится, т. к. у Bitmap нет указателя на данные изображения вместе с заголовком его формата. Поэтому получим его размер (ширину и высоту) в пискселях и создадим QImage аналогичного размера для копирования значений пикселей в него.

Для переноса значений пикселей из Bitmap в QImage воспользуемся методом объекта Bitmap:
void getPixels (int[] pixels, int offset, int stride, int x, int y, int width, int height)

Для этого понадобится создать линейный массив значений пикселей
jintArray pixels = env->NewIntArray(aBitmapPixelsCount);
После того, как пиксели будут скопированы, получим указатель на начало массива, который можно использовать в C++ коде:
jint *pPixels = env->GetIntArrayElements(pixels, nullptr);
Затем в цикле построчно скопируем значения пикселей из массива в изображение Qimage. По завершению копирования освобождаем память, выделенную под массив значений пикселей
env->DeleteLocalRef(pixels);
и возвращаем результат в виде QImage.

Отлично. Миниатюра изображения получена.

Получение полноразмерного изображения

Для получения полноразмерного изображения необходимо воспользоваться классом FileProvider, чтобы получить разделяемый Uri для файла фотоснимка. Обращаю ваше внимание, что у Android, по крайней мере, их два:

  1. androidx.core.content.FileProvider;

  2. android.support.v4.content.FileProvider.

Первый — самый современный, не поддерживается Qt, а для использования второго необходимо настроить среду QtCerator:

Установить дополнительные репозитории

Главное меню (сверху)→ «Инструменты» → «Параметры» → «Устройства»→ вкладка «Android»→ вкладка «SDK Manager»→Развернуть элемент списка «Инструменты» в список→ «Extras»→ «Android Support Repository» — поставить флажок установить и нажить на кнопку «Применить» справа.

Заменить автогенерируемые файлы настройки сборки для Android собственными

Перейти на боковой панели QtCreator на вкладку «Проекты». В левой области окна «Сборка и запуск»→ «Сборка». Тогда в правой области окна «Build Android APK» → «Create Templates». В появившемся диалоговом окне установить флажок «Копировать файлы Gradle в каталог Android», нажать на кнопку «Завершить»:

Добавить каталог со своими настройками сборки в проект

В каталоге с исходными кодами вашего приложения появится каталог «android», который необходимо добавить в проект.

Настроить в файле проекта отключаемую возможность поддержки Android

Если приложение кросс-платформенное и предполагается компиляция не только на Android, то в .pro файле необходимо добавить директиву android: перед каждым добавленным файлом:

android {     QT       +=androidextras } # …  DISTFILES += \ android:    android/AndroidManifest.xml \ android:    android/build.gradle \ android:    android/gradle/wrapper/gradle-wrapper.jar \ android:    android/gradle/wrapper/gradle-wrapper.properties \ android:    android/gradlew \ android:    android/gradlew.bat \ android:    android/res/values/libs.xml \     todo.txt

Отредактировать AndroidManifest.xml

Отредактировать файл «AndroidManifest.xml» в android/AndroidManifest.xml, добавив в секцию после

</activity> <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices ->

текст:

<provider android:name="android.support.v4.content.FileProvider" android:authorities="org.qtproject.example.qsketch.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/> </provider>

Создать файл с указанием каталога совместного использования с другими приложениями

Это нужно для того, чтобы приложение фотографирования по-умолчанию могло передать нашему приложению файл.

В каталоге сборки, там где находится автогенерируемый файл «AndroidManifest.xml» внутри каталога «res» рядом с каталогом «values», создать каталог «xml», а в нём файл «file_paths.xml» (… /abin/AndroidManifest.xml) (… /abin/res/xml/file_paths.xml). В созданный файл поместить следующие строки:

<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="shared" path="shared/" /> </paths>

где shared/ имя каталога в каталоге файлов нашего приложения

Добавить компонент, содержащий FileProvider в сборку

Отредактировать файл android/build.gradle, добвив в секцию dependencies текст:

compile'com.android.support:support-v4:25.3.1'

секция целиком выглядит так:

dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) compile'com.android.support:support-v4:25.3.1' }

Данная инструкция сработает, если был установлен Android Support Repository.

Настройка выполнена на основании статьи Sharing Files on Android or iOS from or with your Qt App — Part 4 и подобных статей на эту тему.

Заголовочный файл (.h) класса имеет вид:

#ifndef CAMSHOT_H #define CAMSHOT_H #include <QObject> #include <QImage> #include <QString> #include <QDebug>  #include <QtAndroid> #include <QAndroidActivityResultReceiver> #include <QAndroidParcel>  #include "auxfunc.h"  class CamShot : public QObject, public QAndroidActivityResultReceiver {     Q_OBJECT public:     static const int RESULT_OK = -1;     static const int REQUEST_IMAGE_CAPTURE = 1;     static const int REQUEST_TAKE_PHOTO = REQUEST_IMAGE_CAPTURE;     enum ImgOrientation {ORIENTATION_UNDEFINED = 0, ORIENTATION_NORMAL = 1, ORIENTATION_FLIP_HORIZONTAL = 2, ORIENTATION_ROTATE_180 = 3, ORIENTATION_FLIP_VERTICAL = 4, ORIENTATION_TRANSPOSE = 5,                        ORIENTATION_ROTATE_90 = 6, ORIENTATION_TRANSVERSE = 7, ORIENTATION_ROTATE_270 = 8};     void handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data) override;     static QImage aBitmapToQImage(const QAndroidJniObject &aBitmap);     static QImage camThumbnailToQImage(const QAndroidJniObject &data);     ImgOrientation needRotateAtRightAngle();     QImage camImageToQImage();     static void applyOrientation(QImage &img, const ImgOrientation &orientation);       explicit CamShot(QObject *parent = nullptr):QObject(parent),QAndroidActivityResultReceiver(){}     ~CamShot(); private:         QAndroidJniObject tempImgURI;     QAndroidJniObject tempImgFile;     QAndroidJniObject tempImgAbsPath;     bool _thumbnailNotFullScaleRequested; public slots:     void aMakeShot(const bool &thumbnailNotFullScale = false); signals:     void createNew(const QImage &img); }; #endif // CAMSHOT_H

Заголовочный файл (.cpp) класса имеет вид:

QImage CamShot::aBitmapToQImage(const QAndroidJniObject &aBitmap){     if (!aBitmap.isValid())         return QImage();     jint aBitmapWidth = aBitmap.callMethod<jint>("getWidth");     jint aBitmapHeight = aBitmap.callMethod<jint>("getHeight");     QAndroidJniEnvironment env;     const int32_t aBitmapPixelsCount = aBitmapWidth * aBitmapHeight;     jintArray pixels = env->NewIntArray(aBitmapPixelsCount);     jint aBitmapOffset = 0;     jint aBitmapStride = aBitmapWidth;     jint aBitmapX = 0;     jint aBitmapY = 0;     aBitmap.callMethod<void>("getPixels","([IIIIIII)V", pixels, aBitmapOffset, aBitmapStride, aBitmapX, aBitmapY, aBitmapWidth, aBitmapHeight);     jint *pPixels = env->GetIntArrayElements(pixels, nullptr);     QImage img(aBitmapWidth, aBitmapHeight, QImage::Format_ARGB32);     int lineSzB = aBitmapWidth * sizeof(jint);     for (int i = 0; i < aBitmapHeight; ++i){         uchar *pDst = img.scanLine(i);         const uchar *pSrc = reinterpret_cast<const uchar*>(pPixels + aBitmapWidth * i + aBitmapWidth);         memcpy(pDst, pSrc, lineSzB);     }     env->DeleteLocalRef(pixels); //env->ReleaseIntArrayElements(pixels, pPixels, 0); отвязывает указатель на данные массива от массива, а надо удалить сам массив, поэтому DeleteLocalRef.     return img; } QImage CamShot::camThumbnailToQImage(const QAndroidJniObject &data){     //Получить дополнительный данные     QAndroidJniObject bundle = data.callObjectMethod("getExtras","()Landroid/os/Bundle;");     qDebug()<<"bundle.isValid() "<<bundle.isValid()<<bundle.toString();     //Создать объект типа jstring (строка Java) со значением "data" - ключ для извлечения из ассоциативного контейнера пар <ключ, значение> миниатюры - объекта типа Bitmap (Java)     QAndroidJniObject bundleKey = QAndroidJniObject::fromString("data");     //Получить по ключу "data" дополнительный данные: миниатюру в виде объекта Bitmap     const QAndroidJniObject aBitmap (data.callObjectMethod("getParcelableExtra", "(Ljava/lang/String;)Landroid/os/Parcelable;", bundleKey.object<jstring>()));     qDebug()<<"aBitmap.isValid() "<<aBitmap.isValid()<<aBitmap.toString();     return aBitmapToQImage(aBitmap); } QImage CamShot::camImageToQImage(){     QAndroidJniObject bitmap = QAndroidJniObject::callStaticObjectMethod("android/graphics/BitmapFactory","decodeFile","(Ljava/lang/String;)Landroid/graphics/Bitmap;",tempImgAbsPath.object<jobject>());     qDebug()<<"bitmap.isValid() "<<bitmap.isValid()<<bitmap.toString();     QImage img = aBitmapToQImage(bitmap);     //Удаление файла     if (tempImgFile.isValid())         tempImgFile.callMethod<jboolean>("delete");     return img; } CamShot::ImgOrientation CamShot::needRotateAtRightAngle(){     //Вызов конструктора объекта     QAndroidJniObject exifInterface = QAndroidJniObject("android/media/ExifInterface","(Ljava/lang/String;)V",                                                      tempImgAbsPath.object<jstring>());     qDebug() << __FUNCTION__ << "exifInterface.isValid()=" << exifInterface.isValid();     QAndroidJniObject TAG_ORIENTATION = QAndroidJniObject::getStaticObjectField<jstring>("android/media/ExifInterface", "TAG_ORIENTATION");     qDebug() << __FUNCTION__ << "TAG_ORIENTATION.isValid()=" << TAG_ORIENTATION.isValid()<<TAG_ORIENTATION.toString();     const jint orientation = exifInterface.callMethod<jint>("getAttributeInt","(Ljava/lang/String;I)I",TAG_ORIENTATION.object<jstring>(),static_cast<jint>(ORIENTATION_UNDEFINED));     return static_cast<ImgOrientation>(orientation); } void CamShot::applyOrientation(QImage &img, const ImgOrientation &orientation){     switch (orientation){     case ORIENTATION_UNDEFINED:     case ORIENTATION_NORMAL:         break;     case ORIENTATION_FLIP_HORIZONTAL:{         img = img.mirrored(true, false);         break;     }     case ORIENTATION_ROTATE_180:         Aux::rotateImgCW180(img);         break;     case ORIENTATION_FLIP_VERTICAL:{         img = img.mirrored(false, true);         break;     }     case ORIENTATION_TRANSPOSE:{         img = img.mirrored(true, false);         Aux::rotateImgCW270(img);         break;     }     case ORIENTATION_ROTATE_90:         Aux::rotateImgCW90(img);         break;     case ORIENTATION_TRANSVERSE:{         img = img.mirrored(true, false);         Aux::rotateImgCW90(img);         break;     }         break;     case ORIENTATION_ROTATE_270:         Aux::rotateImgCW270(img);         break;     } } void CamShot::handleActivityResult(int receiverRequestCode, int resultCode, const QAndroidJniObject &data){     if ( receiverRequestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK )     {         if (_thumbnailNotFullScaleRequested){             const QImage thumbnail (camThumbnailToQImage(data));             if (!thumbnail.isNull())                 emit createNew(thumbnail);             return;         }         const ImgOrientation orientation = needRotateAtRightAngle();         QImage image (camImageToQImage());         if (!image.isNull()){             applyOrientation(image, orientation);             emit createNew(image);         }     } } void CamShot::aMakeShot(const bool &thumbnailNotFullScale) {     QAndroidJniObject action = QAndroidJniObject::fromString("android.media.action.IMAGE_CAPTURE");     //Вызов конструктора объекта     QAndroidJniObject intent=QAndroidJniObject("android/content/Intent","(Ljava/lang/String;)V",                                                  action.object<jstring>());     qDebug() << __FUNCTION__ << "intent.isValid()=" << intent.isValid();     _thumbnailNotFullScaleRequested = thumbnailNotFullScale;     if (thumbnailNotFullScale) {         //Для получения миниатюры         QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this);         return;     }     //Для получения изображения в файл     QAndroidJniObject context = QtAndroid::androidContext();     QString contextStr (context.toString());     qDebug() <<"Context: "<<contextStr;     //Каталог для хранения файлов приложения     QAndroidJniObject extDir = context.callObjectMethod("getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;", NULL);     qDebug() << __FUNCTION__ << "extDir.isValid()=" << extDir.isValid()<<extDir.toString();     //Абсолютный путь к каталогу для хранения файлов приложения в виде строки     QAndroidJniObject extDirAbsPath = extDir.callObjectMethod("getAbsolutePath","()Ljava/lang/String;");     //Добавим имя каталога для совместного использования файлов этого приложения другими приложениями. См. /res/xml/file_paths.xml     extDirAbsPath = QAndroidJniObject::fromString(extDirAbsPath.toString() + "/shared");     const QString extDirAbsPathStr = extDirAbsPath.toString();     qDebug() << __FUNCTION__ << "extDirAbsPath.isValid()=" << extDirAbsPath.isValid()<<extDirAbsPathStr;     //Создать объект типа Файл для разделяемого каталога     QAndroidJniObject sharedFolder=QAndroidJniObject("java.io.File","(Ljava/lang/String;)V",                                                       extDirAbsPath.object<jstring>());     qDebug() << __FUNCTION__ << "sharedFolder.isValid()=" << sharedFolder.isValid()<<sharedFolder.toString();     const jboolean sharedFolderCreated = sharedFolder.callMethod<jboolean>("mkdirs");     Q_UNUSED(sharedFolderCreated);     //Прежде чем пытаться создать файл с заданным именем, нужно проверить файл с этим именем на существование     //Предположительно путь к этому файлу     QAndroidJniObject suggestedFilePath = QAndroidJniObject::fromString(extDirAbsPathStr+"/"+"_tmp.jpg");     qDebug() << __FUNCTION__ << "suggestedFilePath.isValid()=" << suggestedFilePath.isValid()<<suggestedFilePath.toString();     //Создать объект типа Файл     //Вызов конструктора объекта     QAndroidJniObject tempImgFile=QAndroidJniObject("java.io.File","(Ljava/lang/String;)V",                                                  suggestedFilePath.object<jstring>());     qDebug() << __FUNCTION__ << "fileExistsCheck.isValid()=" << tempImgFile.isValid()<<tempImgFile.toString();     //Удаление файла, если он существует     if (tempImgFile.isValid()){         const jboolean deleted = tempImgFile.callMethod<jboolean>("delete");         Q_UNUSED(deleted);     }     //Создать физический файл для записи в него изображения по указанному пути     const jboolean fileCreated = tempImgFile.callMethod<jboolean>("createNewFile");     Q_UNUSED(fileCreated);     //Абсолютный путь к созданному файлу в виде строки     tempImgAbsPath = tempImgFile.callObjectMethod("getAbsolutePath","()Ljava/lang/String;");     qDebug() << __FUNCTION__ << "tempImgAbsPath.isValid()=" << tempImgAbsPath.isValid()<<tempImgAbsPath.toString();     //Получить authority для fileprovider     const QString contextFileProviderStr ("org.qtproject.example.qsketch.fileprovider");     const char androidFileProvider  [] = "android/support/v4/content/FileProvider";     //const char androidxFileProvider [] = "androidx/core/content/FileProvider"; - не поддерживается Qt     /*QAndroidJniObject*/ tempImgURI = QAndroidJniObject::callStaticObjectMethod(androidFileProvider, "getUriForFile", "(Landroid/content/Context;Ljava/lang/String;Ljava/io/File;)Landroid/net/Uri;",                                                                              context.object<jobject>(), QAndroidJniObject::fromString(contextFileProviderStr).object<jstring>(), tempImgFile.object<jobject>());     qDebug() << __FUNCTION__ << "tempImgURI.isValid()=" << tempImgURI.isValid()<<tempImgURI.toString();     //Получить значение константы MediaStore.EXTRA_OUTPUT     QAndroidJniObject MediaStore__EXTRA_OUTPUT         = QAndroidJniObject::getStaticObjectField("android/provider/MediaStore", "EXTRA_OUTPUT", "Ljava/lang/String;");     qDebug() << "MediaStore__EXTRA_OUTPUT.isValid()=" << MediaStore__EXTRA_OUTPUT.isValid();     //Добавить URI путь файла для записи в него данных к задаче     intent.callObjectMethod("putExtra","(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;",MediaStore__EXTRA_OUTPUT.object<jstring>(), tempImgURI.object<jobject>());     qDebug() << __FUNCTION__ << "intent.isValid()=" << intent.isValid();     QtAndroid::startActivity(intent, REQUEST_IMAGE_CAPTURE, this); }

Так-же статические методы класса Aux для поворота изображения.

Заголовочный файл (.h) класса Aux имеет вид:

#ifndef AUXFUNC_H #define AUXFUNC_H  #include <QImage> #include <QColor> #include <QPainter> #include <QMatrix> #include <QSize> #include <QPoint>  class Aux { public:     static void resizeCenteredImg(QImage *image, const QSize &newSize, const QColor bgColor);     static void rotateImg(QImage &img, qreal degrees);     static void rotateImgCW90(QImage &img);     static void rotateImgCW180(QImage &img);     static void rotateImgCW270(QImage &img); };   #endif // AUXFUNC_H

Файл исходного кода (.cpp) класса Aux имеет вид:

void Aux::resizeCenteredImg(QImage *image, const QSize &newSize, const QColor bgColor){     if (image->size() == newSize)         return;     const QSize szDiff = newSize - image->size();     QImage newImage(newSize, QImage::Format_ARGB32);     newImage.fill(bgColor);     QPainter painter(&newImage);     painter.drawImage(QPoint(szDiff.width()/2, szDiff.height()/2), *image);     *image = newImage; } void Aux::rotateImg(QImage &img, qreal degrees){     QPoint center = img.rect().center();     QMatrix matrix;     matrix.translate(center.x(), center.y());     matrix.rotate(degrees);     img = img.transformed(matrix, Qt::SmoothTransformation); } void Aux::rotateImgCW90(QImage &img){     const int w = img.width();     const int h = img.height();     const int maxDim = std::max(w, h);     resizeCenteredImg(&img, QSize(maxDim, maxDim), Qt::white);     rotateImg(img, 90);     resizeCenteredImg(&img, QSize(h, w), Qt::white); } void Aux::rotateImgCW180(QImage &img){     rotateImg(img, 180); } void Aux::rotateImgCW270(QImage &img){     const int w = img.width();     const int h = img.height();     const int maxDim = std::max(w, h);     resizeCenteredImg(&img, QSize(maxDim, maxDim), Qt::white);     rotateImg(img, 270);     resizeCenteredImg(&img, QSize(h, w), Qt::white); }

Указанный метод позволяет получить за один раз или миниатюру или полноразмерное изображение.

Результат зависит от переданного логического параметра «thumbnailNotFullScale». Если он равен логической единице, то будет получена миниатюра, если логическому нулю, то полноразмерное изображение. Попытка получить миниатюру при запросе сохранения полноразмерного изображения в файл приведёт к исключению в JNI-вызовах.

Если миниатюра всегда ориентирована правильно, то полноразмерное изображение направлено в одну строну и его необходимо поворачивать. Информацию о необходимых преобразованиях можно получить из exif-свойств изображения при помощи ExifInterface. В обнаруженных в интернете Java-примерах преобразование к нормальной ориентации производится в Java-коде, в случае с Qt нет смысла мучить себя трудно отлаживаемыми, громоздкими JNI-вызовами и проще выполнить все необходимые преобразования в Qt.

ссылка на оригинал статьи https://habr.com/ru/post/544614/

Бесплатная онлайн-конференция Нарратив в играх

21 марта 2021 года (воскресенье), с 12:00 и до 19:00, состоится Бесплатная онлайн-Конференция: Нарратив в играх.

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

Кроме интересных лекций вас ждет конкурс на лучший сценарий игры (и не только) с крутыми призами.

На конкурс принимаются видео-игры и настолки, выпущенные в 2020-2021 году (даже в «Раннем доступе» или в виде демо/beta) на русском языке.

Для участия в конкурсе необходимо подать заявку до 05.03.2021

Участие в мероприятии абсолютно бесплатно.
Организатор конференции: Центр развития компетенций в бизнес-информатике Высшей школы бизнеса НИУ ВШЭ, при поддержке WN Conference, Talents in Games и Союза Литераторов РФ.
Регистрация на конференцию здесь>>>

ссылка на оригинал статьи https://habr.com/ru/post/544622/

Подключение БД с SSH-туннелем к PowerBI

Всем привет! 

Оказалось, что PowerBI не имеет встроенной возможности настроить доступ к БД, защищённой SSH-туннелем. Приходится выкручиваться. Мне очень помогла эта статья — спасибо тебе добрый и компетентный в написании инструкций человек, без тебя я бы впала в отчаяние. 

И тем не менее, в ней раскрыты не все нюансы. В своём материале я добавлю следующее:

  • Два уникальных совета, как сделать так, чтобы установленный туннель не падал после авторизации

  • Дополнительная инструкция для подключения к SSH при помощи приватного ключа, а не логина и пароля

  • Скрины из самого PowerBI с настройкой БД и советы о том, как работает выборка из подключенной БД и как обновлять данные, полученные по SQL-запросам.

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

Итак, поехали.

Вам понадобится(этап подготовки):

  1. Установленный Putty. Можно взять здесь — https://www.putty.org/

  2. Данные от вашего бекенда или девопса по списку:

    1. IP-адрес SSH-сервера;

    2. порт SSH-сервера;

    3. username для доступа на SSH-сервер;

    4. пароль для доступа или связка приватного и публичного ключа*

    5. IP-адрес самой БД (обычно 127.0.0.1);

    6. порт самой БД;

    7. название БД;

    8. логин доступа к БД (не то же самое, что username для доступа на SSH-сервер);

    9. пароль для доступа к БД.

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

Поднимаем SSH-туннель

  1. Открываем Putty

  2. В Category/Session вводим IP-адрес SSH-сервера, порт SSH-сервера и выставляем радио-баттон Close window on exit на позицию Never

  3. Переходим в Category/Connection/SSH и ставим галочку на Don’t start a shell or command at all

     

  4. Переходим в Category/Connection/SSH/Tunnels, в поле Source port вбиваем порт самой БД, в поле Destination IP-адрес самой БД:порт самой БД. Жмём Add.

  5. *пункт для тех, кто подключается с приватным ключом, если у вас случай с логином и паролем, то переходите сразу к 6 пункту инструкции

    1. Запустите PuttyGen (установился на ваш компьютер вместе с Putty)

    2. Выберите в верхнем меню Conversions/Import Key

    3. В открывшемся окне Проводника откройте папку, куда вы сохранили файлы приватного и публичного ключа (пункт 2d списка “Вам понадобится”) и выберите файл приватного ключа. Иногда Windows делает этот файл скрытым. Возможно, вам надо будет включить отображение скрытых файлов в Проводнике, нажав на Вид и поставив галочку напротив “Скрытые элементы”

    4. Нажимаем Save private key. Даём ключу любое имя на латинице и сохраняем в папку с ключами.

    5. Возвращаемся в Putty. Переходим в Category/Connection/SSH/Auth и нажимаем Browse рядом с Private key file for authentication

    6. В открывшемся окне Проводника выбираем сохранённый в пункте 5d файл приватного ключа.

  6. Переходим в Category/Session, в поле Saved Session вводим имя нашего туннеля (любое), жмём Save. Это позволит нам не вводить все настройки каждый раз заново. После чего жмём Open

  7. В открывшемся окне Терминала рядом с Login as вводим username для доступа на SSH-сервер и жмём Enter

  8. *пункт для тех, у кого авторизация по паролю, если вы авторизовались по связке ключей, то пропускайте этот пункт и переходите сразу к 9. 

    1. Вводим пароль для доступа на SSH-сервер и жмём Enter

Настройка PowerBI

SSH-туннель настроен, не закрывайте окно терминала Putty. Теперь переходим в PowerBI. Жмём Получить данные и выбираем “База данных MySQL” или “База данных PostgreSQL” в зависимости от того, что у вас. Интерфейс будет одинаковым, а вот вероятность успеха — разной, потому что MySQL И PostgreSQL используют разные драйвера. Убедитесь, что выбрали свою БД правильно. 

  1. В поле Сервер вводим IP-адрес самой БД:порт самой БД 

  2. В поле База данных вводим название БД

  3. Жмём “Расширенные настройки” и в поле Инструкция SQL вставляем запрос, по которому нужно импортировать данные. Если вы его не напишете, PowerBI приконнектится ко всей БД, но не позволит вам вытаскивать из неё данные запросами и не позволит построить модели (или я не нашла как, если у вас есть успешный опыт, с удовольствием прочитаю его в комментах)

  4. Жмём ok

  5. Вводим логин доступа к БД и пароль для доступа к БД, жмём Подключение

  6. Возможна вот такая ошибка, это ok

Как обновить данные из БД в PowerBI

  1. Поднимаем SSH-туннель в Putty

  2. Переходим в PowerBI и жмём Обновить. Все созданные запросы ещё раз отправятся на сервер и выгрузят свежую информацию

ссылка на оригинал статьи https://habr.com/ru/post/544634/