2023年2月

Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样. 如果您曾经用过 Moment.js, 那么您已经知道如何使用 Day.js。简单地说,只要你会Moment.js,那么你就会Day.js!

但是我们知道的,Moment.js 的大小是200多KB,而Day.js的大小却是2 KB,瘦身很多但却几乎拥有同样强大的 API。

现在框架基本上都是多端应用的了,所以在ABP框架中整合Winform管理端、Vue&element的BS前端,以及公司动态网站用于发布产品和网站信息等都是常见的应用,有时候,我们还需要根据功能的需要,增加一些小程序的支持,这些对于Web API后端来说,都是很容易接入的应用处理。

而基于Vue + Element 的前端界面,前端功能模块包括用户管理、组织机构管理、角色管理、菜单管理、功能管理及权限分配,日志管理、字典管理、产品管理等管理功能,可实现用户的功能及数据权限进行控制管理。

公司动态门户网站,有时候用于我们发布网站信息和产品信息的一个门户网站,采用了Bootstrap-Vue界面组件,由于大多数门户网站都是基于Bootstrap栅格系统的,因此基于最新Bootstrap-Vue界面组件也就是最佳选择的了,而且可以重用很多Bootstrap的网站模板案例。这样也同时保持了前端模块同时也是基于Vue的,摒弃了以前基于JQuery的繁琐操作DOM处理。

由于现在微信小程序的广泛应用,有时候我们针对一些业务模块功能,可以根据需要推出一些小程序应用场景,这些对接我们的统一授权系统,以及统一的WebAPI调用机制即可满足。

而这些基于Vue的前端,采用类库对日期格式化,或者进行转换的需求场景很多,因此我们我们需要借助JS类库进行日期的处理,比较好的推荐使用day.js的处理。

1、day.js的安装和使用

安装day.js

npm install dayjs -s

然后在项目代码中引入即可:

import dayjs from "dayjs"; //导入日期js
或者var dayjs = require('dayjs')
import * as dayjs from 'dayjs'import* as isLeapYear from 'dayjs/plugin/isLeapYear' //导入插件
import 'dayjs/locale/zh-cn' //导入本地化语言
dayjs.extend(isLeapYear)//使用插件
dayjs.locale('zh-cn') //使用本地化语言

详细了解可以参考地址:
https://dayjs.fenxianglu.cn/
或者
https://dayjs.gitee.io/zh-CN/
或者
https://github.com/iamkun/dayjs

Day.js 支持几十个国家语言,但除非手动加载,多国语言默认是不会被打包到工程里。但你可以随意在各个语言之间自由切换:

dayjs('2018-05-05').locale('zh-cn').format() //在这个实例上使用简体中文

直接调用
dayjs()
将返回一个包含当前日期和时间的 Day.js 对象。

var now = dayjs()

可以对调用dayjs对格式进行处理或者转换。

dayjs("12-25-1995", "MM-DD-YYYY")
dayjs('2018 三月 15', 'YYYY MMMM DD', 'zh-cn')

dayjs('1970-00-00', 'YYYY-MM-DD').isValid() // true
dayjs('1970-00-00', 'YYYY-MM-DD', true).isValid() // false
dayjs('1970-00-00', 'YYYY-MM-DD', 'es', true).isValid() // false

默认情况下,Day.js 只包含核心的代码,并没有安装任何插件.

您可以加载多个插件来满足您的需求,例如官方提供的插件就有:

  • AdvancedFormat 扩展了 dayjs().format API 以支持更多模版
  • RelativeTime 增加了 .from .to .fromNow .toNow 4个 API 来展示相对的时间 (e.g. 3 小时以前).
  • IsLeapYear 增加了 .isLeapYear API 返回一个 boolean 来展示一个 Dayjs's 的年份是不是闰年.
  • WeekOfYear 增加了 .week() API 返回一个 number 来表示 Dayjs 的日期是年中第几周.
  • IsSameOrAfter 增加了 .isSameOrAfter() API 返回一个 boolean 来展示一个时间是否和一个时间相同或在一个时间之后.
  • IsSameOrBefore 增加了 .isSameOrBefore() API 返回一个 boolean 来展示一个时间是否和一个时间相同或在一个时间之前.

