2023年2月

最近需要向客户发送一些宣传资料,Excel列表里面有一两百个记录,本来想手写就算了,估摸着也花不了多少时间,不过写完一个信封我就后悔了,整天敲着键盘,书写的字太难看了,而且感觉手还是有点累。才第一个啊,想着后面还有那么多,感觉整个人头都大了,只好放弃,太没技术含量了。然后寻找有无一些套打的的软件,不过找来找去还是没有满意的,还是自己写一个套打的软件好了,这个小小的还是有点技术含量,呵呵。本篇随笔基于这个困惑,整理了一个信封套打以及批量打印的实现过程,软件可以实现自动批量的信封套打,一两百个的信封地址,也是一阵子的功夫就打印完成了,感觉小有成就,而且以后就基于这个模式来批量打印信封,方便了很多。

1、信封套打的实现思路

基于套打的处理,我在几年前的随笔都有一些随笔介绍,如《
Web打印的解决方案之证件套打
》、《
基于MVC4+EasyUI的Web开发框架经验总结(16)--使用云打印控件C-Lodop打印页面或套打报关运单信息
》这两篇随笔基本思路还是采用打印控件C-Lodop(
http://www.lodop.net/
)来进行打印的,我这样的套打小程序,最好还是做成Winform程序比较方便,因此在Winform里面整合网页浏览就可以实现这个控件的打印处理了。

另外,我们根据打印的地址信息,动态的生成HTML内容即可,基于这样的理念,我们动态生成HTML可以考虑使用NVelocity的模板化处理技术,这个可以参考下我的随笔《
使用NVelocity生成内容的几种方式
》进行了解,这个NVelocity用来处理模板化的内容生成还是非常方便的。

前面介绍的这些内容,其实就是基于C-Lodop 和 NVelocity进行一个信封批量套打的处理实现技术。

这些技术都是之前研究实现过的东西,用起来应该驾轻就熟了,也就不枉费精力去找其他信封套打软件了。

我们先来介绍一下整体的打印效果,首先我们要有一些特制的信封或者普通信封,这样才能基于这个基础上进行套打,把邮政编码、地址和联系人等信息打印上去。

然后你需要有一个打印设备,我这里采用了一个佳能的喷墨打印机(当然其他的也没问题)。

最后就是基于软件的基础上进行套打了,软件可以单个打印,也可以导入Excel进行套打才可以解决实际问题。

一般是在单个打印没问题后,进行批量打印,可以省却大量的时间,下面是基于这个套打软件打印的信封,如果手写的话,不知道写到何年何月了。

2、信封套打的实现过程

下面我们来介绍这个信封套打的实现过程。

首先我们先做一个测试页面,启动C-Lodop的设计界面,我们往里面添加一些信封所需要的元素,如套打的背景图片、邮政编码的文本、地址文本、人名称呼等信息后,大致调整到合适的位置即完成了相关的设计,界面设计效果如下所示。

然后我们通过获取设计样式下的代码

生成代码里面就可以看到具体的内容了。

有了这些信息,我们就可以创建一个HTML模板,然后利用NVelocity来动态生成相关的变量信息即可,HTML模板文件中的逻辑代码(JS代码)如下所示。

接着在我们模板文件中的打印预览函数编写如下所示。

这样就可以实现标准格式的浏览了,文本显示位置不一定和背景完全吻合,背景只是作为参考处理而已。

完成这些就基本上是大功告成了,但是我们打印的时候,我们需要注意在打印设置中采用的纸张,如果采用A4的卡纸宽度和自定义的卡纸宽度的设置是不同的,我们建议根据信封的尺寸自定义格式设置,卡纸的位置就和信封宽度一致即可,防止信封进纸的时候晃动导致位置发生偏差。下面的是我对打印信封的用户自定义设置对话框,采用实际的尺寸(毫米)进行设置即可。

前面介绍的是当个信封打印预览然后进行打印,打印的按钮事件处理代码如下所示。

传入相关的键值参数后,可以实现相关的数据绑定,然后打开HTML后,就可以执行相关的脚本进行信封打印了。

