InstallerProjects打包 - HackerVirus - 博客园

mikel阅读(811)

来源: InstallerProjects打包 – HackerVirus – 博客园

打包桌面应用程序实在是一个不常使用的东西,偶尔使用起来经常会忘东忘西的耽误时间,因此,这篇文章多以图片记录过程,也是用于备忘。

下载打包工具

C#打包桌面应用程序有很多种方法,这里介绍一种使用Microsoft Visual Studio Installer Projects工具打包的方法。

首先,我们先创建一个Windows桌面应用,如图:

接下来我们选择工具,点击扩展和更新,如图:

然后我们得到界面如下,如图:

界面默认显示的是已安装的内容,我们仔细看下内容,会发现,这里都是我们已经安装过的工具,在仔细看,我们会发现,这里大部分工具的创建者都是microsoft,也就是说,这里的工具都是官方给我们提供的工具。

因为我们要下载本机不存在的工具,所以我们点击左侧菜单的【联机】按钮,然后在右上角的搜索框中输入Microsoft Visual Studio Installer Projects进行检索,如图:

然后我们选中Microsoft Visual Studio Installer Projects选项,点击选项中右上角的下载,将该工具下载到本地。

下载完成后,我们会发现,在当前窗体的下方有这样一个提示。

该提示,告诉我们,虽然工具已经下载完成了,但还没有进行安装,需要我们关闭Visual Studio后,才能安装。

关闭Visual Studio后,会自动弹出如下提示框,系统还会提示我们是否允许,我们选择【是】。

接下来,该窗体会显示工具的相关内容,下方会增加一个修改按钮,如下图:

我们点击修改,然后工具就会自动安装了。

创建打包项目

等待工具安装完成后,我们重新打开Visual Studio,打开刚刚我们建立的WPF项目。

在WPF项目所在的解决方案中,我们右键新建项目,然后选择左边菜单的【其他项目类型】—【Setup Project】,创建安装工程KibaInstallSetup,如下图:

点击确定 ,我们可以看到如下窗口。

该窗口左边窗口有三个文件夹图片,对应内容如下:

Application Folder:应用程序包含的文件设置。

User’s Desktop:用户桌面快捷方式设置。

User’s Programs Menu:用户启动菜单的快捷方式设置。

我们先看Application Folder的使用方式,首先选中Application Folder,然后右键—>Add—>项目输出。

点击后,弹出[添加项目输出组页面],如下图,因为解决方案下只有一个项目,所以我这里项目选择中只有一个选项。

点击确定,我们就成功的把项目主输出添加进来了,如下图:

此时,我们右键我们的安装工程KibaInstallSetup—生成,就已经可以生成该项目的可执行文件了,如下图:

不过,此时的安装文件是最基础的安装文件,什么自定义都没有。

下面我们为安装文件增加桌面快捷方式图标和开始菜单的快捷方式,并且修改可执行文件的作者、描述等等信息。

打包项目属性配置

项目属性

我们先进行安装文件的基础信息更改。

首先,我们左键选中项目,然后选择属性,注意,不是右键选择属性,而是如下图一样选择属性。

点击后,修改一下作者和描述,属性界面如下图所示:

桌面快捷方式

现在我们修改安装程序的快捷方式图片。

左键选中User’s Desktop,然后将鼠标移动到右侧窗体,右键选择[创建新的快捷方式],如下图。

然后,系统弹出一个让我们选择.ico文件的界面,但界面中只有三个文件夹,对应的是我们安装工程左上角的三个文件夹,如下图:

现在我们双击第一个应用程序目录选项,然后点击Add File,向安装目录下添加一个logo.ico文件,最后点击OK。

这样我们就成功创建了桌面快捷方式了,如下图。

现在,我们右键这个Shortcut to logo.ico,修改其名字为Kiba,然后左键点击选中,查看其属性,如下图。

可以看到,我们的快捷方式已经成功指向了我们的主输出,并且Icon也已经修改了。如果不满意指向和Icon,可以在属性里二次修改。

用户菜单快捷方式

用户菜单的快捷方式就是在User’s Programs Menu里设置,其设置的方法和桌面快捷方式的设置方法是一模一样的。

依赖文件和依赖框架

依赖文件

我们在打包应用程序时,有时候会需要一些依赖文件,这些文件并不能被主程序引用,但还需要和主程序在同一个安装路径下,那么在打包时,也就需要额外的把这些文件也打包进来。

添加额外依赖文件的方法很简单,选择Application Folder,在其对应的右侧窗体中,右键—Add—文件,如下图:

然后,在弹出的选择文件对话框中选择文件即可。

依赖框架

有时候我们的应用程序需要安装到一个没有Framework的电脑上,那么就需要打包的时候,把Framework也打包进来,或者在用户安装时提示对方下载。

我们右键项目,选择属性,然后在弹出的属性页中点击Prerequisites,如下图:

然后,在系统必备的窗体中,勾选Framework 4.6.1,在选择【从组件供应商的网站上下载系统必备组件】。

这样,我们的可执行文件在运行时,就会提示客户去微软官网下载Framework 4.6.1了,如下图:

当然,我们也可以把Framework 4.6.1打包进来,但这需要我们提前把Framework 的安装包下载下来,然后选择【从下列位置下载系统必备组件】,这样那个置灰的【浏览】按钮就可以使用了。

—————————————————————————————————-

到此,使用InstallerProjects打包桌面应用程序讲解就结束了。

代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/KibaInstall

C# 编写Windows服务实现开机启动一个程序 - Jerry_Wu - 博客园

mikel阅读(827)

来源: C# 编写Windows服务实现开机启动一个程序 – Jerry_Wu – 博客园

如果有一个应用程序,想要在电脑开机的时候自动启动(即使用户未登录Windows也要启动),可以用Windows服务来实现,我只是很简单的尝试了一下,由于前段时间做的一个办公QQ,在服务器端需要始终运行一个控制台程序来监听各种消息,但不能每次开机手动去启动这个控制台程序,所以就写了一个Windows服务,我用VS2008写的,以下是简单做法:

先在VS中创建一个Windows服务的项目,在自动生成的service1.cs文件里添加如下语句:

string StartAppPath = @”C:\Program Files\办公QQ服务\QQService.exe”;

这是我要运行的控制台程序的路径,你用的时候换成你的就可以了,然后在OnStart()函数中添加如下代码:

try

{

Process proc = new Process();

proc.StartInfo.FileName = StartAppPath; //注意路径

proc.Start();

}

catch (System.Exception ex)

{

//错误处理

}

双击Service1.cs打开设计视图,在设计视图中右键,选择添加安装程序,然后在生成的ProjectInstaller.Designer.cs文件中的InitializeComponent()函数中添加如下代码,具体作用不甚清楚:

this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;

 

this.serviceInstaller1.ServiceName = “办公QQ服务”;     //我的服务名称

this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;    \\服务自动运行

好了,现在可以编译生成exe了,下面要用cmd命令将刚才写的这个Windows服务安装并启动:

先cmd运行命令: cd C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\

进入.net2.0的文件夹,因为里面有个InstallUtil.exe的应用程序是我们需要用的,上面这个路径是我安装的路径,你根据需要改成自己的就行了,然后继续写下一个cmd命令:

InstallUtil C:\QQ_WinService.exe

这个.exe应用程序是我开头提到的用VS2008刚创建的,你也要根据需要换成你的,我直接把这个.exe拷出来放在C盘根目录下了,你到时换成你自己的路径就可以了

提示服务安装成功后,继续写下一个命令,启动这个服务:

net start 办公QQ服务

“办公QQ服务”是我安装的服务的名称,这里也需要换成你自己的