Day.js 支持像这样的链式调用:

dayjs().add(7, 'day')
dayjs(
'2019-01-25').add(1, 'day').subtract(1, 'year').year(2009).toString()

Day.js 对象还有很多查询的方法。

Is Before表示 Day.js 对象是否在另一个提供的日期时间之前。

dayjs().isBefore(dayjs('2011-01-01')) //默认毫秒
dayjs().isBefore('2011-01-01', 'year')

Is Same 检查一个 Dayjs 对象是否和另一个 Dayjs 对象时间相同。

Is After
表示 Day.js 对象是否在另一个提供的日期时间之后。

还有其他查询时间区间的函数,如下所示。

如下面一个应用代码函数:

    /**
* 当前日期gantt状态
* row: object 当前行信息
* date: string 当前格子日期
* unit: string 时间单位,以天、月、年计算
*/dayGanttType(row, date, unit= "days") {
let start_date
=row.startTime;
let end_date
=row.endTime;
let between
=dayjs(date).isBetween(start_date, end_date, unit);if(between) {return "item-on";
}
let start
=dayjs(start_date).isSame(date, unit);
let end
=dayjs(end_date).isSame(date, unit);if (start &&end) {return "item-on item-full";
}
if(start) {return "item-on item-start";
}
if(end) {return "item-on item-end";
}
},

2、Moment.js的安装和使用

Moment.js是一个轻量级的JavaScript时间库,它方便了日常开发中对时间的操作,提高了开发效率。 moment.js作为日期处理工具,虽然它和day.js对比显得有点笨重,不过依旧很多项目在广泛的使用者。

npm的安装如下处理。

npm install moment --save

实例代码


