wenmo8 发布的文章

在后台业务管理系统中使用Autofac实现微信接口的处理,我们只需要把相关使用到的DLL放到BIN目录里面即可,通过IOC控制反转方式实现对接口的调用。在实现在业务系统里面,我们本身程序可能已经依赖了很多相关的DLL类库,这种可以降低对相关DLL的强依赖,而以一种松耦合的方式使用我们所需要的微信接口。

在之前随笔《
C#开发微信门户及应用(42)--使用Autofac实现微信接口处理的控制反转处理
》里面介绍了Autofac组件实现IOC控制反转的一些实现细节,本篇随笔介绍在业务管理系统中,我们需要调用微信接口推送一些业务消息,如待办实现、通知信息等等。使用Autofac组件的控制反转方式,使得我们只需要关注接口的处理即可,不需要过于关注实现的具体细节。

1、功能处理的接口和实现项目

如我们创建一个项目,用于处理对微信或者第三方组件的一些封装处理,通过创建处理功能的接口,以及对应的实现,如下所示。

如对微信的处理,我们需要调用微信框架里面相关的接口封装项目,首先定义一个消息推送的接口

namespaceWHC.Common.Handler
{
/// <summary> ///企业微信消息推送接口/// </summary> public interfaceICorpMessage
{
/// <summary> ///企业微信的APPID/// </summary> string CorpId { get; set; }/// <summary> ///企业微信的APPSecret/// </summary> string CorpSecret { get; set; }/// <summary> ///企业微信应用的ID/// </summary> string AgentId { get; set; }/// <summary> ///发送消息内容(如任务通知)/// </summary> /// <param name="touser">发送给的用户企业微信ID</param> /// <param name="title">消息标题</param> /// <param name="description">消息正文(512字节内)</param> /// <param name="url">跳转URL</param> /// <returns></returns> CommonResult SendMessageTextCard(string touser, string title, string description, stringurl);
}

我们定义了几个属性,就是希望接口能够少传一些参数,而且可以在多个实现函数里面通用的。

具体的发送消息实现类如下所示。

namespaceWHC.Common.Handler
{
/// <summary> ///企业微信消息推送实现/// </summary> public classCorpMessage : ICorpMessage
{
/// <summary> ///企业微信的APPID/// </summary> public string CorpId { get; set; }/// <summary> ///企业微信的APPSecret/// </summary> public string CorpSecret { get; set; }/// <summary> ///企业微信应用的ID/// </summary> public string AgentId { get; set; }/// <summary> ///发送消息内容(如任务通知)/// </summary> /// <param name="touser">发送给的用户企业微信ID</param> /// <param name="title">消息标题</param> /// <param name="description">消息正文(512字节内)</param> /// <param name="url">跳转URL</param> /// <returns></returns> public CommonResult SendMessageTextCard(string touser, string title, string description, stringurl)
{
CommonResult result
= newCommonResult();
ICorpBasicApi baseBLL
= newCorpBasicApi();string token =baseBLL.GetAccessToken(CorpId, CorpSecret);if (!string.IsNullOrEmpty(token))
{
ICorpMessageApi bll
= newCorpMessageApi();
CorpSendTextCard msg
= newCorpSendTextCard(title, description, url);
msg.agentid
=AgentId;
msg.touser
=touser;

result
=bll.SendMessage(token, msg);
}
else{
result.ErrorMessage
= "无法获取Token信息";
}
returnresult;
}
}

这里最终调用的是微信框架里面的项目模块,如下代码所示

ICorpBasicApi baseBLL = new CorpBasicApi();

这里我们通过调用接口发送TextCard 文本卡片信息的。

2、在后台业务管理系统对推送信息接口的调用

首先为了使用IOC的控制反转处理,我们项目需要引用Autofac和Autofac.Configuration

然后把需要的配置信息卸载Autofac.Config文件里面,如下文件内容所示,红框里面的就是我们这里使用到的消息发送接口。

但我们完成了配置文件,并把配置文件放在项目根目录下,就可以通过IOC接口控制反转的方式,获得对应的接口实现了,如下代码所示

 //获取对应的企业微信消息推送接口
 var handler = AutoFactory.Instatnce.Container.Resolve<ICorpMessage>();

有了这个接口,我们就可以在实际项目中调用这个接口进行处理企业微信的消息推送了。

例如我们在Web的MVC控制器端实现一个处理函数,如下所示。

        /// <summary>
        ///批量处理多个任务下发企业微信/// </summary>
        /// <param name="billNo">多个billno组成的列表</param>
        /// <returns></returns>
        public ActionResult SendTask(stringbillNoList)
{
CommonResult result
= newCommonResult();try{if (!string.IsNullOrEmpty(billNoList))
{
//获取对应的企业微信消息推送接口 var handler = AutoFactory.Instatnce.Container.Resolve<ICorpMessage>();if (handler != null)
{
//把逗号分隔的字符串转换为列表 List<string> list = billNoList.ToDelimitedList<string>(",");foreach (string billNo inlist)
{
//获取盘点主表信息 AssetCheckInfo info = BLLFactory<AssetCheck>.Instance.FindByBillNo(billNo);if (info != null)
{
//获取盘点明细~信息~ var detailList = BLLFactory<AssetCheckDetail>.Instance.FindByBillNo(billNo);//读取配置信息 AppConfig config = newAppConfig();
handler.CorpId
= config.AppConfigGet("CorpId");
handler.CorpSecret
= config.AppConfigGet("CorpSecret");
handler.AgentId
= config.AppConfigGet("AgentId");//构建推送的消息体内容 string touser =info.CorpUserId;string title = "您有一个盘点任务待处理";
StringBuilder sb
= newStringBuilder();
sb.AppendFormat(
"盘点单号:{0}\r\n", info.BillNo);//sb.AppendFormat("盘点公司:{0}\r\n", info.Company_ID);//sb.AppendFormat("盘点部门:{0}\r\n", info.Dept_ID); sb.AppendFormat("盘点数量:{0}\r\n", info.CheckQty);
sb.AppendFormat(
"指定盘点人:{0}\r\n", info.CorpUserId);
sb.AppendFormat(
"申请日期:{0}\r\n", info.ApplyDate.ToString("yyyy-MM-dd"));string description =sb.ToString();string url = "http://www.iqidi.com";//调用企业微信消息接口推送消息 result =handler.SendMessageTextCard(touser, title, description, url);if(result.Success)
{
//更新盘点表状态 Hashtable ht = newHashtable();
ht.Add(
"TaskStatus", 1);//下发 1 BLLFactory<AssetCheck>.Instance.UpdateFields(ht, info.ID);
}
}
}
}
}
else{
result.ErrorMessage
= "单号为空";
}
}
catch(Exception ex)
{
LogHelper.Error(ex);
result.ErrorMessage
=ex.Message;
}
returnToJsonContent(result);
}

而在前端的界面里面,我们可以通过定义一个JS函数来发起任务消息的推动处理。

                $("#add").modal("hide");//构造参数发送给后台
                var postData ={
billNoList: billnos,
}
url
= '/AssetCheck/SendTask';
$.post(url, postData,
function(json) {var data =$.parseJSON(json);if(data.Success) {//可增加其他处理 //保存成功 1.关闭弹出层,2.刷新表格数据 showTips("下发盘点任务给微信成功");
$(
"#checkUser").modal("hide");
RefreshAsset();
}
else{
showError(
"下发盘点任务给微信失败:" + data.ErrorMessage, 3000);
}
}).error(
function() {
showTips(
"您未被授权使用该功能,请联系管理员进行处理。");
});

最终在我们完成盘点任务创建的时候,通知信息推送到了企业微信客户端和手机端的企业微信界面上。

当然其他客户端如果处理这种对IOC的接口调用,一样的原理,如Winform客户端,或者是其他.net的项目里面,我们都可以通过IOC实现对接口实现的控制反转,进一步解放强依赖的关系,实现松耦合的接口管理。

我们在部署的时候,把使用到的对应DLL复制过去对应的BIN目录下就可以运行起来了,在我们获取对应的接口的时候,相关的DLL会进行关联处理的。

企业号升级到企业微信后,发送应用消息的接口也变化了不少,除了原来的文本、图片、文件、语音、视频、图文消息等消息外,增加了文本卡片、markdown消息、小程序通知消息等内容,不过它们都可以共用一个接口进行发送,只是它们的对象不太一样,本篇随笔主要介绍整个企业微信应用消息处理这部分内容,包括不同消息的实体关系和接口发送的实现等内容。

1、企业微信消息对象的定义

在早期还是微信企业号的时候,我对发送企业号消息的接口也已经实现,参考《
C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)
》,这次对企业号升级到企业微信接口,对所有接口进行了梳理和测试。

我们先看看企业微信对应用消息接口的介绍(
https://work.weixin.qq.com/api/doc#90000/90135/90236

根据消息的类型,我们增加了一些额外的对象实体类,修改后的关系图如下所示。

我们来看看新增的文本卡片、markdown消息、小程序通知消息等内容的对象定义代码。

所有消息的基类信息CorpSendBase基类对象代码如下

    /// <summary>
    ///企业号发送消息的基础消息内容/// </summary>
    public classCorpSendBase
{
/// <summary> ///成员ID列表(消息接收者,多个接收者用‘|’分隔,最多支持1000个)。特殊情况:指定为@all,则向关注该企业应用的全部成员发送/// </summary> public string touser { get; set; }/// <summary> ///部门ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为@all时忽略本参数/// </summary> public string toparty { get; set; }/// <summary> ///标签ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为@all时忽略本参数/// </summary> public string totag { get; set; }/// <summary> ///消息类型/// </summary> public string msgtype { get; set; }/// <summary> ///企业应用的id,整型。可在应用的设置页面查看/// </summary> public string agentid { get; set; }/// <summary> ///表示是否是保密消息,0表示否,1表示是,默认0/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string safe { get; set; }

}

其他的文本卡片、markdown消息、小程序通知消息等内容的对象继承它,增加自己对象的消息即可。

如文本卡片的类及其子类的代码如下所示。

    /// <summary>
    ///文本卡片消息/// </summary>
    public classCorpSendTextCard : CorpSendBase
{
publicCorpSendTextCard()
{
this.msgtype =CorpMsgType.textcard;this.textcard = newCorpSendTextCardEntity();
}
public CorpSendTextCard(string title, string description, string url, string btntxt = "详情")
{
this.msgtype =CorpMsgType.textcard;this.textcard = newCorpSendTextCardEntity(title, description, url, btntxt);
}
/// <summary> ///消息内容/// </summary> public CorpSendTextCardEntity textcard { get; set; }
}
public classCorpSendTextCardEntity
{
/// <summary> ///标题,不超过128个字节,超过会自动截断/// </summary> public string title { get; set; }/// <summary> ///描述,不超过512个字节,超过会自动截断/// </summary> public string description { get; set; }/// <summary> ///点击后跳转的链接。/// </summary> public string url { get; set; }/// <summary> ///按钮文字。 默认为“详情”, 不超过4个文字,超过自动截断。/// </summary> public string btntxt { get; set; }publicCorpSendTextCardEntity()
{ }
public CorpSendTextCardEntity(string title, string description, string url, string btntxt = "详情")
{
this.title =title;this.description =description;this.url =url;this.btntxt =btntxt;
}
}

效果借用官方的效果图,如下所示

markdown消息对象如下所示

   /// <summary>
    ///markdown消息///目前仅支持markdown语法的子集///微工作台(原企业号)不支持展示markdown消息/// </summary>
    public classCorpSendMarkdown : CorpSendBase
{
publicCorpSendMarkdown()
{
this.msgtype =CorpMsgType.markdown;this.markdown = newCorpSendMarkdownEntity();
}
public CorpSendMarkdown(stringcontent)
{
this.msgtype =CorpMsgType.markdown;this.markdown = newCorpSendMarkdownEntity(content);
}
/// <summary> ///消息内容/// </summary> public CorpSendMarkdownEntity markdown { get; set; }
}

效果如下所示

小程序通知消息

    /// <summary>
    ///小程序通知消息/// </summary>
    public classCorpSendMiniProgram : CorpSendBase
{
publicCorpSendMiniProgram()
{
this.msgtype =CorpMsgType.miniprogram_notice;this.textcard = newCorpSendMiniProgramEntity();
}
/// <summary> ///消息内容/// </summary> public CorpSendMiniProgramEntity textcard { get; set; }
}

小程序的消息界面效果如下

有了这些消息的定义,我们就可以统一使用接口进行发送了。

2、发送企业微信信息

定义一个消息发送的接口,接口函数的参数,包括accesstoken和消息对象的基类,如下所示。

    /// <summary>
    ///企业微信消息管理接口定义/// </summary>
    public interfaceICorpMessageApi
{
/// <summary> ///发送消息。///消息型应用支持文本、图片、语音、视频、文件、图文等消息类型。主页型应用只支持文本消息类型,且文本长度不超过20个字。///需要管理员对应用有使用权限,对收件人touser、toparty、totag有查看权限,否则本次调用失败。/// </summary> /// <param name="accessToken"></param> /// <returns></returns> CommonResult SendMessage(stringaccessToken, CorpSendBase data);
}

实现接口的代码如下所示

        /// <summary>
        ///发送消息。///消息型应用支持文本、图片、语音、视频、文件、图文等消息类型。主页型应用只支持文本消息类型,且文本长度不超过20个字。///需要管理员对应用有使用权限,对收件人touser、toparty、totag有查看权限,否则本次调用失败。/// </summary>
        /// <param name="accessToken"></param>
        /// <returns></returns>
        public CommonResult SendMessage(stringaccessToken, CorpSendBase data)
{
CommonResult result
= newCommonResult();var url = string.Format("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}", accessToken);var postData =data.ToJson();//数据不用加密发送 CorpSendResult sendResult = WeJsonHelper<CorpSendResult>.ConvertJson(url, postData);if (sendResult != null)
{
result.Success
= (sendResult.errcode == 0);if (!result.Success)
{
result.ErrorMessage
= string.Format("invaliduser:{0},invalidparty:{1},invalidtag:{2}",
sendResult.invaliduser, sendResult.invalidparty, sendResult.invalidtag);
}
}
returnresult;
}

定义好相应的发送对象后,我们就可以进行统一的消息发送操作,包括文本、图片、文件、语音等等类型的消息,注意有些消息是需要上传到服务器上,然后在根据mediaId进行发送出去的。

程序测试接口发送的调用代码如下所示:

文本卡片发送代码

        /// <summary>
        ///文本卡片消息发送/// </summary>
        private void btnSendTextCard_Click(objectsender, EventArgs e)
{
ICorpMessageApi bll
= newCorpMessageApi();
CorpSendTextCard msg
= new CorpSendTextCard("中秋节礼品领取", "今年中秋节公司有豪礼相送", "http://www.iqidi.com", "更多详情");
msg.agentid
=agentid;
msg.touser
= "wuhuacong";

CommonResult result
=bll.SendMessage(token, msg);if (result != null)
{
Console.WriteLine(
"发送TextCard消息:{0} {1} {2}", fileMediaId, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}
}

Markdown的发送代码如下所示。

        /// <summary>
        ///MarkDown消息发送/// </summary>
        private void btnSendMarkDown_Click(objectsender, EventArgs e)
{
ICorpMessageApi bll
= newCorpMessageApi();string content = @"您的会议室已经预定,稍后会同步到`邮箱`
>**事项详情**
>事 项:<font color='info'>开会</font>
>组织者:@miglioguan
>参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang
>
>会议室:< font color ='info'>广州TIT 1楼 301</font>
>日期:< font color ='warning'>2018年5月18日</font>
>时间:< font color ='comment'>上午9:00-11:00</font>
>
> 请准时参加会议。
>
> 如需修改会议信息,请点击:[修改会议信息] (https://work.weixin.qq.com)
";
CorpSendMarkdown msg
= newCorpSendMarkdown(content);
msg.agentid
=agentid;
msg.touser
= "wuhuacong";

CommonResult result
=bll.SendMessage(token, msg);if (result != null)
{
Console.WriteLine(
"发送Markdown消息:{0} {1} {2}", fileMediaId, (result.Success ? "成功" : "失败"), result.ErrorMessage);
}
}

在开发业务管理系统的时候,往往涉及到资产信息及编码的打印处理,如我们需要对资产信息、条形码、二维码一起打印,以便贴在具体资产信息上面,方便微信公众号、企业微信进行业务处理,那么编码的打印就很有必要了,本篇随笔介绍实际生产环境中的资产编码打印处理。

打印操作,可以使用多种方式处理,如使用PrintThis的JS库打印,或者使用用户界面比较好的LODOP插件来进行打印操作,本篇随笔介绍基于LODOP插件来对资产编码信息的打印操作;另外打印给的时候,如果不恰当的设置,估计打印信息部分打印,那这种情况也是需要避免的,需要人为的增加分页的处理,以便整个编码信息打印正确出来。

1、单个编码信息的打印

打个记录我们可以在表格的后面按钮增加一个处理操作,如下界面所示。

然后触发一个弹出层进行展示即可,如果用户需要打印,则在右下角进行打印。

打印界面如下所示。

以上就是几个操作的界面效果,实际处理代码是如何的呢,我们如何调用LODOP进行页面打印的呢?

定义一个弹出层用来展示资产编码信息,如下所示。

<!--------------------------显示二维码的弹出层---------------------------->
<divid="qrcode"class="modal fade"tabindex="-1"role="dialog"aria-labelledby="myModalLabel"aria-hidden="true">
    <divclass="modal-dialog">
        <divclass="modal-content">
            <divclass="modal-header bg-primary">
                <buttontype="button"class="close"data-dismiss="modal"aria-hidden="true"></button>
                <h4class="modal-title">
                    <iclass="icon-book-open"></i>
                    <spanstyle="font-weight:bold">查看二维码信息</span>
                </h4>
            </div>
            <divclass="modal-body"id="showCodeDiv">
                <tableborder='0'style="width:180px"id="qrcodePrint">
                    <tr>
                        <td>
                            <tableborder='0'style="width:180px;page-break-after:always">
                                <tr>
                                    <td>
                                        <divstyle="padding-left:5px">资产名称:<labelid="lblName"></label></div>
                                        <divstyle="padding-left:5px">管理部门:<labelid="lblChargeDept"></label></div>
                                        <divstyle="padding-left:5px">使用部门:<labelid="lblCurrDept"></label></div>
                                        <divstyle="padding-left:5px">使用人:<labelid="lblUsePerson"></label></div>
                                        <divstyle="padding-left:5px">存放地点:<labelid="lblKeepAddr"></label></div>
                                        <imgalt=""id="img_barcode" />
                                    </td>
                                    <td>
                                        <divid="img_qrcode"style="padding:5px"></div>
                                    </td>
                                </tr>
                            </table>
                        </td>
                    </tr>
                </table>

            </div>
            <divclass="modal-footer bg-info">
                <buttontype="button"class="btn btn-primary"data-dismiss="modal"onclick="PrintQrcode()">打印文档</button>
                <buttontype="button"class="btn default"data-dismiss="modal">关闭</button>
            </div>
        </div>
    </div>
</div>

这里的二维码我使用了qrcodejs插件,条形码使用JsBarcode插件

引入相关的JS文件就可以使用了

<scriptsrc="~/Content/JQueryTools/qrcodejs/qrcode.min.js"></script>
<scriptsrc="~/Content/JQueryTools/JsBarcode/dist/JsBarcode.all.min.js"></script>

通过Ajax在后端获取对象数据,然后绑定信息界面上来,如果是二维码,条形码,则根据对应的接口JS代码进行处理。

        //显示二维码
        functionShowCodeByIds(ID) {if (ID == "") {
showTips(
"请选择一条记录");return;
}
//发送请求 $.getJSON("/Asset/FindByID?r=" + Math.random() + "&id=" + ID, function(info) {
$(
"#lblCode").text(info.Code);
$(
"#lblName").text(info.Name);
$(
"#lblCurrDept").text(info.CurrDept);
$(
"#lblChargeDept").text(info.ChargeDept);
$(
"#lblUsePerson").text(info.UsePerson);
$(
"#lblKeepAddr").text(info.KeepAddr);var code =info.Code;
$(
"#img_qrcode").empty();var qrcode = new QRCode(document.getElementById("img_qrcode"), {
text: code,
width:
100,
height:
100,
colorDark:
"#000000",
colorLight:
"#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
JsBarcode(
"#img_barcode", code, {
format:
"CODE128",
displayValue:
true,
fontSize:
20,
lineColor:
"#0cc",
height:
20,
width:
1.5});
});

$(
"#qrcode").modal("show");
}

打印的代码很简单,主要就是构建HTML打印数据后,调用对象打印即可,代码如下所示。

        //打印数据构建
        var LODOP; //声明为全局变量
        functionCreatePrintData(html) {
LODOP
=getLodop();

LODOP.PRINT_INIT(
""); var strBodyStyle = "<style><!--table { border:1;background-color: #CBCBCC } td {background-color:#FFFFFE;border: 1; }";
strBodyStyle
+= " th { background-color:#F1F1F3;padding-left:5px;border:1}--></style>";var strFormHtml = strBodyStyle + "<body>" + html + "</body>";
LODOP.ADD_PRINT_HTM(
10, 10, 710, "100%", strFormHtml);
LODOP.PREVIEW();
}
//资产编码的打印 functionPrintQrcode() {
CreatePrintData($(
"#qrcodePrint").html());
LODOP.SET_SHOW_MODE(
"PREVIEW_IN_BROWSE", 1);
LODOP.PREVIEW();
}

2、批量记录打印

单个记录打印没问题,有时候,需要多个记录一起打印,这样可以提高效率。

原本想不提供界面,直接生成HTML进行输出打印,不过这个方式处理的话,条形码工作正常但二维码倒是没有出来,只好在界面展示后,在通过按钮进行打印,这样就可以解决这个问题。

先编写一个批量打印的弹出层,用在承载界面内容。

<!--------------------------显示批量打印记录的弹出层---------------------------->
<divid="codeDiv"class="modal fade"tabindex="-1"role="dialog"aria-labelledby="myModalLabel"aria-hidden="true">
    <divclass="modal-dialog">
        <divclass="modal-content">
            <divclass="modal-header bg-primary">
                <buttontype="button"class="close"data-dismiss="modal"aria-hidden="true"></button>
                <h4class="modal-title">
                    <iclass="icon-book-open"></i>
                    <spanstyle="font-weight:bold">查看二维码信息</span>
                </h4>
            </div>
            <divclass="modal-body">
                <divclass="row"id="hiddenDiv">
                </div>
            </div>
            <divclass="modal-footer bg-info">
                <buttontype="button"class="btn btn-primary"data-dismiss="modal"onclick="AssetPrintOK()">打印文档</button>
                <buttontype="button"class="btn default"data-dismiss="modal">关闭</button>
            </div>
        </div>
    </div>
</div>

增加一个按钮的处理入口代码。

<buttontype="button"onclick="AssetPrint()"class="btn btn-circle btn-sm red">
    <iclass="fa fa-plus"></i>资产编码打印</button>

然后编写一个函数,用来选定记录,并动态赋值HTML代码

        functionAssetPrint() {var rows = $table.bootstrapTable('getSelections');if (rows.length == 0) {
showTips(
"请选择你要打印编码的记录");return;
}
var div = $("#hiddenDiv");
div.empty();
div.append(
"<div class='row'>");for (var i = 0; i < rows.length; i++) {
AppendCodeItem(rows[i].Code, rows[i].Name, rows[i].CurrDept, rows[i].ChargeDept, rows[i].UsePerson, rows[i].KeepAddr);
if (i > 0 && i % 3 == 0) {
div.append(
"<p style='page-break-after:always'>&nbsp;</p>");//分页打印 }
}
div.append(
"</div>");

$(
"#codeDiv").modal("show");
}

其中主要的逻辑封装在AppendCodeItem函数里面,这个函数就是批量根据数据进行处理的。

        //动态追加HTML内容在对应的层上,并初始化条形码和二维码的值
        functionAppendCodeItem(Code, Name, CurrDept, ChargeDept, UsePerson, KeepAddr) {var div = $("#hiddenDiv");

div.append(
"<div class='col-md-6'>");
div.append(
"<div class='form-group' style='padding-left:5px'>");
div.append(
"<label class='col-md-12'>资产名称:" + Name + "</label><br/>");
div.append(
"<label class='col-md-12'>管理部门:" + CurrDept + "</label><br/>");
div.append(
"<label class='col-md-12'>使用部门:" + ChargeDept + "</label><br/>");
div.append(
"<label class='col-md-12'>使用人:" + UsePerson + "</label><br/>");
div.append(
"<label class='col-md-12'>存放地点:" + KeepAddr + "</label><br/>");
div.append(
"</div>");//form-group div.append("</div>");//md-6 //加入一个dom,然后绑定二维码 div.append("<div class='col-md-6' style='padding-left;' id='qr_" + Code + "'></div>");//加入一个dom,然后绑定条形码 div.append("<div class='col-md-12'>")
div.append(
"<img id='bar" + Code + "'/>");
div.append(
"</div>");

div.append(
"<hr/>");var barname = "#bar" +Code;
JsBarcode(barname, Code, {
format:
"CODE128",
displayValue:
true,
fontSize:
20,
lineColor:
"#0cc",
height:
20,
width:
1.5});var qrname = "qr_" +Code;//Jquery.qrcode代码 //$("#" + qrname).qrcode({ width: 64, height: 64, text: Code }); //QRCodejs的语法 $("#" +qrname).empty();var qrcode = newQRCode(document.getElementById(qrname), {
text: Code,
width:
80,
height:
80,
colorDark:
"#000000",
colorLight:
"#ffffff",
correctLevel: QRCode.CorrectLevel.H
});
}

批量弹出层界面如下所示。

打印效果如下所示

这里本来想和单个界面设计一样的,不过布局好像不知道是受到什么原因的影响,多个记录的排布总是不理想,因此只好把条形码、二维码挨着放置了。

3、打印的分页处理

上面的代码我们做了打印的分页处理,如果不这样处理,则会导致一个标签信息可能不完整,如下效果所示。

这样如果要裁剪标签的话,这个就会作废了,需要重新打印,但是我们可以通过分页打印进行处理这个问题。

分页打印不复杂,只需要在对应的地方增加一个标记,标注为打印分页部分即可,如下所示。

<pstyle='page-break-after:always'>&nbsp;</p>

那么我们在实际测试,大概几个标签就会排满的时候,增加这个分页的标记就可以了,如下所示。

for (var i = 0; i < rows.length; i++) {
AppendCodeItem(rows[i].Code, rows[i].Name, rows[i].CurrDept, rows[i].ChargeDept, rows[i].UsePerson, rows[i].KeepAddr);
if (i > 0 && i % 3 == 0) {
div.append(
"<p style='page-break-after:always'>&nbsp;</p>");//分页打印 }
}

在实际业务系统的开发中,往往需要几个系统协同一起出来同一个数据库的数据,数据库可以是同一个数据库,也可以根据业务拆分的多个数据库,如我们企业微信的应用、后台业务管理系统、以及专门为数据提供服务的API服务接口等,这样可以极大程度上给我们的业务数据提供支撑,并根据不同的特性进行分开管理和维护。

1、几个系统的协作关系

就上面的示意图来说,我们分为了三个部分,一个是微信管理后台,一个是业务管理后台,一个是对外API接口服务,不同的系统负责不同的功能。

微信管理系统,我以微信开发框架为基础进行扩展开发,满足多账户的管理、多类型账户管理的需求,如可以配置管理公众号、企业微信、小程序等信息,实现菜单和业务接口的对接关系。

业务管理系统,我以Winform混合框架或者Bootstrap框架为桥梁,构建特定的业务管理系统,这些数据都是来源于云端同一份数据库,这样有利于数据库的集中和生产环境数据的控制管理。

1)Web API模式介绍

对外接口API服务,我以Web API接口服务,发布可以用于多个系统、或者对外的API服务,提供数据查询或者业务数据处理的服务。

如果考虑Web API优先,我们可以所有系统都基于Web API 基础进行开发。

Web API层作为一个公共的接口层,我们就很好保证了各个界面应用层的数据一致性,不过这样可能没有直接开发来的快捷。

2)混合框架模式介绍

混合框架是Winform界面的分布式系统。

混合框架的核心是以相对独立的方式,整合各个可重用的模块,如权限管理、字典管理、附件管理、人员管理等模块,我们可以遵循一定的基础上,快速构建统一的应用平台。这个是基于WInform构建的分布式应用,它是通过Web API和服务端进行交互的。

大致来说,系统的界面效果如下所示,根据业务模块的不同,我们可以动态增加很多菜单模块,以方便对业务数据的管理。

例如我们基于混合框架基础上开发的CRM客户关系管理系统。

其中权限管理模块是通用的,字典管理模块也是一样,直接调用就可以了。

3)Bootstrap的Web框架介绍

