显示导航

Grails & SOAP

了解如何使用 Grails 应用程序使用 SOAP 端点

作者:Sergio del Amo

Grails 版本 3.3.1

1 Grails 帮助

Grails 培训 - 由创建并积极维护 Grails 框架的人员开发和交付!

2 开始使用

在本指南中,您将使用 Grails 应用程序来使用 SOAP Web 服务。

2.1 您需要确切内容

要完成本指南,您需要以下内容

  • 时间

  • 一个合适的文本编辑器或 IDE

  • 正确配置了 JAVA_HOME 的 JDK 1.7 或更高版本

2.2 如何完成指南

要开始,请执行以下操作

Grails 指南仓库包含两个文件夹

  • initial 初始项目。通常是一个简单的 Grails 应用,另外还有一些代码,以便您抢得先机。

  • complete 一个已完成的示例。这是按照指南中的步骤操作,并对 initial 文件夹应用这些更改的结果。

要完成指南,请转到 initial 文件夹

  • cdgrails-guides/grails-soap/initial

并按照下一部分中的说明操作。

如果您 cdgrails-guides/grails-soap/complete,则可以直接转到已完成示例

3 编写应用程序

VIES (增值税信息交换系统) 是一种电子方式,用于验证为欧盟内跨境商品或服务交易登记的经济运营商的增值税识别号。

例如,如果您在欧盟中创建一个电子商务 Web 应用程序,那么您需要检查购买者的增值税号码的有效性,以便创建一个正确的发票。

要实现验证检查的自动化,欧盟不提供 REST 终结点,而是提供 SOAP 服务。其 WSDL 文件可在此处获取。

SOAP(最初是简单对象访问协议)是用于在计算机网络中的 Web 服务的实现中交换结构化信息的协议规范。其目的是诱导可扩展性、中立性和独立性

3.1 SOAP 库

为了消费 SOAP Web 服務,我們使用 groovy-wslite。Groovy 的一個庫,提供無繁瑣 SOAP 和 REST webservice 客户端。

添加 wslite 依赖项

build.gradle
compile 'com.github.groovy-wslite:groovy-wslite:1.1.2'

我们把 SOAP 代码封装在 Grails 服务中

grails-app/services/demo/VatService.groovy
package demo

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import wslite.soap.SOAPClient
import wslite.soap.SOAPResponse

@CompileStatic
class VatService {
    String url = 'http://ec.europa.eu/taxation_customs/vies/services/checkVatService'
    SOAPClient client = new SOAPClient("${url}.wsdl")

    @CompileDynamic
    Boolean validateVat(String memberStateCode, String vatNumberCode) {
        SOAPResponse response = client.send(SOAPAction: url) {
            body('xmlns': 'urn:ec.europa.eu:taxud:vies:services:checkVat:types') {
                checkVat {
                    countryCode(memberStateCode)
                    vatNumber(vatNumberCode)
                }
            }
        }
        response.checkVatResponse.valid.text() == 'true'
    }
}

我们使用 Grails 测试框架 来测试它

src/test/groovy/demo/VatServiceSpec.groovy
package demo

import grails.testing.services.ServiceUnitTest
import spock.lang.Specification
import spock.lang.Unroll

class VatServiceSpec extends Specification implements ServiceUnitTest<VatService> {

    @Unroll
    def "#memberState : #vatNumber #unrollDescription"(String memberState, String vatNumber, Boolean expected, String unrollDescription) {

        expect:
        expected == service.validateVat(memberState, vatNumber)

        where:
        memberState | vatNumber   || expected
        'es'        | 'B99286353' || true
        'es'        | 'B19280031' || true
        'es'        | 'XXXXXXXXX' || false

        unrollDescription = expected ? 'is valid' : ' is not valid'
    }
}

3.2 国家/地区

最好在配置文件中封装我们希望支持的国家/地区。

这样,如果某个国家/地区退出欧盟(例如,英国),那么在我们的应用程序中很容易进行更改。

application.yml 中添加国家/地区列表

