(ASP.NET) ASP NET (2010 год)

Шлюз к 1С по протоколу Битрикс

Мне иногда приходится работать с 1С и на этой страничке я хотел описать нижний уровень шлюза для взаимодествия с 1С из ASP.NET.

Это самый нижний уровень шлюза, выполняющий сетевое взаимодействие с 1С по протоколу Bitrix. Описанный на этой страничке уровень, выполняя сетевое взаимодействие с 1С, поднимает на вышележащий уровень программного обеспечения XML-данные, полученные из 1С и, наоборот, затребует от вышележащего уровня строку с XML, которые необходимо отправить для 1С.

Мой шлюз выполняет все необходимые операции для сетевого взаимодествия с 1С:

В сущности, вышележащему уровню софта остается только манипуляции с итоговым XML, который принят из 1С. И, в обратном направлении, подготовка строки с XML для отправки в 1С. Все остальные черновые операции берет на себя мой шлюз, код которого я публикую ниже.

Хотя сама по себе подготовка XML и его парсинг (функционал вышележащего слоя программного обеспечения) - это тоже весьма непростая задача (в моем случае это десятки тысяч строк кода). И именно этот слой обработки XML составляет специфику любой коммерческой системы (в том числе той, что я разработал с использованием данного шлюза). конечно, саму специфику моей коммерческой системы я публиковать не буду - я покажу вместо кода в десятки тысяч строк (размещенного в ImportXML и ExportXML) лишь две пустые заглушки. Для вас использование моего шлюза - дает возможность реализовать всего две функции ImportXML и ExportXML, в которых вы вставите свой собственный код для работы с XML-данными своей собственной коммерческой системы.

Прежде всего моя коммерческая система была разработана на PostgreSQL (ибо это быстрая, весьма функционально продвинутая и совершенно бесплатная СУБД). Работа с которой отличается тем, что она требует ОДНОГО коннекта со всего сайта (и всех реквестов) - так же как надо работать с Ораклом. Как вообще работать с PostgreSQL из ASP.NET я описал для широкой публики здесь - Этюды на ASP.NET. Пример сайта на СУБД PostgreSQL. Соответвенно, если вы используете какие-то другие СУБД - вам придется заменить этот механизм. Ну например в случае mySQL или MS SQL - просто выбросить PG_Safe_Connection и выполнять каждую SQL-операцию в отдельном коннекте.

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

<%@ WebHandler Language="VB" Class="_1c_XML" %>

И поместите в область App_Code своего сайта класс _1c_XML, который будет вызван из 1С:


   1:  Imports Microsoft.VisualBasic
   2:   
   3:  Public Class _1c_XML
   4:      Inherits _1c_Gateway
   5:   
   6:      Public Sub ImportXML(ByVal FullOutXmlFileName As String, ByRef LoadResult As ProcessReceiveXMLResult) Handles MyBase.XMLReceive
   7:  ....
   8:          LoadResult = ProcessReceiveXMLResult.Success
   9:      End Sub
  10:   
  11:      Public Sub ExportXML(ByRef XMLString As String) Handles MyBase.SendXMLRequest
  12:  ....
  13:          XMLString =   "<?xml version='1.0' encoding='UTF-8' ?> <КоммерческаяИнформация ВерсияСхемы='2.04' ДатаФормирования='2010-07-24T11:29:20'>.....</xml>"
  14:      End Sub
  15:  End Class

