Вот и пригодилась опять теория вероятности ;)

Задача: есть два исполнителя. Необходимо обеспечить, чтобы исполнителю №1 доставалось 2/3 заявок, и исполнителю №2 — 1/3 задач.

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

  • Исполнитель 1, с долями 2/3
  • Исполнитель 2, с долями 1/3

Как вычисляем исполнителя:

Всего долей: 3

  • — Создаем кость, с гранями 1,2,3
  • — Грани 1 и 2  = Исполнитель 1
  • — Грань 3  = Исполнитель 2

Кидаем «кость». Какая грань выпадет, такой исполнитель и назначается. В принципе при достаточном количестве бросков, процентное соотношение выпадений исполнителя выходит:

  • Исполнитель 1:  67%
  • Исполнитель 2:  33 %

Ну и кому интересно, вот код реализации:

&НаСервере
Функция ВыбратьИсполнителяНаСервере()
	Наименование=объект.Район;
	нама=СтрРазделить(Наименование," ");
	Район=ВРег(нама[0]);
	Исполнители=Новый Массив();
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	ДоступВЛКДляКонтрагентаРайоныОбслуживания.Ссылка.Ссылка КАК Ссылка,
		|	ДоступВЛКДляКонтрагентаРайоныОбслуживания.Доля КАК Доля
		|ИЗ
		|	Справочник.ДоступВЛКДляКонтрагента.РайоныОбслуживания КАК ДоступВЛКДляКонтрагентаРайоныОбслуживания
		|ГДЕ
		|	ВРЕГ(ДоступВЛКДляКонтрагентаРайоныОбслуживания.Район.Наименование) ПОДОБНО &Наименование";	
	Запрос.УстановитьПараметр("Наименование", "%"+Район+"%");	
	РезультатЗапроса = Запрос.Выполнить();	
	ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();		
	ВсегоДолей=0;
	Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
		Исполнители.Добавить(Новый Структура("исполнитель,доля",ВыборкаДетальныеЗаписи.Ссылка,ВыборкаДетальныеЗаписи.Доля));	
		ВсегоДолей=ВсегоДолей+ВыборкаДетальныеЗаписи.Доля;
	КонецЦикла;
	
	Если Исполнители.Количество()=0 тогда
		возврат неопределено;
	конецесли;	
	// если исполнитель один, то с долями не морочимся
	Если Исполнители.Количество()=1 тогда
		Возврат   Исполнители[0].исполнитель;
	конецесли;	
	
	// если всего долей 0, а исполнителей более чем 1, то тогда исполнитель - первый попавшийся
	если ВсегоДолей=0 тогда
		Возврат   Исполнители[0].исполнитель;
	конецесли;	
	
	
	// А вот тут уже мудрим с долями
	ЗаполненныеДоли=Новый Массив();
	для каждого исполнитель из Исполнители цикл
		для поз=1 по исполнитель.доля цикл
			ЗаполненныеДоли.Добавить(исполнитель.исполнитель);
		конеццикла	
	конеццикла;	
	
	// Здесь уже имеем массив с заполненными развернутыми исполнителями. Например: Вася,Вася,Вася,Петя,Петя
	// Осталось сгенерировать случайное число 1-5, и назначить в соответствии с выпавшим числом исполнителя
	Генератор = Новый ГенераторСлучайныхЧисел(ТекущаяУниверсальнаяДатаВМиллисекундах());
	СлучайноеЧисло = Генератор.СлучайноеЧисло(0,ЗаполненныеДоли.Количество()-1);
	Возврат ЗаполненныеДоли[СлучайноеЧисло];
		
КонецФункции

API Map Yandex: Изменение содержимого метки «на лету»

Задача: разместить внутри метки поле ввода текста (комментарий) и сохранять его значение

Решение:

При формировании Json данных FeatureCollection, внутри каждой метки, в настройке balloonContentBody разместим теги <textarea style=’width: 100%;’></textarea>. И кнопку сохранения результата. Но! Сохранить результат в БД не сложно. Сложно добиться того, чтобы то что пользователь ввел в поле, сохранилось, после перевыбора метки мышкой. Содержимое поля скидывается каждый раз к изначальному, т.к. движек яндекса каждый раз перерисовывает отображение html на основании данных которые у него » в душе». Поэтому функция сохранения будет выглядеть как-то так:

function SaveComment(ls_id){
    console.log("-сохраняем комментарий "+ls_id);
    comment=$("#comment_"+ls_id).val();
    $.post("?r=receipts2/savecomment", {  
            ls_id:ls_id,
            comment:comment
    }).done(function(data) {                        
        console.log(data);
        balloonContentBody=objectManager.objects.getById(ls_id).properties.balloonContentBody;
        start_=balloonContentBody.slice(0,balloonContentBody.search("%;'")+4);
        end_=balloonContentBody.slice(balloonContentBody.search("</textarea>"),balloonContentBody.length);
        balloonContentBody=start_+comment+end_;
        objectManager.objects.setObjectProperties(ls_id,{balloonContentBody:balloonContentBody});
    });                      
}

Что тут происходит?

  1. Изменения сохраняются в базу данных при помощи вызова скрипта receipts2/savecomment
  2. Читаем данные которые яндекс хранит у себя в нутри
  3. Изменем эти данные, добавив комментарий
  4. Сохраняем данные «внутри»

В результате после перещелкивания по метке, комментарий сохраняется.

YII2 выполнение кода в каждом контроллере

В продолжении темы примитивного логгирования. Понадобилось записывать, какой пользователь открывает какие страницы. И если с клиентским отображением в принципе ничего сложного, просто вставил в /view/layouts/main.php код вида:

if (isset(Yii::$app->user->identity)){
  if (isset(Yii::$app->user->identity->username)){
    Logger::Log(Yii::$app->user->identity->username." >> ".$_SERVER["REQUEST_URI"]);
  }
};

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

<?php
namespace app\components;
use yii\base\Component;
use app\models\Logger;

class Logging_at_start extends Component{
    public function init() {
           Logger::Log(" >> ".$_SERVER["REQUEST_URI"]);
        parent::init();
    }
}   

И добавил/отредактировал в /config/web строчки:

...
'bootstrap' => ['log','Logging_at_start'],
...
    'components' => [
        'Logging_at_start'=>[
            'class'=>'app\components\Logging_at_start'
        ],        

Плюс добавил сохранение сессии в константу sess в web/index.php

Итого, в логах теперь красуется всё то нужно:

Примитивное логгирование в YII2

Иногда необходимо в проект добавить логгирование событий работы, однако не всегда удобно тащить для этого кучу сторонних библиотек. Тем более что особых требований и не нужно. Если достаточно что бы:

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

То вполне можно написать свою простенькую модель вида:

<?php
namespace app\models;
use Yii;

class Logger {
    
    const Error=1;
    const Info=2;
    const Warning=3;
    const Success=4;
    const Fail=5;
    
 public static function Log($message,$type=self::Info){
     $prefix="Info:";
     if ($type==self::Error)    $prefix="Error";
     if ($type==self::Info)     $prefix="Info";
     if ($type==self::Warning)  $prefix="Warning";
     if ($type==self::Success)  $prefix="Success";
     if ($type==self::Fail)  $prefix="Fail";
     /usr/bin/logger "(GEO) $prefix: $message";
 }   
}

И использовать её далее в любом месте в коде, например так:

        Logger::Log("-попытка авторизации в AD $username");        
        // авторизуемся в AD
        $ldapbind = @ldap_bind($ldap_con, $username."@".$value["ldap_server"], $password);                        
        if ($ldapbind==false) {
            Logger::Log("--ошибка",Logger::Fail);
            return null;            
        };
        Logger::Log("--ok",Logger::Success);

Логи можно будет наблюдать в стандартном /var/log/messages:P.S. Раньше тоже велосипедил с добавлением лога в отдельный файл, а потом подумал «А зачем?». В большинстве случаев достаточно так.

OpenVPN клиент на RaspberryPi

В наличии: файл ovpn. Задача установить соединение.

Решении: напишем скрипт и положим в крон:

# Сей дивный скрипт устанавливает VPN содениение с сервером грибовы.рф и если оное установлено, то включает светодиодик..
result=$(ifconfig | grep tun0)
echo $result
if [[ "$result" == *"tun0"* ]]; then
  echo "-ok";
  /root/scripts/gpio/led2_on.py
else
  echo "-fail";
  /usr/sbin/openvpn /root/vpn/malgino.ovpn
fi
1 2