我们开发的基于MVC的Bootstrap的Web开发框架,可以快速实现后台框架代码和界面代码的生成,能够快速应用于实际项目的开发中去,同样也是模块的整合,和混合框架的权限、字典等基础模块通用,并重用同一个数据库架构信息。

根据不同的业务需要,我们可以开发不同的业务系统管理应用。

这个系统基于MVC的Web业务快速开发,可以通过代码生成工具进行后台代码的开发,界面快速生成等处理,可以快速生成分页,查询,打印,导入,导出、编辑、增加、查看等处理功能。

4)微信开发框架介绍

微信开发框架也是基于Bootstrap开发框架基础上进行开发的,采用基于
MVC + Bootstrap + Ajax + Enterprise Library
的技术路线,界面层采用Boostrap + Metronic组合的前端框架,数据访问层支持Oracle、SQLServer、MySQL、PostgreSQL等数据库。

微信开发框架整合了公众号、企业微信、小程序等不同类型的账号管理,并对这几个类型的微信账号提供服务端接口的封装,以及各种业务案例。

企业微信的菜单管理界面

为了有效的开发不同的应用开发,我们底层数据库完全兼容,各个模块底层的逻辑也是相同的,这样就包含Winform框架、Winform混合框架、Bootstrap业务系统、微信后台管理系统这些系统模块的重用,以及数据库的重用,特别是菜单模块、权限模块、字典模块等基础信息完全一致的使用,管理起来更加方便。