Теперь рассмотрим собственно код моего шлюза - класс _1c_Gateway, от которого унаследован класс _1c_XML, непосредственно вызываемый хандлером.


   1:  'Этот хандлер реализует протокол шлюза с 1С по протоколу http://v8.1c.ru/edi/edi_stnd/131/ на самом нижнем уровне
   2:  '1С-шлюз выполняет передачу данных в 1С одним фрагментом, а прием многими фрагментами (в обоих направлениях в ZIP-формате)
   3:  'и вызывает код обработки принятых или отправляемых в 1С XML-файлов
   4:  '
   5:  'Хандлер требует три параметра в конфиге:
   6:  '<add key="Cache" value="Temp" />                                  - имя рабочего каталога-буфера для обработки файлов
   7:  '<add key="ZipPath" value="C:\Program Files (x86)\7-Zip\7z.exe" /> - местоположение зиипера
   8:  '<add key="1С_FullLog" value="True" />                             - необходимость ведения полного журнала
   9:  '
  10:  'хандлеру требуется постоянный коннект к PostgreSQL (класс PG1.SQL_Postgres его поддерживается через интерфейс IRequiresSessionState)
  11:  'в базе хандлер использует таблу 1C_Exchange для учета принятых фрагментов и таблу TerminalError для записи ошибок
  12:  '
  13:  'при приеме файла вызывается событие XMLReceive, которое обрабатывается во внешнем коде (коде более высокого уровня)
  14:  'этому событию передаются имя принятого файла, обработчик события возвращает результат обработки
  15:  '
  16:  'при передаче в 1С данных, шлюз вызывает событие SendXMLRequest, которому внешний код более высокого уровня передает сформированную строку XML для передачи и учетный номер (номер заказа)
  17:   
  18:  Imports System
  19:  Imports System.Web
  20:  Imports Microsoft.VisualBasic
  21:   
  22:  'это интерфейс события, обрабатываемого во внешнем коде - ему передаются имя принятого файла и надо возвратить результат обработки полученных от 1С данных
  23:  Public Delegate Sub XMLReceived(ByVal FileName As String, ByRef ProcessReceiveResult As ProcessReceiveXMLResult)
  24:  Public Enum ProcessReceiveXMLResult
  25:      Success = 1
  26:      Failure = 2
  27:      Progress = 3
  28:  End Enum
  29:   
  30:  'это интерфейс события для внешнего кода, который формирует XML для передачи в 1С
  31:  Public Delegate Sub SendingXMLRequest(ByRef XMLstring As String)
  32:   
  33:   
  34:  Public Class _1c_Gateway : Implements IHttpHandler, IRequiresSessionState
  35:   
  36:      Public Event XMLReceive As XMLReceived
  37:      Public Event SendXMLRequest As SendingXMLRequest
  38:   
  39:      Dim DataSegmentSize As Integer = 10000 'размер сегмента данных для передачи из битрикса
  40:   
  41:      Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
  42:          '
  43:          PG_Safe_Connection(context)
  44:          '
  45:          If context.Request.QueryString("type") IsNot Nothing And context.Request.QueryString("mode") IsNot Nothing Then
  46:              If context.Request.QueryString("type") = "catalog" And context.Request.QueryString("mode") = "checkauth" Then
  47:                  Receive_Step1(context)
  48:              ElseIf context.Request.QueryString("type") = "catalog" And context.Request.QueryString("mode") = "init" And context.Request.Cookies("SessionID") IsNot Nothing Then
  49:                  Receive_Step2(context)
  50:              ElseIf context.Request.QueryString("type") = "catalog" And context.Request.QueryString("mode") = "file" And context.Request.QueryString("filename") IsNot Nothing And context.Request.Cookies("SessionID") IsNot Nothing Then
  51:                  Receive_Step3(context)
  52:              ElseIf context.Request.QueryString("type") = "catalog" And context.Request.QueryString("mode") = "import" And context.Request.QueryString("filename") IsNot Nothing And context.Request.Cookies("SessionID") IsNot Nothing Then
  53:                  Receive_Step4(context)
  54:              ElseIf context.Request.QueryString("type") = "sale" And context.Request.QueryString("mode") = "checkauth" Then
  55:                  Send_Step1(context)
  56:              ElseIf context.Request.QueryString("type") = "sale" And context.Request.QueryString("mode") = "init" And context.Request.Cookies("SessionID") IsNot Nothing Then
  57:                  Send_Step2(context)
  58:              ElseIf context.Request.QueryString("type") = "sale" And context.Request.QueryString("mode") = "query" And context.Request.Cookies("SessionID") IsNot Nothing Then
  59:                  Send_Step3(context)
  60:              ElseIf context.Request.QueryString("type") = "sale" And (context.Request.QueryString("mode") = "success" Or context.Request.QueryString("mode") = "failure") And context.Request.Cookies("SessionID") IsNot Nothing Then
  61:                  Send_Step4(context)
  62:              End If
  63:          End If
  64:      End Sub
  65:   
  66:  #Region "Прием данных из 1С"
  67:   
  68:      Sub Receive_Step1(ByVal context As HttpContext)
  69:          Dim GUID1 As Guid = Guid.NewGuid
  70:          context.Session("SessionID") = GUID1.ToString
  71:          context.Response.ContentType = "text/plain"
  72:          context.Response.Write("success" & vbCrLf & "SessionID" & vbCrLf & GUID1.ToString & vbCrLf & vbCrLf)
  73:          '
  74:          TraceLog(context, "Receive_Step1: " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",SessionID=" & context.Session("SessionID") & ",Cookies=" & GetAllCookies(context))
  75:      End Sub
  76:   
  77:      Sub Receive_Step2(ByVal context As HttpContext)
  78:          context.Response.ContentType = "text/plain"
  79:          context.Response.Write("zip=yes" & vbCrLf & "file_limit=" & DataSegmentSize.ToString & vbCrLf)
  80:          '
  81:          TraceLog(context, "Receive_Step2: " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",Cookies=" & GetAllCookies(context))
  82:      End Sub
  83:   
  84:      Sub Receive_Step3(ByVal context As HttpContext)
  85:          'собственно прием и запись сегмента данных в файловую систему
  86:          Dim Buf1(DataSegmentSize) As Byte
  87:          Try
  88:              TraceLog(context, "Receive_Step3: (Start) " & ",type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",filename=" & context.Request.QueryString("filename").Replace("\", "\\") & ",Cookies=" & GetAllCookies(context))
  89:              '
  90:              Dim DataSegmentNum As Integer = 0
  91:              DataSegmentNum = GetLastDataSegmentNum(context, context.Request.Cookies("SessionID").Value)
  92:              DataSegmentNum += 1
  93:              '
  94:              Dim WorkingDir As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString)
  95:              '
  96:              Dim SegmentFileName As String = context.Request.Cookies("SessionID").Value & "_" & DataSegmentNum
  97:              '
  98:              Dim FullZipFileName As String = IO.Path.Combine(WorkingDir, SegmentFileName & "_" & context.Request.QueryString("filename"))
  99:              If Not My.Computer.FileSystem.FileExists(FullZipFileName) Then
 100:                  If HttpContext.Current.Request.InputStream.Length <= Buf1.Length Then
 101:                      While HttpContext.Current.Request.InputStream.CanRead
 102:                          Dim CountReadingBytes As Integer = HttpContext.Current.Request.InputStream.Read(Buf1, 0, HttpContext.Current.Request.InputStream.Length)
 103:                          If CountReadingBytes = 0 Then Exit While
 104:                          ReDim Preserve Buf1(HttpContext.Current.Request.InputStream.Length - 1)
 105:                          My.Computer.FileSystem.WriteAllBytes(FullZipFileName, Buf1, False)
 106:                          ReDim Buf1(DataSegmentSize)
 107:                      End While
 108:                      '
 109:                      SaveReceiveFileName(context, context.Request.Cookies("SessionID").Value, DataSegmentNum, context.Request.QueryString("filename"), context.Request.InputStream.Length)
 110:                      '
 111:                      context.Response.ContentType = "text/plain"
 112:                      context.Response.Write("success" & vbCrLf) 'сегмент принят
 113:                      '
 114:                      TraceLog(context, "Receive_Step3 (success) : " & FullZipFileName.Replace("\", "\\"))
 115:                  Else
 116:                      context.Response.ContentType = "text/plain"
 117:                      context.Response.Write("failure" & vbCrLf & "Buffer overflow" & vbCrLf & vbCrLf) 'ошибка
 118:                      TraceLog(context, "Receive_Step3 (failure) : " & FullZipFileName.Replace("\", "\\") & " Buffer overflow")
 119:                  End If
 120:              Else
 121:                  context.Response.ContentType = "text/plain"
 122:                  context.Response.Write("failure" & vbCrLf & FullZipFileName & " Is present" & vbCrLf & vbCrLf) 'ошибка
 123:                  TraceLog(context, "Receive_Step3 (failure): " & FullZipFileName.Replace("\", "\\") & " Is present")
 124:              End If
 125:          Catch ex As Exception
 126:              TraceLog(context, "Receive_Step3 (failure): " & ex.Message)
 127:              context.Response.ContentType = "text/plain"
 128:              context.Response.Write("failure" & vbCrLf & ex.Message & vbCrLf & vbCrLf) 'ошибка
 129:          End Try
 130:      End Sub
 131:   
 132:   
 133:      'окончательная обработка принятых сегментов
 134:      Sub Receive_Step4(ByVal context As HttpContext)
 135:          Dim ResultCode As String = "failure"
 136:          Dim FullZipFileName As String = ""    'полное имя ZIP-файла после конкатенации фрагментов  (например G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e.zip)
 137:          Dim XmlFileName As String = ""        'имя раззипованного XML-файла, переданного 1С (например offers.xml)
 138:          Dim FullOutXmlFileName As String = "" 'полное  (например G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e\offers.xml)
 139:          '
 140:          Try
 141:              '(1) - сначала отберем имена всех принятых из 1С фрагментов и сольем принятые сегменты в один (context.Request.Cookies("SessionID").Value=f160a80d-183d-46a3-b0a7-e3ad2d008a0e")
 142:              If Not ConcatAllSegments(context, context.Request.Cookies("SessionID").Value, FullZipFileName) Then
 143:                  GoTo EndReceive
 144:              End If
 145:              '
 146:              '(2) - определяем имя принятого из 1С файла
 147:              If Not GetZippedFileName(context, FullZipFileName, XmlFileName) Then
 148:                  GoTo EndReceive
 149:              End If
 150:              '  
 151:              '(3) - раззиповываем каталог с упакованным в нем XML-файлом         
 152:              If Not UnZip(context, FullZipFileName, XmlFileName, FullOutXmlFileName) Then
 153:                  GoTo EndReceive
 154:              End If
 155:              '
 156:              '(4) - вызов внешнего кода, который распарсит принятый XML и уложит его в базу
 157:              Dim LoadResult As ProcessReceiveXMLResult = ProcessReceiveXMLResult.Progress
 158:              RaiseEvent XMLReceive(FullOutXmlFileName, LoadResult)
 159:              '
 160:              Select Case LoadResult
 161:                  Case ProcessReceiveXMLResult.Success : ResultCode = "success"
 162:                  Case ProcessReceiveXMLResult.Failure : ResultCode = "failure"
 163:                  Case ProcessReceiveXMLResult.Progress : ResultCode = "progress"
 164:              End Select
 165:              '
 166:              '(5) - отметились у себя в базе о результатах згрузки данных из 1С
 167:              SaveXMLLoadCode(context, context.Request.Cookies("SessionID").Value, ResultCode)
 168:          Catch ex As Exception
 169:              TraceLog(context, "Receive_Step4: (error)" & ex.Message)
 170:              GoTo EndReceive
 171:          End Try
 172:          '
 173:  EndReceive:
 174:          context.Response.ContentType = "text/plain"
 175:          context.Response.Write(ResultCode & vbCrLf)
 176:          '
 177:          TraceLog(context, "Receive_Step4: (" & ResultCode & ") " & ",type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",filename=" & context.Request.QueryString("filename").Replace("\", "\\") & ",Cookies=" & GetAllCookies(context) & ", FullZipFileName=" & FullZipFileName & ", XmlFileName=" & XmlFileName & ", FullOutXmlFileName=" & FullOutXmlFileName)
 178:      End Sub
 179:  #End Region
 180:   
 181:  #Region "Файловые операции при приеме данных из 1С"
 182:   
 183:      'функция сливает в один фрагмент все принятые из 1С фрагменты файла и возвращает полное имя собранного файла (и ошибку - если не получилось)
 184:      Function ConcatAllSegments(ByVal context As HttpContext, ByVal SessionID As String, ByRef FullZipFileName As String) As Boolean
 185:          Dim WorkingDir1 As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString) 'C:\WWW\LocalUser\delmar\WWW\Temp1
 186:          'сначала отберем имена всех принятых из 1С фрагментов
 187:          Dim ConcatCommand As New StringBuilder
 188:          Try
 189:              Dim DT1 As Data.DataTable
 190:              DT1 = GetAllDataSegment(context, SessionID)
 191:              If DT1 Is Nothing Then
 192:                  Return False
 193:              Else
 194:                  For i As Integer = 0 To DT1.Rows.Count - 1
 195:                      Dim OneSegmentFileName As String = DT1.Rows(i)("SessionID") & "_" & DT1.Rows(i)("DataSegmentNum") & "_" & DT1.Rows(i)("FileName")
 196:                      Dim FullSegmentFileName As String = IO.Path.Combine(WorkingDir1, OneSegmentFileName)
 197:                      ConcatCommand.Append(FullSegmentFileName & " /B + ")
 198:                  Next
 199:                  ConcatCommand.Remove(ConcatCommand.Length - 2, 2)
 200:                  FullZipFileName = IO.Path.Combine(WorkingDir1, SessionID & ".zip")
 201:                  ConcatCommand.Append(FullZipFileName & " /Y")
 202:              End If
 203:          Catch ex As Exception
 204:              TraceLog(context, "Receive_Step4: (ConcatError) " & ex.Message)
 205:              Return False
 206:          End Try
 207:          '
 208:          'сливаем принятые сегменты в один
 209:          Dim Concat As New System.Diagnostics.Process
 210:          Concat.StartInfo.RedirectStandardOutput = True
 211:          Concat.StartInfo.UseShellExecute = False
 212:          Concat.StartInfo.CreateNoWindow = True
 213:          Dim CopyLog As String
 214:          Try
 215:              Concat.StartInfo.FileName = "cmd.exe"
 216:              Concat.StartInfo.Arguments = "/C copy " & ConcatCommand.ToString
 217:              '"G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e_1_v8_C26_25.zip /B + G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e_2_v8_C26_25.zip /B + G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e_3_v8_C26_25.zip /B G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e.zip /Y"
 218:              Concat.StartInfo.WorkingDirectory = WorkingDir1
 219:              Concat.StartInfo.WindowStyle = Diagnostics.ProcessWindowStyle.Hidden
 220:              Concat.Start()
 221:              Concat.WaitForExit()
 222:              CopyLog = Concat.StandardOutput.ReadToEnd() '.... 1 file(s) copied.
 223:              Concat.Close()
 224:          Catch ex As Exception
 225:              TraceLog(context, "Receive_Step4: (CopyError) " & ex.Message)
 226:              Return False
 227:          End Try
 228:          '
 229:          If My.Computer.FileSystem.FileExists(FullZipFileName) Then
 230:              Return True
 231:          Else
 232:              TraceLog(context, "Receive_Step4: (CopyError) " & FullZipFileName & " not found")
 233:              Return False
 234:          End If
 235:      End Function
 236:   
 237:      'функция определяет имя содержащегося в зипе 1C XML-файла (например offers.xml)
 238:      Function GetZippedFileName(ByVal context As HttpContext, ByVal FullZipFileName As String, ByRef XmlFileName As String) As Boolean
 239:          Dim ZipPath1 As String = System.Configuration.ConfigurationManager.AppSettings("ZipPath")               'C:\Program Files (x86)\7-Zip\7z.exe 
 240:          Dim ZipParm1 As String = " l " & FullZipFileName
 241:          Dim ZipCacheDir1 As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString) 'C:\WWW\LocalUser\delmar\WWW\Temp1
 242:          Dim Zip1 As New System.Diagnostics.Process
 243:          Zip1.StartInfo.RedirectStandardOutput = True
 244:          Zip1.StartInfo.UseShellExecute = False
 245:          Zip1.StartInfo.CreateNoWindow = True
 246:          Dim ZipLog1 As String = ""
 247:          Try
 248:              Zip1.StartInfo.FileName = ZipPath1
 249:              Zip1.StartInfo.Arguments = ZipParm1
 250:              Zip1.StartInfo.WorkingDirectory = ZipCacheDir1
 251:              Zip1.Start()
 252:              Zip1.WaitForExit()
 253:              ZipLog1 = Zip1.StandardOutput.ReadToEnd()
 254:              Zip1.Close()
 255:          Catch ex As Exception
 256:              TraceLog(context, "Receive_Step4: (ZipError) " & ", ZipParm=" & ZipParm1 & ", Ziplog=" & ZipLog1 & " ; " & ex.Message)
 257:              Return False
 258:          End Try
 259:          '
 260:          'парсим журнал, чтобы найти имя файла
 261:          If Len(ZipLog1) < 40 Then
 262:              TraceLog(context, "Receive_Step4: (ZipError) " & "ZIP не удалось определить имя файла, содержащееся в ZIP-архиве" & ", ZipParm=" & ZipParm1 & ", Ziplog=" & ZipLog1)
 263:              Return False
 264:          Else
 265:              ZipLog1 = ZipLog1.Replace("  ", " ")
 266:              Dim Pos2 As Integer = ZipLog1.IndexOf(".xml")
 267:              If Pos2 = 0 Or Pos2 < 40 Then
 268:                  TraceLog(context, "Receive_Step4: (ZipError) " & "ZIP не удалось определить имя файла, содержащееся в ZIP-архиве" & ", ZipParm=" & ZipParm1 & ", Ziplog=" & ZipLog1)
 269:                  Return False
 270:              Else
 271:                  Dim Pos1 As Integer = Mid(ZipLog1, 1, Pos2).LastIndexOf(" ")
 272:                  XmlFileName = Mid(ZipLog1, Pos1 + 1, Pos2 - Pos1 + 4).Trim
 273:                  If XmlFileName.Contains(".xml") Then
 274:                      Return True
 275:                  Else
 276:                      TraceLog(context, "Receive_Step4: (ZipError) " & "ZIP не удалось определить имя файла, содержащееся в ZIP-архиве" & ZipLog1)
 277:                      Return False
 278:                  End If
 279:              End If
 280:          End If
 281:      End Function
 282:   
 283:      Function UnZip(ByVal context As HttpContext, ByVal FullZipFileName As String, ByVal XmlFileName As String, ByRef FullOutXmlFileName As String) As Boolean
 284:          Dim OutFileDir As String = IO.Path.GetFileNameWithoutExtension(FullZipFileName)                        'f160a80d-183d-46a3-b0a7-e3ad2d008a0e
 285:          Dim FullOutFileDir As String = IO.Path.Combine(context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString), OutFileDir) 'G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e
 286:          Dim FullOutFileName As String = IO.Path.Combine(FullOutFileDir, XmlFileName)                           'G:\Projects\DelmarDisk\www\Temp\f160a80d-183d-46a3-b0a7-e3ad2d008a0e\offers.xml
 287:          Dim ZipPath As String = System.Configuration.ConfigurationManager.AppSettings("ZipPath")               'C:\Program Files (x86)\7-Zip\7z.exe 
 288:          Dim ZipParm As String = " e -y " & FullZipFileName & " -o" & FullOutFileDir
 289:          Dim ZipCacheDir As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString) 'E:\
 290:          Dim Zip As New System.Diagnostics.Process
 291:          Dim ZipLog As String = ""
 292:          Zip.StartInfo.UseShellExecute = False
 293:          Zip.StartInfo.CreateNoWindow = True
 294:          Zip.StartInfo.RedirectStandardOutput = True
 295:          'Раззиповывываем и возвращаем имя раззипованного файла
 296:          Try
 297:              Zip.StartInfo.FileName = ZipPath
 298:              Zip.StartInfo.Arguments = ZipParm
 299:              Zip.StartInfo.WorkingDirectory = ZipCacheDir
 300:              Zip.Start()
 301:              Zip.WaitForExit()
 302:              ZipLog = Zip.StandardOutput.ReadToEnd()
 303:              Zip.Close()
 304:              If My.Computer.FileSystem.FileExists(FullOutFileName) Then
 305:                  'единственный нормальный исход чтобы двигаться дальше
 306:                  FullOutXmlFileName = FullOutFileName
 307:                  Return True
 308:              Else
 309:                  'нет результата исполнения команды
 310:                  TraceLog(context, "Receive_Step4: (ZipError) " & "ZIP output empty" & ", ZipParm=" & ZipParm & ", Ziplog=" & ZipLog)
 311:                  Return False
 312:              End If
 313:          Catch ex As Exception
 314:              TraceLog(context, "Receive_Step4: (ZipError) " & ", ZipParm=" & ZipParm & ", Ziplog=" & ZipLog & " ; " & ex.Message)
 315:              Return False
 316:          End Try
 317:   
 318:      End Function
 319:   
 320:  #End Region
 321:   
 322:  #Region "Передача заказов в 1С"
 323:   
 324:      Sub Send_Step1(ByVal context As HttpContext)
 325:          Dim GUID1 As Guid = Guid.NewGuid
 326:          context.Session("SessionID") = GUID1.ToString
 327:          context.Response.ContentType = "text/plain"
 328:          context.Response.Write("success" & vbCrLf & "SessionID" & vbCrLf & GUID1.ToString & vbCrLf & vbCrLf)
 329:          '
 330:          TraceLog(context, "Send_Step1: " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",SessionID=" & context.Session("SessionID") & ",Cookies=" & GetAllCookies(context))
 331:      End Sub
 332:   
 333:      Sub Send_Step2(ByVal context As HttpContext)
 334:          context.Response.ContentType = "text/plain"
 335:          context.Response.Write("zip=yes" & vbCrLf & "file_limit=" & DataSegmentSize.ToString & vbCrLf)
 336:          '
 337:          TraceLog(context, "Send_Step2: " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",Cookies=" & GetAllCookies(context))
 338:      End Sub
 339:   
 340:      Sub Send_Step3(ByVal context As HttpContext)
 341:          Dim ResultCode As String = "failure"
 342:          '
 343:          TraceLog(context, "Send_Step3 (start): " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",Cookies=" & GetAllCookies(context))
 344:          '
 345:          Dim FullZipFileName As String = ""
 346:          Dim FullDirName As String = ""
 347:          Dim Len As Integer
 348:          Try
 349:              '
 350:              '(1) - получили XML для передачи в 1С
 351:              Dim XMLString As String = ""
 352:              RaiseEvent SendXMLRequest(XMLString)
 353:              If XMLString = "" Then
 354:                  ResultCode = "progress"
 355:                  GoTo EndSave
 356:              End If
 357:              '
 358:              '(2) - записали его на диск
 359:              If Not WriteXMLToFile(context, context.Request.Cookies("SessionID").Value, XMLString, FullDirName) Then 'context.Request.Cookies("SessionID").Value="f57feea1-3033-4780-bae2-2660c9ae9a3a"
 360:                  GoTo EndSave
 361:              End If
 362:              '
 363:              '(3) - зазиповали директорию
 364:              If Not ZipDir(context, FullDirName, FullZipFileName) Then
 365:                  GoTo EndSave
 366:              End If
 367:              '
 368:              '(4) - выполнили учет
 369:              Dim FileInfo As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo(FullZipFileName)
 370:              Len = FileInfo.Length
 371:              SaveSendingFileName(context, context.Request.Cookies("SessionID").Value, 0, IO.Path.GetFileName(FullZipFileName), Len) 'context.Request.Cookies("SessionID").Value="f57feea1-3033-4780-bae2-2660c9ae9a3a"
 372:              '
 373:              '(5) -  передали данные
 374:              context.Response.ContentType = "application/octet-stream" '"text/xml"
 375:              context.Response.BinaryWrite(My.Computer.FileSystem.ReadAllBytes(FullZipFileName))
 376:          Catch ex As Exception
 377:              TraceLog(context, "Send_Step3: (error): " & ex.Message)
 378:              GoTo EndSave
 379:          End Try
 380:   
 381:  EndSave:
 382:          context.Response.ContentType = "text/plain"
 383:          context.Response.Write(ResultCode & vbCrLf)
 384:          '
 385:          TraceLog(context, "Send_Step3 (end): " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",Cookies=" & GetAllCookies(context) & ", FullDirName=" & FullDirName & ", FullZipFileName=" & FullZipFileName & ", Len=" & Len.ToString)
 386:          ''
 387:      End Sub
 388:   
 389:      Sub Send_Step4(ByVal context As HttpContext)
 390:          '
 391:          SaveSendingResult(context, context.Request.Cookies("SessionID").Value, context.Request.QueryString("mode"))
 392:          '
 393:          TraceLog(context, "Send_Step4: " & "type=" & context.Request.QueryString("type") & ",mode=" & context.Request.QueryString("mode") & ",Cookies=" & GetAllCookies(context))
 394:      End Sub
 395:   
 396:  #End Region
 397:   
 398:  #Region "Файловые операции при передаче заказов в 1С"
 399:      'записали XML файла с заказами на диск 
 400:      Function WriteXMLToFile(ByVal context As HttpContext, ByVal SessionID As String, ByVal XMLstring As String, ByRef FullDirName As String) As Boolean
 401:          Try
 402:              Dim WorkingDir As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString)
 403:              FullDirName = IO.Path.Combine(WorkingDir, SessionID)
 404:              If My.Computer.FileSystem.DirectoryExists(FullDirName) Then
 405:                  My.Computer.FileSystem.DeleteDirectory(FullDirName, FileIO.DeleteDirectoryOption.DeleteAllContents)
 406:              End If
 407:              My.Computer.FileSystem.CreateDirectory(FullDirName)
 408:              Dim FullFileName As String = IO.Path.Combine(FullDirName, SessionID & ".xml")
 409:              My.Computer.FileSystem.WriteAllText(FullFileName, XMLstring, False, Text.Encoding.UTF8)
 410:              Return True
 411:          Catch ex As Exception
 412:              context.Response.ContentType = "text/plain"
 413:              context.Response.Write("failure" & vbCrLf)
 414:              '
 415:              TraceLog(context, "Send_Step3: (WriteXMLToFile) " & ex.Message)
 416:              Return False
 417:          End Try
 418:          Return True
 419:      End Function
 420:   
 421:      'зазиповали директорию
 422:      Function ZipDir(ByVal context As HttpContext, ByVal FullDirName As String, ByRef FullZipFileName As String) As Boolean
 423:          Dim ZipPath1 As String = System.Configuration.ConfigurationManager.AppSettings("ZipPath")               'C:\Program Files (x86)\7-Zip\7z.exe 
 424:          Dim ZipCacheDir1 As String = context.Server.MapPath(ConfigurationManager.AppSettings("Cache").ToString) 'C:\WWW\LocalUser\delmar\WWW\Temp1
 425:          Dim DirName As String = IO.Path.GetFileName(FullDirName)
 426:          FullZipFileName = IO.Path.Combine(ZipCacheDir1, DirName & ".zip")
 427:          Dim ZipParm1 As String = " a -tzip " & FullZipFileName & " " & FullDirName & "\*"
 428:          Dim Zip1 As New System.Diagnostics.Process
 429:          Zip1.StartInfo.RedirectStandardOutput = True
 430:          Zip1.StartInfo.UseShellExecute = False
 431:          Zip1.StartInfo.CreateNoWindow = True
 432:          Dim ZipLog1 As String = ""
 433:          Try
 434:              Zip1.StartInfo.FileName = ZipPath1
 435:              Zip1.StartInfo.Arguments = ZipParm1
 436:              Zip1.StartInfo.WorkingDirectory = ZipCacheDir1
 437:              Zip1.Start()
 438:              Zip1.WaitForExit()
 439:              ZipLog1 = Zip1.StandardOutput.ReadToEnd()
 440:              Zip1.Close()
 441:          Catch ex As Exception
 442:              TraceLog(context, "Send_Step3: (ZipError) " & ZipParm1 & " ; " & ex.Message & " ; " & ZipLog1)
 443:              Return False
 444:          End Try
 445:          '
 446:          If My.Computer.FileSystem.FileExists(FullZipFileName) Then
 447:              Return True
 448:          Else
 449:              TraceLog(context, "Send_Step3: (ZipError) " & "ZIP output not found" & ", ZipParm=" & ZipParm1 & ", ZipLog=" & ZipLog1)
 450:              Return False
 451:          End If
 452:      End Function
 453:  #End Region
 454:   
 455:  #Region "Доступ к Посгресу и ведение журнала с ошибками "
 456:      'Вся работа приложения ведется в одном коннекте к Постгресу
 457:      Dim PG1 As PG1.SQL_Postgres
 458:      Sub PG_Safe_Connection(ByVal context As HttpContext)
 459:          If context.Session("PG1") IsNot Nothing Then
 460:              PG1 = context.Session("PG1")
 461:          Else
 462:              PG1 = New PG1.SQL_Postgres
 463:              context.Session("PG1") = PG1
 464:          End If
 465:          PG1.CheckConnect()
 466:      End Sub
 467:   
 468:      'Журнал
 469:      Sub TraceLog(ByVal context As HttpContext, ByVal Data As String)
 470:          Try
 471:              If System.Configuration.ConfigurationManager.AppSettings("1С_FullLog") Then
 472:                  PG1.PG.ExecRDR("Insert into ""_Delmar"".""TerminalError""(crdate,txt) values (now(), '" & Data & "');")
 473:              End If
 474:          Catch ex As Exception
 475:              context.Response.ContentType = "text/plain"
 476:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 477:              Exit Sub
 478:          End Try
 479:      End Sub
 480:   
 481:   
 482:      Function GetAllCookies(ByVal context As HttpContext) As String
 483:          Dim Cook_Str As String = ""
 484:          For Each One As String In context.Request.Cookies.AllKeys
 485:              Cook_Str &= context.Request.Cookies(One).Name & "= " & context.Request.Cookies(One).Value & ";"
 486:          Next
 487:          Return Cook_Str
 488:      End Function
 489:   
 490:  #End Region
 491:   
 492:   
 493:  #Region "SQL-операции при приеме данных из 1С"
 494:   
 495:      'Достать из базы все принятые в последней сессии сегменты данных
 496:      Private Function GetAllDataSegment(ByVal context As HttpContext, ByVal SessionID As String) As Data.DataTable
 497:          Dim DT1 As New Data.DataTable
 498:          Try
 499:              Dim RDR1 As Npgsql.NpgsqlDataReader = PG1.PG.ExecRDR("select * from ""_Delmar"".""1C_Exchange"" where ""SessionID""='" & SessionID & "' order by i asc;")
 500:              If RDR1.HasRows Then
 501:                  DT1.Load(RDR1)
 502:              End If
 503:              RDR1.Close()
 504:              Return DT1
 505:          Catch ex As Exception
 506:              TraceLog(context, "1c_exchange (GetAllDataSegment): " & ex.Message)
 507:              context.Response.ContentType = "text/plain"
 508:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 509:          End Try
 510:      End Function
 511:   
 512:      'Достать из базы последний DataSegmentNum в этой сессии
 513:      Private Function GetLastDataSegmentNum(ByVal context As HttpContext, ByVal SessionID As String) As Integer
 514:          Dim LastDataSegmentNum As Integer = 0
 515:          Try
 516:              Dim RDR1 As Npgsql.NpgsqlDataReader = PG1.PG.ExecRDR("select * from ""_Delmar"".""1C_Exchange"" where ""SessionID""='" & SessionID & "' order by i desc limit 1;")
 517:              If RDR1.Read Then
 518:                  LastDataSegmentNum = RDR1("DataSegmentNum")
 519:              End If
 520:              RDR1.Close()
 521:              Return LastDataSegmentNum
 522:          Catch ex As Exception
 523:              TraceLog(context, "1c_exchange (GetLastDataSegmentNum): " & ex.Message)
 524:              context.Response.ContentType = "text/plain"
 525:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 526:          End Try
 527:      End Function
 528:   
 529:      'Сохраняем имя принятого сегмента данных
 530:      Sub SaveReceiveFileName(ByVal context As HttpContext, ByVal SessionID As String, ByVal DataSegmentNum As Integer, ByVal FileName As String, ByVal Length As Integer)
 531:          Try
 532:              PG1.PG.ExecRDR("INSERT INTO ""_Delmar"".""1C_Exchange"" (""CrDate"", ""SessionID"", ""DataSegmentNum"", ""FileName"", ""Length"") VALUES (now(),'" & SessionID & "', '" & DataSegmentNum & "', '" & FileName & "', '" & Length & "' );")
 533:          Catch ex As Exception
 534:              TraceLog(context, "1c_exchange (SaveReceiveFileName): " & ex.Message)
 535:              context.Response.ContentType = "text/plain"
 536:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 537:              Exit Sub
 538:          End Try
 539:      End Sub
 540:   
 541:      'отметились у себя в базе о результатах згрузки данных из 1С
 542:      Sub SaveXMLLoadCode(ByVal context As HttpContext, ByVal SessionID As String, ByVal ResultCode As String)
 543:          Try
 544:              PG1.PG.ExecRDR("UPDATE ""_Delmar"".""1C_Exchange"" SET ""Result"" = '" & ResultCode & "' where ""SessionID""='" & SessionID & "';")
 545:          Catch ex As Exception
 546:              TraceLog(context, "1c_exchange (SaveXMLLoadCode): " & ex.Message)
 547:              context.Response.ContentType = "text/plain"
 548:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 549:              Exit Sub
 550:          End Try
 551:      End Sub
 552:   
 553:  #End Region
 554:   
 555:   
 556:  #Region "SQL-операции при передаче заказов в 1С"
 557:   
 558:      Sub SaveSendingFileName(ByVal context As HttpContext, ByVal SessionID As String, ByVal DataSegmentNum As Integer, ByVal FileName As String, ByVal Length As Integer)
 559:          Try
 560:              PG1.PG.ExecRDR("INSERT INTO ""_Delmar"".""1C_Exchange"" (""CrDate"", ""SessionID"", ""DataSegmentNum"", ""FileName"", ""Length"") VALUES (now(),'" & SessionID & "', '" & DataSegmentNum & "', '" & FileName & "', '" & Length & "' );")
 561:          Catch ex As Exception
 562:              TraceLog(context, "1c_exchange (SaveSendingFileName): " & ex.Message)
 563:              context.Response.ContentType = "text/plain"
 564:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 565:              Exit Sub
 566:          End Try
 567:      End Sub
 568:   
 569:      Sub SaveSendingResult(ByVal context As HttpContext, ByVal SessionID As String, ByVal ResultCode As String)
 570:          Try
 571:              PG1.PG.ExecRDR("UPDATE ""_Delmar"".""1C_Exchange"" SET ""Result"" = '" & ResultCode & "' where ""SessionID""='" & SessionID & "';")
 572:          Catch ex As Exception
 573:              TraceLog(context, "1c_exchange (SaveSendingResult): " & ex.Message)
 574:              context.Response.ContentType = "text/plain"
 575:              context.Response.Write("failure" & vbCrLf & "BAD SQL COMMAND:" & ex.Message)
 576:              Exit Sub
 577:          End Try
 578:      End Sub
 579:   
 580:   
 581:  #End Region
 582:   
 583:      Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
 584:          Get
 585:              Return False
 586:          End Get
 587:      End Property
 588:   
 589:  End Class

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

Прежде всего это наличие конечно 7zip и необхоимых параметров в конфиге сайта:


        <add key="1С_FullLog" value="True" />
        <add key="Cache" value="Temp" />
        <add key="ZipPath" value="C:\Program Files (x86)\7-Zip\7z.exe" />

Особое внимание при разработке шлюза я уделил журналированию. Шлюз создает подробнейший журнал работы. Журналивание ведется в такую таблу.


   1:  CREATE SEQUENCE "_Delmar".debug_i_seq
   2:    INCREMENT 1
   3:    MINVALUE 1
   4:    MAXVALUE 9223372036854775807
   5:    START 1
   6:    CACHE 1;
   7:   
   8:  CREATE TABLE "_Delmar"."TerminalError"
   9:  (
  10:    i integer NOT NULL DEFAULT nextval('"_Delmar".debug_i_seq'::regclass),
  11:    crdate timestamp without time zone DEFAULT now(),
  12:    status integer,
  13:    "TerminalID" character varying(250),
  14:    txt character varying(4000),
  15:    CONSTRAINT iiiiiiiiii PRIMARY KEY (i)
  16:  )
  17:  WITH (
  18:    OIDS=FALSE
  19:  );

Вторая табла используется для сборки единого ZIP-файла из маленьких отдельных фрагментов, которые передает 1С. В ней также можно видеть общий итог сеанса связи с 1С в обоих направлениях:


   1:  CREATE SEQUENCE "_Delmar"."1C_i_seq"
   2:    INCREMENT 1
   3:    MINVALUE 1
   4:    MAXVALUE 9223372036854775807
   5:    START 27
   6:    CACHE 1;
   7:   
   8:  CREATE TABLE "_Delmar"."1C_Exchange"
   9:  (
  10:    i integer NOT NULL DEFAULT nextval('"_Delmar"."1C_i_seq"'::regclass),
  11:    "CrDate" date NOT NULL,
  12:    "DataSegmentNum" character varying(255) NOT NULL,
  13:    "FileName" character varying(255) NOT NULL,
  14:    "Length" integer,
  15:    "SessionID" character varying(255),
  16:    "Result" character varying(255),
  17:    CONSTRAINT "1C_pkey" PRIMARY KEY (i)
  18:  )
  19:  WITH (
  20:    OIDS=FALSE
  21:  );

Разумеется имя схемы безопастности PostgreSQL, которое стоит у меня "Delmar" - вы должны заменить на имя своей схемы безопасности, прием как в коде, так и в SQL. Это имя я привел просто для примера - чтобы код шлюза был полностью корректным.

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


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


   1:      Public Function GetTagText(ByVal XqueryPatch As String, ByVal Node As Xml.XmlNode) As String
   2:          If Node.SelectNodes(XqueryPatch).Count > 0 Then
   3:              If Node.SelectNodes(XqueryPatch & "/text()")(0) IsNot Nothing Then
   4:                  Return Node.SelectNodes(XqueryPatch & "/text()")(0).Value
   5:              Else
   6:                  Return ("")
   7:              End If
   8:          Else
   9:              Return ("")
  10:          End If
  11:      End Function

Потом идут довольно обьемные определения вот такого плана в которых из XML выковыриваются важные параметры 1С, управляющие алгоритмом парсинга XML. Затем следует выполнить основной алгоритм XML-парсера (тоже довольно обьемный) предствляющий собой вложенную систему циклов примерно такого плана. Основной алгоритм вызывает операции доступа к базе (это тоже код приличного обьема). Конечно, весь этот весьма обьемный код публиковать нет никакого смысла, ибо это с одной стороны коммерческая тайна проекта, с другой стороны - это монотонный и ничем не поучительный код.

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



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/1C-Gateway/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>