Nesse vídeo demonstraremos a utilização do comando WsRestFul, que serve para criar um WebService do tipo REST.
Abaixo o código fonte desenvolvido para o exemplo em vídeo acima:
//Bibliotecas
#Include "Totvs.ch"
#Include "RESTFul.ch"
#Include "TopConn.ch"
/*/{Protheus.doc} WSRESTFUL zWSProdutos
Exemplo de Webservice usando REST
@author Atilio
@since 07/04/2022
@version 1.0
@see https://tdn.totvs.com/display/public/framework/WSRESTFUL
@obs
**** Apoie nosso projeto, se inscreva em https://www.youtube.com/TerminalDeInformacao ****
/*/
WSRESTFUL zWSProdutos DESCRIPTION 'WebService Cadastro de Produtos'
//Atributos
WSDATA id AS STRING
WSDATA updated_at AS STRING
WSDATA limit AS INTEGER
WSDATA page AS INTEGER
//Métodos
WSMETHOD GET ID DESCRIPTION 'Retorna o registro pesquisado' WSSYNTAX '/zWSProdutos/get_id?{id}' PATH 'get_id' PRODUCES APPLICATION_JSON
WSMETHOD GET ALL DESCRIPTION 'Retorna todos os registros' WSSYNTAX '/zWSProdutos/get_all?{updated_at, limit, page}' PATH 'get_all' PRODUCES APPLICATION_JSON
WSMETHOD POST NEW DESCRIPTION 'Inclusão de registro' WSSYNTAX '/zWSProdutos/new' PATH 'new' PRODUCES APPLICATION_JSON
END WSRESTFUL
/*/{Protheus.doc} WSMETHOD GET ID
Busca registro via ID
@author Atilio
@since 07/04/2022
@version 1.0
@param id, Caractere, String que será pesquisada através do MsSeek
@obs Codigo gerado automaticamente pelo Autumn Code Maker
@see http://autumncodemaker.com
/*/
WSMETHOD GET ID WSRECEIVE id WSSERVICE zWSProdutos
Local lRet := .T.
Local jResponse := JsonObject():New()
Local cAliasWS := 'SB1'
//Se o id estiver vazio
If Empty(::id)
//SetRestFault(500, 'Falha ao consultar o registro') //caso queira usar esse comando, você não poderá usar outros retornos, como os abaixo
Self:setStatus(500)
jResponse['errorId'] := 'ID001'
jResponse['error'] := 'ID vazio'
jResponse['solution'] := 'Informe o ID'
Else
DbSelectArea(cAliasWS)
(cAliasWS)->(DbSetOrder(1))
//Se não encontrar o registro
If ! (cAliasWS)->(MsSeek(FWxFilial(cAliasWS) + ::id))
//SetRestFault(500, 'Falha ao consultar ID') //caso queira usar esse comando, você não poderá usar outros retornos, como os abaixo
Self:setStatus(500)
jResponse['errorId'] := 'ID002'
jResponse['error'] := 'ID não encontrado'
jResponse['solution'] := 'Código ID não encontrado na tabela ' + cAliasWS
Else
//Define o retorno
jResponse['cod'] := (cAliasWS)->B1_COD
jResponse['desc'] := (cAliasWS)->B1_DESC
jResponse['tipo'] := (cAliasWS)->B1_TIPO
jResponse['um'] := (cAliasWS)->B1_UM
jResponse['locpad'] := (cAliasWS)->B1_LOCPAD
jResponse['grupo'] := (cAliasWS)->B1_GRUPO
EndIf
EndIf
//Define o retorno
Self:SetContentType('application/json')
Self:SetResponse(jResponse:toJSON())
Return lRet
/*/{Protheus.doc} WSMETHOD GET ALL
Busca todos os registros através de paginação
@author Atilio
@since 07/04/2022
@version 1.0
@param updated_at, Caractere, Data de alteração no formato string 'YYYY-MM-DD' (somente se tiver o campo USERLGA / USERGA na tabela)
@param limit, Numérico, Limite de registros que irá vir (por exemplo trazer apenas 100 registros)
@param page, Numérico, Número da página que irá buscar (se existir 1000 registros dividido por 100 terá 10 páginas de pesquisa)
@obs Codigo gerado automaticamente pelo Autumn Code Maker
Poderia ser usado o FWAdapterBaseV2(), mas em algumas versões antigas não existe essa funcionalidade
então a paginação foi feita manualmente
@see http://autumncodemaker.com
/*/
WSMETHOD GET ALL WSRECEIVE updated_at, limit, page WSSERVICE zWSProdutos
Local lRet := .T.
Local jResponse := JsonObject():New()
Local cQueryTab := ''
Local nTamanho := 10
Local nTotal := 0
Local nPags := 0
Local nPagina := 0
Local nAtual := 0
Local oRegistro
Local cAliasWS := 'SB1'
//Efetua a busca dos registros
cQueryTab := " SELECT " + CRLF
cQueryTab += " TAB.R_E_C_N_O_ AS TABREC " + CRLF
cQueryTab += " FROM " + CRLF
cQueryTab += " " + RetSQLName(cAliasWS) + " TAB " + CRLF
cQueryTab += " WHERE " + CRLF
cQueryTab += " TAB.D_E_L_E_T_ = '' " + CRLF
If ! Empty(::updated_at)
cQueryTab += " AND ((CASE WHEN SUBSTRING(B1_USERLGA, 03, 1) != ' ' THEN " + CRLF
cQueryTab += " CONVERT(VARCHAR,DATEADD(DAY,((ASCII(SUBSTRING(B1_USERLGA,12,1)) - 50) * 100 + (ASCII(SUBSTRING(B1_USERLGA,16,1)) - 50)),'19960101'),112) " + CRLF
cQueryTab += " ELSE '' " + CRLF
cQueryTab += " END) >= '" + StrTran(::updated_at, '-', '') + "') " + CRLF
EndIf
cQueryTab += " ORDER BY " + CRLF
cQueryTab += " TABREC " + CRLF
TCQuery cQueryTab New Alias 'QRY_TAB'
//Se não encontrar registros
If QRY_TAB->(EoF())
//SetRestFault(500, 'Falha ao consultar registros') //caso queira usar esse comando, você não poderá usar outros retornos, como os abaixo
Self:setStatus(500)
jResponse['errorId'] := 'ALL003'
jResponse['error'] := 'Registro(s) não encontrado(s)'
jResponse['solution'] := 'A consulta de registros não retornou nenhuma informação'
Else
jResponse['objects'] := {}
//Conta o total de registros
Count To nTotal
QRY_TAB->(DbGoTop())
//O tamanho do retorno, será o limit, se ele estiver definido
If ! Empty(::limit)
nTamanho := ::limit
EndIf
//Pegando total de páginas
nPags := NoRound(nTotal / nTamanho, 0)
nPags += Iif(nTotal % nTamanho != 0, 1, 0)
//Se vier página
If ! Empty(::page)
nPagina := ::page
EndIf
//Se a página vier zerada ou negativa ou for maior que o máximo, será 1
If nPagina <= 0 .Or. nPagina > nPags
nPagina := 1
EndIf
//Se a página for diferente de 1, pula os registros
If nPagina != 1
QRY_TAB->(DbSkip((nPagina-1) * nTamanho))
EndIf
//Adiciona os dados para a meta
jJsonMeta := JsonObject():New()
jJsonMeta['total'] := nTotal
jJsonMeta['current_page'] := nPagina
jJsonMeta['total_page'] := nPags
jJsonMeta['total_items'] := nTamanho
jResponse['meta'] := jJsonMeta
//Percorre os registros
While ! QRY_TAB->(EoF())
nAtual++
//Se ultrapassar o limite, encerra o laço
If nAtual > nTamanho
Exit
EndIf
//Posiciona o registro e adiciona no retorno
DbSelectArea(cAliasWS)
(cAliasWS)->(DbGoTo(QRY_TAB->TABREC))
oRegistro := JsonObject():New()
oRegistro['cod'] := (cAliasWS)->B1_COD
oRegistro['desc'] := (cAliasWS)->B1_DESC
oRegistro['tipo'] := (cAliasWS)->B1_TIPO
oRegistro['um'] := (cAliasWS)->B1_UM
oRegistro['locpad'] := (cAliasWS)->B1_LOCPAD
oRegistro['grupo'] := (cAliasWS)->B1_GRUPO
aAdd(jResponse['objects'], oRegistro)
QRY_TAB->(DbSkip())
EndDo
EndIf
QRY_TAB->(DbCloseArea())
//Define o retorno
Self:SetContentType('application/json')
Self:SetResponse(jResponse:toJSON())
Return lRet
/*/{Protheus.doc} WSMETHOD POST NEW
Cria um novo registro na tabela
@author Atilio
@since 07/04/2022
@version 1.0
@obs Codigo gerado automaticamente pelo Autumn Code Maker
Abaixo um exemplo do JSON que deverá vir no body
* 1: Para campos do tipo Numérico, informe o valor sem usar as aspas
* 2: Para campos do tipo Data, informe uma string no padrão 'YYYY-MM-DD'
{
"cod": "conteudo",
"desc": "conteudo",
"tipo": "conteudo",
"um": "conteudo",
"locpad": "conteudo",
"grupo": "conteudo"
}
@see http://autumncodemaker.com
/*/
WSMETHOD POST NEW WSRECEIVE WSSERVICE zWSProdutos
Local lRet := .T.
Local aDados := {}
Local jJson := Nil
Local cJson := Self:GetContent()
Local cError := ''
Local nLinha := 0
Local cDirLog := '\x_logs\'
Local cArqLog := ''
Local cErrorLog := ''
Local aLogAuto := {}
Local nCampo := 0
Local jResponse := JsonObject():New()
Local cAliasWS := 'SB1'
Private lMsErroAuto := .F.
Private lMsHelpAuto := .T.
Private lAutoErrNoFile := .T.
//Se não existir a pasta de logs, cria
IF ! ExistDir(cDirLog)
MakeDir(cDirLog)
EndIF
//Definindo o conteúdo como JSON, e pegando o content e dando um parse para ver se a estrutura está ok
Self:SetContentType('application/json')
jJson := JsonObject():New()
cError := jJson:FromJson(cJson)
//Se tiver algum erro no Parse, encerra a execução
IF ! Empty(cError)
//SetRestFault(500, 'Falha ao obter JSON') //caso queira usar esse comando, você não poderá usar outros retornos, como os abaixo
Self:setStatus(500)
jResponse['errorId'] := 'NEW004'
jResponse['error'] := 'Parse do JSON'
jResponse['solution'] := 'Erro ao fazer o Parse do JSON'
Else
DbSelectArea(cAliasWS)
//Adiciona os dados do ExecAuto
aAdd(aDados, {'B1_COD', jJson:GetJsonObject('cod'), Nil})
aAdd(aDados, {'B1_DESC', jJson:GetJsonObject('desc'), Nil})
aAdd(aDados, {'B1_TIPO', jJson:GetJsonObject('tipo'), Nil})
aAdd(aDados, {'B1_UM', jJson:GetJsonObject('um'), Nil})
aAdd(aDados, {'B1_LOCPAD', jJson:GetJsonObject('locpad'), Nil})
aAdd(aDados, {'B1_GRUPO', jJson:GetJsonObject('grupo'), Nil})
//Percorre os dados do execauto
For nCampo := 1 To Len(aDados)
//Se o campo for data, retira os hifens e faz a conversão
If GetSX3Cache(aDados[nCampo][1], 'X3_TIPO') == 'D'
aDados[nCampo][2] := StrTran(aDados[nCampo][2], '-', '')
aDados[nCampo][2] := sToD(aDados[nCampo][2])
EndIf
Next
//Chama a inclusão automática
MsExecAuto({|x, y| MATA010(x, y)}, aDados, 3)
//Se houve erro, gera um arquivo de log dentro do diretório da protheus data
If lMsErroAuto
//Monta o texto do Error Log que será salvo
cErrorLog := ''
aLogAuto := GetAutoGrLog()
For nLinha := 1 To Len(aLogAuto)
cErrorLog += aLogAuto[nLinha] + CRLF
Next nLinha
//Grava o arquivo de log
cArqLog := 'zWSProdutos_New_' + dToS(Date()) + '_' + StrTran(Time(), ':', '-') + '.log'
MemoWrite(cDirLog + cArqLog, cErrorLog)
//Define o retorno para o WebService
//SetRestFault(500, cErrorLog) //caso queira usar esse comando, você não poderá usar outros retornos, como os abaixo
Self:setStatus(500)
jResponse['errorId'] := 'NEW005'
jResponse['error'] := 'Erro na inclusão do registro'
jResponse['solution'] := 'Nao foi possivel incluir o registro, foi gerado um arquivo de log em ' + cDirLog + cArqLog + ' '
lRet := .F.
//Senão, define o retorno
Else
jResponse['note'] := 'Registro incluido com sucesso'
EndIf
EndIf
//Define o retorno
Self:SetResponse(jResponse:toJSON())
Return lRet
Bom pessoal, por hoje é só.
Abraços e até a próxima.
Oi Daniel, tudo bem?
No meu caso utilizo o T-Cloud e a porta interna do REST é a ‘:2076’, entrando com o link ‘:2076/rest’ lista tudo normalmente, consigo ver os detalhes também. Quando chega nessa parte do Postman, qualquer API que tento acessar gera um erro 500.
Nesse ambiente de desenvolvimento a parte de segurança fica sem, porém já tentei também com usuário e senha e aparece o mesmo erro 500. O que poderia ser nesse caso, por favor?
Uma observação que notei agora, as APIs que não fazem consulta ao banco de dados elas funcionam. O problema está relacionado as que fazem consulta apenas.
Bom dia Caique, tudo joia graças a Deus e você?
Ah então pode ser mesmo a PrepareIn, veja como que ela esta no appserver.ini, se está apontando para uma filial em específico.
Tipo, até daria para você usar um RPCSetEnv / Prepare Environment direto na sua API, mas não é o recomendado, mas se quiser fazer testes só pra ver se é algum problema na PrepareIn mesmo.
Tenha uma ótima e abençoada sexta feira.
Um grande abraço.
Bom dia Caique, tudo joia graças a Deus e você?
O erro 500, ele pode ser um Internal Server Error, então tem duas coisas que você pode investigar.
A primeira é como que está o PrepareIn no appserver.ini.
A segunda é olhar no console.log onde tem “Thread Error” para encontrar a mensagem mais específica do que está acontecendo. Nesse link tem um exemplo – https://terminaldeinformacao.com/2024/01/19/como-descobrir-o-que-causa-o-internal-server-error-em-um-webservice/
Tenha uma ótima e abençoada sexta feira.
Um grande abraço.
Boa tarde.
Alguém teve problema para compilar fontes iguais a esse com a versão 2.0.11 do TDS?
Bom dia Cleiton, tudo joia?
Aqui usando a versão mais antiga da extensão TDS no VSCode está funcionando.
Ainda não fiz testes com a versão 2.0.11, mas se for o caso tente fazer um downgrade provisoriamente, conforme demonstrado nesse link: https://terminaldeinformacao.com/2020/10/01/como-voltar-a-versao-de-uma-extensao-no-vscode/
Tenha uma ótima e abençoada terça feira.
Um grande abraço.