【delphi】微信公众号控件开发(二)
- 三、架构说明
- 1. 使用的控件
- 2. 网页授权
- 3. 网页中有Ajax请求的约定
- 3.1 公众号JSSDK授权:1001
- 3.2 身份证识别:1002
- 3.3 通用JSON请求命令:999
- 4. 重要函数说明
- 5. SparkleGenericServerProcessRequest函数处理流程图
)
三、架构说明
本章说明微信公众号控件内部使用的控件以及整体架构。
1. 使用的控件
序号 | 控件 | 描述 |
1 | TNetHTTPClient | 用来进行HTTP请求,可以支持HTTPS。以前都是用的是TidHTTP,现在新版的delphi已经原生的支持HTTPS了,所以就不需要使用Indy控件了,也就不需要附带额外的DLL了。 |
2 | TTimer | 用来做刷新Token的定时器 |
3 | TSparkleGenericServer | 用来作为微信公众号的Web服务器,这个是TMS的控件。需要购买! |
4 | TSparkleHttpSysDispatcher | 用来实现Web分发的中间件,需要和TSparkleGenericServer 配合使用,都是TMS的控件。 |
5 | TXDataServer | 能够实现和数据库直接连接的提供Restful API的控件。 |
6 | TXDataConnectionPool | XData的数据库连接池 |
7 | TAureliusConnection | XData需要使用的控件 |
8 | TSparkleGenericMiddleware | 中间件,可以直接修改XData的Web请求底层数据,其中6、7、8控件都是配合TXDataServer 的。 |
具体的程序定义如下:
//创建Web服务
FSparkleGenericServer := TSparkleGenericServer.Create(nil);
FSparkleHttpSysDispatcher := TSparkleHttpSysDispatcher.Create(nil);
FSparkleGenericServer.Dispatcher := FSparkleHttpSysDispatcher;
FSparkleGenericServer.OnProcessRequest := SparkleGenericServerProcessRequest;
//创建Xdata相关
FAureliusConnection := TAureliusConnection.Create(nil); //定义Xdata
FXDataConnectionPool := TXDataConnectionPool.Create(nil);
FXDataServer := TXDataServer.Create(nil);
FXDataServerGeneric := TSparkleGenericMiddleware.Create(nil);
FXDataServerGeneric.OnRequest := FXDataServerGenericRequest;
FXDataServer.MiddlewareList.Add(FXDataServerGeneric);
//设置相关属性
FXDataServer.Dispatcher := FSparkleHttpSysDispatcher;
FXDataServer.Pool := FXDataConnectionPool;
FXDataConnectionPool.Connection := FAureliusConnection;
程序通过 TSparkleGenericServer 提供微信公众号的Web基本服务器,如果需要和数据库打交道,此时是通过TXDataServer 控件提供Web服务。
2. 网页授权
对于微信公众号网页授权,需要授权的网址要求附带 state=A01参数。否则将作为一般的请求,不进行授权请求处理。
3. 网页中有Ajax请求的约定
不同的请求,功能不一样,所以需要使用what参数来区别,请求的格式为:http://sensorwx.a3650.com/wxh?what=1002,如果还有其他个性参数没有问题,但是必须有what=nnn这个参数,后台会根据nnn的不同进行处理。
3.1 公众号JSSDK授权:1001
- 请求格式:http://sensorwx.a3650.com/wxh?what=1001
- 入口参数:{“url" : “需要授权的网址”}
- 请求方法:post
- 返回参数:’{“timestamp”:"%s",“noncestr”:"%s",“signature”:"%s",“appid”:"%s"}’
前端 javascript 程序:请注意wx.config,这个是微信JSSDK授权的函数调用。
//生成入口参数
function myBuild_JSSDKData(){
var json = {
"url":window.location.href
};
return json;
};
function myGet_JSSDK_Post(){
//ajax注入权限验证
$.ajax({
type: "post",
url:'http://sensorwx.a3650.com/wxh?what=1001',
dataType : "json", //此处必须是json
contentType: 'application/json;charset="UTF-8"', //此处必须有这一句
data: JSON.stringify(myBuild_JSSDKData()) ,
complete: function(XMLHttpRequest, textStatus){ },
error: function(XMLHttpRequest, textStatus, errorThrown){
alert("发生错误:"+errorThrown);
},
success: function(res){
var appid = res.appid;
var noncestr = res.noncestr;
var timestamp = res.timestamp;
var signature = res.signature;
alert(JSON.stringify(res));
alert(res.appid + '.' + res.noncestr + '.' + res.timestamp + '.' + res.signature);
wx.config({
debug: true, //开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: appid, //必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: noncestr, //必填,生成签名的随机串
signature: signature,// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ',
'onMenuShareWeibo','onMenuShareQZone','chooseImage',
'uploadImage','downloadImage','startRecord','stopRecord',
'onVoiceRecordEnd','playVoice','pauseVoice','stopVoice',
'translateVoice','openLocation','getLocation','hideOptionMenu',
'showOptionMenu','closeWindow','hideMenuItems','showMenuItems',
'showAllNonBaseMenuItem','hideAllNonBaseMenuItem'] //必填,需要使用的JS接口列表,
});
},
});
};
delphi 后端程序函数:
function TWeiXinComponent.Get_jssdk_signature({noncestr, jsapi_ticket, timestamp,} url: string): string;
const
JSON = '{"timestamp":"%s","noncestr":"%s","signature":"%s","appid":"%s"}';
var
strs: TStringList;
tmpStr: string;
noncestr, jsapi_ticket, timestamp : string;
begin
Result := '';
noncestr := 'sensor' + FormatDateTime('hhmmsszzz',Now);
timestamp := IntToStr(DateTimeToUnix(Now));
jsapi_ticket := Fjsapi_ticket;
strs := TStringList.Create;
try
strs.Add('noncestr=' + noncestr);
strs.Add('jsapi_ticket=' + jsapi_ticket);
strs.Add('timestamp=' + timestamp);
strs.Add('url=' + url);
strs.Sort; //这个排序不严谨,但是这里使用的参数没有问题
tmpStr := strs[0] + '&' + strs[1] + '&' + strs[2] + '&' + strs[3];
//进行数据签名
tmpStr := THashSHA1.GetHashString(tmpStr);
Result := Format(JSON,[timestamp,nonceStr,tmpStr,FAppID]);
finally
FreeAndNil(strs);
end;
end;
3.2 身份证识别:1002
- 请求格式:http://sensorwx.a3650.com/wxh?what=1002
- 请求方法:post
- 入口参数:{“media_id”:"%s" ,“user_id”:"%s"},参数格式JSON
- 出口参数:{“name”:"%s",“id”:"%s",“addr”:"%s",“gender”:"%s",“nationality”:"%s"}
前端请求流程:
- 通过 wx.chooseImage 选择一张身份证图片,返回选定照片的本地ID列表;
- 通过 wx.uploadImage 上传选定的照片到腾讯后台,使用第一步得到的本地照片ID上传,上传的照片会保存到公众号的临时素材中,返回一个临时素材的media_id;
- 调用 http://sensorwx.a3650.com/wxh?what=1002 接口,post数据为 {“media_id”:"%s" ,“user_id”:"%s"},其中media_id是第2步返回的serverId(实际是media_id),user_id 是签写调用者自己是谁,可以为空。
- 后台通过 media_id 从腾讯后台下载临时素材命令抓取到照片,将照片保存成本地一个文件,然后再将这个文件传送给腾讯后台进行识别,识别结果以JSON数据的格式返回。
前端 javascript 程序:
$('#my_TestJSSDK').on('click', function(){
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
//上传到服务器
wx.uploadImage({
localId: localIds[0], // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (result) {
var serverId = result.serverId; // 返回图片的服务器端ID
//alert(serverId);
$('#loadingToast').fadeIn('fast');
//$.showLoading("数据加载中"); //只有小程序才可以这样使用
$.ajax({
type: "post",
url:'http://sensorwx.a3650.com/wxh?what=1002',
dataType : "json", //此处必须是json
contentType: 'application/json;charset="UTF-8"', //此处必须有这一句
data: '{"media_id":"' + serverId +'","user_id":"AAAA"}', // serverId, //发送的是一个 Media_id
//data: {"url" : url},
complete: function(XMLHttpRequest, textStatus){ },
error: function(XMLHttpRequest, textStatus, errorThrown){
alert("发生错误:"+errorThrown);
},
success: function(res){
if (res.errcode) {alert(res.errcode + ' ' + res.errmsg)};
if (res.name) { alert(res.name + ' / ' + res.id + ' / ' + res.addr + ' /' + res.gender + ' / ' + res.nationality)};
$('#loadingToast').fadeOut('fast');
//$.hideLoading();
},
});
}
});
}
});
});
后端delphi 程序
procedure TWeiXinComponent.Process_REC_IDCARD(const JSON_Str: string; context: THttpServerContext);
const
JSON = '{"name":"%s","id":"%s","addr":"%s","gender":"%s","nationality":"%s"}';
var
media_id, user_id : string;
Pic_IDCard_path : string;
FileName : string;
Media_Type : integer;
Filestream : TStringStream;
IDCard_info : TIDCard_Front;
jo : TJSONObject;
jv : TJSONString;
S : string;
B : TBytes;
begin
PutLogFile('收到: ' + JSON_Str);
jo := TjsonObject.ParseJSONValue(JSON_Str) as TjsonObject;
if not Assigned(jo) then
begin
Return_ERRORMSG('-99','上报的JOSN字符串不合法:' + JSON_Str,context);
Exit;
end;
if jo.TryGetValue('media_id',jv) then media_id := jv.Value;
if jo.TryGetValue('user_id',jv) then user_id := jv.Value;
if media_id = '' then
begin
Return_ERRORMSG('-99','上报的JOSN字符串media_id不能为空:' + JSON_Str,context);
Exit;
end;
PutLogFile('收到A: ' + JSON_Str);
//1. 首先判断本地存放图片的文件目录是否存在
Pic_IDCard_path := FBaseRootDir + '\Pic_IDCard';
if not DirectoryExists(Pic_IDCard_path) then ForceDirectories(Pic_IDCard_path);
//2. 构建文件名称
FileName := Pic_IDCard_path + '\' + User_id + '__' + media_id + '.jpg';
//3. 下载到本地
Filestream := TStringStream.Create;
try
if not Temporary_material_Get(Filestream, media_id, Media_Type) then
begin
Return_ERRORMSG('-99','获取图片失败 media_id:' + media_id,context);
Exit;
end;
Filestream.SaveToFile(FileName);
finally
Filestream.Free;
end;
PutLogFile('收到B: ' + FileName);
//4. 准备识别这张招照片
if not Distinguish_IDCard_Front_File(FileName, IDCard_info) then
begin
PutLogFile('收到c: 失败!');
Return_ERRORMSG('-99','识别图片失败!',context);
Exit;
end;
S := Format(JSON,[IDCard_info.name,IDCard_info.id,IDCard_info.addr,IDCard_info.gender,IDCard_info.nationality]);
B := TEncoding.UTF8.GetBytes(S);
context.Response.StatusCode := 200;
context.Response.Content.Write(B[0],Length(B));
context.Response.Close;
end;
3.3 通用JSON请求命令:999
如果前端发送这个命令,微信控件将会触发 TOn001_JSONRequest 这个事件。
procedure(const Request_JSON : string; var Response_JSON : string) of object;
参数说明:
序号 | 参数 | 说明 |
1 | Request_JSON | 前端请求的JSON字符串 |
2 | Response_JSON | 后端经过处理后返回给前端的JSON字符串 |
- 请求格式:http://sensorwx.a3650.com/wxh?what=999
- 请求方法:post
- 请求参数:Request_JSON,是一个JSON字符串
- 返回参数:Response_JSON,返回一个JSON字符串
4. 重要函数说明
序号 | 函数名称 | 说明 |
1 | SparkleGenericServerProcessRequest | 负责监听所有微信Web服务器的请求并分发回复,是控件TSparkleGenericServer的事件处理函数,这个是一个核心函数。 |
2 | FXDataServerGenericRequest | XData的底层监听处理函数 |
5. SparkleGenericServerProcessRequest函数处理流程图