现在这个Windows服务就已经写好了,当然,这些cmd命令也可以写成一个批处理文件,要停止这个服务的话可以用:

Net stop 办公QQ服务

然后删除这个服务:

cd C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\

InstallUtil /u C:\QQ_WinService.exe

System.InvalidOperationException:“线程间操作无效: 从不是创建控件“btnSearch”的线程访问它。” - 二等碗 - CSDN博客

mikel阅读(1347)

来源: System.InvalidOperationException:“线程间操作无效: 从不是创建控件“btnSearch”的线程访问它。” – 二等碗 – CSDN博客

System.InvalidOperationException:“线程间操作无效: 从不是创建控件“btnSearch”的线程访问它。”
这个问题属于跨线程问题

在Form1重载中写上一行代码

public Form1()
{
InitializeComponent();
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false
}

这样问题就解决了

这个问题属于跨线程问题

————————————————
版权声明:本文为CSDN博主「盗理者」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36051316/article/details/80026874

Log4Net异常日志记录在asp.net mvc3.0的应用 - aehyok - 博客园

mikel阅读(982)

来源: Log4Net异常日志记录在asp.net mvc3.0的应用 – aehyok – 博客园

前言

log4net是.Net下一个非常优秀的开源日志记录组件。log4net记录日志的功能非常强大。它可以将日志分不同的等级,以不同的格式,输出到不同的媒介。本文主要是简单的介绍如何在Visual Studio2010(ASP.NET Mvc3.0)中使用log4net快速创建系统日志,如何扩展以输出自定义字段。

用户可以从http://logging.apache.org/log4net/下载log4net的源代码。解压软件包后,在解压的src目录下将log4net.sln载入Visual Studio .NET,编译后可以得到log4net.dll。用户要在自己的程序里加入日志功能,只需将log4net.dll引入工程即可。

在项目中配置

第一步:首先在项目中引用log4net.dll文件。

第二步:在Web.config文件中进行添加configSections的节点

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
    <!--配置一个结点 名称为log4net-->
  </configSections>

第三步:添加log4net配置节点

<log4net debug="true">
</log4net>

然后在log4net节点下添加

写入本地文本文件中的配置

复制代码
         <appender name="LogFileAppender" type="log4net.Appender.FileAppender" >
             <!--定义的是日志记录到文件的附着器 name表示该附着器的名称-->         
             <!--在log4net中还有一个附着器RollingFileAppender 它表示会循环生成很多文件,举例来说,就是设置一共可以生成20个文件,每个文件的大小为2K,那么如果第一个、-->          
             <!--文件的大小超过2K,就会自动创建一个按顺序命名的文件-->
             <param name="File" value="c:\Log\DBLog.txt" /> <!--日志记录的存在路径-->
             <param name="AppendToFile" value="true" /><!--为true就表示日志会附加到文件,为false,则会重新创建一个新文件-->
             <layout type="log4net.Layout.PatternLayout">
               <!--输出内容控制-->
               <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
             </layout>     
         </appender>
复制代码

注释很清楚,就不解释了。

写入指定邮箱的配置

复制代码
        <appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
        <!--设置发送电子邮件的附着器-->
            <authentication value="Basic" />
            <to value="455043818@qq.com" />
            <from value="aehyok@163.com" />
            <username value="帐号" />
            <password value="密码" />
            <subject value="程序异常日志记录邮件发送" />
            <smtpHost value="smtp.163.com" />
            <bufferSize value="512" />
            <lossy value="true" />
            <evaluator type="log4net.Core.LevelEvaluator">
               <threshold value="debug"/>  
            </evaluator>
            <layout type="log4net.Layout.PatternLayout">
                 <conversionPattern value="%newline%date [%thread] %-5level %logger [%property{NDC}] - %message%newline%newline%newline" />    
            </layout>
        </appender>
复制代码

通过的是163邮箱服务器发送

将日志写入数据库的相关配置,还要建立一张对应的数据库表

复制代码
         <appender name="ADONetAppender" type="log4net.Appender.ADONetAppender">
             <!--存储到数据库的操作-->
             <bufferSize value="10"/>
             <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
             <connectionString value="server=.;database=Log4Net;user id=sa;password=saa"/>
             <commandText value="INSERT INTO _Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) VALUES (@log_date,                                @log_thread, @log_level, @log_logger, @log_message, @log_exception)"/>
             <parameter>
                 <parameterName value="@log_date"/>
                 <dbType value="DateTime"/>
                 <layout type="log4net.Layout.RawTimeStampLayout"/><!--可以认为是记录日志的时间-->
             </parameter>
             <parameter>
                 <parameterName value="@log_thread"/>
                 <dbType value="String"/>
                 <size value="255"/>
                 <layout type="log4net.Layout.PatternLayout">
                    <conversionPattern value="%thread"/><!--记录日志时的线程号--> 
                </layout>  
             </parameter>
             <parameter>
                 <parameterName value="@log_level"/>
                 <dbType value="String"/>
                 <size value="50"/>
                 <layout type="log4net.Layout.PatternLayout">
                    <conversionPattern value="%level"/><!--日志级别-->
                 </layout>
             </parameter>
             <parameter>
                 <parameterName value="@log_logger"/>
                 <dbType value="String"/>
                 <size value="255"/>
                 <layout type="log4net.Layout.PatternLayout">
                     <conversionPattern value="%logger"/><!--哪个记录器存储的该日志-->
                 </layout>
             </parameter>
             <parameter>
                 <parameterName value="@log_message"/>
                 <dbType value="String"/>
                 <size value="4000"/>
                 <layout type="log4net.Layout.PatternLayout">
                     <conversionPattern value="%message"/><!--日志信息-->
                 </layout>
             </parameter>
             <parameter>
                 <parameterName value="@log_exception"/>
                 <dbType value="String"/>
                 <size value="255"/>
                 <layout type="log4net.Layout.ExceptionLayout"/><!--异常信息-->
             </parameter>  
         </appender>
复制代码

另外一种写入文件的方式配置

复制代码
         <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
             <!--这个就是我在上面提到的RollingFileAppender-->
             <file value="example.log" /><!--文件名称-->
             <appendToFile value="false" /><!--会创建新文件,一般设置为true,这里设置为false,是为了看到创建的文件-->
             <maximumFileSize value="1KB" /><!--文件大小-->
             <maxSizeRollBackups value="20" /><!--创建最大文件数-->
             <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%level %thread %logger - %message%newline" />
             </layout>
         </appender>
复制代码

在log4net节点中还有两个节点

复制代码
      <logger name="Loggering">
        <level value="Warn"/>
        <appender-ref ref="ADONetAppender"/>
      </logger>
      <root>
        <level value="info" />
          <!--<appender-ref ref="ADONetAppender" />-->
          <appender-ref ref="SmtpAppender"/>
          <!--<appender-ref ref="LogFileAppender"/>
          <appender-ref ref="ColoredConsoleAppender"/>
          <appender-ref ref="EventLogAppender"/>
          <append-ref ref="NetSendAppender"/>
          <appender-ref ref="RollingFile"/>-->
      </root>
复制代码

在框架的体系里,所有的日志对象都是根日志(root logger)的后代。 因此如果一个日志对象没有在配置文件里显式定义,则框架使用根日志中定义的属性。在<root>标签里,可以定义level级别值和Appender的列表。如果没有定义LEVEL的值,则缺省为Debug。可以通过<appender-ref>标签定义日志对象使用的Appender对象。<appender-ref>声明了在其他地方定义的Appender对象的一个引用。在一个logger对象中的设置会覆盖根日志的设置。而对Appender属性来说,子日志对象则会继承父日志对象的Appender列表。这种缺省的行为方式也可以通过显式地设定<logger>标签的additivity属性为false而改变。

