Журнал регистрации 1С (sql lite) в web app

Администрирование - Журнал регистрации

Данная публикация рассматривает построение компонентного решения работы журнала регистрации в стороннем приложении(web app). Встала задача миграции sql lite жр во внешнюю базу. Данное решение было создано: 1. для хранения жр за весь период 2. для ускорения работы с жр 3. для ускорения сервера предприятия, так как именно он (а точнее рагент) пытается записать данные в жр sql lite(фактически файл на диске), после увеличения размера файла более 10 гб, поступали жалобы по вопросу быстродействия 1с (и не только ради этого) Данная публикация может быть полезной администраторам, программистам, оптимизаторам.

Компоненты, технологии:

1. вин служба(vb net)  - читает данные из sql lite,  вставляет в базу ms sql, удаляет записи из sql lite

2. web app back end(python) - многопоточная обработка запросов front end, выполняет запросы, отдает json на front

3.  web app front end(js) - форма для пользовательской работы

4. nginx

5. 1c для чтения истории изменения объекта из 1с

6. ms sql server

За основу миграции данных  из sql lite был взят проект(сами не стали писать, погуглили, нашли, посмотрели - вроде подходит):

https://github.com/alekseybochkov/EventLogLoader/tree/master/EventLogLoaderManager. Алексей(автор проекта) проделал хорошую работу, спасибо ему огромное, для законченной функциональности мне не хватало:

1. очистка sql lite после чтения

2. ссылка на 1с в формате 1с предприятия

Данные пункты запилил. Компонент миграции данных из sql lite в сиквел готов. Все работает, все ок.

Доработка базы(ms sql)+win служба:

 

Добавим вьюху, чтобы с нее читать с но лок:

SELECT     dbo.Events.DateTime, dbo.Events.DataString, dbo.Events.DataStructure, dbo.Events.Comment, dbo.Computers.Name AS ComputerName, 
                      dbo.Users.Name AS UsersName, dbo.Events.MetadataID, dbo.Metadata.Name AS MetadataName, dbo.Infobases.Name AS InfobasesName, dbo.Events.UserName, 
                      dbo.Events.ref_ones, dbo.Events.InfobaseCode, 
                      CASE WHEN dbo.EventsType.Name = '_$Session$_.Start' THEN 'Сеанс. Начало' WHEN dbo.EventsType.Name = '_$Session$_.Finish' THEN 'Сеанс. Завершение' WHEN
                       dbo.EventsType.Name = '_$InfoBase$_.ConfigUpdate' THEN 'Информационная база. Изменение конфигурации' WHEN dbo.EventsType.Name = '_$InfoBase$_.DBConfigUpdate'
                       THEN 'Информационная база. Изменение конфигурации  базы данных' WHEN dbo.EventsType.Name = '_$InfoBase$_.EventLogSettingsUpdate' THEN 'Информационная база. Изменение  параметров журнала регистрации'
                       WHEN dbo.EventsType.Name = '_$InfoBase$_.InfoBaseAdmParamsUpdate' THEN 'Информационная база. Изменение  параметров информационной базы' WHEN
                       dbo.EventsType.Name = '_$InfoBase$_.MasterNodeUpdate' THEN 'Информационная база. Изменение главного  узла' WHEN dbo.EventsType.Name = '_$InfoBase$_.RegionalSettingsUpdate'
                       THEN 'Информационная база. Изменение  региональных установок' WHEN dbo.EventsType.Name = '_$InfoBase$_.TARInfo' THEN 'Тестирование и исправление. Сообщение'
                       WHEN dbo.EventsType.Name = '_$InfoBase$_.TARMess' THEN 'Тестирование и исправление. Предупреждение' WHEN dbo.EventsType.Name = '_$InfoBase$_.TARImportant'
                       THEN 'Тестирование и исправление. Ошибка' WHEN dbo.EventsType.Name = '_$Data$_.New' THEN 'Данные. Добавление' WHEN dbo.EventsType.Name = '_$Data$_.Update'
                       THEN 'Данные. Изменение' WHEN dbo.EventsType.Name = '_$Data$_.Delete' THEN 'Данные. Удаление' WHEN dbo.EventsType.Name = '_$Data$_.TotalsPeriodUpdate'
                       THEN 'Данные. Изменение периода рассчитанных итогов' WHEN dbo.EventsType.Name = '_$Data$_.Post' THEN 'Данные. Проведение' WHEN dbo.EventsType.Name
                       = '_$Data$_.Unpost' THEN 'Данные. Отмена проведения' WHEN dbo.EventsType.Name = '_$User$_.New' THEN 'Пользователи. Добавление' WHEN dbo.EventsType.Name
                       = '_$User$_.Update' THEN 'Пользователи. Изменение' WHEN dbo.EventsType.Name = '_$User$_.Delete' THEN 'Пользователи. Удаление' WHEN dbo.EventsType.Name
                       = '_$Job$_.Start' THEN 'Фоновое задание. Запуск' WHEN dbo.EventsType.Name = '_$Job$_.Succeed' THEN 'Фоновое задание. Успешное завершение' WHEN dbo.EventsType.Name
                       = '_$Job$_.Fail' THEN 'Фоновое задание. Ошибка выполнения' WHEN dbo.EventsType.Name = '_$Job$_.Cancel' THEN 'Фоновое задание. Отмена' WHEN dbo.EventsType.Name
                       = '_$PerformError$_' THEN 'Ошибка выполнения' WHEN dbo.EventsType.Name = '_$Transaction$_.Begin' THEN 'Транзакция. Начало' WHEN dbo.EventsType.Name
                       = '_$Transaction$_.Commit' THEN 'Транзакция. Фиксация' WHEN dbo.EventsType.Name = '_$Transaction$_.Rollback' THEN 'Транзакция. Отмена' ELSE dbo.EventsType.Name
                       END AS EventTypeName, dbo.Applications.Name AS ApplicationName, dbo.Events.EventType, dbo.Events.EventID, dbo.Events.TransactionStatus
