我们如果要在服务器上发布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表达式进行序列化和反序列化

由于一些客户的内部系统需要提取一些记录信息,如果手工录入会变得比较麻烦,因此考虑使用百度云的OCR进行图片文字的提取处理,综合比较了一下开源免费的Tesseract 类库进行处理,不过识别效果不太理想,因此转为了百度的OCR云接口处理方式,测试的效果比较理想,基本上较少出现错别字。本篇随笔介绍如何利用百度OCR进行图片文字的提取处理,以便从别的系统中批量化获得响应的系统数据,然后进行相应的格式化处理。

1、百度OCR服务申请

百度的OCR接口调用是收费的接口,不过一般是在超过一定量的情况后进行收费,我们可以申请获得每月免费的额度。

百度智能云地址:
https://login.bce.baidu.com/

首先我们去到百度智能云里,在左边菜单栏的产品服务中搜索"文字识别",点击【领取免费资源】,将所有的免费资源领取,领取之后要半个小时之后才能生效。

回到主面板,我们要进行创建应用,填写相关的信息即可创建成功,应用创建好之后会生成AppID、API Key和Secret Key,使用百度AI的SDK,使用API Key和Secret Key参数接口调用处理!

2、项目测试/接口调用

创建一个Winform的测试项目及性能接口的调用测试,在Nuget上添加对应的Baidu.AI的SDK包,以及Json.NET的类库,如下所示。

设计一个界面,对图片文件的内容进行识别处理。

具体测试的代码如下所示。

        private void btnLoadImage_Click(objectsender, EventArgs e)
{
var imageFile = FileDialogHelper.OpenImage(false, "");if(imageFile != null)
{
var str =BaiduOcr(imageFile);//var str = TesseractOcr(imageFile); this.txtResuilt.Text =str;
}
}
private static string BaiduOcr(stringimageFile)
{
var APIKey = "**********";var SecretKey = "********";var ocr = newBaidu.Aip.Ocr.Ocr(APIKey, SecretKey);var currentImage =FileUtil.FileToBytes(imageFile);var result =ocr.Accurate(currentImage);string str = string.Empty;var txts = (from obj in (JArray)result.Root["words_result"]select (string)obj["words"]).ToList();string startString = "手术助手";int startIndex = 0;for (int i = 0; i < txts.Count(); i++)
{
if (txts[i] ==startString)
{
startIndex
= i; //记录二维表的开始位置 }
}
var newList = txts.Skip(startIndex + 1).ToList();

str
= string.Join("\r\n", newList);returnstr;
}

如系统界面的图片如下所示。

使用百度OCR接口进行测试,可以看到具体的测试结果即可,然后根据结果进行格式化的转换就可以输出对应Excel格式或者其他格式了

能够获取列表的记录,进行相应的处理即可,这样可以减少数据提取或者录入的时间,通过OCR的自动化处理,可以极大的提高数据的处理效率。

今年开年,最火的莫过于ChatGPT的相关讨论,这个提供了非常强大的AI处理,并且整个平台也提供了很多对应的API进行接入的处理,使得我们可以在各种程序上无缝接入AI的后端处理,从而实现智能AI的各种应用。ChatGPT的API可以在前端,以及一些后端进行API的接入,本篇随笔主要介绍基于ChatGPT的API的C#接入研究。

1、ChatGPT的介绍

ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI研发的聊天机器人程序 ,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文 等任务。

ChatGPT是美国人工智能研究实验室OpenAI新推出的一种人工智能技术驱动的自然语言处理工具,使用了Transformer神经网络架构,也是GPT-3.5架构,这是一种用于处理序列数据的模型,拥有语言理解和文本生成能力,尤其是它会通过连接大量的语料库来训练模型,这些语料库包含了真实世界中的对话,使得ChatGPT具备上知天文下知地理,还能根据聊天的上下文进行互动的能力,做到与真正人类几乎无异的聊天场景进行交流。ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

ChatGPT 的官网地址如下:
https://platform.openai.com/
,如果我们需要使用它的对话处理以及强大的API能力,需要注册才能使用,至于如何注册以及接收验证码的操作,请百度一下一下即可,这里忽略,只是介绍它的功能以及如何介入使用。

ChatGPT 可以做很多不同类型的工作,常规的问答聊天,编写各种语言的应用代码,编写论文、摘要等,以及图片处理,翻译等等,如下是它们的一些功能分类和介绍。

如我们可以在对话中测试其聊天/应答功能。

我们也可以让它编写一段操作代码,如下所示。

如果我们要了解ChatGPT平台的API介绍,可以参考
https://platform.openai.com/docs/api-reference/introduction
,其大概分类也是很常规的功能应用相关的,如下是它的列表介绍。

我们要学会如何使用API的话,需要了解它的相关模型概念,以及对各种处理的定义。

2、基于ChatGPT的API的C#接入

从上面的API介绍中,我们可以看到,API平台没有针对不同的语义给出不同的实现方式,只是给出了一个通用的调用方式,主要是基于输入参数,响应的数据格式的参考,具体的不同语言的应用如C#的接入,需要根据实际的格式进行对接。

主要的代码如下所示。

usingRestSharp;usingSystem;namespaceGpt3APIExample
{
classProgram
{
static void Main(string[] args)
{
//OpenAI API地址 string apiUrl = "https://api.openai.com/v1/engines/gpt-3/jobs";//OpenAI API密钥 string apiKey = "YOUR_API_KEY";//创建一个RestClient对象 var client = newRestClient(apiUrl);//创建一个RestRequest对象 var request = newRestRequest(Method.POST);//在请求头中添加API密钥 request.AddHeader("Authorization", "Bearer" +apiKey);//添加请求内容 request.AddJsonBody(new{
model
= "text-davinci-002",
prompt
= "What is the capital of France?",
max_tokens
= 100,
n
= 1,
stop
= null,
temperature
= 0.5,
});
//发送请求并获取响应 IRestResponse response =client.Execute(request);//显示响应内容 Console.WriteLine(response.Content);
Console.ReadLine();
}
}
}