那么上面就会有数据库日志的写入和邮箱的写入

在Global.asax文件初始化配置

复制代码
        protected void Application_Start()
        {
            //读取日志  如果使用log4net,应用程序一开始的时候,都要进行初始化配置
            log4net.Config.XmlConfigurator.Configure();
            
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);
        }
复制代码

调用

复制代码
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger("Loggering");
        public ActionResult About()
        { 
             log.Info("log日志信息");
             log.Debug("debug信息");
             log.Error("error信息");
             log.Warn("warn信息");
             Exception ex = new Exception("测试的异常信息");
             log.Fatal("fatal信息", ex);
            return View();
        }
复制代码

运行一下

总结

Log4net中 Debug、INFO、WARN、ERROR 区分得很好。正常的 Debug、INFO 的日志, 就让它记录在 日志文件里面吧。

对于 WARN、ERROR 级别的日志, 记录到日志文件的同时, 顺便发送电子邮件到我的信箱里面。 这样一来, 我也不必每天去看日志文件, 二来,出了什么问题, 能及时通过电子邮件得到通知。

示例代码下载链接http://url.cn/Tz89RW

数据库结构

复制代码
create database Log4Net
go
use Log4Net
create table _log
(
    id int identity(1,1) primary key not null,
    date datetime null,
    thread int null,
    level varchar(10) null,
    logger varchar(20) null,
    Message varchar(100) null,
    Exception varchar(100) null
)
复制代码

还待优化,有空了再来琢磨琢磨。

FileHelper 一个极好的数据文本文件处理类库 - Blues - 博客园

mikel阅读(1517)

来源: FileHelper 一个极好的数据文本文件处理类库 – Blues – 博客园

这个我应该是看Scott的博客,看他介绍的经典小工具的时候看到的,感谢。
我经常接触到一些csv格式存储的数据文件,把这些文件导入数据库,或者把数据库的数据导出成为excel文件或者csv文件。
自己写功能解析自然好,但是需要处理的地方太多,如果是标准文件还好,但是如果文本文件里含有保留分隔符,或者数据格式有一些小错误,都需要自己处理。而且如果数据格式变化了,每次还要修改自己的代码。所以,我一直没写这个代码。
偷懒,一直用Excel的Text to Data 功能凑合,或者用SQL的导入导出功能,再就是用从网页上拷贝粘贴数据到excel,然后清除格式的方法。
这些方法其实都有弊端,典型的就是大量的手工才做,重复劳动,这些到还能忍受了,当涉及到数据库,用的最多的自然就是SQL自带的导入导出功能。这个功能大体上好用,可是如果文本文件里面有一点错误,他就罢工。这倒还可以忍受,后来我在64位机器上装了64位的SQL,这样微软居然不支持直接由Excel导入,说64位版本不需要这个功能,我在网上看到了许多类似需求,微软都坚持说不提供这个64位的flat文件驱动!一等就是3年,SQL2005年发布,现在已经2008年了。可怕。
终于看到了这个阿根廷人编写的开源文本文件导入导出帮助类库!
本来以为还挺难使用,谁知,人家的帮助写得好,类库做的也好。我稍微调试了一下,就上手了!

最基本的用法可以看他主页的教程。
根据自己的文本文件格式,建立类库模板,定义好接受字段之类的。
增加引用类库文件到自己的项目。
一次性读取文本文件到内存。
最后是写入到其他的数据库或者转换格式保存。

我的问题是,我的文本文件比较大,大于50M,一次性读入的时候总是内存溢出,看了一下发现这个类库居然还提供了异步导入方式,就是读取一行,写一行!
略微看了一下,也好解决。

语法上,本类库兼容.net 1.0 和 .net 2.0,所以可以直接用.net 2.0的最新的语法写。写起来很方便!读取出来的数据可以直接赋给列表对象!
当然,类库本身也提供了对写入SQL,到处Excel的支持!由于作者的写入SQL的方式需要把SQL密码写道代码中,我就没有采用,而是自己用SQL数据连接写入SQL库的。
当然那个导出到EXcel的方式用起来还是很方便的。

总之这个工具最到的好处是,相当于你自己写了一个导入导出的功能,用法强大,容错性高,方便包含在自己的项目中,对文本进行自动化处理,完全省掉了自己编写功能的辛苦!强烈推荐!

The FileHelpers are a free and easy to use .NET library to import/export data from fixed length or delimited records in files, strings or streams.

The idea is pretty simple:

You can strong type your flat file (fixed or delimited) simply describing a class that maps to each record and later read/write your file as an strong typed .NET array

The Library also has support for import/export data from differents storages like Excel, Access, SQLServer, etc.



最新版下载地址(RC1的原因是因为作者最近在陪老婆生孩子,没时间发布最终版,恭喜!这个链接可能被国内拦住了,所以你可能需要代理访问下载!):
http://www.devoo.net/FileHelpers_2.2_RC1.rar

应要求,我把作者主页上的文件转到rapidshare上去,这个站点国内可以访问(本来想转到博客园控件,看了一下,才30M,这也太小了点吧???)需要的同学可以从rapidshare下载。
2.0编译版
http://rapidshare.com/files/127794302/FileHelpers_2_0_0_bin_docs_wizard.zip.html
2.0带源代码版
http://rapidshare.com/files/127794795/FileHelpers_2_0_0_source_2003_2005.zip.html
2.2更新
http://rapidshare.com/files/127795043/FileHelpers_2.2_RC1.rar.html

主页在这里,这个国内可以访问,有十分详细的帮助文档,一看就会,不要担心!!!
http://www.filehelpers.com/

下面是我做实验的代码,加了注释,你可以看一下,十分简单。

复制代码

1using System;
2using System.Configuration;
3using System.Data.SqlClient;
4//在此处增加对这个类库的引用就可以了
5using FileHelpers;
6
7
8namespace FileHelpersSample
9{
10    public partial class _Default : System.Web.UI.Page
11    {
12        protected void Page_Load(object sender, EventArgs e)
13        {
14
15        }

16
17        protected void btImportFLFtoSQLbyLINQ_Click(object sender, EventArgs e)
18        {
19
20            //清空所有已存在记录,
21            string SQLCommand = “TRUNCATE TABLE dbo.prm”;
22            int sqlresult = RunSQLStatement(SQLCommand);
23            Response.Write(sqlresult);
24            Response.Write(“<hr />”);
25
26            string strCSVFile = System.Web.HttpContext.Current.Server.MapPath(“./”) + “Data\\rrr.csv”;
27
28            //一次性读入整个文本文件
29            //FileHelperEngine<FLFRecordClass> engine = new FileHelperEngine<FLFRecordClass>();
30            //FLFRecordClass[] res = engine.ReadFile(strCSVFile);
31
32            //异步方式,一次读入一行,处理一行
33            FileHelperAsyncEngine<FLFRecordClass> engine = new FileHelperAsyncEngine<FLFRecordClass>();
34            engine.BeginReadFile(strCSVFile);
35
36            int i = 0;
37            while (engine.ReadNext() != null)
38            {
39                FLFRecordClass f = engine.LastRecord;
40
41                string Command;
42                
60
61                int j = RunSQLStatement(Command);
62                Response.Write(j);
63                Response.Write(“<br />”);
64                //if (i > 2) break;
65                i++;
66            }

67               engine.Close;
Response.Write(“<hr />”);
68            Response.Write(i);
69
70
71        }

72
73
74        public static SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings[“FLFConnectionString”].ConnectionString);
75
76        //自定义函数,用于实现对SQL数据库的更新操作
77        public int RunSQLStatement(string Command)
78        {
79            SqlCommand DataCommand = new SqlCommand(Command, conn);
80            DataCommand.Connection.Open();
81            int i = DataCommand.ExecuteNonQuery();
82            DataCommand.Connection.Close();
83            return i;
84        }

85
86    }

