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 如何完成指南
要开始,请执行以下操作
-
下载并解压该源文件
或
-
克隆 Git 仓库
git clone https://github.com/grails-guides/grails-soap.git
Grails 指南仓库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用,另外还有一些代码,以便您抢得先机。 -
complete
一个已完成的示例。这是按照指南中的步骤操作,并对initial
文件夹应用这些更改的结果。
要完成指南,请转到 initial
文件夹
-
cd
到grails-guides/grails-soap/initial
并按照下一部分中的说明操作。
如果您 cd 到 grails-guides/grails-soap/complete ,则可以直接转到已完成示例。 |
3 编写应用程序
VIES (增值税信息交换系统) 是一种电子方式,用于验证为欧盟内跨境商品或服务交易登记的经济运营商的增值税识别号。
例如,如果您在欧盟中创建一个电子商务 Web 应用程序,那么您需要检查购买者的增值税号码的有效性,以便创建一个正确的发票。
要实现验证检查的自动化,欧盟不提供 REST 终结点,而是提供 SOAP 服务。其 WSDL 文件可在此处获取。
SOAP(最初是简单对象访问协议)是用于在计算机网络中的 Web 服务的实现中交换结构化信息的协议规范。其目的是诱导可扩展性、中立性和独立性
3.1 SOAP 库
为了消费 SOAP Web 服務,我們使用 groovy-wslite。Groovy 的一個庫,提供無繁瑣 SOAP 和 REST webservice 客户端。
添加 wslite 依赖项
compile 'com.github.groovy-wslite:groovy-wslite:1.1.2'
我们把 SOAP 代码封装在 Grails 服务中
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 测试框架 来测试它
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 中添加国家/地区列表
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
package demo
import groovy.transform.CompileStatic
@CompileStatic
class Country {
String code
String name
}
读取一个 Grails 服务中的国家/地区
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 控制器和视图
该应用程序允许用户提交表单,并检查增值税号码对特定州是否有效。
我们将请求封装在一个命令对象中
package demo
import grails.validation.Validateable
class VatCommand implements Validateable {
String code
String vatNumber
static constraints = {
code nullable: false
vatNumber nullable: false
}
}
创建一个 VatController 来处理请求,与之前引入的服务进行协作,并通过 flash 消息或错误生成响应。
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 消息或错误。
<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 功能测试 了解更多信息。
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 环境。
integrationTest {
systemProperties System.properties
}
为每个 Geb 环境配置不同的浏览器驱动程序。
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.htmlunit.HtmlUnitDriver
environments {
htmlUnit {
driver = { new HtmlUnitDriver() }
}
firefox {
driver = { new FirefoxDriver() }
}
}
创建一个 Geb 页面来封装表单标记。使用 Geb 页面使我们的测试更容易维护。
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()
}
}
创建一个功能测试,使用多个值提交表单
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'
}
}