这个代码主要就是针对输入信息和输出的内容进行简单的包装,使用 RestSharp 的一个Http类库进行访问的包装。

而为了更好的使用ChatGPT的API处理,我们可以使用更高级一点的类库,如下面介绍的两个开源C#包装ChatGPT的类库:

https://github.com/OkGoDoIt/OpenAI-API-dotnet

https://github.com/RageAgainstThePixel/OpenAI-DotNet

我觉得他们都是很不错的,都是基于ChatGPT的API格式进行了一定程度的面向对象的包装,使得我们基于C#开发起来更方便。

例如我们基于
https://github.com/RageAgainstThePixel/OpenAI-DotNet

来查看使用代码,它的介绍也比较详细,基本上覆盖到了各个方面。

我们可以模仿来生成自己的代码即可,如下所示。

    /// <summary>
    ///基于OpenAI-DotNet的使用/// </summary>
    public classTest2
{
public static asyncTask ExcuteCode()
{
var api = new OpenAIClient(newOpenAIAuthentication(Constants.ApiKey));var result = await api.CompletionsEndpoint.CreateCompletionAsync("读取图片文件的展示到窗体的C#代码", null, null, 1000, 0.1);
Log.Information(result.ToJson());
}

运行效果,可以看到输出的结果。

对于图片的生成和处理,我们也可以按照类似的API进行调用接口,如下是一段寻找图片的代码。

            var results = await api.ImagesEndPoint.GenerateImageAsync("中国布达拉宫的全景图片", 1, ImageSize.Large);foreach (var item inresults)
{
Log.Information(item);
}

为了验证图片的AI处理,我们生成两张正方形的图片,图片右下角扣掉一块空白的图片,图片格式使用PNG格式,然后调用如下代码进行替换处理。

            var imageAssetPath = "images/休息区.png";var maskAssetPath = "images/休息区2.png";var imageResults = await api.ImagesEndPoint.CreateImageEditAsync(Path.GetFullPath(imageAssetPath), Path.GetFullPath(maskAssetPath), "阳光明媚的室内休息区,有一个鸭子在池塘中", 1, ImageSize.Small);foreach (var item inimageResults)
{
Log.Information(item);
}

生成后的图片效果如下所示。

生成的空白地方,填入一个所需要的区域,红色框是我加上去醒目的。