87}

88

复制代码

读取文本文件时,需要实现定义一个类,这个类的语法也十分简单,而且作者还提供了一个帮助向导,帮助你自动生成这个类文件(真是体贴周到!)
我这里为了偷懒,读入的时候就都都成了字符型,而且也没加什么处理,其实这个类库还只是纠错,按格式读取,读取的时候转换格式等功能!你自己慢慢看,需要的时候再用。我是一次性转到SQL临时表里,自己用程序再处理的,当然你可以用作者提供的类库在读入的时候一次性处理,究竟哪种方式好,我还没研究,感觉自己的方式比较省事(你说的?愿意听你的看法。)


1using System;
2using FileHelpers;
3
4namespace FileHelpersSample
5{
6
7    [IgnoreFirst(1)]
8    [IgnoreEmptyLines()]
9    [DelimitedRecord(“,”)]
10    public sealed class FLFRecordClass
11    {
12
13        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
14        public String Field1;
15
16        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
17        public String Field2;
18
19        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
20        public String Field3;
21
22        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
23        public String Field4;
24
25        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
26        public String Field5;
27
28        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
29        public String Field6;
30
31        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
32        public String Field7;
33
34        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
35        public String Field8;
36
37        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
38        public String Field9;
39
40        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
41        public String Field10;
42
43        //.
44
45        [FieldQuoted(‘”‘, QuoteMode.OptionalForRead, MultilineMode.AllowForRead)]
46        public String Field111;
47
48    }

49
50
51}

52

当前不会命中断点还未为文档加载任何符号——问题探究 - 刘奇云 - 博客园

mikel阅读(1269)

来源: 当前不会命中断点还未为文档加载任何符号——问题探究 – 刘奇云 – 博客园

from:http://blog.csdn.net/tr1912/article/details/51111824

今天在调试牛腩网页的时候遇到了一个问题需要用到断点调试来解决,可是加了断点之后出现了下面的情况:

然后就郁闷了,查了百度之后得到了如下的结果:

断点调试是VS中的一大利器,有了它我们可以快速定位到代码的问题所在。在某些情况下会导致设置了断点后程序无法在断点处停下,下面分4种情况来解决断点不会命中的问题:

1、检查VS的调试模式。VS的调试分为两种,Debug和Release。Debug是专门真对检查程序的bug而设置的一种模式。Release模式对程序的编译进行了优化,一般用于程序发布。首先确保是在Debug模式下设置的断点。

 

 

2、检查设置断点的模块在调试时有没有输出。调试时遇到最多的警告“当前不会命中断点 还没有为该文档加载任何符号 ”。原因是我们设置断点的代码块并没有编译输出。找到程序的生成目录,通常是在bin文件夹下,删除设置断点代码文件对应的dll文件或exe文件。重新调试项目。

 

 

3、统一引用目录,将引用设置到同一个目录下,防止源码与dll版本不匹配

4、工具——选项——调试——常规中的“要求源文件和原始版本完全匹配”的勾去掉

经过仔细检查后发现,自己项目的.net版本不一致,导致了这个错误,修改后就好了。

十分钟搞定你自己的多图片/文件服务器 - 坦荡 - 博客园

mikel阅读(923)

来源: 十分钟搞定你自己的多图片/文件服务器 – 坦荡 – 博客园

本文版权归博客园和作者吴双本人共同所有,转载和爬虫必须在显要位置注明原文地址,www.cnblogs.com/tdws

 写在前面

图片/文件服务器,顾名思义就是存文件呗,有的人用阿里云的现有服务,有的把文件Post到文件服务器,在文件服务器一端用一个应用程序来接收并保存,方法各不相同。老司机们各种服务器已经玩烂了。在使用图片/文件多服务器的几个问题就在于:

1.web上传的文件如何post到文件服务器上及其速度如何,占用带宽和耗时如何。

2.如何使文件均衡的分布在各台图片服务器上。

3.如何拓展新的图片服务器,并保障对已分配的内容影响降到最小。

本篇分享一方面希望有经验的司机给点意见,另一方面分享给新司机和记录自己的历程。

 第一分钟确定最终目标

1.各文件服务器不仅提供存储功能,还要有对外服务能力,以降低各个Web服务器的带宽占用和磁盘IO消耗。

2.提供简便和容易的可横向拓展能力

3.Async异步IO保证线程非阻塞,如果对异步有疑问的,可以异步这篇文章 http://www.cnblogs.com/tdws/p/6172207.html

4.文件快速到达图片服务器

 第三分钟否定自己

1.否认采取Http Post图片到图片服务器的方式

2.否认采取Hash取模算法

3.否认使用一致性哈希,避免服务器横向拓展后文件移动,即使是少量的

 第五分钟确定实现方式

1.把文件服务器磁盘共享出一个文件夹,作为特定用户间“网络磁盘”,就是说该共享文件夹,只有web服务器有权限访问,你可以参照局域网内如何共享文件夹或者阿里云共享文件夹。

2.通过IIS虚拟目录,将其物理路径指向网络磁盘。你知道共享文件夹间使用Tcp/Ip协议和Netbios协议通信。

3.配置WebConfig访问网络磁盘身份(用户名和密码)—必须配置哦

4.第一步所共享的文件,在文件服务器部署在IIS下WebApp中的一个文件下,以便暴露URL路径。因为你知道IIS除了使用文件读取的方式,是不可以获取父级文件夹中的任何内容的。

4.确定根据文件名均匀分配图片位置的方式—文件后缀名方式,后缀名采用”文件名-01″(一号服务器),后缀名采用”文件名-02″(二号服务器),随机数使用Random来取,随机数是几,我们就存在几号服务器。经过多次测试100万次随机,1,2,3分别得到33W次左右,保障了均衡。在将来拓展的时候,Random随机数多开启几位就行了,不需要文件的移动,也没有reshash。并且在横向拓展服务器数量后,希望保证已有三台服务器不继续存储文件,Random范围可调。在数据库或者缓存中我们已经存下了文件名称,并且其有后缀名,在为前台返回图片URL的时候,只需要根据后缀名称,来拼出文件服务器IP或域名。

 第七分钟按照步骤动手操作

1.部署FileServerApp,其功能是对图片暴露URL,其实很简单啦,建立一个ASP.NET空项目,建立出存储图片的文件夹。并将该文件夹共享给Web服务器,细节看最后一节的部署图。

2.假定你已经实现文件夹共享

3.在IIS站点下新建虚拟目录,注意物理路径指向的是网络共享文件夹的网络路径

4.在Webconfig下  <system.web>增加 <identity impersonate=”true” userName=”ben” password=”yourpwd” /> ,IIS对该虚拟目录设置身份验证为 应用程序池标识。

5.使用Server.MapPath可以获取FileServer2的网络路径,并使用你的读写文件方式对目录中的文件进行读写。

6.写入文件建议使用Async的方式,异步IO会在你将IO请求包发给网络驱动或者磁盘驱动后,返回去处理其他事情,在将来的某个时刻,文件写入成功,状态机恢复你的上下文并以同步的方式继续向下执行,能大大提高你Web服务器的吞吐能力。

 写在最后的重点

如果还有模糊的地方,看这里就全明白啦

 

 

2017-5-3更新。读淘宝技术这十年笔记,有兴趣可以看看。

 

一束激光冒充人声:110米外黑掉智能音箱,手机电脑平板也中招_IT新闻_博客园

mikel阅读(1257)

来源: 一束激光冒充人声:110米外黑掉智能音箱,手机电脑平板也中招_IT新闻_博客园