var now = moment(); //取当前时间
now.format('YYYY-MM-DD'); //格式化输出
var day = moment("9/12/2010 19:05:25", "MM/DD/YYYY HH

在一些内部OA或者流转的文件,或者给一些客户的报价文件、合同,或者一些医院出示的给保险机构的病历资料等,有时候可能都希望快速的使用电子签章的处理方式来给文件盖上特定的印章,本篇随笔介绍基于Vue&Element的前端项目采用第三方组件

vue-drag-resize和图片转换Base64的方式实现图片印章的盖章处理。

1、图片转换为Base64处理

图片转换为Base64编码可以通过在线工具的转换方式实现图片转Base64编码,网上很多在线的处理,百度一下即可。
如:
https://c.runoob.com/front-end/59/
或者
https://tool.chinaz.com/tools/imgtobase
试过都可以,非常方便。
如果我们喜欢通过C#代码进行图片的转换也可以使用自己封装的函数实现处理,如下所示。

Base64 在CSS中的使用

.demoImg{ background-image: url("data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...."); }

Base64 在HTML中的使用

<imgwidth="40"height="30"src="data:image/jpg;base64,/9j/4QMZRXhpZgAASUkqAAgAAAAL...." />

我们使用C#代码转换的处理代码如下所示。

        private void btnBase64_Click(objectsender, EventArgs e)
{
var base64 = ImageHelper.ImageToBase64Str(this.pictureBox1.Image);if(chkData.Checked)
{
base64
= "data:image/jpeg;base64," +base64;
}
this.txtBase64.Text =base64;
}

而其中调用是通过我们公用类库中的图片辅助类进行,详细转换代码如下所示。

        /// <summary>
        ///从文件中转换图片对象到Base64编码/// </summary>
        /// <param name="imageFilePath">图片文件路径</param>
        /// <returns></returns>
        public static string ImageToBase64Str(stringimageFilePath)
{
Image image
=Image.FromFile(imageFilePath);using (MemoryStream ms = newMemoryStream())
{
image.Save(ms, image.RawFormat);
//ImageFormat.Jpeg byte[] imageBytes =ms.GetBuffer();string imgBase64Str =Convert.ToBase64String(imageBytes);//释放资源,让别的使用 image.Dispose();returnimgBase64Str;
}
}
/// <summary> ///转换图片对象到Base64编码/// </summary> /// <param name="image">Image图片对象</param> /// <returns></returns> public static stringImageToBase64Str(Image image)
{
using (MemoryStream ms = newMemoryStream())
{
image.Save(ms, image.RawFormat);
//ImageFormat.Jpeg byte[] imageBytes =ms.GetBuffer();string imgBase64Str =Convert.ToBase64String(imageBytes);returnimgBase64Str;
}
}

这样,我们在前端Vue的项目中,就可以赋值这段图片Base64代码到HTML文件中就可以了,如下是前端Vue项目代码所示(缩减了部分Base64编码)。

这样我们就可以在页面中放置一个Base64编码的图片在页面中了。

2、使用vue-drag-resize组件实现印章图片的拖动

常规的图片,放置在页面中,位置是固定的,如果我们需要拖动印章,那么就需要引入可拖动面板的Vue组件vue-drag-resize来处理它了。

vue-drag-resize是Github上的一个开源组件,地址是:
https://github.com/kirillmurashov/vue-drag-resize

这个组件的使用和其他组件的使用方式一样,非常方便。

import Vue from 'vue'import VueDragResize from'vue-drag-resize'Vue.component('vue-drag-resize', VueDragResize)

在组件或者页面中使用的代码如下所示。

import VueDragResize from 'vue-drag-resize'exportdefault{
components: { VueDragResize },

它的HTML代码如下所示。

<template>
    <divid="app">
        <VueDragResize:isActive="true":w="200":h="200"v-on:resizing="resize"v-on:dragging="resize">
            <h3>Hello World!</h3>
            <p>{{ top }} х {{ left }} </p>
            <p>{{ width }} х {{ height }}</p>
        </VueDragResize>
    </div>
</template>

为了把印章图片可以拖动,我们在HTML中放置印章图片包含在这个组件面板中。

如下面代码所示。

把图片放置在这个组件容器中后,图片就可以随意拖动,确认位置后,就可以确定它的位置,我们可以通过记录图片的位置X, Y的值并存储起来,下次直接确定位置也可以。

定义组件的初始X位置。

<vue-drag-resize:x="0">

定义组件的初始Y位置。

<vue-drag-resize:y="0">

例如我们定义了一个报价单,并通过设置,把图片放置在页面中,让使用者可以通过拖动印章的方式,放到合适的位置上去,然后进行打印报价单即可带有印章的报价单出来了。

这样就可以实现电子印章的拖动和放置处理了,记住其位置和状态,下次就可以直接定位到指定的位置上了。

另外,一般文档都需要打印,关于打印的处理,有很多方式,可以使用print.js(
https://github.com/crabbly/Print.js
),也可以使用
vue-print-nb
(
https://github.com/Power-kxLee/vue-print-nb
),甚至复杂的定义可以考虑使用CLODOP组件来处理,不过我们这里可以简单的内置Windows 对象的打印操作方式来处理普通的页面打印即可。

print () {
const print
= this.$refs.print.innerHTML
const printPart
= print +csstyle;
const newTab
= window.open('_blank');
newTab.document.body.innerHTML
=printPart;
newTab.print();
newTab.close();
},

打印效果如下所示,采用了对应的CSS样式处理后,和实际的页面效果相当。

在项目中,涉及到了html内容打印的需求,调用了浏览器的window.print用于打印
显示不全问题

由于window.print是1:1打印,打印内容过宽时,浏览器会自动从左截取掉超宽部分,因此在打印前需将页面进行调整
打印页边距设定为 0mm 时,网页内最大元素的分辨率:794×1123
因此可以将内容div设置为700px,剩余空间设置为页边距

去除浏览器默认页眉页脚

页眉打印默认有页眉页脚信息,展现到页面外边距范围,我们可以通过去除页面模型page的外边距,使得内容不会延伸到页面的边缘,再通过设置 body 元素的 margin 来保证 A4 纸打印出来的页面带有外边距

由于window.print打印自带页眉页脚,用于存放打印url,日期时间,页面名称等内容,为屏蔽这些内容可使用css进行屏蔽

@media print{@page {
margin
:0; }body{margin:1cm; }}

只屏蔽页脚

@page{margin-bottom:0; }
事件监听

有两个事件可以监听到到打印事件,一个是
beforeprint
,一个是
afterprint
,分别表示打印事件触发前后。
这个事件在 IE6 就已经支持了,兼容大概是 Firefox、IE全支持, Chrome63+支持, Safari暂不支持

我们可在处理事件onbeforeprint() 将一些不需要打印的元素隐藏,和打印后的处理事件 onafterprint()放开隐藏的元素

window.addEventListener('beforeprint', ()=>{
document.body.innerHTML
= '打印前触发';
});

window.addEventListener(
'afterprint', ()=>{
document.body.innerHTML
= '打印后触发';
});
设置打印布局(横向、纵向、边距)
@media print{@page {
// 纵向
size
:portrait;// 横向
size
:landscape;// 边距
margin
:0cm 0cm 0cm 0cm; }}

如果需要了解更多Vue&Element的开发知识,可以查看我的随笔分类,里面总结了很多我在实际开发过程中遇到的问题,和经验分享。


循序渐进VUE+Element


在前面随笔介绍了《
在基于ABP框架的前端项目Vue&Element项目中采用电子签章处理文件和打印处理
》的处理,有的时候,我们在流程中或者一些文件签署的时候,需要签上自己的大名,一般通过签名表的方式(银行很常见)实现电子签名的处理。本篇随笔介绍如何基于Vue &Element前端的技术实现电子签名的处理。

1、实现电子签名的组件

我们知道,很多常见的功能,我们都会引入对应的组件来做页面功能,会非常简洁方便,毕竟不用重复制造轮子。

我们在Github上可以找到很多基于Vue前端技术的界面组件,其中实现电子签名的也有好几个,大多数处理界面和功能差不多。


vue-sign-canvas
,它也是基于写字或者签名模式来实现电子签名的处理,案例可以参考地址:
https://langyuxiansheng.github.io/vue-sign-canvas/

案例本身就是一个很好的例子,提供了很多配置属性来实现不同的效果。


vue-esign
也是另一款实现个人电子签名处理的组件,也提供了类似的功能。

npm install vue-esign --save
<!--vue2-->
<vue-esignref="esign":width="800":height="300":isCrop="isCrop":lineWidth="lineWidth":lineColor="lineColor":bgColor.sync="bgColor" />
<!--vue3-->
<vue-esignref="esign":width="800":height="300":isCrop="isCrop":lineWidth="lineWidth":lineColor="lineColor"v-model:bgColor="bgColor" />

<!--isClearBgColor为false时,不必再给bgColor加sync修饰符或v-model-->

<button@click="handleReset">清空画板</button> 
<button@click="handleGenerate">生成图片</button>
data () {return{
lineWidth:
6,
lineColor:
'#000000',
bgColor:
'',
resultImg:
'',
isCrop:
false}
},
methods: {
handleReset () {
this.$refs.esign.reset()
},
handleGenerate () {
this.$refs.esign.generate().then(res =>{this.resultImg =res
}).
catch(err =>{
alert(err)
//画布没有签字时会执行这里 'Not Signned' })
}
}

界面效果如下所示。

本篇随笔基于
vue-esign
来实现电子签名处理。

2、在页面中实现电子签名

在页面中引入对应的电子签名组件

import Vue from 'vue'import vueEsign from'vue-esign' //电子签名
exportdefault{
name:
'Signature',
components: {
vueEsign
},

在页面HTML代码中,放置一块区域来实现电子签名,引入Vue-esign组件,如下代码所示。

  <divclass="sigbut">
    <divclass="tip">请在这里签名</div>
    <divclass="left-bu">
      <el-buttonclass="clear-bu"size="mini"@click="handleReset">清空签名</el-button>
      <el-buttonclass="sure"size="mini":disabled="!showSig"@click="handleGenerate">确定签名</el-button>
    </div>
  </div>
  <divclass="sig">
    <vue-esignv-if="showSig"ref="esign":width="800":height="150":is-crop="isCrop":line-width="lineWidth":line-color="lineColor":bg-color.sync="bgColor" />
    <imgv-if="!showSig":src="resultImg"alt=""class="sig-img">
  </div>
methods: {//清空画布
handleReset () {this.resultImg = ''
      this.showSig = true;this.$nextTick(() =>{this.$refs.esign.reset()
})
},
//生成签名图片 handleGenerate () {this.$refs.esign.generate().then(res =>{if(res) {this.showSig = false;this.resultImg =res;
}
}).
catch(err =>{this.$message({
message:
'请签名',
type:
'warning'});
})
},

最后得到界面效果如下所示。

由于电脑没有签名笔,因此使用鼠标胡乱画的名字,效果一般,呵呵。

如果使用签名笔来实现模拟真实的签名,其实和书写效果就很接近了,因此也可以作为一般的个人签章处理。

对于语音识别,一般有实时语音识别和语音文件的识别处理等方式,如在会议、培训等场景中,可以对录制的文件进行文字的转录,对于转录文字的成功率来说,如果能够转换90%以上的正确语音内容,肯定能减轻很多相关语音文本编辑的繁琐工作,而目前大多数语音转录的接口基本都能够保证在这个成功率上,有些甚至超过98%以上,非常不错,因为他们对于各种场景的滤波,可以更加提供文字的准确性。本篇随笔对各种语音开发平台做一个介绍,并针对依图语音API的C#封装以及调用进行语音转写进行介绍。

1、语音识别的接口提供商及API情况

语音识别有很多提供商,如常见的百度、阿里云、依图语音等,他们都提供了不同的API接口来实现外部的调用处理,本篇随笔主要针对依图语音(号称能够识别99%以上的智能转录)进行测试,对于其他类型的语音API,由于接口处理方式大同小异,并没有一一测试。

1)
百度语音转录

2)
阿里云语音转录