当我们需要结合企业微信和业务系统的时候,我们需要建立一个企业微信应用,然后在微信管理后台中绑定对应参数,这样可以利用企业微信的服务端API对接相关的功能,包括提交菜单,以及获取对应的企业微信组织机构、发送消息等常规操作;而业务系统则可以把对应的业务流程和企业微信进行对接,包括消息的推送,以及利用JSSDK对相关业务数据的展示和处理等。本篇随笔介绍在利用企业微信前的相关处理步骤,如创建企业应用,绑定企业应用信息,以及相关的业务处理交互等。

1、创建企业微信应用

首先我们需要注册申请一个企业微信的账号,注册申请需要提交相关的企业资质信息,这里就不再赘述。

有了企业微信账号后,我们扫码进入企业微信的管理后台,在【应用与小程序】模块中创建一个应用,这个就是我们对应的业务应用了,企业微信可以创建多个不同场景的业务应用,其本身也自带了很多相关的应用在里面。

创建应用,我们需要录入相关的应用信息和Logo图标信息,如下界面所示。

例如我创建的一个企业微信应用,我们记住它的应用ID和它的秘钥,这个是一个很重要的信息,需要绑定在微信系统里面,我们后面对相关的企业微信服务端API接口调用,都需要利用到这些参数的。