FROM         dbo.Events WITH (NOLOCK) LEFT OUTER JOIN
                      dbo.EventsType ON dbo.Events.EventID = dbo.EventsType.Code AND dbo.Events.InfobaseCode = dbo.EventsType.InfobaseCode LEFT OUTER JOIN
                      dbo.Applications ON dbo.Events.InfobaseCode = dbo.Applications.InfobaseCode AND dbo.Events.AppName = dbo.Applications.Code LEFT OUTER JOIN
                      dbo.Metadata ON dbo.Events.InfobaseCode = dbo.Metadata.InfobaseCode AND dbo.Events.MetadataID = dbo.Metadata.Code LEFT OUTER JOIN
                      dbo.Users ON dbo.Events.InfobaseCode = dbo.Users.InfobaseCode AND dbo.Events.UserName = dbo.Users.Code LEFT OUTER JOIN
                      dbo.Computers ON dbo.Events.ComputerName = dbo.Computers.Code AND dbo.Events.ComputerName = dbo.Computers.Code AND 
                      dbo.Events.InfobaseCode = dbo.Computers.InfobaseCode LEFT OUTER JOIN
                      dbo.Infobases ON dbo.Events.InfobaseCode = dbo.Infobases.Code

Доработка EventLogProcessor.vb:

 Sub LoadEvents83(FileName As String)
...

 Command.CommandText = "SELECT 
                                            [rowID],
                                            [severity],
                                            [date],
                                            [connectID],
                                            [session],
                                            [transactionStatus],
                                            [transactionDate],
                                            [transactionID],
                                            [userCode],
                                            [computerCode],
                                            [appCode],
                                            [eventCode],
                                            [comment],
                                            [metadataCodes],
                                            [sessionDataSplitCode],
                                            [dataType],
                                            [data],
                                            [dataPresentation],
                                            [workServerCode],
                                            [primaryPortCode],
                                            [secondaryPortCode],

											CASE
											    WHEN  instr(data, ':')>0 
                                                    THEN 
                                                        substr(data, 25 + instr(data, ':'), 8) || '-' ||
                                                        substr(data, 21 + instr(data, ':'), 4) || '-' ||
                                                        substr(data, 17 + instr(data, ':'), 4) || '-' || 
                                                        substr(data, 1 + instr(data, ':'), 4)  || '-' ||
                                                        substr(data, 5 + instr(data, ':'), 12)
													ELSE  
                                                         ''
											END  as ref_ones

                                        FROM [EventLog] 
                                        WHERE [rowID] > @LastEventNumber83 AND [date] >= @MinimumDate
                                        ORDER BY 1
                                        LIMIT 100000"

                Command.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)

                Dim noOfSeconds As Int64 = (LoadEventsStartingAt - New Date).TotalSeconds
                Command.Parameters.AddWithValue("MinimumDate", noOfSeconds * 10000)