grails-app/conf/application.yml
eu:
    countries:
        -
            code: AT
            name: Austria
        -
            code: BE
            name: Belgium
        -
            code: BG
            name: Bulgaria
        -
            code: CY
            name: Cyprus
        -
            code: CZ
            name: Czech Republic
        -
            code: DE
            name: Germany
        -
            code: DK
            name: Denmark
        -
            code: EE
            name: Estonia
        -
            code: EL
            name: Greece
        -
            code: ES
            name: Spain
        -
            code: FI
            name: Finland
        -
            code: FR
            name: France
        -
            code: GB
            name: United Kingdom
        -
            code: HR
            name: Croatia
        -
            code: HU
            name: Hungary
        -
            code: IE
            name: Ireland
        -
            code: IT
            name: Italy
        -
            code: LT
            name: Lithuania
        -
            code: LU
            name: Luxembourg
        -
            code: LV
            name: Latvia
        -
            code: MT
            name: Malta
        -
            code: NL
            name: The Netherlands
        -
            code: PL
            name: Poland
        -
            code: PT
            name: Portugal
        -
            code: RO
            name: Romania
        -
            code: SE
            name: Sweden
        -
            code: SI
            name: Slovenia
        -
            code: SK
            name: Slovakia

创建一个 POGO

src/main/groovy/demo/Country.groovy
package demo

import groovy.transform.CompileStatic

@CompileStatic
class Country {
    String code
    String name
}

读取一个 Grails 服务中的国家/地区

grails-app/services/demo/CountryService.groovy
package demo

import grails.config.Config
import grails.core.support.GrailsConfigurationAware
import groovy.transform.CompileStatic

@CompileStatic
class CountryService implements GrailsConfigurationAware {

    List<Country> countries = [] as List<Country>

    @Override
    void setConfiguration(Config co) {
        List<Map> l = co.getProperty('eu.countries', List)
        for ( Map m : l ) {
            countries << new Country(name: m.get('name') as String, code: m.get('code') as String)
        }
    }

    List<Country> findAll() {
        countries
    }


}

3.3 控制器和视图

该应用程序允许用户提交表单,并检查增值税号码对特定州是否有效。

我们将请求封装在一个命令对象中

grails-app/controllers/demo/VatCommand.groovy
package demo

import grails.validation.Validateable

class VatCommand implements Validateable {

    String code
    String vatNumber

    static constraints = {
        code nullable: false
        vatNumber nullable: false
    }
}

创建一个 VatController 来处理请求,与之前引入的服务进行协作,并通过 flash 消息或错误生成响应。

grails-app/controllers/demo/VatController.groovy
package demo

import groovy.transform.CompileStatic
import org.springframework.context.MessageSource

@CompileStatic
class VatController {

    static allowedMethods = [index: 'GET', validate: 'GET']

    VatService vatService

    CountryService countryService

    MessageSource messageSource

    def index() {
        [countries: countryService.findAll()]
    }

    def validate(VatCommand cmd) {

        if ( cmd.hasErrors() ) {
            render view: 'index', model: [cmd: cmd, countries: countryService.findAll()]
            return
        }
        boolean isValid = vatService.validateVat(cmd.code, cmd.vatNumber)
        if ( isValid ) {
            flash.message = messageSource.getMessage('vat.valid',
                    [cmd.code, cmd.vatNumber] as Object[],
                    "${cmd.code} : ${cmd.vatNumber} is valid",
                    request.locale)

        } else {
            flash.error = messageSource.getMessage('vat.valid',
                    [cmd.code, cmd.vatNumber] as Object[],
                    "${cmd.code} : ${cmd.vatNumber} is NOT valid",
                    request.locale)
        }
        render view: 'index', model: [cmd: cmd, countries: countryService.findAll()]


    }
}

创建一个 GSP 来呈现表单,并呈现 flash 消息或错误。

grails-app/views/vat/index.gsp
<html>
<head>
    <title>VAT Validator</title>
    <meta name="layout" content="main" />
    <style type="text/css">
        form ol li { list-style-type: none; }
    </style>