如果我们的企业微信应用设计到支付信息的,需要打开企业支付的应用获得对应的秘钥信息的,如下所示。

打开【企业支付】就可以看到支付对应的密钥了,这个很重要,如我们需要利用企业微信发送红包或者直接付款给员工的时候,这个就需要用到了,如我前面随笔介绍的《
企业微信支付的发送红包及相关接口使用

2、绑定企业微信相关参数

在企业微信后台创建对应的应用,并获得对应的应用ID、密钥等信息后,我们可以在微信后台管理系统里面录入我们获得的应用信息了。

一般来说,微信后台管理系统能够管理不同类型:公众号、企业微信、小程序等类型的账号信息,我们在对应的分类里面录入相关的企业应用信息,如下录入界面所示。

有了这些对应的信息后,我们可以对企业微信应用编辑相关的菜单信息,然后通过接口提交到微信服务端上去,这样我们的应用就具有了我们自定义的业务菜单了,如下是微信管理系统里面对菜单的管理。

菜单列表管理界面如下所示。

这样配置好菜单并设置了相关的处理事件或者对应的JSSDK页面路径后,我们就可以提交到服务端上去,马上就可以看到企业应用的菜单变化了。

以上就是实际应用的菜单界面效果,这样我们企业应用就有了相关的处理入口了,有些是扫码事件,有些是自定义事件,有些则是JSSDK编写的业务入口,如资产录入、盘点任务等。