3)
依云语音开发平台

对于短语音的测试,依图语音的转录基本上在98%以上,偶尔有一两个词语文字因为不常见而出错而已,非常不错。

由于依图语音平台虽然提供了Java的案例代码,但是没有对应C #API调用案例,因此需要根据接口的说明进行转换。

对于依图语音的API说明,还是介绍的比较详细的,如下所示。

一般根据这些很容易编写对应的接口API封装函数,我这里主要用来封装对应的C#调用API。

2、基于依图语音API的C#封装

我们针对API进行了封装,然后在界面上进行测试对应的功能,如下Winform界面测试所示。

一个短句,发现一个错误的字,不过整体效果还是比较好,断句以及内容都算很好的。

这个项目分为三部分,第一是对请求接口数据和返回数据的对象封装(简称
DTO对象),第二是对依图长语音和短语音接口的
API接口封装,包括授权信息的处理;长语音转写的任务创建、任务查询、任务停止几个部分;以及短语音的转写处理。第三是
Winform界面的功能测试和日志处理操作,方便了解功能的实际处理情况。

【创建长语音转录任务】主要用于演示如何生成签名并调用接口创建转写任务的操作,操作后提示是否成功,并记录下任务ID,供查询任务或停止任务使用。同时在日志和文本框里面生成相关的JSON数据信息,日志在运行目录的log/log.txt中记录。

对于依图语音转换来说,我们一般使用异步的处理方式,所以更好的是等待任务处理完后回调告诉我们处理结果更好,因此适合于部署Web API的应用,用于让依图语音平台的服务器进行回调处理结果。

首先第一步我们需要处理API的授权签名,用于后续接口的调用,我们可以根据签名的规则进行编写代码即可。

    /// <summary>
    ///签名处理/// </summary>
    public classSignatureHelper
{
/// <summary> ///生成授权签名信息/// </summary> /// <param name="data"></param> /// <param name="key"></param> /// <returns></returns> public static string CalculateRFC2104HMAC(string data, stringkey)
{
key
= key ?? "";var encoding = newSystem.Text.UTF8Encoding();byte[] keyByte =encoding.GetBytes(key);byte[] messageBytes =encoding.GetBytes(data);using (var hmacsha256 = newHMACSHA256(keyByte))
{
byte[] hashmessage =hmacsha256.ComputeHash(messageBytes);
StringBuilder builder
= newStringBuilder();for (int i = 0; i < hashmessage.Length; i++)
{
builder.Append(hashmessage[i].ToString(
"x2"));
}
returnbuilder.ToString();
}
}
/// <summary> ///生成请求授权信息/// </summary> /// <returns></returns> public staticAuthRequestDto GetAuthDto()
{
var builder = newConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile(
"appsettings.json", true, reloadOnChange: true);var config =builder.Build();//读取配置 var DevId = config["YITU:DevId"];var DevKey = config["YITU:DevKey"];var dto = newAuthRequestDto();
dto.DevId
=DevId;
dto.Timestamp
=DateTime.Now.DateTimeToInt();var accessKey =DevKey;var signKey = dto.DevId + dto.Timestamp; //签名加密键 dto.Signature =SignatureHelper.CalculateRFC2104HMAC(signKey, accessKey);returndto;
}
/// <summary> ///对请求的HttpClient设置相关的授权请求Header信息/// </summary> /// <param name="client"></param> /// <param name="dto"></param> public static voidSetSigature(HttpClient client, AuthRequestDto dto)
{
client.DefaultRequestHeaders.Add(
"x-dev-id", dto.DevId);
client.DefaultRequestHeaders.Add(
"x-request-send-timestamp", dto.Timestamp.ToString());
client.DefaultRequestHeaders.Add(
"x-signature", dto.Signature);

client.DefaultRequestHeaders.Add(
"Date", DateTime.Now.AddHours(-8).ToString("R"));
}
}

这个按钮功能,主要测试一下使用辅助类生成的授权认证信息,授权认证信息是根据加密规则构建的签名信息和账号信息。

这个主要是演示辅助类生成签名,并获得的数据信息:

然后我们定义一个控制器,用于接收依图语音的服务器API回调处理,用于记录转换的结果内容。

namespaceCallBackApi.Controllers
{
/// <summary> ///用于接收长语音信息回调的处理/// </summary> [ApiController]
[Route(
"[controller]")]public class LongVoiceController : ControllerBase

在控制器中编写存储的逻辑,写入数据库。

        /// <summary>
        ///回调写入结果, 路由:post /longvoice/// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
[HttpPost]public async Task<CommonResultDto>CallBack(LongVoiceResultDto input)
{
CommonResultDto result
= null;var json =JsonConvert.SerializeObject(input, Formatting.Indented);
_logger.LogInformation(json);
try{var helper = newLongVoiceDbHelper();var bResult = awaithelper.SaveData(input);
result
= new CommonResultDto(0, bResult ? "操作成功" : "操作失败");
}
catch(Exception ex)
{
result
= new CommonResultDto(1, ex.Message);
_logger.LogError(ex, ex.Message);
}
returnresult;
}

我们根据依图语音的对象模型,构建对应的对象信息属性,然后保存起来即可。

        /// <summary>
        ///保存数据到数据库/// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        public async Task<bool>SaveData(LongVoiceResultDto dto)
{
bool result = false;if(dto != null)
{
using(var db =CreateDb())
{
var info = newConsultationInfo();
info.DiscernStatus
=dto.taskId;
info.OperateStatus
= "未识别";if (dto.data != null && dto.data.speechResult != null)
{
if (dto.data.statusCode == 3)
{
info.OperateStatus
= "已识别";
}
var speechResult =dto.data.speechResult;
info.DiscernText
=speechResult.resultText;
}

result
= await db.Insertable(info).ExecuteCommandAsync() > 0;
}
}
returnresult;
}

回调接口主要用于长语音任务执行完成后,服务器的回调处理,我这里使用.net core5的Web API项目来处理,通过使用Restful规则的API控制处理,接收服务器的回调数据请求,并把请求数据记录数据库或者在log/log.txt文件中。

我们先发起语音转录请求,如下代码所示。

       privateLongVoiceRequestDto GetVoice1()
{
var input = newLongVoiceRequestDto()
{
audioUrl
= "https://www.***.com/downloads/iqidi.mp3", //语音文件 callback = "http://www.***.com:8080/longvoice", //回调地址 fileData = newFileData
{
aue
= "mpeg2",
audioName
= "iqidi.mp3",
sampleRateHertz
= 16000,
lang
= "MANDARIN"},
speechConfig
= newSpeechConfig
{
scene
= "GENERAL",
byWords
= true,
customWords
= new List<string>()
{
//"开发框架提供商",//"广州爱奇迪软件科技有限公司" },
useCustomWordsIds
= new List<int>()
{
},
addPunctuation
= true,
wordsReplace
= true,
numOfSpeakers
= 1,
convertNumber
= true,//disfluency = true },
wordsReplace
= new List<WordsReplace>()
{
newWordsReplace()
{
keywords
= "他妈的",
replace
= "*"},newWordsReplace()
{
keywords
= "破坏",
replace
= "##"}
}
};
returninput;
}

对于长语音的转换,我们只是发起请求,有时候需要等待一点时间,服务器才会根据请求的回调地址进行会写操作的。发起长语音的请求如下代码所示。

        private async void btnTransfer_Click(objectsender, EventArgs e)
{
try{var input = this.radSmall.Checked ?GetVoice1() : GetVoice2();var result = awaitlongApi.Transfer(input);if (result != null)
{
this.txtTaskId.Text = result.taskId;//任务ID,可供查询,停止 MessageUtil.ShowTips($"任务创建{(result.rtn == 0 ?"成功":"异常:"+ result.message)},任务ID:{result.taskId}");
}
var json =JsonConvert.SerializeObject(result, Formatting.Indented);this.richLog.AppendText(json);this.richLog.AppendText(Environment.NewLine);
Serilog.Log.Information(json);
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}
}

回调接口主要用于长语音任务执行完成后,服务器的回调处理。

而如果是短语音转写,就和我们前面说到的一样,马上就可以出结果了。

        private async void btnShort_Click(objectsender, EventArgs e)
{
try{var input =GetShortVoice();var result = awaitshortApi.Transfer(input);if (result != null)
{
MessageUtil.ShowTips($
"短语音听写, 操作{(result.rtn == 0 ?"成功":"异常:"+ result.message)}");
}
var json =JsonConvert.SerializeObject(result, Formatting.Indented);this.richLog.AppendText(json);this.richLog.AppendText(Environment.NewLine);
Serilog.Log.Information(json);
}
catch(Exception ex)
{
MessageUtil.ShowError(ex.Message);
}
}

我们只需要把返回的结果信息,转换为相关的模型对象,然后进行显示或者存储即可完成。

项目可以通过VS发布方式发布一个文件包,部署到服务器上即可。

配置项目后台一直运行。

以上就是关于依图语音的测试处理,对应后端的回调处理,我们可以转录长语音文件,不过依图语音平台对于身份的审核比较严格,也是收费的API(其他阿里、百度也都是),因此在商用的时候,需要考虑好即可。

不管我们开发是基于Asp.net core 还是.net core,或者还是.net framework的相关应用,如果有一个比较全面的公用类库以及相关的扩展函数来支撑,我们开发项目上会节省很多相关的处理方法查询和封装。本篇随笔主要针对性的介绍基于一些对象进行扩展函数的封装带来的便利性进行说明,毕竟基于上下文的调用扩展函数,比不断地去自己去类库中查找那个方法合适,会更高效一些。

1、针对常规对象进行扩展函数的封装

我们常常使用到的对象,可能包括有字符串(String)、日期对象(DateTime)、列表集合(List/IEnumerable)、常规对象Object、UI控件(如普通的Control对象处理,其他常规控件对象等),一般来说,这些常规类型的对象我们会在代码中应用很多,对于上下文的一些快速的调用可以快速减少我们翻查代码的时间,随着时间的累积,我们会使用的更加快捷和熟悉。

字符串可能是我们日常使用C#代码,操作最多的一个.net 基础类型了,一般字符串涉及到格式判断、格式转换、内容截取、正则表达式处理、编码处理等等,几乎是五花八门。

我们一般为了方便,都会给字符串类型编写一些扩展函数,以便于常规的处理。编写扩展字符串函数的处理也很简单,创建一个静态类,并在方法中第一个方法参数指定为this即可,如下所示。

我们可以编写很多用于常规处理的字符串函数,如格式转换和内容提取,以及正则表达式等。

字符串的格式转换是非常常见的处理,如对UrlEncode/HtmlEncode/Base64转换/byte[]字节转换等等,一般我们可以把常见的放在一个静态类里面,这样便于维护。

日期类型也是我们常见的,因此可以针对日期的格式转换、日期的处理等内容整合到一个扩展函数里面,便于快速的调用和维护。

针对常规的C#对象的扩展,我们编写、收集整理了不少扩展函数,主要就是提高开发的便利性。如下截图所示。

有时候,我们在开发常规Windows项目的时候,需要用到对常规控件的赋值、数据绑定等常规处理,我们也可以通过编写扩展函数,并重载不同的方法,来更加的方便调用扩展函数的代码处理。

而对于第三方的UI控件,如DevExpress的一些控件,如果处理的时候,也进行一些控制封装,会使得调用代码更加简洁的,如下是针对一些常规DevExpress控件的扩展函数封装。

2、扩展函数的调用及便利

这些不同类型的控件扩展函数的封装,使得我们操作对象的处理更加便捷,如字符串转换为int类型的时候,我们直接调用扩展函数转换即可。

        /// <summary>
        ///获取表的所有记录数量/// </summary>
        /// <returns></returns>
        public intGetRecordCount()
{
var action = "GetRecordCount";string url =GetTokenUrl(action);return JsonHelper<DataTable>.ConvertString(url).ToInt32();
}

还有我们在开发界面的时候,更加方便调用来进行数据处理。如常规的对TreeList控件的处理代码,需要添加列和属性的时候,代码如下。

        /// <summary>
        ///初始化树控件/// </summary>
        private voidInitTree()
{
this.tree.Columns.Clear();//添加显示列 this.tree.Columns.Add(new TreeListColumn { FieldName = "Id", Caption = "Id" });//增加一个隐藏的字段,存储需要的ID this.tree.Columns.Add(new TreeListColumn { FieldName = "Name", Caption = "菜单名称", Width = 160, VisibleIndex = 0});//设置树控件的层次关系及属性 tree.KeyFieldName = "Id";
tree.ParentFieldName
= "PID";this.tree.OptionsBehavior.Editable = false;this.tree.OptionsView.EnableAppearanceOddRow = true;this.tree.OptionsView.EnableAppearanceEvenRow = true;this.tree.OptionsDragAndDrop.DragNodesMode = DragNodesMode.None;//不允许拖动

而使用扩展函数的时候,可以简化它的调用方式,并比较直观的处理。

       /// <summary>
        ///初始化树控件/// </summary>
        private voidInitTree()
{
this.tree.Columns.Clear();this.tree.OptionsDragAndDrop.DragNodesMode = DragNodesMode.None;//不允许拖动//控件扩展函数封装处理 this.tree.CreateColumn("Name", "菜单名称", 160, true);this.tree.InitTree("Id", "PID", null, false, false);

详细了解这个处理,可以参考随笔《
在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

有时候,我们还可以结合数据库处理,对扩展函数进行一定的封装处理,如下代码所示。

        /// <summary>
        ///绑定下拉列表控件为指定的数据字典列表/// </summary>
        /// <param name="combo">下拉列表控件</param>
        /// <param name="dictTypeName">数据字典类型名称</param>
        /// <param name="defaultValue">控件默认值</param>
        public static void BindDictItems(this ComboBoxEdit combo, string dictTypeName, stringdefaultValue)
{
Dictionary
<string, string> dict = BLLFactory<DictData>.Instance.GetDictByDictType(dictTypeName);
List
<CListItem> itemList = new List<CListItem>();foreach (string key indict.Keys)
{
itemList.Add(
newCListItem(key, dict[key]));
}

BindDictItems(combo, itemList, defaultValue);
}

用的时候,就非常简单了,如下代码是实际项目中对字典列表绑定的操作,字典数据在字典模块里面统一定义的。

/// <summary>
///初始化数据字典/// </summary>
private voidInitDictItem()
{
txtInDiagnosis.BindDictItems(
"入院诊断");
txtLeaveDiagnosis.BindDictItems(
"最后诊断");//初始化代码 this.txtFollowType.BindDictItems("随访方式");this.txtFollowStatus.BindDictItems("随访状态");
}

这样就非常简化了我们对字典数据源的绑定操作了,非常方便易读,下面是其中一个功能界面的下拉列表展示。

扩展函数的功能主要即使便于我们对一些常规类型的对象进行快速的操作,因此积累丰富的扩展类库,可以提高我们的开发能力,同时也可以给我们提供很好的参考,有时候可以把一些新的内容加入到已有的扩展函数中去,随着逐渐对对象的处理函数更加熟悉,我们就会更加娴熟的使用它们。