....
 

Command_DELETE.CommandText = "PRAGMA journal_mode = TRUNCATE; DELETE From EventLog WHERE rowID < @LastEventNumber83;"
                Command_DELETE.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)

                Try

                    Command_DELETE_Count = Command_DELETE.ExecuteNonQuery()

                    Log.Info("Try delete(EventLog) success: " + Command_DELETE_Count.ToString + " LastEventNumber83:" + LastEventNumber83.ToString)

                Catch ex As Exception

                    Log.Error(ex, "Try delete failed")

                End Try

                Command_DELETE.CommandText = "PRAGMA journal_mode = TRUNCATE; DELETE From EventLogMetadata WHERE eventLogID < @LastEventNumber83;"
                Command_DELETE.Parameters.AddWithValue("LastEventNumber83", LastEventNumber83)

...

 

web app.

Итак, backend python:

1.api_log.py(основной файл)

from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading
import json
from urllib.parse import urlparse, parse_qs
import socket
import api_log_func

class Handler(BaseHTTPRequestHandler):

    def do_GET(self):

        path = urlparse(self.path).path
        qs = urlparse(self.path).query
        qs = parse_qs(qs)

        res =  api_log_func.callback(path, qs, self)

        resp = json.dumps(res, default=api_log_func.json_serial).encode()

        self.send_response(200)
        self.send_header("Content-type", "application/json")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Expose-Headers", "Access-Control-Allow-Origin")
        self.send_header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
        self.end_headers()
        self.wfile.write(resp)
        
        return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('0.0.0.0', 8010), Handler)
    print('Starting server, use <Ctrl-C> to stop')
    server.serve_forever()

слушает порт, выполняет методы, возвращает json

2. api_log_func

#import peewee

#import json
from datetime import datetime
from datetime import timedelta

#import os
#import orm.fields
#import gevent
#import threading
## from call import Call # TODO loopback!

#import inspect

## import asyncio

import pymssql, base64, sys, json, time

def get_mssql_conn():
    
    conn = pymssql.connect(server='', user='sa', password='', database='ExLogBook2')  
    cursor = conn.cursor()  

    return cursor

def api_log_get_meta_data(qs):
   
    #res = (OrgCRM
    #    .select(OrgCRM.ref, OrgCRM.name)
    #    .where(OrgCRM.mark == False)
    #    .where(OrgCRM.show == True))

    #res = res.dicts()
    #return list(res) 
   
    d = []

    cursor = get_mssql_conn()

    cursor.execute('SELECT [Code],[Name] FROM [ExLogBook2].[dbo].[Metadata] WHERE [InfobaseCode]=1;')  

    row = cursor.fetchone()  
    while row:  
        
        d.append({'code':row[0],'name':row[1]})

        row = cursor.fetchone()  

    #return sorted(d, key=lambda e: e['name'])
    return d

def api_log_get_event_objs(qs):
    
    #in:
    #metadata_id
    #data_string
     
    d = []

    query_text = 'SELECT DISTINCT [DataString], [DataStructure], [ref_ones] FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 %data_string% %metadata_id% %date_start% %date_end%'

    if qs.get('data_string'):
        query_text = query_text.replace('%data_string%', ' AND [DataString] LIKE \'%'+qs.get('data_string')[0]+'%\'')
    else:
        query_text = query_text.replace('%data_string%', '')
        
    if qs.get('metadata_id'):
        query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = '+qs.get('metadata_id')[0])
    else:
        query_text = query_text.replace('%metadata_id%', '')
        
    if qs.get('date_start'):
        query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, \''+qs.get('date_start')[0]+' 00:00\', 103)')
    else:
        query_text = query_text.replace('%date_start%', '')
            
    if qs.get('date_end'):
        query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, \''+qs.get('date_end')[0]+' 23:59\', 103)')
    else:
        query_text = query_text.replace('%date_end%', '')
    
    cursor = get_mssql_conn()

    cursor.execute(query_text)  
    row = cursor.fetchone()  
    while row:  
        
        d.append(
            {
                #'date_time':'',
                #'comment':row[1],
                'data_string':row[0],
                'data_structure':row[1],
                #'user_name':row[4],
                #'computer_name':row[5]
                'ref_ones':row[2]
             }
            )

        row = cursor.fetchone()  

    return sorted(d, key=lambda e: e['data_string'])