郭一璞 光栗子 发自 凹非寺
量子位 报道 公众号 QbitAI

周末的下午,你正在家里打游戏,不曾注意到身旁的智能音箱上,多出了一个小绿点。

那是一束激光,来自窗外马路对面的另一栋建筑。

突然,房间的灯开了。

空调、空气净化器、扫地机器人启动了,手机收到了电商平台的扣款提示,甚至你外面的车库门也已然洞开……

而你的手机和平板电脑也突然开始发疯,疯狂的下载删除不同的应用,播放奇怪的视频和音乐,给社交软件上的好友发奇怪的信息……

到底发生了什么?是谁,不知不觉侵入了你的生活?

其实,这是来自日本电气通信大学和美国密歇根大学科学家的一项新发现:

当激光打在装有语音助手的设备上,就可以冒充人类的语音,被麦克风转换成电信号,悄无声息的发出指令,控制相连的设备。

因此,那些和 Google Assistant、亚马逊 Alexa、苹果 Siri 相连的机器,不管是智能的灯具、门锁、电器这些硬件设施,还是各种电商、支付、社交 App,都会不知不觉间被控制。

虽然科学家们还没有在其他品牌的语音助手上测试,不过量子位采访到了腾讯安全团队 Tencent Blade Team,他们说:从原理上讲问题大都是相通的。

不需要太强的激光,普通激光笔的强度就可以,就算距离有110 米远,就算在外面的另一栋建筑里,就算要穿过玻璃窗,都可以控制你家里的智能音箱、手机和平板们。

恐怖的演示过程

来看看科学家们的实地演示。

将命令 Google Assistant 打开车库门的语句“OK Google, open the garage door”的嵌入激光中,打在智能音箱的麦克风上。

智能音箱回了一句“OK, opening”,接着车库门就开了。

那么如果把距离设置的非常远呢?

在第二段演示中,激光发射器和智能音箱的距离长达110 米

科学家们将询问时间的语句“OK Google, what time is it?”的嵌入激光中,打在智能音箱的麦克风上。

“It’s 9:43”在没有任何人发话询问的情况下,智能音箱自己突然说了一个时间。

即使在窗外的另一栋建筑里,也不影响激光对智能音箱的控制。

在第三段演示中,科学家们将激光源挪到了远处的一栋高高的建筑上,隔着玻璃窗发射激光,将命令 Google Assistant 打开车库门的语句“OK Google, open the garage door”的嵌入激光中,打在窗口智能音箱的麦克风上。

因为这次发射点又高又远,所以科学家们干脆给激光配了一个长焦镜头。

智能音箱还是顺利的回复“OK, opening”,打开了车库的门。

当然,演示中并没有“鬼故事现场”的感觉,一个原因是激光可见,另一个原因是语音助手的声音你可以听到。

因此,科学家们也尝试了人类肉眼看不见的红外光,在比较近的距离是可以起到作用的;

至于,语音助手回话会被主人听见,先用激光发个指令把音量调零,就真的悄无声息控制一切了。

看到这里你可能疑惑,激光怎么能冒充人声?

让麦克风听成人声

故事是从去年春天开始。

来自日本的菅原健,是个研究网络安全问题的科学家。他专程跑去美国,给密歇根大学的同行傅佳伟 (Kevin Fu) 教授,秀了一波自己刚刚解锁的技能:

把一束高强度的激光,对准iPad的麦克风,然后用每秒震荡大约 1000 次的正弦波,不停地调整激光的强度。

傅佳伟在一旁带着耳机,听麦克风收到了什么。让人惊讶的是,他听到了一种高频音调。

明明是接收声波的设备,却把光波当成声波接收了,这是MEMS 麦克风的一个重要弱点。而大部分手机和智能音箱,都是使用 MEMS 麦克风,因此。

自从有了这个神奇的发现,菅原君就开始和傅佳伟的实验室一起,用激光去欺骗智能音箱,攻击各种接收语音指令的设备。

科学家说,只要用一种特定的频率去调整激光的强度,激光便会用同样的频率去干扰麦克风,让麦克风把光波解调成电信号

就像下面这张图,上为激光发射的信号,下为麦克风接收的信号,频率几乎一致:

不用指定发射位置,只要对准麦克风射出激光,麦克风就把光线转换成电信号了,像日常把声波转换成电信号一样。

当然,如果只是随意的电信号,并不足以让音箱乖乖听你的话。必须让它以为是有人类发出语音才行。

所以,研究人员还要对激光做调幅 (AM) ,让麦克风转出接近人类语音的信号。

就像开头展示的那样,他们选定了一系列指令,包括:“现在几点了”“把音量调零”“买一支激光笔”“打开车库门”等等。

然后,用这些词句的语音波形,来定制激光的强度变化。

这样,智能音箱收到的电信号,就会和听到人声的时候差不多了。

一开始,他们用60 毫瓦的激光,测试了16 台不同的智能音箱。

结果,50 米是成功接收的最远距离。

攻击手机,就稍微困难一些了:iPhone 需要10 米以内,安卓手机需要5 米以内。

后来,科学家们又想测试一下,这项技术的极限在哪里。于是,把激光强度调低到了5 毫瓦,相当于一支廉价激光笔的水平,把距离拉远到110 米

虽然,许多音箱都没有响应,但Google Home和初代Echo Plus依旧中了招,就是开头看到的那样。

进一步加大难度,隔着窗户发射激光,76 米距离。这次没骗到一只 Echo,但 Google Home 依然被骗了,堪称硕果仅存:

至于,麦克风为什么对光波也有反应,哈佛大学电气工程系的退休教授 Paul Horowitz 说,至少有两种物理机制,可以让麦克风把误解成声波

一是激光的脉冲会加热麦克风的振膜,令周围的空气膨胀,产生一种压力。声音也是依靠产生声压,才被麦克风捕捉到的。

二是,如果被攻击的设备,不是完全不透光的话,光线其实可以直接穿过麦克风,直接到达芯片的所在,这样就能把光波的振动,翻译成电信号了。

这可能跟太阳能板里的二极管,还有光纤电缆末端的光电效应,原理一样。如果真是这样,想让激光被当做语音指令,就更容易了。

另外,量子位还采访到了腾讯的安全团队 Tencent Blade Team,他们的理解是:

这项研究和此前业界的 “海豚音攻击”有异曲同工之妙,都是利用了麦克风的一些特殊的硬件特性进行攻击,但这次的“光攻击”可以从更远距离(超过 100 米)发起,在现实生活中的攻击利用难度更低。

攻击的难度降低,防御的难度提升。Blade 团队认为,这项研究的意义十分重大:

从目前公开的信息来看,厂商很难从软件层面对这个漏洞进行彻底修复,之前安全圈内也没有 MEMS 麦克风会将光信号转换为电信号相关的安全研究,这项研究还是具有很高的创新性与实战意义。

怎样才能不被黑?

看到这样的攻击效果,谷歌和亚马逊很快就回应了:

谷歌说,已经在仔细观察这次的研究成果,并且强调一直对保护用户、提升设备的安全性能非常重视。

亚马逊也发表了声明说,正在看论文,后面将会和作者们交流,更深入地了解这项研究。

在厂商们给出补救措施之前,研究人员先为他们提供了一些友善的建议:

比如,可以设置让用户先输入语音密码,解锁后才能发布指令。

比如,可以在麦克风周围加上光屏蔽,抵挡激光的攻击。

再比如,在音箱两侧依靠两个麦克风同时接收指令,然后对比。因为两个麦克风,很难同时被击中。

当然,对产品做出这样的升级,还需要不少时间。

而你现在能做的就是,不要把智能音箱放在黑客能看到的地方。

不然,还是去用那些需要解锁的设备吧,人脸解锁和指纹解锁都能起到保护作用,避免语音助手接收到黑客的指令。