3、业务处理对接

有了具体的菜单入口,我们需要处理我们入口的处理逻辑了,如果是扫码,我们需要在后台进行一定的事件响应,如在资产查看里面扫码后发送一个文本信息,供跳转到相关的查看资产信息界面上去。

在之前随笔介绍过对这些事件的处理,如《
C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密
》,如果需要了解企业微信发送消息的过程,可以参考下《
企业微信发送应用消息的实现
》随笔的介绍即可。

例如对于扫码入口,企业微信的后台对这些事件进行捕捉,并匹配到对应的处理模块上去处理,如下代码所示。

        /// <summary>
        ///扫码推事件且弹出“消息接收中”提示框的事件推送的处理/// </summary>
        /// <param name="info">扫描信息</param>
        /// <returns></returns>
        public stringHandleEventScancodeWaitmsg(RequestEventScancodeWaitmsg info, AccountInfo accountInfo)
{
string result = "";try{var handler = AutoFactory.Instatnce.Container.Resolve<IQRCodeHandler>();if (handler != null)
{
result
=handler.HandleScancodeWaitmsg(info, accountInfo);
}
}
catch(Exception ex)
{
LogHelper.Error(ex);
}
returnresult;
}

通过 AutoFactory.Instatnce.Container.Resolve<IQRCodeHandler>(); 我们可以看到业务的流程调整到了IOC的一个配置处理模块上去了,这里利用了Autofac的配置信息自动加载对应的处理模块。