def api_log_get_results(qs):
   
    #in:
    #data_structure
    #data_string    
    ##data_string

    d = []

    query_text = 'SELECT [DateTime], [Comment], [DataString], [DataStructure], [UsersName], [ComputerName], [MetadataName], [EventTypeName], [ApplicationName], [EventType], [EventID], [TransactionStatus] FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 %data_structure% %data_string% %metadata_id% %ref_ones% %date_start% %date_end%'

    if qs.get('data_string'):
        query_text = query_text.replace('%data_string%', ' AND [DataString] LIKE \'%'+qs.get('data_string')[0]+'%\'')
    else:
        query_text = query_text.replace('%data_string%', '')
    
    if qs.get('data_structure'):
        query_text = query_text.replace('%data_structure%', ' AND [DataStructure] = '+qs.get('data_structure')[0])
    else:
        query_text = query_text.replace('%data_structure%', '')         
        
    if qs.get('metadata_id'):
        query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = '+qs.get('metadata_id')[0])
    else:
        query_text = query_text.replace('%metadata_id%', '')
    
    if qs.get('ref_ones'):
        query_text = query_text.replace('%ref_ones%', ' AND [ref_ones] = \''+qs.get('ref_ones')[0]+'\'')
    else:
        query_text = query_text.replace('%ref_ones%', '') 
    
    if qs.get('date_start'):
        query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, \''+qs.get('date_start')[0]+' 00:00\', 103)')
    else:
        query_text = query_text.replace('%date_start%', '')
            
    if qs.get('date_end'):
        query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, \''+qs.get('date_end')[0]+' 23:59\', 103)')
    else:
        query_text = query_text.replace('%date_end%', '')
    
    cursor = get_mssql_conn()

    cursor.execute(query_text)  
    row = cursor.fetchone()  
    while row:  
        
        d.append(
            {
                'date_time':row[0],
                'comment':row[1],
                'data_string':row[2],
                'data_structure':row[3],
                'user_name':row[4],
                'computer_name':row[5],
                'metadata_name':row[6],
                'event_type_name':row[7],
                'application_name':row[8],
                'event_type':row[9],
                'event_id':row[10],
                'transaction_status':row[11]
             }
            )

        row = cursor.fetchone()  

    return sorted(d, key=lambda e: e['date_time'])

def callback(path, qs, self):

    res = eval('%s(qs)' % path[1:])

    return res

def api_log_get_result_mod_zn(qs):
   
    #in:
    #data_structure
    #data_string    
    ##data_string

    d = []

    query_text = 'SELECT DISTINCT DataString, UsersName, mod_post_doc, ref_ones, var_1, var_2, var_3, var_4 FROM (SELECT [DateTime], [Comment], [DataString], [DataStructure], [UsersName], [ComputerName], [MetadataName], [EventTypeName], [ApplicationName], [EventType], [EventID], [TransactionStatus], CASE WHEN [Comment] like \'%'+'Статус проведения документа до изменения:Да'+'%\' THEN 1 ELSE 0 END AS mod_post_doc, [ref_ones], CASE WHEN [Comment] like \'%<Контрагент>:%\' AND [Comment] like \'%<Автомобиль>:%\'THEN 1 ELSE 0 END AS var_1, CASE WHEN [Comment] like \'%<Автомобиль>:%\'THEN 1 ELSE 0 END AS var_2, CASE WHEN [Comment] like \'%<Контрагент>:%\'THEN 1 ELSE 0 END AS var_3, CASE WHEN [Comment] like \'%Таблица товаров до записи:%\' THEN 1 ELSE 0 END AS var_4 FROM [ExLogBook2].[dbo].[v_Events] WHERE [InfobaseCode]=1 and ([Comment] like \'%'+'Измененнные реквизиты шапки до записи:'+'%\' or [Comment] like \'%+'+'Таблица товаров до записи:'+ '%\' or [Comment] like \'%'+'Статус проведения документа до изменения:%\') %metadata_id% %date_start% %date_end% ) AS T1 WHERE (T1.mod_post_doc=0 and (T1.var_1=1 or T1.var_2=1 or T1.var_3=1)) or (T1.mod_post_doc=1 and T1.var_4=1)  order by var_1 desc, var_2 desc, var_3 desc, var_4 desc '

    query_text = query_text.replace('%metadata_id%', ' AND [MetadataId] = 12')

    if qs.get('date_start'):
        query_text = query_text.replace('%date_start%', ' AND [DateTime] >= CONVERT(DATETIME, \''+qs.get('date_start')[0]+' 00:00\', 103)')
    else:
        query_text = query_text.replace('%date_start%', '')
            
    if qs.get('date_end'):
        query_text = query_text.replace('%date_end%', ' AND [DateTime] <= CONVERT(DATETIME, \''+qs.get('date_end')[0]+' 23:59\', 103)')
    else:
        query_text = query_text.replace('%date_end%', '')
    
    cursor = get_mssql_conn()

    cursor.execute(query_text)  
    row = cursor.fetchone()  
    while row:  
        
        d.append(
            {
                #'date_time':row[0],
                #'comment':row[1],
                'data_string':row[0],
                #'data_structure':row[3],
                'user_name':row[1],
                #'computer_name':row[5],
                #'metadata_name':row[6],
                #'event_type_name':row[7],
                #'application_name':row[8],
                #'event_type':row[9],
                #'event_id':row[10],
                #'transaction_status':row[11],
                'mod_post_doc':row[2],
                'ref_ones':row[3],
                'var_1':row[4],
                'var_2':row[5],
                'var_3':row[6],
                'var_4':row[7]
             }
            )

        row = cursor.fetchone()  

    return d