腾讯 Blade Team 还提示,最好关闭声纹识别 (因为声纹也可以用激光冒充) ,也可以在设备外部的麦克风口贴上黑色标签纸,阻挡激光攻击。

作者们


一作菅原健,就是从日本跑到美国炫 (mian) 技 (ji) 的那一位,电子通信大学 (UBE) 的准教授。


傅佳伟 (Kevin Fu) ,密歇根大学的教授,专注攻击各种 AI。量子位之前报道过一种把硬盘改造成窃听器的方法,也是他参与的研究。难怪,菅原君会不远万里去找他。


Daniel Genkin,密歇根大学助理教授。他和傅佳伟都是这项研究的负责人。

另外,还有两位作者,他们是傅佳伟教授实验室的成员,Benjamin Cyr 以及 Sara Rampazzi。

传送门

论文

Light Commands: Laser-Based Audio Injection Attacks on Voice-Controllable Systems

Takeshi Sugawara, Benjamin Cyr, Sara Rampazzi, Daniel Genkin, Kevin Fu

https://lightcommands.com/20191104-Light-Commands.pdf

主页

https://lightcommands.com/

日本中二少年教你用姿势估计把自己变成3D人物,动作实时同步,iOS上也能实现

mikel阅读(1390)

来源: 日本中二少年教你用姿势估计把自己变成3D人物,动作实时同步,iOS上也能实现

大数据文摘出品
作者:刘俊寰
不知道从什么时候开始,3D动画就热起来了,但是很多经典动画3D化后就变味了,人物的肢体动作看上去僵硬了不少。并且,传统3D靠一帧一帧制作,费时费力。
现在,你就拥有一个拯救3D动画的机会!
一位日本中二少年自学了机器学习后,就给自己做了个酷炫的模型,可以把自己的动作实时变成流畅的3D人物动作,而且整个过程非常简单易操作。
话不多说先看效果图:
这个推特名为幸彦青柳(Yukihiko Aoyagi)的日本小哥将3D姿态估计与3D开发平台和一些渲染引擎(比如Unity)相结合,于此更够跟准确地跟踪3D空间中的人体运动。上面的动图就是针对动作的实时估计和生成。
不过可惜的是,这个项目目前还只支持单人动作,不能实现双人对打。
项目已经在GitHub上开源:
https://github.com/yukihiko/ThreeDPoseUnitySample?source=post_page—–e74d7d347c2———————-
趁着它还没刷爆朋友圈,赶紧上手试一试!
用3D姿势估计的Onnx模型移动Unity

青柳君尝试过多种实现方式,包括WindowsML,ML.Net,Onnx Runtime等,但最终选择了OpenCVSharp,也就是OpenCV模型导入功能,在Unity中加载和执行Onnx,因为OpenCVSharp在Unity和.Net环境中可以用相同的方式处理,图像也不会被转换为Mat格式。
尽管看上去处理起来很容易,但目前还缺少相关数据,青柳君特意总结了他的这次尝试,将文章公布在了Qiita上。
相关链接:
https://qiita.com/yukihiko_a/items/386e3a86a5e523757707
有关Onnx的代码部分如下:
// Properties for onnx and estimation    private Net Onnx;    private Mat[] outputs = new Mat[4];
    private const int inputImageSize = 224;    private const int JointNum = 24;    private const int HeatMapCol = 14;    private const int HeatMapCol_Squared = 14 * 14;    private const int HeatMapCol_Cube = 14 * 14 * 14;
    char[] heatMap2Dbuf = new char[JointNum * HeatMapCol_Squared * 4];    float[] heatMap2D = new float[JointNum * HeatMapCol_Squared];    char[] offset2Dbuf = new char[JointNum * HeatMapCol_Squared * 2 * 4];    float[] offset2D = new float[JointNum * HeatMapCol_Squared * 2];
    char[] heatMap3Dbuf = new char[JointNum * HeatMapCol_Cube * 4];    float[] heatMap3D = new float[JointNum * HeatMapCol_Cube];    char[] offset3Dbuf = new char[JointNum * HeatMapCol_Cube * 3 * 4];    float[] offset3D = new float[JointNum * HeatMapCol_Cube * 3];
    public void InitONNX()    {        Onnx = Net.ReadNetFromONNX(Application.dataPath + @"\MobileNet3D2.onnx");        for (var i = 0; i < 4; i++) outputs[i] = new Mat();    }
    /// <summary>    /// Predict    /// </summary>    /// <param name="img"></param>    public void Predict(Mat img)    {        var blob = CvDnn.BlobFromImage(img, 1.0 / 255.0, new OpenCvSharp.Size(inputImageSize, inputImageSize), 0.0, false, false);        Onnx.SetInput(blob);        Onnx.Forward(outputs, new string[] { "369", "373", "361", "365" });
        // copy 2D outputs        Marshal.Copy(outputs[2].Data, heatMap2Dbuf, 0, heatMap2Dbuf.Length);        Buffer.BlockCopy(heatMap2Dbuf, 0, heatMap2D, 0, heatMap2Dbuf.Length);        Marshal.Copy(outputs[3].Data, offset2Dbuf, 0, offset2Dbuf.Length);        Buffer.BlockCopy(offset2Dbuf, 0, offset2D, 0, offset2Dbuf.Length);        for (var j = 0; j < JointNum; j++)        {            var maxXIndex = 0;            var maxYIndex = 0;            jointPoints[j].score2D = 0.0f;            for (var y = 0; y < HeatMapCol; y++)            {                for (var x = 0; x < HeatMapCol; x++)                {                    var l = new List<int>();                    var v = heatMap2D[(HeatMapCol_Squared) * j + HeatMapCol * y + x];
                    if (v > jointPoints[j].score2D)                    {                        jointPoints[j].score2D = v;                        maxXIndex = x;                        maxYIndex = y;                    }                }
            }
            jointPoints[j].Pos2D.x = (offset2D[HeatMapCol_Squared * j + HeatMapCol * maxYIndex + maxXIndex] + maxXIndex / (float)HeatMapCol) * (float)inputImageSize;            jointPoints[j].Pos2D.y = (offset2D[HeatMapCol_Squared * (j + JointNum) + HeatMapCol * maxYIndex + maxXIndex] + maxYIndex / (float)HeatMapCol) * (float)inputImageSize;        }
        // copy 3D outputs        Marshal.Copy(outputs[0].Data, heatMap3Dbuf, 0, heatMap3Dbuf.Length);        Buffer.BlockCopy(heatMap3Dbuf, 0, heatMap3D, 0, heatMap3Dbuf.Length);        Marshal.Copy(outputs[1].Data, offset3Dbuf, 0, offset3Dbuf.Length);        Buffer.BlockCopy(offset3Dbuf, 0, offset3D, 0, offset3Dbuf.Length);        for (var j = 0; j < JointNum; j++)        {            var maxXIndex = 0;            var maxYIndex = 0;            var maxZIndex = 0;            jointPoints[j].score3D = 0.0f;            for (var z = 0; z < HeatMapCol; z++)            {                for (var y = 0; y < HeatMapCol; y++)                {                    for (var x = 0; x < HeatMapCol; x++)                    {                        float v = heatMap3D[HeatMapCol_Cube * j + HeatMapCol_Squared * z + HeatMapCol * y + x];                        if (v > jointPoints[j].score3D)                        {                            jointPoints[j].score3D = v;                            maxXIndex = x;                            maxYIndex = y;                            maxZIndex = z;                        }                    }                }            }
            jointPoints[j].Now3D.x = (offset3D[HeatMapCol_Cube * j + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)maxXIndex / (float)HeatMapCol) * (float)inputImageSize;            jointPoints[j].Now3D.y = (float)inputImageSize - (offset3D[HeatMapCol_Cube * (j + JointNum) + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)maxYIndex / (float)HeatMapCol) * (float)inputImageSize;            jointPoints[j].Now3D.z = (offset3D[HeatMapCol_Cube * (j + JointNum * 2) + HeatMapCol_Squared * maxZIndex + HeatMapCol * maxYIndex + maxXIndex] + (float)(maxZIndex - 7) / (float)HeatMapCol) * (float)inputImageSize;        }    }