</head>
<body>
<div id="content" role="main">
    <section class="row colset-2-its">
        <g:if test="${flash.message}">
            <p class="message">${flash.message}</p>
        </g:if>
        <g:if test="${flash.error}">
            <p class="errors">${flash.error}</p>
        </g:if>
        <g:if test="${cmd}">
            <g:hasErrors bean="${cmd}">
                <div class="errors">
                    <g:eachError><p><g:message error="${it}"/></p></g:eachError>
                </div>
            </g:hasErrors>
        </g:if>
        <g:form controller="vat" method="GET">
            <ol>
                <li>
                    <label for="code"><g:message code="vat.country" default="Country"/></label>
                    <g:select id="code" name='code' value="${cmd?.code}"
                              noSelection="${['null':'Select One...']}"
                              from='${countries}'
                              optionKey="code" optionValue="name"></g:select>
                </li>
                <li>
                    <label for="code"><g:message code="vat.vatNumber" default="VAT Number"/></label>
                    <g:textField name="vatNumber" id="vatNumber" vatNumber="${cmd?.vatNumber}"/>
                </li>
                <li>
                    <g:actionSubmit id="submit" value="${message(code:'vat.check', default: 'Check')}" action="validate"/>
                </li>
            </ol>
        </g:form>
    </section>
</div>
</body>
</html>

3.4 功能测试

添加多个 Geb 依赖项。阅读我们的指南 使用多个浏览器运行 Grails Geb 功能测试 了解更多信息。

build.gradle
testCompile "org.grails.plugins:geb"
compile "org.seleniumhq.selenium:selenium-firefox-driver:2.53.1"
compile "org.seleniumhq.selenium:selenium-support:2.53.1"
compile "net.sourceforge.htmlunit:htmlunit:2.18"
compile "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"

确保您可以将系统属性传递给 Gradle 任务 integrationTest。您可以通过系统属性 geb.env 提供 geb 环境。

build.gradle
integrationTest {
    systemProperties System.properties
}

为每个 Geb 环境配置不同的浏览器驱动程序。

src/integration-test/groovy/GebConfig.groovy
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.htmlunit.HtmlUnitDriver

environments {

    htmlUnit {
        driver = { new HtmlUnitDriver() }
    }

    firefox {
        driver = { new FirefoxDriver() }
    }
}

创建一个 Geb 页面来封装表单标记。使用 Geb 页面使我们的测试更容易维护。

src/integration-test/groovy/demo/VatFormPage.groovy
package demo

import geb.Page

class VatFormPage extends Page {

    static url = '/vat/index'

    static content = {
        vatNumberInput { $('input#vatNumber', 0) }
        codeSelect { $('select#code', 0) }
        submitButton { $('input#submit', 0) }
    }

    void validate(String code, String vatNumber ) {
        vatNumberInput = vatNumber
        codeSelect = code.toUpperCase()
        submitButton.click()
    }
}

创建一个功能测试,使用多个值提交表单

src/integration-test/groovy/demo/VatControllerSpec.groovy
package demo

import geb.spock.GebSpec
import grails.testing.mixin.integration.Integration
import spock.lang.Unroll

@Integration
class VatControllerSpec extends GebSpec {

    @Unroll
    def "#memberState : #vatNumber validation #unrollDescription when you submit the form"(String memberState, String vatNumber, Boolean expected, String unrollDescription) {

        when:
        VatFormPage page = browser.to VatFormPage
        page.validate(memberState, vatNumber)

        then:
        if ( expected) {
            browser.driver.pageSource.contains("${memberState} : ${vatNumber} is valid")
        } else {
            browser.driver.pageSource.contains("${memberState} : ${vatNumber} is NOT valid")
        }

        where:
        memberState | vatNumber   || expected
        'ES'        | 'B99286353' || true
        'ES'        | 'B19280031' || true
        'ES'        | 'XXXXXXXXX' || false

        unrollDescription = expected ? 'is successful' : ' fails'
    }
}

4 在 Grails 中获取帮助

对象计算公司 (OCI) 赞助了本指南的创建。提供各种咨询和支持服务。

OCI 是 Grails 的宗旨

认识团队