def json_serial(obj):
  if isinstance(obj, (datetime, datetime.date)):
    return obj.isoformat()
  raise TypeError("Type is not serializable %s" % type(obj))

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

Общая логика работы:

1. слушать порт

2. выполнить запрос

3.  преобразовать в json

4. отдать по запросу

Фронт енд

Фронт енд запиливал коллега. Ничего сказать не могу. Выглядит так:

web app готово.

Общий вид работы:

Пример получения данных из 1С:

Процедура ЗаполнитьДаннымиИзВнешнегоЖурналаРегистрации(ЖурналРегистрации, ОбъектСсылка)
	
	ИдОбъекта = Строка(ОбъектСсылка.УникальныйИдентификатор());
	Если Не ЗначениеЗаполнено(ИдОбъекта) Тогда 
		
		Возврат;
		
	КонецЕсли;
	
	HTTPСоединение = HTTPСоединение();

	Результат = Неопределено;
	
	ИмяФайлаОтвета = ПолучитьИмяВременногоФайла();
	
	Адрес = "/api_log_get_results?ref_ones="+ИдОбъекта;
	
	HTTPЗапрос = Новый HTTPЗапрос(Адрес);
	
	HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос, ИмяФайлаОтвета);
	
	Если HTTPОтвет.КодСостояния=200 Тогда 
		
		Результат = ПрочитатьФайлОтвета(ИмяФайлаОтвета);
		
	КонецЕсли;
	
	УдалитьФайлы(ИмяФайлаОтвета);
	
	HTTPОтвет = Неопределено;
	HTTPСоединение = Неопределено;
	
	Если Не Результат=Неопределено Тогда 
		
		Для Каждого ТекСтрокаРез Из Результат.Данные Цикл
			
			СтрТЗ = ЖурналРегистрации.Добавить();
			
		КонецЦикла;
		
	КонецЕсли;
	
КонецПроцедуры

Результат:

 

Тех требования:

1С Предприятие 8.Х (жр в sql lite)

nginx

python 3.5>

ms sql server

ОС не имеет значения, мы деплоили на debian, на windows тоже вполне себе работать будет

Результат работы над поделкой превзошел все мои ожидания (в плане скорости отклика жр)

В архивах сорцы. гуд лак

Скачать файлы

Наименование Файл Версия Размер
src web app
.7z 1,16Mb
09.07.18
0
.7z 1,16Mb Скачать
src win ser допиленный (EventLogLoader-master)
.7z 48,11Mb
08.07.18
0
.7z 48,11Mb Скачать

См. также

Комментарии
Сортировка: Древо
1. swenzik 09.07.18 20:52 Сейчас в теме
сейчас придёт Люстин и будет ругаться что не в грэйлог
делов-то

Запрос = Новый HTTPЗапрос();
	Запрос.АдресРесурса="/gelf";
	Запрос.Заголовки=Новый Соответствие();
	Запрос.Заголовки.Вставить("Content-Type","application/json");
	Запрос.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());

	Соединение = Новый HTTPСоединение("10.10.10.10",12203);
2. dmarenin 10.07.18 04:45 Сейчас в теме
Оставьте свое сообщение