Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdemyanov/0e1a8717a0f6829f7b63fd221993ee6e to your computer and use it in GitHub Desktop.
Save mdemyanov/0e1a8717a0f6829f7b63fd221993ee6e to your computer and use it in GitHub Desktop.
Как передавать файлы между серверами Naumen SD и ITSM 365

Как передавать файлы между серверами Naumen SMP и ITSM 365

Почему стандартный метод подойдет не всем

Текйщий стандартный метод add-file для передачи данных в Naumen SD 4 и ITSM 365 позволяет загрузить файл на удаленный сервер, но не возвращает уникальный идентификатор созданного объекта. Предложенный модуль является рабочим обходным решением, который позволяет обойти системное ограничение оптимальным способом.

Описание обходного решения

На обоих серверах должен быть установлен скриптовый модуль из файла ниже с кодом restFileTransfer.

Передача файлов ссылками

Процесс передачи файлов при использовании этого модуля будет выглядеть следующим образом:

  1. Вызываем метод sendByUrl, в который передаем файл, адрес и ключ доступа удаленного сервера, идентификатор объекта, к оторому нужно добавить файл и атрибут (опционально), если файл нужно привязать к объекту в рамках атрибута.
  2. В процессе исполнения метод отправляет на удаленный сервер запрос, в котором содержится информация о файле и объекту, к которому его нужно добавить.
  3. Метод attachByUrl добавляет файл к целевому объекту, при этом контент файла получает по ссылке. В случае успеха, метод возвращает ассоциативный массив, где в ключе result лежит идентификатор созданного файла. В противном случае, метод возвращает массив с ключем error, в котором находится описание ошибки.

Передача файлов запросом

Процесс передачи файлов при использовании этого модуля будет выглядеть следующим образом:

  1. Вызываем метод attachByRequest, передавая в него объект "Файл", адрес сервера, ключ доступа, UUID объекта, к которому нужно добавить файл и (опционально) код атрибута, в который файл нужно записать.
  2. Метод attachByRequest добавляет файл к целевому объекту, получая его контент из HTTP запроса.