对于扫码处理逻辑,我们显示根据资产信息,构建一个文本消息发给企业微信客户端,然后引导用户打开响应的连接就可以跳转到对应的资产信息查看界面上去了,如下所示。

        /// <summary>
        ///处理扫码结果/// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private stringHandleScanResult(RequestEventScanCode info, AccountInfo accountInfo)
{
ResponseText response
= newResponseText(info);if (info.ScanCodeInfo != null)
{
response.Content
= string.Format("您的信息为:{0},可以结合后台进行数据查询。", info.ScanCodeInfo.ScanResult);var isUrl =ValidateUtil.IsURL(info.ScanCodeInfo.ScanResult);if (!isUrl)
{
string assetcode =info.ScanCodeInfo.ScanResult;if (!string.IsNullOrEmpty(assetcode))
{
response.Content
=ConvertAssetInfo(info, accountInfo, assetcode);
}
else{
response.Content
= string.Format("扫码内容为空。");
}
}
}
var result =response.ToXml();returnresult;
}
/// <summary> ///转换资产信息为文本消息/// </summary> /// <returns></returns> private string ConvertAssetInfo(RequestEventScanCode info, AccountInfo accountInfo, stringassetcode)
{
StringBuilder sb
= newStringBuilder();
BLLFactory
<Asset>.Instance.SetConfigName("workflow");var assetInfo = BLLFactory<Asset>.Instance.FindByCode(assetcode);if (assetInfo != null)
{
//~资产代码~、资产名称、管理部门、使用部门、使用人、存放地点、数量、资产动态、在用类型 sb.AppendFormat("资产代码:{0}", assetInfo.Code).Append("\n");
sb.AppendFormat(
"资产名称:{0}", assetInfo.Name).Append("\n");
sb.AppendFormat(
"管理部门:{0}", assetInfo.ChargeDept).Append("\n");
sb.AppendFormat(
"使用部门:{0}", assetInfo.CurrDept).Append("\n");
sb.AppendFormat(
"使用人:{0}", assetInfo.UsePerson).Append("\n");
sb.AppendFormat(
"存放地点:{0}", assetInfo.KeepAddr).Append("\n");
sb.AppendFormat(
"数量:{0}", assetInfo.Qty).Append("\n");
sb.AppendFormat(
"资产动态:{0}", assetInfo.Status).Append("\n");
sb.AppendFormat(
"在用类型:{0}", assetInfo.UseType).Append("\n");

sb.AppendFormat(
"<a href='{0}/QyH5/AssetInfo?code={1}' >点击查看详细信息</a>", WebsiteDomain, assetInfo.Code);
}
else{
sb.AppendFormat(
"资产代码【{0}】不存在。", assetcode).Append("\n");
sb.AppendFormat(
"<a href='{0}/QyH5/Asset?devicecode={1}' >点击添加设备信息</a>", WebsiteDomain, assetcode);
}
returnsb.ToString();
}