模型输入224×224的图像,输出的关节数为24个,热图(Heatmap)为14×14。
2D热图格式是24x14x14,3D的是24x14x14x14。将其作为与热图的坐标偏移值,输出的2D(x,y)变为2x24x14x14,3D(x,y,z)变为3x24x14x14x14。
 public void InitONNX()    {        Onnx = Net.ReadNetFromONNX(Application.dataPath + @"\MobileNet3D2.onnx");        for (var i = 0; i < 4; i++) outputs[i] = new Mat();    }

首先,使用InitONNX()读取Onnx文件。
由于OpenCV的输出是通过Mat对象返回的,需要准备四个数组。
 public void Predict(Mat img)    {        var blob = CvDnn.BlobFromImage(img, 1.0 / 255.0, new OpenCvSharp.Size(inputImageSize, inputImageSize), 0.0, false, false);        Onnx.SetInput(blob);        Onnx.Forward(outputs, new string[] { "369", "373", "361", "365" });
        // copy 2D outputs        Marshal.Copy(outputs[2].Data, heatMap2Dbuf, 0, heatMap2Dbuf.Length);        Buffer.BlockCopy(heatMap2Dbuf, 0, heatMap2D, 0, heatMap2Dbuf.Length);        Marshal.Copy(outputs[3].Data, offset2Dbuf, 0, offset2Dbuf.Length);        Buffer.BlockCopy(offset2Dbuf, 0, offset2D, 0, offset2Dbuf.Length);

Predict方法参数的Mat对象是正常的CV_8UC3 Mat图像数据,需要将其转换为Blob Mat才能传递给Onnx,这个过程利用BlobFromImage就能完成。
在Output中,“369”和“373”是3D,“361”和“365”是2D。但如果是Mat对象,处理起来就稍微复杂一些,因为还需要将其转换为float数组。
然后,通过改变关节数和热图大小,找到最大热图。
由于3D是一个相当大的循环,最好再做一些改进,但是由于它现在移动得足够快,保持原样也是可以的。
在iOS上也能实现3D姿势估计

去年的日本黄金周,青柳君第一次接触机器学习,也一直在3D姿势估计这块有所钻研。
今年3月份,他在iOS上实现了3D姿势估计。据本人推特发言称,他用了一天时间学习,然后做出了这个模型。
根据青柳君本人介绍,iOS项目的学习环境是Windows10/PyTorch0.4,执行环境是iPhone XS Max,至于选择iPhone XS Max的原因,青柳君说,iPhone XS Max的A12处理器功能非常强大。
还是先看看效果如何:
  
青柳君准备了2D和3D的数据集,2D数据集是利兹运动姿势数据集,利兹运动姿势扩展训练数据集、MPII人类姿势数据集、Microsoft COCO;而3D数据集是原始数据集。
在此之前他还做了很多准备,包括从AssetStore购买的数据等,当然还有Unity。
然后就可以利用Unity创建3D角色动画了,创建角色图像和坐标,包括肩膀、肘部、手腕、拇指、中指、脚、膝盖、脚踝、脚趾、耳朵、眼睛、鼻子,以输出身体的中心位置,即肚脐。
该数据集由于许可原因结果变得十分复杂,导致发布失败。
由于这是CG,因此可以随意更改角色的纹理和姿势。最初,他希望更改每个时期数据集的内容,以提高泛化性能,但没有效果,为此大约有100,000个副本用于学习。
即使是用3D版本的图像,也可以照原样学习,最后可以获得相似的图像,但是无法获得预期的性能。
将通过PyTorch学习得到的模型导出到Onnx,用coremltools转换为CoreML模型,此时就算是估计到了相同的图像,结果也会有所不同,所以准确度未知。
将模型导入Mac,使用XCode的iPhone版本,通过实时捕获后方摄像机图像执行3D估计。
XS Max能以大约40fps的速度运行,但是,一段时间,手机会变热,速记也会下降至约30fps。如果仅用于学习2D模型,其运行速度会接近100fps。
由于这是个3D项目,显示时无法从摄像机看到的部分,判断热图的阈值已降低到几乎为零。例如,如果手臂正常可见,热图的最大部分为0.5或更高(最大值为1.0);如果看不到手臂,将得到0.2或0.1的值,阈值降低。
但就结果而言,无论身在何处,系统都可以判断为有人。
Adobe发布最新动作追踪软件

上周,Adobe也发布了一款用于视觉效果和动态图形软件After Effects,该软件的AI功能能够自动跟踪人体运动并将其应用于动画。
简单地说,就是能够把现实人物的动作直接转换成为动画。
与青柳君的机器学习项目的效果相差无几!
Adobe研究科学家Jimei Yang在演示中说,这一功能利用了Adobe的人工智能平台Sensei,该平台用超过10000张图像进行了训练,从而能够识别人体的关键点。
据了解,人体跟踪器在源视频中能够检测到人体的运动,胳膊、躯干和腿部的18个关节点将生成相关跟踪点,然后将跟踪点转移到动画角色上,利用该功能,快速创建2D人物动画根本不在话下!
怎么样,有没有觉得打开了新世界的大门?
当然,对于姿势估计的实现还远远不止现在的程度,未来希望不仅是青柳君和Adobe,有更多人都参与到这个领域的研究和学习中来,促进相关领域的发展。

干货来袭-整套完整安全的API接口解决方案 - hubro - 博客园

mikel阅读(735)

来源: 干货来袭-整套完整安全的API接口解决方案 – hubro – 博客园

在各种手机APP泛滥的现在,背后都有同样泛滥的API接口在支撑,其中鱼龙混杂,直接裸奔的WEB API大量存在,安全性令人堪优

在以前WEB API概念没有很普及的时候,都采用自已定义的接口和结构,对于公开访问的接口,专业点的都会做下安全验证,数据签名之类

反而现在,谁都可以用WEB API估接口,安全性早忘一边了,特别是外包小公司的APP项目,80%都有安全漏洞(面试了大半年APP开发得出的结论)

特在过年之前,整理了下在用的解决方案,本方案解决了

  • 数据安全问题
  • 标准消息结构
  • 接口测试程序
  • 接口文档体现

正文

数据结构

对于一个接口,返回的内容除了要返回业务数据外,还得返回处理状态,并且这个状态是在每个接口都得有

所以数据格式都会定义为:

数据头(描述数据信息)

———————————–

数据体(具体数据)

本文定义结构为

复制代码
/// <summary>
    /// 处理结果
    /// </summary>
    public class DealResult
    {
        /// <summary>
        /// 处理结果
        /// </summary>
        public bool Result
        {
            get;
            set;
        }
        /// <summary>
        /// 消息
        /// </summary>
        public string Message
        {
            get;
            set;
        }
        /// <summary>
        /// 关联数据
        /// </summary>
        public object Data
        {
            get;
            set;
        }
    }
复制代码

所有接口都返回此对象,会描述本次请求的状态,和对应的数据,服务端则根据实际情况,返回处理结果和对应的数据

 

数据安全

开方式接口安全性就不用多说了,解决方法为加密,或数据签名验证,本文方案为进行数据签名

同返回的数据一样,提交到服务器的数据格式也统一约定,定义一个数据头基类

复制代码
    /// <summary>
    /// 参数基类
    /// </summary>
    [Serializable]
    public class ParameBase
    {
        string time = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
        /// <summary>
        /// 时间 格式 yyyy-MM-dd hh:mm:ss
        /// </summary>
        public string Time
        {
            get
            {
                return time;
            }
            set
            {
                time = value;
            }
        }
        /// <summary>
        /// 来源网站 = 1, IOS = 2,Android = 3, 微信 = 4
        /// </summary>
        public int SourceFrom
        {
            get;
            set;
        }
        /// <summary>
        /// 签名
        /// </summary>
        public string Token
        {
            get;
            set;
        }
       
    }
复制代码

 

一个登录对象表示为

复制代码
    /// <summary>
    /// 登录
    /// </summary>
    public class Login : ParameBase
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string Name
        {
            get;
            set;
        }
        /// <summary>
        /// 密码
        /// </summary>
        public string Password
        {
            get;
            set;
        }
    }
复制代码

数据签名表示为(KEY稍后讲到)

Token=MD5(属性值1+值2….+KEY)

按此对象表示为 MD5(Name+PassWord+Source+Time+KEY)

如果是GET参数怎么办,一样,按参数名计算,同时传递的参数要附带上Source,Time,Token

 

密钥机制

有的喜欢把密钥放在客户端,或固定密钥,显然都有安全问题,解决方法是动态获取

这就意味着在设计接口时,有一个接口是首先要调用的,让服务器返回密钥,于是就有了登录的概念

过程表示为

登录>返回用户信息和密钥=>存储用户信息和密钥=>使用密钥调用其它接口

这样只有登录者和服务器才知道自已的密钥了

综上所述,数据结构表示为

客户端提交结构为 ParameBase(附带签名信息)

服务端返回结构为 DealResult

 

登录机制

同网页请求一样,怎么知道多次调用是同一个人呢,这里采用了COOKIE的形式,登录后服务端返回一个COOKIE,客户端再请求时带上这个COOKIE

服务端需要存储这个COOKIE标识,所有的验证处理都会基于此标识来判断用户

 

有了上面基础,进入项目阶段

WEB API项目

其实用什么项目类型都行,只是WEB API方便了对象结构序列化和传参

默认WEB API路由RESUFUL形式,没有控制器方法,只能按METHOD来定义,很不方便,改成控制器的形式,这样就能用方法名来访问了

更改路由配置为

1
2
3
4
5
config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",//加上路由ACTION参数
                defaults: new { id = RouteParameter.Optional }
            );

在此文,数据分为请求和返回,以登录返回用户信息为例,登录为请求,用户信息为返回,示例对象结构为

用户对象

复制代码
/// <summary>
    /// 登录返回用户
    /// </summary>
    public class User
    {
        /// <summary>
        /// 用户编号
        /// </summary>
        public int Id
        {
            get;
            set;
        }
        /// <summary>
        /// 名称
        /// </summary>
        public string Name
        {
            get;
            set;
        }
        /// <summary>
        /// 本次登录的KEY
        /// </summary>
        public string Key
        {
            get;
            set;
        }
        /// <summary>
        /// 本资登录的凭证
        /// </summary>
        public string Voucher
        {
            get;
            set;
        }

       
    }
复制代码

 

请求方式

这里只采用了GET,POST两种方式,根据实际情况定义,控制器方法一定需要都标明,不然会出现路由BUG

定义登录方法

 View Code

这里可以看到,创建了两个GUID,一个为用户凭证,一个为用户密钥,放入用户信息返回,同时调用LoginStatusContext.SetLoginStatus保存登录信息

同时使用了AnonymousSign标注,此方法使用默认签名Setting.DefaultKey

定义获取用信息方法

复制代码
        /// <summary>
        /// 基本信息
        /// </summary>
        /// <param name="name">参数name</param>
        /// <returns>User</returns>
        [HttpGet]
        public DealResult GetBasicInfo(string name)
        {
            var user = new User() { Name = name, Id = CurrentUserId };
            return DealResult(true, string.Empty, user);
        }
复制代码

 

示例控制器完整定义

复制代码
 /// <summary>
    /// 帐号操作
    /// </summary>
    [SignCheckAttribute]
    public class AccountController : BaseController
    {
        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="parame"></param>
        /// <returns>User</returns>
        [HttpPost]
        [AnonymousSign]
        public DealResult Login([FromBody] Login parame)
        {
            if (parame.Password != "123")
            {
                return DealResult(false, "密码不正确");
            }
            string key2 = System.Guid.NewGuid().ToString();
            string voucher = System.Guid.NewGuid().ToString();
            var user = new User() { Name = parame.Name, Id = 1, Key = key2, Voucher = voucher };
            var timeDiff = (DateTime.Now - Convert.ToDateTime(parame.Time)).TotalSeconds;//保存客户端和服务端时间差
            LoginStatusContext.SetLoginStatus(voucher, user.Id, key2, timeDiff);
            CoreHelper.CookieHelper.AddCookies("user", voucher);//存入COOKIE
            return DealResult(true, "", user);
        }


        /// <summary>
        /// 基本信息
        /// </summary>
        /// <param name="name">参数name</param>
        /// <returns>User</returns>
        [HttpGet]
        public DealResult GetBasicInfo(string name)
        {
            var user = new User() { Name = name, Id = CurrentUserId };
            return DealResult(true, string.Empty, user);
        }

        /// <summary>
        /// 测试异常
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public DealResult TestException()
        {
            int a = 0;
            var b = 10 / a;
            return DealResult(true);
        }

    }
复制代码

 

此控制器标注了SignCheckAttribute用以进行签名判断

具体实现可看SignCheckAttribute代码

SignCheckAttribute里实现了有

  • 数据签名判断
  • 签名超时判断
  • 用户登录限制
  • 签名重复使用处理(一个签名只能使用一次)
  • 过期登录用户处理(没有主动退出用户清理)

为了统一处理异常,配置了异常处理

1
GlobalConfiguration.Configuration.Filters.Add(new ExceptionAttribute());

对接口进行测试

大杀器来了,配合此方案放出了对应的测试工具,虽然WEB API有个扩展,但没法对此方案测试

使用此工具能方便按方案要求调用接口,为了方便参数拼接,POST和GET都采用URL参数的形式输入

测试登录/api/account/login

测试获取信息/api/account/GetBasicInfo

测试异常处理/api/account/TestException

在未登录情况下调用获取信息

接口文档

接口结构文档一直是很让人头疼的事,手写更改了又得维护,版本不一样还麻烦,自动生成最好了,同样WEB API 带扩展没法表示此结构详细

大杀器2号来了,按代码注释动态生成接口文档,文档格式与控制器保持一致

Home控制器代码实现

复制代码
    public ActionResult Index(SummaryAnalysis.ExportType exportType = SummaryAnalysis.ExportType.NONE)
        {
            if (exportType != SummaryAnalysis.ExportType.NONE)
            {
                var str = SummaryAnalysis.Load(exportType);
                return File(str, "application/octet-stream", "Model_" + exportType + ".zip");
            }
            else
            {
                if (string.IsNullOrEmpty(outPut))
                {
                    outPut = SummaryAnalysis.Load(exportType);
                }
                ViewBag.OutPut = outPut;
                return View();
            }
        }
    }
复制代码

在见过的开发文档,我觉得这是最好的展现形式了,还有锚点,快速定位到对象结构,并且与源代码保持一致

附WEB API 自带文档生成区别

附上项目源码

http://pan.baidu.com/s/1c2rDacK

项目结构:

———-WPF测试程序

———-接口示例

虽然跟CRL快速开发框架无关,但还是加上CRL的名,好文要顶!