1 概述
zdb平台与第三方平台对接的一个范例。
基于sealink wms定义的接口实现。
接口技术要求简述如下:
l http协议
l 消息body采用json格式
l 通过url传递系统参数
zdb端实现上行部分的接口(WTE),2个平台之间彼此调用,实现业务流程的衔接。
本文用采购进货和验收确认为例描述具体的代码实现。
2 接口
作为开发示例的2个接口定义如下。
2.1 进货通知(ETW_PURCHASE)
zdb在采购订单审核后调用。
请求参数
名称 | 类型 | 是否必须 | 示例值 | 更多限制 | 描述 |
sheetid | String | 是 |
|
|
|
warehouse_no | String | 是 |
|
| 仓库编码 |
owner_no | String | 是 | 123 |
| 货主编码 |
org_no | String | 否 |
|
| 机构代码 |
venderid | String | 否 |
|
| 进货供货商编码 |
purchase_no | String | 否 |
|
| 采购单号 |
sdate | Date | 否 |
|
| 采购单审核日期 |
checker | String | 是 |
|
| 制单员姓名 |
class_type | String | 否 |
|
| 类型 |
purdate | Date | 否 |
|
| 送货日期 |
validdate | Date | 是 |
|
| 有效日期 |
notes | String | 是 |
|
| 订单说明 |
orderlist | Purchase[] | 否 |
|
|
|
serialid | Number | 否 |
|
| 单内序号 |
owner_article_no | String | 否 |
|
| 商品编码 |
pkcount | Number | 是 |
|
| 订货规格 |
qty | Number | 否 |
|
| 进货总量 |
owner_cust_no | String | 是 |
|
| 客户代码 |
cust_qty | Number | 是 |
|
| 客户要货量 |
cust_po_no | String | 是 |
|
| 客户订单号 |
cust_note | String | 是 |
|
| 客户备注 |
响应参数
名称 | 类型 | 示例值 | 描述 |
flag | String | success |
|
code | String | 000 | 返回状态码 |
message | String |
| 返回状态描述 |
2.2 验收确认(WTE_IM_CHECK)
wms调用。
请求参数
名称 | 类型 | 是否必须 | 示例值 | 更多限制 | 描述 |
sheetid | String | 否 |
|
| 传单单据编号 |
warehouse_no | String | 否 |
|
| 仓库编码 |
owner_no | String | 否 | 123 |
| 货主编码 |
po_no | String | 否 |
|
| 进货通知单号 |
checkno | String | 否 |
|
| 验收单号 |
supplier_no | String | 否 |
|
| 供货商编码 |
sdate | Date | 否 |
|
| 验收时间 |
orderlist | Purchase[] | 否 |
|
|
|
serialid | Number | 否 |
|
| 单内序号 |
owner_article_no | String | 否 |
|
| 商品编码 |
prodate | Date | 是 |
|
| 生产日期 |
qty | Number | 否 |
|
| 验收总量 |
响应参数
名称 | 类型 | 示例值 | 描述 |
flag | String | success |
|
code | String | 000 | 返回状态码 |
message | String |
| 返回状态描述 |
3 实现
ETW接口实现在client,WTE接口实现在server模块中。
3.1 进货通知(ETW_PURCHASE)
3.1.1 请求定义
根据接口定义文档定义请求业务对象和请求传输请求体对象。
请求业务对象类型:
///< 进货通知单 struct CPurchase { string sheetid_;/// 传单单据编号 string warehouse_no_;/// 仓库编码 string owner_no_;/// 货主编码 string org_no_; /// 机构代码 string venderid_; /// 进货供货商编码 string purchase_no_;/// 采购单号 string sdate_; /// 采购单审核日期 string checker_;/// 制单员姓名 string class_type_;/// 类型 string purdate_; /// 送货日期 string validdate_;///有效日期 string notes_;/// 订单说明 struct CItem { ///< 商品明细 int serialid_;///单内序号 string owner_article_no_; int pkcount_; ///< 订货规格 double qty_; /// 进货总量 string owner_cust_no_;/// 客户代码 double cust_qty_;///客户要货量 string cust_po_no_;/// 客户订单号 string cust_note_; /// 客户备注 CItem():serialid_(0),qty_(0),cust_qty_(0),pkcount_(1) { } MEMBER_DEFINE(CItem); int Load(mpm_ns::CPurchase::CPurchaseDetail &item); }; CAutoVector<CItem*> items_; ///< 商品明细
CPurchase() { org_no_ = ORG_NO; class_type_ = "0"; }
MEMBER_DEFINE(CPurchase); int Load(mpm_ns::CPurchase &purchase); }; |
请求体类型:
///< 进货通知单请求体 struct CPurchaseRequestBody : public CMasterSlaveRequestBody<CPurchase,CPurchase::CItem> { MEMBER_DEFINE(CPurchaseRequestBody);
CPurchaseRequestBody():CMasterSlaveRequestBody("orderlist") { method_ = "ETW_PURCHASE"; } }; |
CMasterSlaveRequestBody为主从结构类型的请求体,模板参数CPurchase为请求业务对象类型,CPurchase::CItem为明细类型。
orderlist为明细数组名称。
ETW_PURCHASE为方法名称。
3.1.2 请求与消息绑定
绑定建立json消息与对象成员之间的映射。
SET_MEMBER_BEGIN(CPurchase) SET_MEMBER2(CPurchase,sheetid_,"sheetid",false), SET_MEMBER2(CPurchase,warehouse_no_,"warehouse_no",false), SET_MEMBER2(CPurchase,owner_no_,"owner_no",false), SET_MEMBER2(CPurchase,org_no_,"org_no",false), SET_MEMBER2(CPurchase,venderid_,"venderid",false), SET_MEMBER2(CPurchase,purchase_no_,"purchase_no",false), SET_MEMBER2(CPurchase,sdate_,"sdate",false), SET_MEMBER2(CPurchase,checker_,"checker",true), SET_MEMBER2(CPurchase,class_type_,"class_type",false), SET_MEMBER2(CPurchase,purdate_,"purdate",false), SET_MEMBER2(CPurchase,validdate_,"validdate",true), SET_MEMBER2(CPurchase,notes_,"notes",true), SET_MEMBER2(CPurchase,purdate_,"purdate",false), SET_MEMBER2(CPurchase,validdate_,"validdate",true), SET_MEMBER_END(CPurchase)
SET_MEMBER_BEGIN(CPurchase::CItem) SET_MEMBER2(CPurchase::CItem,serialid_,"serialid",false), SET_MEMBER2(CPurchase::CItem,owner_article_no_,"owner_article_no",false), SET_MEMBER2(CPurchase::CItem,pkcount_,"pkcount",true), SET_MEMBER2(CPurchase::CItem,qty_,"qty",false), SET_MEMBER2(CPurchase::CItem,owner_cust_no_,"owner_cust_no",true), SET_MEMBER2(CPurchase::CItem,cust_qty_,"cust_qty",true), SET_MEMBER2(CPurchase::CItem,cust_po_no_,"cust_po_no",true), SET_MEMBER2(CPurchase::CItem,cust_note_,"cust_note",true), SET_MEMBER_END(CPurchase::CItem) |
SET_MEMBER2的3个参数含义分别如下:成员变量名称,接口参数名称,是否允许空。
3.1.3 接口转换
接口转换把业务对象转换为请求对象。
本步骤基于业务相关设计人员对接口转换规则明确描述后进行。
业务对象是zdb系统内部描述业务的对象,请求对象为接口定义的用于接口之间的调用的对象。
本接口的业务对象类型为mpm_ns::CPurchase,转换为上面定义的CPurchase类型,包括明细类型转换。
下行转换方法为Load,上行转换方法为Output。
业务对象转换为请求对象
int CPurchase::Load(mpm_ns::CPurchase &purchase) { GET_SHARDINGID(purchase.eid_,_inner_env_->sharding_id_,-1); this->sheetid_ = GetSheetId(); this->warehouse_no_ = LogMsg("%d",purchase.stock_id_); this->owner_no_ = LogMsg("%d",purchase.eid_); this->org_no_ = ORG_NO; this->venderid_ = LogMsg("%d",purchase.co_eid_); this->purchase_no_ = GET_UNIQUE_SHEETID(_inner_env_->sharding_id_,purchase.sheet_id_); this->sdate_ = purchase.check_date_; this->checker_ = purchase.checker_; this->class_type_ = "0"; this->purdate_ = purchase.delivery_date_; this->notes_ = purchase.notes_;
if (purchase.details_.size()==0) return 0; vector<CSheetDetail*>::iterator iter = purchase.details_[0]->begin(); while(iter!=purchase.details_[0]->end()) { mpm_ns::CPurchase::CPurchaseDetail *detail = (mpm_ns::CPurchase::CPurchaseDetail*)*iter; CPurchase::CItem *item = new CPurchase::CItem; item->Load(*detail); this->items_.push_back(item); iter++; }
return 0; } |
此方法从业务对象(mpm_ns::CPurchase类型)生成对应的请求对象数据。
GET_UNIQUE_SHEETID用于生成唯一单据编号,由于单据编号在zdb各分区主站内唯一,但全局不唯一,因此由分区id和原始单据编号拼接而成,分区id占起始4个字符。
上行确认时需要反向操作,拆分出分区id和原始单据编号。
明细类型转换
int CPurchase::CItem::Load(mpm_ns::CPurchase::CPurchaseDetail &item) { this->serialid_ = item.line_num_; this->owner_article_no_ = LogMsg("%lu",item.goods_id_); this->pkcount_ = item.pack_unit_qty_; this->qty_ = item.bulk_qty_ + item.pack_qty_*item.pack_unit_qty_; this->cust_note_ = item.notes_;
return 0; } |
3.1.4 增加事件处理函数
在插件头文件中声明采购订单审核事件函数,并实现。
int CSealinkWMS::OnNewPurchase(CEvent *e) { return HandleMasterSlave<mpm_ns::CPurchase,CPurchaseRequestBody>(e,"OnNewPurchase"); } |
3.1.5 登记事件
在event_handle_map中增加事件响应函数项。
CEventHandleMapItem event_handle_map[] = { {EVENT_NEW_PURCHASE,(EventHandleFunc)&CSealinkWMS::OnNewPurchase},///<进货通知 }; |
EVENT_NEW_PURCHASE是在采购订单审核后由业务系统激发,由wms接口的事件代理捕获,执行上面注册的函数CSealinkWMS::OnNewPurchase。
3.2 验收确认(WTE_IM_CHECK)
3.2.1 请求定义
根据接口定义文档定义请求业务对象和请求传输请求体对象。
请求业务对象类型:
///< 验收(进货)确认单 struct CPurchaseAck : public CAckBase { string sheetid_;/// 传单单据编号 STRING(20) 不允许 string warehouse_no_;/// 仓库编码 STRING(3) 不允许 仓库编码 string po_no_; /// 进货通知单号 STRING(20) 不允许 进货通知单号 string checkno_; /// 验收单号 STRING(20) 不允许 (WMS验收单单号) string supplier_no_; /// 供货商编码 STRING(10) 不允许 进货供货商编码 string sdate_;/// 验收时间 date 日期格式:2008-10-11
struct CItem { int serialid_;///单内序号 NUMBER 不允许 采购单内序号 string owner_article_no_;/// 商品编码 STRING(20) 不允许 double qty_; /// 验收总量 Decimal(12,3) string prodate_;/// 生产日期 date 允许 CItem():qty_(0),serialid_(1) { } MEMBER_DEFINE(CItem); int Output(gsm_ns::CStorage::CStorageDetail &item); };
CAutoVector<CItem*> items_;
CPurchaseAck() { } MEMBER_DEFINE(CPurchaseAck); int Output(gsm_ns::CStorage &storage); };
|
这里只实现Output方法,把请求业务对象转换为系统业务对象。
请求体类型:
///< 进货确认单请求体 struct CPurchaseAckRequestBody : public CMasterSlaveRequestBody<CPurchaseAck,CPurchaseAck::CItem> { MEMBER_DEFINE(CPurchaseAckRequestBody);
CPurchaseAckRequestBody():CMasterSlaveRequestBody("orderlist") { method_ = "WTE_IM_CHECK"; } }; |
CMasterSlaveRequestBody为主从结构类型的请求体,模板参数CPurchaseAck为请求业务对象类型,CPurchaseAck::CItem为明细类型。
orderlist为明细数组名称。
WTE_IM_CHECK为方法名称。
3.2.2 请求与消息绑定
绑定建立json消息与对象成员之间的映射。
SET_MEMBER_BEGIN(CPurchaseAck) SET_MEMBER2(CPurchaseAck,sheetid_,"sheetid",false), SET_MEMBER2(CPurchaseAck,warehouse_no_,"warehouse_no",false), SET_MEMBER2(CPurchaseAck,owner_no_,"owner_no",false), SET_MEMBER2(CPurchaseAck,po_no_,"po_no",false), SET_MEMBER2(CPurchaseAck,checkno_,"checkno",false), SET_MEMBER2(CPurchaseAck,supplier_no_,"supplier_no",false), SET_MEMBER2(CPurchaseAck,sdate_,"sdate",true), SET_MEMBER_END(CPurchaseAck)
SET_MEMBER_BEGIN(CPurchaseAck::CItem) SET_MEMBER2(CPurchaseAck::CItem,owner_article_no_,"owner_article_no",false), SET_MEMBER2(CPurchaseAck::CItem,prodate_,"prodate_",true), SET_MEMBER2(CPurchaseAck::CItem,qty_,"qty",false), SET_MEMBER2(CPurchaseAck::CItem,serialid_,"serialid",false), SET_MEMBER_END(CPurchaseAck::CItem) |
3.2.3 接口转换
把请求对象转换为业务对象。
int CPurchaseAck::Output(gsm_ns::CStorage &storage) { storage.eid_ = atoi(this->owner_no_.c_str()); string dbc_name; if (PinDB(dbc_name)) return -1; storage.dbc_name_ = dbc_name;
storage.stock_id_ = atoi(this->warehouse_no_.c_str()); SEPARATE_SHEETID(this->po_no_,storage.ref_sheet_id_,-1); storage.co_eid_ = atoi(this->supplier_no_.c_str()); storage.check_date_ = this->sdate_; storage.notes_ = ACK_NOTES_TEXT; storage.creator_ = ACK_USER; storage.oper_date_ = CDateTime::Now().GetDateTime();
if (this->items_.size()==0) return 0;
vector<CPurchaseAck::CItem*>::iterator iter = items_.begin(); while(iter!=items_.end()) { CPurchaseAck::CItem *item = *iter; gsm_ns::CStorage::CStorageDetail *detail = new gsm_ns::CStorage::CStorageDetail; if (item->Output(*detail)) return -1; storage.details_[0]->push_back(detail); iter++; }
return 0; } |
PinDB根据请求对象内容的货主,确定数据库分区并存储在TLS中,获取数据库连接名。
SEPARATE_SHEETID从请求对象的单据编号,分离出原始单据编号。
3.2.4 增加响应函数
int CSealinkWMS::OnPurchaseAck(string &request,CResponseBodyBase **response) { CPurchaseAckRequestBody req; return req.Handle<gsm_ns::CStorage >(request, SDT_PURCHASE,6049); } |
request为请求的消息体(json)内容.
这里Handle调用的含义是对采购确认生成入库单(gsm_ns::CStorage类型),SDT_PURCHASE为入库类型,入库后自动审核,审核采用6049协议。
3.2.5 登记接口
在GetHandleFunc函数的func_maps中增加响应函数入口项:
HandleRequestFuncPtr GetHandleFunc(string &method) { static struct CFuncMap { string method_; HandleRequestFuncPtr func_ptr_; } func_maps[] = { {"WTE_IM_CHECK",&CSealinkWMS::OnPurchaseAck},///<验收(进货)确认 }; for (unsignedint i=0;i<sizeof(func_maps)/sizeof(func_maps[0]);i++) { CFuncMap &item = func_maps[i]; if (stricmp(item.method_.c_str(),method.c_str())==0) return item.func_ptr_; } return 0; }
|
4 后记
基于一份有映射关系的接口文档开发,根据接口文档声明类型;根据业务数据映射规则进行成员赋值。
所采用的实现已经尽可能简化了。
基于此方法开发,工作量容易评估,bug率及重复率低,维护扩展容易。
如增加一参数,基本就增加一个变量,绑定一下,映射一下,共3行代码就可以解决。
若接口支持xml,这些代码无需任何变动。
理论上,再简化还有2个方向:
l 应用无关:不涉及业务对象,这是不同的抽象级别,完全是另一种实现
l 代码生成工具:程序就是取代人做重复的程序化的事情的,基于接口描述生成上面的代码主干再修改。
工具的价值取决于重复使用量,低价值的工具不值得开发。
本文就如何进行此类开发进行演示说明,背后的支撑是广泛的,不在此赘言。