其他的部分入口是通过编写JSSDK页面的方式实现业务数据的处理的,在JSSDK编写里面,我们有时候需要获取当前登录的用户身份信息,如企业微信的userid,那么我们就可以通过跳转的方式获取code,然后根据对应的code解析为userid即可。

            //获取企业微信账号信息
            var accountInfo =GetAccount(ConfigData.CorpAccountNo);//如果传入了userid,使用传入的参数
            if (string.IsNullOrEmpty(userid))
{
//通过重定向的code获取对应的UserId userid =GetUserId(accountInfo.AppID, accountInfo.AppSecret);
}

而为了避免反复的解析code参数导致出错(code只能被用一次,后续再用会出错的),那么我们可以把用户的userid存放在session里面,这样判断如果这个Session不存在了,我们再解析code就没问题了。

        /// <summary>
        ///根据当前的Code获取对应的openid(获取获取openid的参数)/// </summary>
        /// <param name="accountInfo">登陆账号信息</param>
        /// <returns></returns>
        protected virtual string GetUserId(string appid, stringappsecret)
{
string userId = Request.QueryString["userid"];if (string.IsNullOrEmpty(userId))
{
var user_id = Session["user_id"];if (user_id != null)
{
userId
=user_id.ToString();
}
else{//如果没有传递userId,那么就根据code参数获取userId string code = Request.QueryString["code"];if (!string.IsNullOrEmpty(code))
{
string accessToken =baseApi.GetAccessToken(appid, appsecret);var result =baseApi.GetUserInfo(accessToken, code);if (result != null)
{
userId
=result.UserId;
Session[
"user_id"] = userId;//存储在Session }
}
}
}
returnuserId;
}

下面是一个资产信息录入的界面效果,利用JSSDK进行编写的页面。

其中的一些选择框,我们可以填写相关的字典数据,或者列表信息供选择。

这些数据最终可以提交到业务管理系统里面,从而实现了企业微信和业务管理系统的数据流对接。

还有一个如盘点任务一样的工作,我们可以交给企业微信端进行处理,通过手机进行移动端的数据处理,更加方便。

以上就是企业微信和业务管理系统的交互过程的一部分,我们具体可以根据自己的业务需要,扩展很多相关的处理页面。

通过整合企业微信和业务系统的数据流,我们可以更加方便的了解企业业务信息,也更加方便的利用手机终端进行一些快捷的查询或者处理业务操作。