而对于批量打印,处理方式和单个打印有所差异,就是不会每个都介入预览操作,我们是需要把一批对象进行打印。

那么我们如果要实现批量的数据打印,就需要利用JS里面的数组操作,把对应的对象放到里面,然后批量进行打印就可以了。

而这个数据的生成,我们就是利用NVelocity的模板函数进行处理即可。

完成这些,我们就可以传入对应的打印集合,让其在NVelocity模板里面生成对应的HTML代码,生成对应的对象加入到打印的数组里面,从而完成批量的数据打印了。

批量套打是在我们确认单个打印格式吻合已有信封的位置情况下,进行批量的打印处理。

一旦开启批量打印,我们的剩下的工作就是不断的往打印机上面放置足够的信封即可,可以边喝茶边等待完成,悠然自得的了。

以上就是基于NVelocity+C-LODOP控件实现的信封套打整个思路和实现方式,对于我们技术人来说,做一个这样的软件所花费的时间,比手写几百个信封浪费的时间更有意义,所获得的成就感也是有那么一些的。

总之,技术就是为生活服务。

Swift是苹果推出的一个比较新的语言,它除了借鉴语言如C#、Java等内容外,好像还采用了很多JavaScript脚本里面的一些脚本语法,用起来感觉非常棒,作为一个使用C#多年的技术控,对这种比较超前的语言非常感兴趣,之前也在学习ES6语法的时候学习了阮一峰的《
ECMAScript 6 入门
》,对JavaScript脚本的ES6语法写法叹为观止,这种Swift语言也具有很多这种脚本语法的特点,可以说这个Swift在吸收了Object C的优点并摒弃一些不好的东西外,同时吸收了大量新一代语言的各种特点,包括泛型、元祖等特点。我在学习Swift的时候,发现官方的语言介绍文章(
The Swift Programming Language
)还是非常浅显易懂,虽然是英文,不过代码及分析说明都很到位,就是内容显得比较多一些,而我们作为技术人员,一般看代码就很好了解了各种语法特点了,基于这个原因,我对官网的案例代码进行了一个摘要总结,以代码的方式进行Swift语言的语法特点介绍,总结一句话就是:快看Sample代码,速学Swift语言。

1、语法速览

var myVariable = 42myVariable= 50let myConstant= 42

变量定义用var,常量则用let,类型自行推断。

let apples = 3let oranges= 5let appleSummary= "I have \(apples) apples."let fruitSummary= "I have \(apples + oranges) pieces of fruit."

用括号包含变量

let quotation = """I said"I have \(apples) apples."And then I said"I have \(apples + oranges) pieces of fruit."
"""

代码通过三个双引号来包含预定格式的字符串(包括换行符号),左侧缩进空格省略。

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[
1] = "bottle of water" var occupations =["Malcolm": "Captain","Kaylee": "Mechanic",
]
occupations[
"Jayne"] = "Public Relations"

数组和字典集合初始化符合常规,字典后面可以保留逗号结尾

let emptyArray =[String]()
let emptyDictionary
= [String: Float]()

初始化函数也比较简洁。

let individualScores = [75, 43, 103, 87, 12]var teamScore = 0
for score inindividualScores {if score > 50{
teamScore
+= 3}else{
teamScore
+= 1}
}
print(teamScore)

控制流的if-else这些和其他语言没有什么差异,for ... in 则是迭代遍历的语法,控制流方式还支持其他的while、repeat...while等不同的语法。

var optionalString: String? = "Hello"print(optionalString==nil)var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name =optionalName {
greeting
= "Hello, \(name)"}

这部分则是可空类型的使用,以及可空判断语句的使用,可空判断语句在Swift中使用非常广泛,这种相当于先求值再判断是否进入大括符语句。

let vegetable = "red pepper"
switchvegetable {case "celery":
print(
"Add some raisins and make ants on a log.")case "cucumber", "watercress":
print(
"That would make a good tea sandwich.")case let x where x.hasSuffix("pepper"):
print(
"Is it a spicy \(x)?")default:
print(
"Everything tastes good in soup.")
}

Switch语法和常规的语言不同,这种简化了一些语法,每个子条件不用显式的写break语句(默认就是返回的),多个条件逗号分开即可公用一个判断处理。

let interestingNumbers =["Prime": [2, 3, 5, 7, 11, 13],"Fibonacci": [1, 1, 2, 3, 5, 8],"Square": [1, 4, 9, 16, 25],
]
var largest = 0 for (kind, numbers) ininterestingNumbers {for number innumbers {if number >largest {
largest
=number
}
}
}
print(largest)

上面字典遍历的方式采用for...in的方式进行遍历,另外通过(
kind,
numbers)的方式进行一个参数的解构过程,把字典的键值分别付给kind,numbers这两个参数。

var total = 0
for i in 0..<4{
total
+=i
}
print(total)

上面的for...in循环采用了一个语法符号..<属于数学半封闭概念,从0到4,不含4,同理还有全封闭符号:...全包含左右两个范围的值。

func greet(person: String, day: String) ->String {return "Hello \(person), today is \(day)."}
greet(person:
"Bob", day: "Tuesday")

上面是函数的定义,以func关键字定义,括号内是参数的标签、名称和类型内容,返回值通过->指定。

上面函数需要输入参数名称,如果不需要参数名称,可以通过下划线省略输入,如下

func greet(_ person: String, on day: String) ->String {return "Hello \(person), today is \(day)."}
greet(
"John", on: "Wednesday")

另外参数名称可以使用标签名称。

func greet(person: String, from hometown: String) ->String {return "Hello \(person)!  Glad you could visit from \(hometown)."}
print(greet(person:
"Bill", from: "Cupertino"))//Prints "Hello Bill! Glad you could visit from Cupertino."

嵌套函数如下所示。

func returnFifteen() ->Int {var y = 10func add() {
y
+= 5}
add()
returny
}
returnFifteen()

复杂一点的函数的参数可以传入函数进行使用,这种类似闭包的处理了

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) ->Bool {for item inlist {ifcondition(item) {return true}
}
return false}
func lessThanTen(number: Int)
->Bool {return number < 10}var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

下面是一个闭包的函数,闭包通过in 来区分参数和返回的函数体

numbers.map({ (number: Int) -> Int inlet result= 3 *numberreturnresult
})

class Shape {var numberOfSides = 0func simpleDescription()->String {return "A shape with \(numberOfSides) sides."}
}

类的定义通过class关键字进行标识,默认的权限是internal,在项目模块内部可以访问的,非常方便。

使用则如下所示,可以通过点语法直接获取属性和调用方法。

var shape =Shape()
shape.numberOfSides
= 7 var shapeDescription = shape.simpleDescription()

class NamedShape {var numberOfSides: Int = 0
    varname: String

init(name: String) {
self.name
=name
}

func simpleDescription()
->String {return "A shape with \(numberOfSides) sides."}
}

类通过使用init的指定名称作为构造函数,使用deinit来做析构函数,使用self来获取当前的类引用,类似于其他语言的this语法,super获取基类的引用。

其他的处理方式如继承、重写的语法和C#类似。

class Square: NamedShape {varsideLength: Double

init(sideLength: Double, name: String) {
self.sideLength
=sideLength
super.init(name: name)
numberOfSides
= 4}

func area()
->Double {return sideLength *sideLength
}

override func simpleDescription()
->String {return "A square with sides of length \(sideLength)."}
}
let test
= Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

类的属性使用get、set语法关键字,和C#类似

class EquilateralTriangle: NamedShape {var sideLength: Double = 0.0init(sideLength: Double, name: String) {
self.sideLength
=sideLength
super.init(name: name)
numberOfSides
= 3}varperimeter: Double {
get {
return 3.0 *sideLength
}
set {
sideLength
= newValue / 3.0}
}

class TriangleAndSquare {vartriangle: EquilateralTriangle {
willSet {
square.sideLength
=newValue.sideLength
}
}
varsquare: Square {
willSet {
triangle.sideLength
=newValue.sideLength
}
}

类属性的赋值可以进行观察,如通过willSet在设置之前调用,didSet在设置之后调用,实现对属性值得监控处理。

enum Rank: Int {case ace = 1
    casetwo, three, four, five, six, seven, eight, nine, tencasejack, queen, king
func simpleDescription()
->String {switchself {case.ace:return "ace" case.jack:return "jack" case.queen:return "queen" case.king:return "king" default:returnString(self.rawValue)
}
}
}
let ace
=Rank.ace
let aceRawValue
= ace.rawValue

和类及其他类型一样,枚举类型在Swift中还可以有方法定义,是一种非常灵活的类型定义,这个和我们之前接触过的一般语言有所差异。

enum ServerResponse {caseresult(String, String)casefailure(String)
}

let success
= ServerResponse.result("6:00 am", "8:09 pm")
let failure
= ServerResponse.failure("Out of cheese.")switchsuccess {caselet .result(sunrise, sunset):
print(
"Sunrise is at \(sunrise) and sunset is at \(sunset).")caselet .failure(message):
print(
"Failure... \(message)")
}

struct Card {varrank: Rankvarsuit: Suit
func simpleDescription()
->String {return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"}
}
let threeOfSpades
=Card(rank: .three, suit: .spades)
let threeOfSpadesDescription
= threeOfSpades.simpleDescription()

结构类型和类的各个方面很类似,结构支持构造函数,方法定义,属性等,重要一点不同是结构在代码传递的是副本,而类实例传递的是类的引用。

protocol ExampleProtocol {varsimpleDescription: String { get }
mutating func adjust()
}

这里的协议,类似很多语言的接口概念,不过比常规语言(包括C#)的接口更加多样化、复杂化一些。

Swift的协议,可以有部分方法实现,协议可以可选,继承其他协议等等。

extension Int: ExampleProtocol {varsimpleDescription: String {return "The number \(self)"}
mutating func adjust() {
self
+= 42}
}
print(
7.simpleDescription)

扩展函数通过extension进行标识,可以为已有的类进行扩展一些特殊的方法处理,这个类似C#的扩展函数。

func send(job: Int, toPrinter printerName: String) throws ->String {if printerName == "Never Has Toner"{throwPrinterError.noToner
}
return "Job sent"}

异常处理中,函数声明通过throws关键字标识有异常抛出,在函数里面通过throw进行异常抛出处理。

而在处理有异常的地方进行拦截,则通过do...catch的方式进行处理,在do的语句里面,通过try来拦截可能出现的异常,默认catch里面的异常名称为error。

do{
let printerResponse
= try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
}
catch{
print(error)
}

可以对多个异常进行判断处理

do{
let printerResponse
= try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
}
catchPrinterError.onFire {
print(
"I'll just put this over here, with the rest of the fire.")
}
catchlet printerError as PrinterError {
print(
"Printer error: \(printerError).")
}
catch{
print(error)
}

还可以通过使用try?的方式进行友好的异常处理,如果有异常返回nil,否者获取结果赋值给变量

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure
= try? send(job: 1885, toPrinter: "Never Has Toner")

var fridgeIsOpen = falselet fridgeContent= ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String)
->Bool {
fridgeIsOpen
= truedefer {
fridgeIsOpen
= false}

let result
=fridgeContent.contains(food)returnresult
}
fridgeContains(
"banana")
print(fridgeIsOpen)

使用defer的关键字来在函数返回前处理代码块,如果有多个defer函数,则是后进先出的方式进行调用,最后的defer先调用,依次倒序。

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) ->[Item] {var result =[Item]()for _ in 0..<numberOfTimes {
result.append(item)
}
returnresult
}
makeArray(repeating:
"knock", numberOfTimes: 4)

Swift支持泛型,因此可以大大简化很多函数的编写,提供更加强大的功能。

enum OptionalValue<Wrapped>{casenonecasesome(Wrapped)
}
var possibleInteger: OptionalValue<Int> =.none
possibleInteger
= .some(100)

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) ->Bool
where T.Iterator.Element: Equatable, T.Iterator.Element
==U.Iterator.Element {for lhsItem inlhs {for rhsItem inrhs {if lhsItem ==rhsItem {return true}
}
}
return false}
anyCommonElements([
1, 2, 3], [3])

泛型的参数支持where的关键字进行泛型类型的约束,如可以指定泛型的参数采用什么协议或者继承哪个基类等等。

Swift语言是一个新的编程语言,用于iOS, macOS, watchOS, 和 tvOS的开发,不过Swift很多部分内容,我们可以从C或者Objective-C的开发经验获得一种熟悉感。Swift提供很多基础类型,如Int,String,Double,Bool等类型,它和Objective-C的相关类型对应,不过他是值类型,而Objective-C的基础类型是引用类型,另外Swift还提供了几个集合类型,如
Array
,
Set
, 和
Dictionary;Swift引入一些Objective-C里面没有的元祖类型,这个在C#里倒是有类似的,也是这个名词。 Swift语言是一种类型安全的强类型语言,不是类似JavaScript的弱类型,能够在提供开发效率的同时,减少常规出错的可能,使我们在开发阶段尽量发现一些类型转换的错误并及时处理。

常量和变量

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

常量用let定义,变量用var定义,它们均可以通过自动推导类型,如上面的就是指定为整形的类型。

也可以通过逗号分开多个定义,如下所示

var x = 0.0, y = 0.0, z = 0.0

如果我们的变量没有初始化值来确定它的类型,我们可以通过指定类型来定义变量,如下所示

var welcomeMessage: String

var red, green, blue: Double

变量的打印,可以在输出字符串中用括号包含变量输出,括号前加斜杠 \ 符号。

print(friendlyWelcome)//Prints "Bonjour!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")//Prints "The current value of friendlyWelcome is Bonjour!"

注释符

// This is a comment.

/* This is also a comment
 but is written over multiple lines. */

/* This is the start of the first multiline comment.
 /* This is the second, nested multiline comment. */
 This is the end of the first multiline comment. */

上面分别是常规的的注释,以及Swift支持嵌套的注释符号

分号

Swift语句的划分可以不用分号,不过你加分号也可以,如果加分号,则可以多条语句放在一行。


let cat = "

运算符是用来检查,更改或组合值的特殊符号或短语。Swift提供的很多常规的运算符,如+、-、*、/、%、=、==等,以及逻辑运算的&&、||等等,基本上不需要重复介绍,我们在这里只需要了解一些不太一样的运算符就可以了。如Swift引入的新运算符,范围操作符号,包括..<和...两个,该随笔介绍Swift常规的运算符中,以及和其他语言有所差异的部分。

赋值运算符

let b = 10
var a = 5
a = b
// a is now equal to 10

赋值语句,处理和其他语言一样。

let (x, y) = (1, 2)
// x is equal to 1, and y is equal to 2

这种代码是类似ECMAScript 6的脚本写法,通过把右边元祖对象解构赋值给左边对应的参数。

数学运算符

1 + 2       // equals 3
5 - 3       // equals 2
2 * 3       // equals 6
10.0 / 2.5  // equals 4.0

这些都是和其他语言没有什么不同,循例列出参考下

对于字符,也可以使用+符号进行连接新的字符串

"hello, " + "world"  // equals "hello, world"

一元操作符中的-、+运算,和算术里面的负负得正,正负得负的意思一样了。

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three"

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

组合运算符提供+= 、-=的运算符操作

var a = 1
a += 2
// a is now equal to 3

对比运算符和其他语言差不多

  • 等于 (
    a == b
    )

  • 不等于 (
    a != b
    )

  • 大于 (
    a > b
    )

  • 小于 (
    a < b
    )

  • 大于等于 (
    a >= b
    )

  • 小于等于 (
    a <= b
    )

另外值得注意的是,Swift提供了对比引用的两个操作符号,
===

!==,用来检查两个引用是否完全相等;或者不相等的。而==只是用来对比两个对象的值是否一致。

1 == 1   // true because 1 is equal to 1
2 != 1   // true because 2 is not equal to 1
2 > 1    // true because 2 is greater than 1
1 < 2    // true because 1 is less than 2
1 >= 1   // true because 1 is greater than or equal to 1
2 <= 1   // false because 2 is not less than or equal to 1

对比运算符也经常用来If条件语句里面

let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// Prints "hello, world", because name is indeed equal to "world".

三元运算符

三元运算符 ? :和C#里面表现是一样的

question ? answer1 : answer2

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)

空值转换操作符

空值转换符是对可空类型(可选类型)的一个值得转换出来(
a ?? b
)。

let defaultColorName = "red"
var userDefinedColorName: String?   // defaults to nil
 
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is not nil, so colorNameToUse is set to "green"

范围操作符

闭合范围运算符 ... 和半闭合范围运算符 ..< 两个

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

半闭合的范围运算符

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

或者如下使用

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

以及一侧范围的运算符,包括左侧和右侧两个部分

for name in names[2...] {
    print(name)
}
// Brian
// Jack
 
for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

逻辑运算符

let allowedEntry = false
if !allowedEntry {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

或者使用括号使之更加方便阅读

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

在我们常规的业务处理中,一般内部处理的接口多数都是以数据库相关的,基于混合式开发的Winform开发框架,虽然在客户端调用的时候,一般选择也是基于Web API的调用,不过后端我们可能不仅仅是针对我们业务数据库的处理,也可以能是调用其他外部接口,如物流、供应商接口等接口,本随笔就是主要介绍基于混合式开发框架如何整合外部API接口的调用。

1、混合式框架的结构介绍

我们知道,混合式的框架是可以在客户端访问Web API服务、WCF服务或者直接连接数据库几种方式的综合,而尤以Web API为最广泛的应用,它的整个框架的结构如下所示。

在客户端中,通过统一的工厂类CallerFactory<T>对相应的接口进行访问,这里主要就是服务器端Web API服务接口的处理,以及客户端对Web API接口的封装,两部分通过一些基类进行简化处理,可以极大提高开发效率。

对于外部第三方的Web API接口,我们也可以在自己的Web API接口中进行包装,使得客户端通过相应的接口进行交互即可,不需要理会内部还是外部的接口,从而实现透明的接口调用。

2、RFID外部接口的整合处理

在一个客户的应用案例中,需要整合服务商RFID接口实现相应的数据交互,本篇随笔也是基于这个案例进行整个过程的分析和操作,使得我们了解在混合框架中如何整合第三方Web API接口为我们内部框架所用。

一般来说,Web API接口,需要明确API的URL、数据提交方式(POST/GET)、提交参数、返回集合,以及一些特殊的数据等,而一般接口的操作,也是需要一个访问令牌的,这些都是Web API接口调用的关键。

基本上我们有了上面Web API的1/2/3步骤的信息就可以进行接口编程了,这些是Web API开发非常重要的信息。

我们需要特别主要到,步骤1中的信息

这里面的token是额外的接口信息,是需要设置Http Request请求的头部信息里面的,是用户身份的重要信息,所以我们一般需要先通过指定的授权接口获取这个token信息。

在这个外部的接口集合里面,我们找到统一登录验证的接口定义如下所示。

通过上面的分析,我们首先需要需要处理好登录验证接口,然后通过接口传递令牌token给其他接口进行数据处理的。

结合我们的混合框架结构,这里我以测试项目TestProject项目为例进行介绍,我们调整WHC.TestProject.Caller项目的对应类,如下所示。

其中Facade层接口类IRFIDService.cs代码如下所示。

    /// <summary>
    ///RFID服务外部接口/// </summary>
[ServiceContract]public interfaceIRFIDService 
{
/// <summary> ///终端用户统一登录验证/// </summary> [OperationContract]
CheckinResult CheckIn(
string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id);/// <summary> ///获取标签发货通知单批量/// </summary> [OperationContract]
TagOrderAsnResult TagOrderAsn(
int brand_id, string factcode, string start_time, string end_time, PagerInfo pagerInfo, stringtoken);/// <summary> ///标签订单出库物流信息回写/// </summary> [OperationContract]
CommonResult TagOutPost(
string docno_asn, string factcode, string dest_factcode, List<FreightInfo> freight, stringtoken);
}

这里面的接口定义,我们是根据输入参数、输出参数进行定义的,另外token是额外增加的令牌参数,用于请求头部写入信息的。

这个接口的定义其实和我们常规的Web API接口定义没有太多的不同,如下是一个内部客户信息接口定义。

    /// <summary>
    ///客户信息的服务接口/// </summary>
[ServiceContract]public interface ICustomerService : IBaseService<CustomerInfo>{/// <summary>
        ///根据客户名称获取客户列表/// </summary>
        /// <param name="name">客户名称</param>
        /// <returns></returns>
[OperationContract]
List
<CustomerInfo> FindByName(stringname);
}

差别就是它们接口继承类有所不同,外部接口由于不需要和数据库打交道,我们不需要继承IBaseService接口

根据这些接口的定义,我们还需要实现我们具体的Web API 服务,逻辑上它是对外部Web API接口的封装,但是对于客户端来说,并不需要知道是内部还是外部接口,客户端只需要知道如果提交参数或者结果即可。

由于Web API涉及多个参数的数据提交,一般来说这种情况都是以POST方式处理的,数据参数则统一在Web API端通过定义一个JObject对象来传递即可,登录认证的Web API接口定义如下所示。

    /// <summary>
    ///基于RFID的应用接口/// </summary>
    public classRFIDController : BaseApiController
{
/// <summary> ///终端用户统一登录验证/// </summary> /// <param name="param">包含多个属性的对象</param> /// <param name="token">访问令牌</param> [HttpPost]publicCheckinResult CheckIn(JObject param)
{
CheckinResult result
= null;dynamic obj =param;if (obj != null)
{
//使用POST数据 var postData =param.ToJson();//使用具体的URL var queryUrl = "https://***.***.***/api/v6/rfid/terminal/checkin/post";var helper = newHttpHelper();
helper.ContentType
= "application/json";var content = helper.GetHtml(queryUrl, postData, true);
RFIDBaseData
<CheckinResult> jsonResult = JsonConvert.DeserializeObject<RFIDBaseData<CheckinResult>>(content);if (jsonResult != null && jsonResult.code == 0)
{
result
=jsonResult.data;
}
returnresult;
}
else{throw new MyApiException("传递参数错误");
}
}

其中输入的参数这里用了JObject param的参数,我们提交给外部Web API接口的时候,我们把这个参数再次序列号为JSON格式的字符串即可:

var postData =param.ToJson();

其中CheckinResult和RFIDBaseData是根据输入参数、输出结果进行的实体类定义,目的是序列化为强类型的实体类,方便数据处理操作。

在客户端,我们只需要对接好和Web API服务端的接口,那么调用起来就非常方便,其中对应的Web API接口客户端封装类 RFIDCaller 如下所示。

    /// <summary>
    ///对RFID控制的接口调用封装/// </summary>
    public classRFIDCaller : NormalApiService, IRFIDService
{
publicRFIDCaller()
{
this.ConfigurationPath = ApiConfig.ConfigFileName; //Web API配置文件 this.configurationName =ApiConfig.RFID;
}
public CheckinResult CheckIn(string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url =GetNormalUrl(action);var postData = new{
username
=username,
password
=password,
device_uuid
=device_uuid,
device_type
=device_type,
last_app_version
=last_app_version,
app_id
=app_id,
}.ToJson();
var result = JsonHelper<CheckinResult>.ConvertJson(url, postData);returnresult;
}

有了这些,我们直接在客户端的界面里面,就可以通过调用CallerFactory<T>进行处理操作了,如下是客户端窗体获取验证身份令牌数据的代码

        private string token = null;//访问RFID接口的token
        /// <summary>
        ///根据终端用户统一登录验证获取相关访问token/// </summary>
        /// <returns></returns>
        private stringGetRFIDToken()
{
string username = "wuhuacong";string password = "123456";string device_uuid = "aaaaaaa";string device_type = "iphone";string last_app_version = "xxxxxxx";string app_id = "ntdf5543581a2f066e74cf2fe456";var result = CallerFactory<IRFIDService>.Instance.CheckIn(username, password, device_uuid, device_type, last_app_version, app_id);if(result != null)
{
token
=result.token;
}
returntoken;
}

上面是认证身份的接口,其他类型的接口类似的处理方式,如增加了一个

获取标签发货通知单批量

操作后,对应的客户端封装类如下所示。

    /// <summary>
    ///对RFID控制的接口调用封装/// </summary>
    public classRFIDCaller : NormalApiService, IRFIDService
{
publicRFIDCaller()
{
this.ConfigurationPath = ApiConfig.ConfigFileName; //Web API配置文件 this.configurationName =ApiConfig.RFID;
}
public CheckinResult CheckIn(string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url =GetNormalUrl(action);var postData = new{
username
=username,
password
=password,
device_uuid
=device_uuid,
device_type
=device_type,
last_app_version
=last_app_version,
app_id
=app_id,
}.ToJson();
var result = JsonHelper<CheckinResult>.ConvertJson(url, postData);returnresult;
}
public TagOrderAsnResult TagOrderAsn(int brand_id, string factcode, string start_time, string end_time, Pager.Entity.PagerInfo pagerInfo, stringtoken)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url = GetNormalUrl(action) + string.Format("?token={0}", token);var postData = new{
page
=pagerInfo.CurrenetPageIndex,
pagesize
=pagerInfo.PageSize,
brand_id
=brand_id,
factcode
=factcode,
start_time
=start_time,
end_time
=end_time,
}.ToJson();
var result = JsonHelper<TagOrderAsnResult>.ConvertJson(url, postData);returnresult;
}

获取标签发货通知单批量

的Web API接口如下代码定义

        /// <summary>
        ///获取标签发货通知单批量/// </summary>
        /// <param name="param"></param>
        /// <param name="token"></param>
        /// <returns></returns>
[HttpPost]public TagOrderAsnResult TagOrderAsn(JObject param, stringtoken)
{
TagOrderAsnResult result
= null;dynamic obj =param;if (obj != null)
{
//使用POST方式 var postData =param.ToJson();var queryUrl = "https://***.***.***/api/v6/rfid/tag/tag_order_asn/get"; var helper = newHttpHelper();
helper.ContentType
= "application/json";
helper.Header.Add(
"token", token);var content = helper.GetHtml(queryUrl, postData, true);

RFIDBaseData
<TagOrderAsnResult> jsonResult = JsonConvert.DeserializeObject<RFIDBaseData<TagOrderAsnResult>>(content);if (jsonResult != null && jsonResult.code == 0)
{
result
=jsonResult.data;
}
returnresult;
}
else{throw new MyApiException("传递参数错误");
}

其中表头信息,我们通过下面的代码指定,设置特殊的token表头信息

                var helper = newHttpHelper();
helper.ContentType
= "application/json";
helper.Header.Add(
"token", token);

而在客户端的调用窗体里面,我们调用对应的接口就可以获取该接口的数据了。

        privateTagOrderAsnResult asnResult;/// <summary>
        ///根据参数获取标签生产订单批量信息/// </summary>
        /// <returns></returns>
        privateTagOrderAsnResult GetResult()
{
PagerInfo pagerInfo
= new PagerInfo() { PageSize = 50, CurrenetPageIndex = 1 };//初始化一个分页条件 var brand_id = this.txtbrand_id.Text.ToInt32();var factcode = this.txtfactcode.Text;var start_time = this.txtstart_time.DateTime.ToString("yyyy-MM-dd HH:mm:ss");var end_time = this.txtend_time.DateTime.ToString("yyyy-MM-dd HH:mm:ss");

asnResult
= CallerFactory<IRFIDService>.Instance.TagOrderAsn(brand_id, factcode, start_time, end_time, pagerInfo, Token);returnasnResult;
}

通过上面的代码演示,我们了解了在混合框架基础上增加外部Web API接口的方法,通过增加Facade层接口,增加Web API接口,以及对应的客户端封装类,具体处理参数根据Web API接口的输入参数、输出数据等信息进行综合处理即可。

最后我们来看看数据的展示界面。