/*! UTF8 */
//Автор: mdemyanov
//Дата создания: 31.05.2020
//Код: restFileTransfer
//Назначение:
/**
* Модуль для отправки и загрузки файлов ссылками
*/
//Версия: 4.8.*
//Категория:
//Параметры------------------------------------------------------
package ru.itsm365.restFileTransfer
import org.apache.http.client.methods.CloseableHttpResponse
import org.springframework.web.multipart.*
import java.nio.charset.Charset
import org.apache.http.client.methods.HttpPost
import org.apache.http.client.utils.URIBuilder
import org.apache.http.entity.mime.content.ByteArrayBody
import org.apache.http.entity.mime.MultipartEntity
import org.apache.http.entity.mime.HttpMultipartMode
import org.apache.http.impl.client.BasicResponseHandler
import com.google.gson.Gson
import groovyx.net.http.HTTPBuilder
import org.apache.http.client.config.RequestConfig
import org.apache.http.impl.client.HttpClients
import javax.servlet.http.HttpServletRequest
import static groovyx.net.http.ContentType.JSON
import static groovyx.net.http.Method.GET
import static groovyx.net.http.Method.POST
class Constants {
static final Integer DEFAULT_TIMEOUT_IN_MILLISECONDS = 60000
static final Gson GSON = new Gson()
}
class Client {
HTTPBuilder http
HashMap<String, Object> defaultQuery = [:]
Gson gson
Client(String address, String accessKey, Integer timeout) {
http = new HTTPBuilder(address)
defaultQuery.accessKey = accessKey
http.client = HttpClients.custom().setDefaultRequestConfig(
RequestConfig.custom()
.setConnectionRequestTimeout(timeout)
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.build()
).build()
gson = new Gson()
}
def post(String path, def queryData, def data) {
def serverResponse
queryData.putAll(defaultQuery)
http.request(POST, JSON) { req ->
uri.path = path
uri.query = queryData
headers.Accept = JSON
body = gson.toJson(data)
response.success = { resp, reader ->
serverResponse = reader
}
response.failure = { resp ->
throw new Exception("Ошибка переноса файлов ${uri.toString()}: ${resp.status}, ${resp.entity.content.text}")
}
}
return serverResponse
}
def get(def path, Map queryData) {
def serverResponse
queryData.putAll(defaultQuery)
http.request(GET) { req ->
uri.path = path
uri.query = queryData
response.success = { resp, reader ->
serverResponse = reader
}
response.failure = { resp ->
throw new Exception("Ошибка переноса файлов ${uri.toString()}: ${resp.status}, ${resp.entity.content.text}")
}
}
return serverResponse
}
}
class Transfer {
String objId, attrCode, fileName, mimeType, description, fileUrl
List toAttach() {
List data = []
if (attrCode) {
data.add(attrCode)
}
data.addAll([
fileName,
mimeType,
description,
new URL(fileUrl).bytes
])
return data
}
}
//Функции--------------------------------------------------------
String attachByUrl(def requestContent) {
Transfer transfer = Constants.GSON.fromJson(Constants.GSON.toJson(requestContent), Transfer.class)
Map<String, Object> resp = [:]
try {
api.tx.call {
resp.result = utils.attachFile(
utils.get(transfer.objId),
*transfer.toAttach()
).UUID
}
} catch (Exception e) {
resp.error = e.message
}
return new Gson().toJson(resp)
}
def sendByUrl(def file, String address, String accessKey, String remoteObjId, String attrCode = null) {
Client client = new Client(address, accessKey, Constants.DEFAULT_TIMEOUT_IN_MILLISECONDS)
String shareAlias = file.shareAlias
if (shareAlias == null) {
shareAlias = 'get' + UUID.randomUUID().toString().replaceAll('-', '')
utils.edit(file, [shareAlias: shareAlias], true)
}
return client.post(
'/sd/services/rest/exec-post',
[
func : 'modules.restFileTransfer.attachByUrl',
params: 'requestContent'
],
new Transfer(
objId: remoteObjId,
fileName: file.title,
mimeType: file.mimeType,
description: file.description,
fileUrl: "${api.rest.getBaseUrl()}/share/${file.shareAlias}"
)
)
}
List sendByRequest(def file, String address, String accessKey, String remoteObjId, String attrCode = null) {
HttpPost post = new HttpPost(new URIBuilder(address)
.setPath('/sd/services/rest/exec-post')
.setParameter('accessKey', accessKey)
.setParameter('func', 'modules.restFileTransfer.attachByRequest')
.setParameter('params', "request,'$remoteObjId'${attrCode ? ",'$attrCode'" : ''}")
.setParameter('raw', 'true')
.build())
def entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"))
entity.addPart(file.title, new ByteArrayBody(utils.readFileContent(file), file.title))
post.setEntity(entity)
def httpClient = HttpClients.createDefault()
def response = httpClient.execute(post)
String responseString = new BasicResponseHandler().handleResponse(response)
return new Gson().fromJson(responseString, List.class)
}
String attachByRequest(HttpServletRequest request, String objectId) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request
def obj = utils.get(objectId)
return new Gson().toJson(multipartRequest.getFileMap().collect { String name, MultipartFile file ->
return utils.attachFile(
obj,
file.getOriginalFilename(),
file.getContentType(),
null,
file.getBytes()
).UUID
})
}
String attachByRequest(HttpServletRequest request, String objectId, String attrCode) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request
def obj = utils.get(objectId)
return new Gson().toJson(multipartRequest.getFileMap().collect { String name, MultipartFile file ->
return utils.attachFile(
obj,
attrCode,
file.getOriginalFilename(),
file.getContentType(),
null,
file.getBytes()
).UUID
})
}
//Основной блок -------------------------------------------------
@sdmit6
Copy link

sdmit6 commented Jun 15, 2020

Возможно я что-то делаю не так, но система не видит класс Constants. Решилось прямым прописыванием таймаута и объявления GSON в используемом методе.

@mdemyanov
Copy link
Author

Подскажите, что значит не видит?

Лучше всего, если опишите подробно, как и откуда какие методы вызываете, а также приложите код.

Например:

  1. Вызываете метод из Действия по событию
  2. При этом код выглядит так:
modules.restFileTransfer.sendByRequest(file, address, accessKey, remoteObjId)

@sdmit6
Copy link

sdmit6 commented Jun 15, 2020

Выполняем код в консоли

class Constants {
    Integer DEFAULT_TIMEOUT_IN_MILLISECONDS = 60000
}
return  Constants.DEFAULT_TIMEOUT_IN_MILLISECONDS

Получаем ошибку

No such property: DEFAULT_TIMEOUT_IN_MILLISECONDS for class: Constants
Possible solutions: DEFAULT_TIMEOUT_IN_MILLISECONDS

При вызове модуля возвращалась та же ошибка.
Изменения в коде модуля(используется метод sendByUrl):

130 строчка: Client client = new Client(address, accessKey, 60000)
131 строчкой добавлено: Gson GSON = new Gson()

@mdemyanov
Copy link
Author

Здесь была моя вина - значения должны быть статичными. Поправил в коде:

class Constants {
    static final Integer DEFAULT_TIMEOUT_IN_MILLISECONDS = 60000
    static final Gson GSON = new Gson()
}

@mdemyanov
Copy link
Author

Спасибо, что заметили ошибку и сообщили! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment