2023年2月

有时候,为了给前端页面输出内容,有时候我们需要准备和数据库不一样的实体信息,因为数据库可能记录的是一些引用的ID或者特殊字符,那么我们为了避免前端单独的进行转义处理,我们可以在后端进行统一的格式化后再行输出,后端处理可以采用不同的DTO尸体信息,后端对不同的实体进行映射处理即可,也可以采用同一个实体,在SqlSugar实体信息中忽略对应的字段写入实现,本篇随笔介绍后者的处理方式,实现在在工作流列表页面中增加一些转义信息的输出处理。

1、后端的转义处理

大多数页面,我们的前端显示信息DTO和后端的数据库实体信息Entity是一致的,只有部分信息的差异,特别在工作流模块中,由于继承原来历史的数据库设计结构,因此很多引用的字段是int类型的,那么为了避免前端对内容的频繁解析,因此必要的时候在后端对内容进行统一的处理,实现内容的转义。

例如我们以其中的模板流程的实体信息定义来看,除了对常规的信息,我们还需要对一些转义信息的处理。

如实体类对应字段的SqlSugar的标识,只需要增加SqlsugarColumn的标识即可。

        [SqlSugar.SugarColumn(ColumnName = "PROC_TYPE")]public virtual int ProcType { get; set; }

如下所示的实体类

如果我们需要额外增加一些信息的承载,而在保存或者提取数据库字段信息的时候,进行忽略处理,那么标识为Ignor即可。

        [SqlSugar.SugarColumn(IsIgnore = true)]public virtual string ProcTypeName { get; set; }

如下实体类代码所示

有了实体信息的定义,我们在SqlSurgar框架的服务层返回列表信息的时候,可以对列表的内容进行统一的转换,而列表返回是在基类定义的统一泛型函数,如下定义所示。

        /// <summary>
        ///根据条件获取列表/// </summary>
        /// <param name="input">分页查询条件</param>
        /// <returns></returns>
        public virtual async Task<PagedResultDto<TEntity>>GetListAsync(TGetListInput input)
{
var query =CreateFilteredQueryAsync(input);var totalCount = awaitquery.CountAsync();

query
=ApplySorting(query, input);
query
=ApplyPaging(query, input);var list = awaitquery.ToListAsync();return new PagedResultDto<TEntity>(
totalCount,
list
);
}

因此需要在继承的子类中重写一下进行处理,如下代码所示。

而对于附加信息的多少,则根据我们的业务规则适当调整即可,有些实体信息附加的内容可能会多一些,有些会少一些,有些可能保存原状即可。

2、前端的列表显示

介绍了后端的内容转义,前端相对处理就比较简单了,只需要把对应的内容进行显示即可。如前端的Vue3+TypeScript+ElementPlus的代码如下。

<!--表格列表信息-->
<el-tablev-loading="loading":data="list"border
fit
stripe
highlight-current-row
:header-cell-style
="{ background: '#eef1f6', color: '#606266' }"@selection-change="selectionChange"@row-dblclick="rowDbclick"@sort-change="sortChange" > <el-table-columntype="selection"width="40" /> <el-table-columnalign="center"sortable="custom"prop="proc_Name"label="流程环节名称"> <templatev-slot="scope">{{ scope.row.procName }}</template> </el-table-column> <el-table-columnalign="center"sortable="custom"prop="proc_Type"label="处理类型"> <templatev-slot="scope">{{ scope.row.procTypeName}}</template> </el-table-column> <el-table-columnalign="center"sortable="custom"prop="form_ID"label="对应表单"> <templatev-slot="scope">{{ scope.row.formName ?? '所有表单' }}</template> </el-table-column>

js代码也只需简单的获取对应list的分页列表即可。前端没有额外增加工作量。

工作流部分转义页面显示效果如下所示。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面


基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍


基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理

在工作流页面中,除了特定的业务表单信息外,往往也需要同时展示通用申请单的相关信息,因此在页面设计的时候需要使用一些组件化的概念来实现动态的内容展示处理,本篇随笔介绍Vue3+TypeScript+ElementPus的前端工作流模块中实现统一的表单编辑和表单详情查看处理。

1、查看申请单的模块设计处理

在工作流处理表中,首先我们区分流程模板和流程实例两个部分,这个其实就是类似模板和具体文档的概念,我们一份模板可以创建很多个类似的文档,文档样式结构类似的。同理,流程模板实例为流程实例后,就是具体的一个流程表单信息了,其中流程模板和流程实例表单都包括了各个流程步骤。在流程实例的层次上,我们运行的时候,需要记录一些日志方便跟踪,如流程步骤的处理日志,流程实例表单的处理日志等这些信息。

一旦流程实例根据模板创建后,流程先根据模板初始化后,在处理过程还可以动态增加一些审批步骤,使得我们的处理更加弹性化。

当然,为了更好的处理流程的相关信息,还需要记录流程处理人,流程会签人、流程阅办人,以及常用审批意见等相关辅助表,以便对流程的各个处理信息进行合理处理和展示。

对于一个流程处理操作,我们知道一般有审批通过、拒绝、退回到某步骤、转发到内部阅读、阅读,以及包括起草者能撤销表单呢等操作,当然如果还有一些具体的业务,可能还会有一些流程的处理才操作,不过基本上也可以归结为上面几种,只是他们每步处理的数据内容不同而已。因此审批的操作步骤分类如下所示。

在基于 Vue3+TypeScript+ElementPus的前端工作流模块中,我们在查看表单明细的时候,需要包含公用表单信息,特定表单信息两部分内容。前者表单数据可以统一呈现,而后者则是不同业务的表单数据不同。为了实现更好的维护性,把它们分开作为两部分处理,但是页面入口设计为统一的呈现页面。

表单数据按内容区分分为了两类:通用业务表单、特定业务表单

如果我们要把两者统一在一个通用页面中进行展示,就需要根据不同表单名称,动态加载属于特定表单的展示模块,也就是动态组件的实现方式,大概的业务规则如下所示。

页面效果如下图所示。

而编辑界面也是类似,通过动态化组件的方式合并公用信息和特定表单组件信息。

2、查看、编辑页面路由设置及项目视图目录

通过动态化组件的呈现处理,可以实现编辑和查看申请单页面的动态呈现,我们Vue的前端也可以只需要定义一个查看页面路由,和一个编辑界面的路由即可,极大的降低开发代码和复杂度。如下面是路由的JSON文件中关于查看、编辑页面的路由信息。

在 Vue3+TypeScript+ElementPus的前端项目中,我们创建了几个不同的目录来放置不同的页面代码,如edit是编辑特定表单的组件页面目录,view是查看特定表单的组件页面目录,list则是该表单的详细列表信息,而system工作流系统的管理页面等等,如下图所示。

其中Edit、View目录下,都是对应表单名称的页面组件(页面代码)

在通用的查看表单页面中,我们定义了两个部分的内容,包括公用处理单的信息,以及特定表单的信息展示,如下代码所示。

而特定表单的内容展示,这是通过动态化组件的呈现方式(is)来指定具体渲染的那个页面组件

而通用的申请单编辑页面中,则是动态展示编辑对应组件页面的信息即可,如下所示。

而动态组件的处理,我们使用vue3的
defineAsyncComponent
(需要了解可以查看官网)的处理方式进行加载对应组件页面。

我们在ts的setup代码块中的代码如下所示。

let viewType = ref(null); //查看明细的组件类型

//根据申请单的模块类型定义,确定组件名称
functiongetViewType() {if(applyid.value) {//一般规则:通过申请单的DataTable去掉前缀,转换小写,获得模块名称,如TW_Payment => payment
    var param ={ applyId: applyid.value };
apply.GetModuleName(param).then(data
=>{if(data) {
console.log(data);
let pageComponent
= defineAsyncComponent(() => import(`/@/views/workflow/modules/view/${data.toLowerCase()}.vue`));//console.log(pageComponent); viewType.value =markRaw(pageComponent);
}
});
}
}

而其中viewType就是我们组件的名称,这里能够呈现出来的内容,必须是这些组件在对应的工作流目录中的,通过动态的加载方式,可以实现页面组件的动态渲染处理了。

而我们定义的表单内容可能很多,如下目录所示。

其中我们以报销申请单的查看页面来了解,页面展示部分如下代码所示。

      <el-formref="viewRef":model="viewForm"label-width="120px">
        <el-tabstype="border-card">
          <el-tab-panelabel="基本信息">
            <el-descriptionstitle="":column="2"border>
              <el-descriptions-itemlabel="报销类型">{{ viewForm.category }}</el-descriptions-item>
              <el-descriptions-itemlabel="报销事由">{{ viewForm.reason }}</el-descriptions-item>
              <el-descriptions-itemlabel="总金额">
                <el-inputv-model="viewForm.totalAmount"disabled style="width: 150px">
                  <template#suffix></template>
                </el-input>
              </el-descriptions-item>
              <el-descriptions-itemlabel="备注信息":span="2">{{ viewForm.note }}</el-descriptions-item>
              <el-descriptions-itemlabel="明细清单":span="2">
                <vxe-tableref="xTable"stripe highlight-current-row highlight-hover-row :data="detailData">
                  <vxe-columntype="seq"width="60" />
                  <vxe-columnfield="feeType"title="费用类型"width="100" />
                  <vxe-columnfield="occurTime"title="发生时间"width="250" />
                  <vxe-columnfield="feeAmount"title="费用金额"width="100" />
                  <vxe-columnfield="feeDescription"title="费用说明"width="250" />
                </vxe-table>
              </el-descriptions-item>
              <el-descriptions-itemlabel="附件":span="2">
                <my-uploadv-model="viewForm.attachGUID":disabled="true":data="{ guid: viewForm.attachGUID, folder: '申请单图片' }" />
              </el-descriptions-item>
            </el-descriptions>
          </el-tab-pane>
        </el-tabs>
      </el-form>

主从表的数据,我们通过函数来实现加载处理,而后端对应提供相关的数据结构即可。

//挂载的时候初始化数据
onMounted(async () =>{
await getData();
//打开新增窗体的时候,初始化公司列表 });functiongetData() {
let applyid
= props.applyid + '';if(applyid) {
reimbursement.FindByApplyId(applyid).then(data
=>{
Object.assign(viewForm, data);
//获取从表明细记录 var headerId =viewForm.id;
reimbursement.FindDetailByHeaderId(headerId).then(data
=>{
detailData.value
=data;
});
});
}
}

对于审批,我们提供一些通过、退回、取消的申请单处理操作。

审批表单的界面

发起会签操作界面

撤销自己表单的处理界面

由于申请单的各种类型节点的处理不同,如果页面引入这些会显得很臃肿,因此我们把这些处理步骤组件化,然后再通过查看页面中整合审批、发起会签、会签、批示阅办、撤销、阅办等操作即可。

这样我们把一些常用节点的处理,单独作为组件开发,放置在组件目录中即可,方便维护。

Vue的组件化,可以简化页面的内容处理,把特定的部分放在一个组件中实现,更好的实现关注点的分离,以及可以自由组合更好的页面效果。

而为了方便,特定处理单的列表页面,我们也提供了查询展示的处理,便于跟踪查询对应类型的业务表单信息。

以上就是对于 Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理的一些总结,希望对您有所启发和帮助。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面


基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍


基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理


基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换


基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理

我们如果要在服务器上发布https前端应用和WebAPI的应用,那么我们就需要用到https证书了。我们一般发布的应用的云服务器上,都会提供一定量的相关的免费证书(一般为20个)供我们使用,每个一年期限,到期再续即可,一般情况下基本上满足要求了,本篇随笔介绍如何基于云服务提供商的免费证书,在服务器上发布Nginx的前端应用和基于IIS的Web API接口的https应用处理。

1、申请免费证书

如阿里云和腾讯云,他们云服务器管理控制台上,都可以找到对应免费https的SSL证书申请的入口,如下所示。

在申请界面上,填入所需的域名,以及相关信息就可以发起申请了,申请后等待一点时间就会成功了,如阿里云的申请界面如下。

而腾讯云上的申请入口也是类似,如下界面所示。

申请成功后,在列表中就可以看到下载SSL证书的信息了。如下所示。

在下载界面上,我们可以看到不同部署服务器上的不同证书下载入口,选择我们具体的(如这里用到了Nginx和IIS的SSL证书文件)

我们选择所需的证书文件下载下来备用即可。下面会继续介绍IIS证书的安装和使用,以及Nginx的证书文件处理实现https的应用和接口服务。

2、发布基于IIS的Web API的https应用接口

如我们先下载IIS的证书文件,我们可以看到除了证书文件,还有一个附带的文本文件,是证书的密码信息。

我们双击进行证书的安装,选择本地计算机的存储位置即可。

然后输入所需的证书密码,完成安装就可以了。

发布一个IIS的Web API应用,然后在右键进行端口的绑定处理,设置绑定的为https,指定端口,并指定具体的SSL证书就是了,如下所示。

绑定的界面如下所示。

这样IIS的服务器端的Web API就可以使用https的协议了。

3、发布Nginx的前端应用

我们的前端是基于Vue的应用的,因此应用发布后,使用Nginx发布前端应用更为方便,因此这里介绍使用SSL免费证书在服务器上发布Nginx的前端应用,以便使用https协议访问。

前面我们提到了在申请完免费的SSL证书后,下载对应的Nginx的SSL证书文件。

基于Nginx的SSL证书设置,有两种方式,一种是创建一个ssl.conf文件,设置ssl.conf的方式指定对应的证书信息,如下所示。

#ssl.conf文件内容

server {
listen
8080 ssl http2;server_name localhost;ssl_certificate C:/WebRoot/nginx/conf/ssl/www.iqidi.com_bundle.crt;ssl_certificate_key C:/WebRoot/nginx/conf/ssl/www.iqidi.com.key; #先配置签名证书,再配置加密证书,签名加密证书私钥 key 为同一个!
ssl_session_timeout 5m
;ssl_protocols TLSv1.2;ssl_ciphers SM2-WITH-SMS4-SM3:ECDH:AESGCM:HIGH:MEDIUM:!RC4:!DH:!MD5:!aNULL:!eNULL;ssl_prefer_server_cipherson;location/{
root html
/CollectDataApp;index index.html index.htm;}
}

这样我们在conf/nginx.conf 文件中设置端口侦听,就可以了

server {
listen
8080 ssl;server_name localhost; #charset koi8-r; #access_log logs/host.access.log main;location/{
root html
/CollectDataApp;index index.html index.htm;try_files$uri $uri/ /index.html =404;}}

如果是不想独立分开两个配置文件,也可以把SSL证书位置信息写在conf/nginx.conf 文件中,也是可以的,如下所示。

server {
listen
9002 ssl;server_name localhost;ssl_certificate C:/WebRoot/nginx/conf/ssl/www.iqidi.com_bundle.crt;
ssl_certificate_key C:/WebRoot/nginx/conf/ssl/www.iqidi.com.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
#charset koi8-r; #access_log logs/host.access.log main;location/{
root html
/AssetCheckApp;index index.html index.htm;try_files$uri $uri/ /index.html =404;}}

这样就合并了SSL设置和端口侦听的文件在一起,测试后正常使用了。

以上就是关于利用云服务提供商的免费证书,在服务器上发布https前端应用和WebAPI的应用的整个过程,证书解决了,根据不同的应用服务器,设置好对应的方式就可以实现https应用了。

一旦我们完成了免费证书的申请、下载,那么在服务器上不同端口的应用,都可以使用这个证书作为SSL证书,从而实现多个不同应用端口上公用一个SSL证书了,因为证书对应的是一个相同域名的,因此可以正常使用。

在前面随笔介绍的基于SqlSugar的WInform端管理系统中,数据提供者是直接访问数据库的方式,不过窗体界面调用数据接口获取数据的时候,我们传递的是标准的接口,因此可扩展性比较好。我曾经在随笔《
基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
》中介绍过,该SqlSugar开发框架本身是基于IOC控制反转的,因此对于接入不同的数据提供者,只需要切换到对应的实现层上即可。本篇随笔介绍基于SqlSugar开发框架的Winform端,实现包括对直接访问数据库,远程调用Web API接口的两种不同的处理方式的整合。

1、Winform模块中对具体接口的调用及接口注册

Winform中的界面展示,以及数据处理,都需要具体实现的支撑,由于本身IOC控制反转的接口设计,我们对具体数据的访问,也是基于特定的接口层进行调用的,具体的实现,则是在程序启动的时候,注入对应的接口实现即可。

例如对于客户信息的展示业务操作,代码如下所示

/// <summary>
///数据显示的函数/// </summary>
public async override voidDisplayData()
{
if (!string.IsNullOrEmpty(ID))
{
#region 显示信息 var info = await BLLFactory<ICustomerService>.Instance.GetAsync(ID);if (info != null)
{
tempInfo
= info;//重新给临时对象赋值,使之指向存在的记录对象txtName.Text=info.Name;
txtAge.Value
=info.Age;
}
#endregion //this.btnOK.Enabled = HasFunction("Customer/Edit"); }else{//this.btnOK.Enabled = HasFunction("Customer/Add"); }
}

上面代码可以看到,我们是调用接口进行数据的处理的,而这个接口就是在程序启动之处,通过自动的方式获得对应的接口和实现类,然后进行注入的。

.net 中 负责依赖注入和控制反转的核心组件有两个:IServiceCollection和IServiceProvider。其中,IServiceCollection负责注册,IServiceProvider负责提供实例。

在注册接口和类时,
IServiceCollection
提供了三种注册方法,如下所示:

1、services.AddTransient<IDictDataService, DictDataService>();  // 瞬时生命周期
2、services.AddScoped<IDictDataService, DictDataService>();     // 域生命周期
3、services.AddSingleton<IDictDataService, DictDataService>();  // 全局单例生命周期

如果使用
AddTransient
方法注册,
IServiceProvider
每次都会通过
GetService
方法创建一个新的实例;

如果使用
AddScoped
方法注册, 在同一个域(
Scope
)内,
IServiceProvider
每次都会通过
GetService
方法调用同一个实例,可以理解为在局部实现了单例模式;

如果使用
AddSingleton
方法注册, 在整个应用程序生命周期内,
IServiceProvider
只会创建一个实例。

前面说到,接口我们是自动遍历响应的程序集进行注册的,注册接口的逻辑,我们可以统一抽取唯一个公用的函数处理,如下代码所示。

        /// <summary>
        ///配置依赖注入对象/// </summary>
        /// <param name="services"></param>
        public static voidConfigureRepository(IServiceCollection services)
{
#region 自动注入对应的服务接口 var path = AppDomain.CurrentDomain.RelativeSearchPath ??AppDomain.CurrentDomain.BaseDirectory;var getFiles = Directory.GetFiles(path, "*.dll").Where(Match); //.Where(o=>o.Match()) var referencedAssemblies = getFiles.Select(Assembly.LoadFrom).ToList(); //.Select(o=> Assembly.LoadFrom(o)) var baseType = typeof(IDependency);var types =referencedAssemblies
.SelectMany(a
=>a.DefinedTypes)
.Select(type
=>type.AsType())
.Where(x
=> x != baseType &&baseType.IsAssignableFrom(x)).ToList();var implementTypes = types.Where(x =>x.IsClass).ToList();var interfaceTypes = types.Where(x =>x.IsInterface).ToList();

RegisterService(services, implementTypes, interfaceTypes);
#endregion}

如果我们这里增加一个对Web API的调用,那么在这里注册的时候,切换向Web API代理的注册接口就可以,如下图所示。

因此原来的Winform界面上的调用代码,不需要任何变化,只需要注入不同的接口实现,就能获得不同的方式:普通访问数据库方式,还是分布式获取服务WebAPI的处理方式。

通过切换开关变量的方式,客户可以非常方便的自由切换不同的数据访问方式。数据提供服务,可以是直接访问数据库的方式,也可以是远端的Web API服务方式,从而实现更加广泛的业务需求。

根据不同开关变量,处理不同的接口注册的代码如下所示。

/// <summary>
///根据配置文件,决定采用直连的DLL,还是代理API的DLL,构建接口进行注入/// </summary>
/// <param name="services"></param>
public static voidConfigureRepositoryAuto(IServiceCollection services)
{
var config = newAppConfig();string callerType = config.AppConfigGet("CallerType");if (!string.IsNullOrWhiteSpace(callerType) && callerType.Equals("api", StringComparison.OrdinalIgnoreCase))
{
//如果配置为API模式 ConfigureRepositoryApi(services);
}
else{//如果配置为普通模式 ConfigureRepository(services);
}
}

API方式的注册,和普通的注册方式类似,就是定位具体的实现,获得接口和具体的实现对象,进行服务注册即可,在此不再赘述。

2、具体的Web API代理实现

在随笔《
基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
》我们介绍过具体实现类的继承关系,一般都是构建相应的基类和接口,然后才是具体的业务实现和接口,这样处理可以重用基类的很多接口,提高代码的重用效率。

我们以其中简单的Customer业务表为例,它的服务类代码如下所示(主要关注服务类的定义即可)。

    /// <summary>
    ///客户信息应用层服务接口实现/// </summary>
    public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService
{
...............
}

而对应Web API的代理调用类,那么为了极大的重用常规的接口处理,我们需要类似的继承关系。

具体的代码实现关系如下所示。

/// <summary>
///客户信息的Web API调用处理/// </summary>
public class CustomerApiCaller : AsyncCrudApiCaller<CustomerInfo, string, CustomerPagedDto>, ICustomerService
{
}

我们可以利用代码生成工具生成主要的继承关系,然后实现具体的函数封装即可。我们独立一个项目用来承载API的代理类处理。

在AsyncCrudApiCaller 类中做了很多Web API的调用封装,对于接口的访问,是需要令牌的,因此在用户访问其他接口前,需要获取用户身份令牌信息,并缓存起来供后续使用。

/// <summary>
///对用户身份进行认证/// </summary>
/// <param name="username">用户名</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
public async virtual Task<AuthenticateResultDto> Authenticate(string username, stringpassword)
{
var url = string.Format("{0}/api/Login/Authenticate", ServerRootAddress);var input = new{
UsernameOrEmailAddress
=username,
Password
=password
};
var result = await apiClient.PostAsync<AuthenticateResultDto>(url, input);returnresult;
}

后续每次接口访问的时候,填入相应的令牌信息。

/// <summary>
///重新增加相应的请求头,如认证的信息/// </summary>
protected virtual voidAddRequestHeaders()
{
//读取需要设置的请求头 apiClient.RequestHeaders.Clear();foreach (var item inRequestHeaders)
{
apiClient.RequestHeaders.Add(item);
}
//从缓存里面读取令牌信息,并在请求的时候自动加入(如果没有加的话) var accessToken = Cache.Instance["AccessToken"] as string;if (!string.IsNullOrWhiteSpace(accessToken))
{
var bearer = new NameValue("Authorization", "Bearer " +accessToken);if (apiClient.RequestHeaders != null && !apiClient.RequestHeaders.Contains(bearer))
{
apiClient.RequestHeaders.Add(bearer);
}
}
}

而ApiCaller的实现类此对于具体的调用,由于封装了相应的处理类,因此操作代码是比较简单的。

/// <summary>
///获取所有对象列表/// </summary>
/// <returns></returns>
public async virtual Task<ListResultDto<TEntity>>GetAllAsync()
{
return await DoActionAsync<PagedResultDto<TEntity>>("all");
}
/// <summary> ///获取所有对象列表/// </summary> /// <param name="input">获取所有条件</param> /// <returns></returns> public async virtual Task<ListResultDto<TEntity>> GetAllByIdsAsync(IEnumerable<TPrimaryKey>input)
{
return await DoActionAsync<PagedResultDto<TEntity>>("all-byids", input);
}

GET参数可以选用Dict方式传递,或者直接传入匿名类也可以,后台代码自动生成相关的URL参数传递的。

public async Task<bool> SetDeletedFlag(int id, bool deleted = true)
{
var action = $"set-deleted";var input = new{
id,
deleted
};
return await DoActionAsync<bool>(action, input, HttpVerb.Post);
}
public async Task<OuInfo> FindByName(stringname)
{
var action = $"byname/{name}";var dict = new Dictionary<string, string> { { "name", name } };return await DoActionAsync<OuInfo>(action, dict, HttpVerb.Get);
}

剩下的任务就是完善ApiCaller项目的类,与Web API控制器提供的接口的对应关系了,处理完成后,就可以进行测试了。

只要做好模块接口的对接关系,界面的处理代码不用变化就可以切换到其他方式上去了(如Web API的数据提供方式)。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面


基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍


基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理


基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换


基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理


基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求


基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化

在上篇随笔《
基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求
》中介绍了基于一个接口,实现对两种不同接入方式(直接访问数据库实现,基于Web API代理类实现)的处理,由于定义的接口中,我们为了方便,也是用了Lambda表达式的进行一些参数的处理,那么如果在Web API代理类中,Lambda表达式是不能直接传递给Web API的控制器的,那么如何对这个Lambda表达式进行序列化和反序列化还原就是一个急需解决的问题。 本篇随笔介绍采用Serialize.Linq 第三方组件的方式实现对Lambda表达式进行序列化和反序列化的处理。

1、Lambda表达式的接口使用

Lambda 表达式本质上是一个匿名函数,是C#中一种特殊语法,它的引入,使得匿名方法更加简单易用,最直接的是在方法体内调用代码或者为委托传入方法体的形式与过程变得更加优雅。 使用Lambda表达式可大大减少代码量,使得代码更加的优美、简洁,更有
可观性。由于Lambda表达式的便利性,因此虽然在整合多个接入实现比较麻烦一些,我依旧希望通过寻找方法实现对Lambda表达式的兼容处理。

例如,以下就是一个根据名称简单进行判断的Lambda表达式的处理。

/// <summary>
///新增状态下的数据保存/// </summary>
/// <returns></returns>
public async override Task<bool>SaveAddNew()
{
CustomerInfo info
= tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用 SetInfo(info); try{#region 新增数据 //检查是否还有其他相同关键字的记录 bool isExist = await BLLFactory<ICustomerService>.Instance.IsExistAsync(s=>s.Name.Equals(info.Name));if(isExist)
{
MessageDxUtil.ShowTips(
"指定的【姓名】已经存在,不能重复添加,请修改");return false;
}
var success = await BLLFactory<ICustomerService>.Instance.InsertAsync(info);if(success)
{
//可添加其他关联操作 return true;
}
#endregion}catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageDxUtil.ShowError(ex.Message);
}
return false;
}

它的函数原型就是一个Lambda表达式,如下所示的定义

/// <summary>
///判断是否存在指定条件的记录/// </summary>
/// <param name="input">表达式条件</param>
/// <returns></returns>
Task<bool> IsExistAsync(Expression<Func<TEntity, bool>> input);

有些稍微复杂一点的函数,如下定义所示。

/// <summary>
///获取某字段数据字典列表/// </summary>
Task<List<string>> GetFieldList(Expression<Func<TEntity, object>> selector, Expression<Func<TEntity, bool>> where = null);

调用的时候,如下所示。

/// <summary>
///初始化数据字典/// </summary>
private async voidInitDictItem()
{
//初始化代码 var list = await BLLFactory<IFormService>.Instance.GetFieldList(s=>s.Category);this.txtCategory.BindDictItems(list, "");
}

不过简单是简单了,但是本身Lambda表达式不能直接传递给Web API端参数,因为它无法直接序列化进行传递。

我们在之前说过,接入两种不同的数据提供方式。

因此我们为了继续使用Lambda表达是的优点,就需要使用Serialize.Linq对Lambda表达式进行序列化和反序列化。这样就可以在Web API端和Web API 代理端对Lambda表达式进行正常的使用了。

2、采用Serialize.Linq 对Lambda表达式进行序列化和反序列化的处理

首先在需要的地方,引入Serialize.Linq对Lambda表达式进行序列化和反序列化处理。

为了更好通用的实现Lambda表达式的正常序列化为文本,以及对文本的反序列化到Lambda表达式,我们这里编写了一个扩展函数,以便更方便的处理。

    /// <summary>
    ///对Lambda表达式的序列号和反序列化处理/// </summary>
    public static classSerializeExtension
{
/// <summary> ///序列化 LINQ Expression 表达式为JSON文本/// </summary> /// <typeparam name="TEntity">处理对象类型</typeparam> /// <typeparam name="TResult">返回结果类型</typeparam> /// <param name="express"></param> /// <returns></returns> public static string SerializeText<TEntity, TResult>(this Expression<Func<TEntity, TResult>>express)
{
//使用Serialize.Linq组件序列化表达式,传递给API端,API端需要对应反序列化的处理操作进行转换Expression var serializer = new ExpressionSerializer(newJsonSerializer());var expressJson =serializer.SerializeText(express);//接收端的反序列化处理//var express = (Expression<Func<TEntity, TResult>>)serializer.DeserializeText(expressJson); returnexpressJson;
}
/// <summary> ///反序列化JSON文本为LINQ Expression 表达式/// </summary> /// <typeparam name="TEntity">处理对象类型</typeparam> /// <typeparam name="TResult">返回结果类型</typeparam> /// <param name="text"></param> /// <returns></returns> public static Expression<Func<TEntity, TResult>> DeserializeText<TEntity, TResult>(this stringjson)
{
Expression
<Func<TEntity, TResult>> express = null;if (!string.IsNullOrWhiteSpace(json))
{
var serializer = new ExpressionSerializer(newJsonSerializer());
express
= (Expression<Func<TEntity, TResult>>)serializer.DeserializeText(json);
}
returnexpress;
}
}

这样我们来看看两个对Lambda表达式的Web API代理类的封装处理代码

        /// <summary>
        ///根据条件,获取所有记录/// </summary>
        public virtual async Task<ListResultDto<TEntity>> GetAllAsync(Expression<Func<TEntity, bool>> input, string orderBy = null)
{
var express = input.SerializeText(); //使用扩展函数处理生成JSON var postData = new{
express,
orderBy
};
return await DoActionAsync<ListResultDto<TEntity>>("all-expression", postData, HttpVerb.Post);
}
/// <summary> ///根据条件计算记录数量/// </summary> /// <returns></returns> public virtual async Task<long> CountAsync(Expression<Func<TEntity, bool>>input)
{
var expressJson = input.SerializeText(); //使用扩展函数处理生成JSON return await DoActionAsync<long>("count-expression", expressJson, HttpVerb.Post);
}

而对应的在Web API的基类控制器中,我们对这个通用的实现处理下就可以了

        /// <summary>
        ///根据条件,获取所有记录/// </summary>
[HttpPost]
[Route(
"all-expression")]public async Task<ListResultDto<TEntity>>GetAllAsync(ExpressionOrderDtoinput)
{
ListResultDto
<TEntity>? result = null;string json =input.expression;var express = json.DeserializeText<TEntity, bool>();if (express != null)
{
result
= await _service.GetAllAsync(express!);
}
returnresult;
}

[HttpPost]
[Route(
"count-expression")]public virtual async Task<long> CountAsync(dynamicexpressJson)
{
long result = 0;string json =expressJson;var express = json.DeserializeText<TEntity, bool>();if (express != null)
{
result
= await _service.CountAsync(express!);
}
returnresult;
}

这样在服务器端的Web API控制器上,就还原了原先的Lambda表达式,可以正常的接收到客户端提交的条件处理了。

通过这样的迂回的方式,我们就可以在常规的接口,以及Web  API的代理类中,都可以使用到Lambda表达式带来的便利性了,而不需要为了兼容而抛弃Lambda表达式接口参数的方式了。

我们可以把其中相关的Lambda表达式,放在一个区块中,方便查看和处理,如下代码所示是在服务端的Web API控制器的基类函数处理代码。

系列文章:


基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用


基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理


基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发


基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理


基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转


基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口


基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传


基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录


基于SqlSugar的开发框架循序渐进介绍(9)-- 结合Winform控件实现字段的权限控制


基于SqlSugar的开发框架循序渐进介绍(10)-- 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理


基于SqlSugar的开发框架循序渐进介绍(11)-- 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结


基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理


基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用


基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用


基于SqlSugar的开发框架循序渐进介绍(15)-- 整合代码生成工具进行前端界面的生成


基于SqlSugar的开发框架循序渐进介绍(16)-- 工作流模块的功能介绍


基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理


基于SqlSugar的开发框架循序渐进介绍(18)-- 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面


基于SqlSugar的开发框架循序渐进介绍(19)-- 基于UniApp+Vue的移动前端的功能介绍


基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理


基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换


基于SqlSugar的开发框架循序渐进介绍(22)-- Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理


基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求


基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化