
长期从事.NET 开发,热爱开源技术。现在国内某知名IT厂打杂
前言 Gulp,简而言之,就是前端自动化开发工具,利用它,我们可以提高开发效率。 比如: 1、 压缩js 2、 压缩css 3、 压缩less 4、 压缩图片 等等… 我们完全可以利用Gulp来自动化地完成这些重复性很强的工作。 Gulp可以帮助我们 用自动化构建工具增强你的工作流程! 好了,废话不多说了。既然要了解Gulp,就得先安装它。Gulp是基于node来实现的,so你得先有个node环境 优势: node环境有了后,安装Gulp就很easy咯 入门指南 1. 全局安装 gulp: $ npm install --global gulp (后面加-g代表全局) 安装完成后,输入gulp –v查看是否安装成功。 如下: 但,就算你这么安装了全局gulp,你每次到项目中时,还得在相应目录下安装gulp。 原因就是,gulp就这么设置的,避免发生版本冲突。 所以这步安装gulp可以可无,不过就当初步了解它嘛 2. 作为项目的开发依赖(devDependencies)安装: $ npm install --save-dev gulp 3. 在项目根目录下创建一个名为 gulpfile.js 的文件: var gulp = require('gulp'); gulp.task('default', function() { // 将你的默认的任务代码放在这 }); 4. 运行 gulp: $ gulp 默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。 想要单独执行特定的任务(task),请输入 gulp <task> <othertask>。 说明:gulpfile.js是gulp项目的配置文件,是位于项目根目录的普通js文件(其实将gulpfile.js放入其他文件夹下亦可) //导入工具包 require('node_modules里对应模块') var gulp = require('gulp'), //本地安装gulp所用到的地方 less = require('gulp-less'); //定义一个testLess任务(自定义任务名称) gulp.task('testLess', function () { gulp.src('src/less/index.less') //该任务针对的文件 .pipe(less()) //该任务调用的模块 .pipe(gulp.dest('src/css')); //将会在src/css下生成index.css }); gulp.task('default',['testLess', 'elseTask']); //定义默认任务 elseTask为其他任务,该示例没有定义elseTask任务 //gulp.task(name[, deps], fn) 定义任务 name:任务名称 deps:依赖任务名称 fn:回调函数 //gulp.src(globs[, options]) 执行任务处理的文件 globs:处理的文件路径(字符串或者字符串数组) //gulp.dest(path[, options]) 处理完后文件生成路径 完整版Gulpfile处理js/css文件: "use strict"; var gulp = require("gulp"),//引入Gulp依赖 concat = require("gulp-concat"), cssmin = require("gulp-cssmin"), htmlmin = require("gulp-htmlmin"), uglify = require("gulp-uglify"), merge = require("merge-stream"), del = require("del"), bundleconfig = require("./bundleconfig.json"), less = require("gulp-less"), path = require("path"), gulpSequence = require("gulp-sequence"); //正则匹配表达式 var regex = { css: /\.css$/, html: /\.(html|htm)$/, js: /\.js$/, less: /\.less$/ }; //任务 gulp.task("min", gulpSequence("less", ["min:js", "min:css", "min:html"])); gulp.task('less', function () { return gulp.src('./Layout/Less/*.less') .pipe(less()) .pipe(gulp.dest('./wwwroot/css')); }); gulp.task("min:js", function () { var tasks = getBundles(regex.js).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: "." }) .pipe(concat(bundle.outputFileName)) .pipe(uglify()) .pipe(gulp.dest(".")); }); return merge(tasks); }); gulp.task("min:css", function () { var tasks = getBundles(regex.css).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: "." }) .pipe(concat(bundle.outputFileName)) .pipe(cssmin()) .pipe(gulp.dest(".")); }); return merge(tasks); }); gulp.task("min:html", function () { var tasks = getBundles(regex.html).map(function (bundle) { return gulp.src(bundle.inputFiles, { base: "." }) .pipe(concat(bundle.outputFileName)) .pipe(htmlmin({ collapseWhitespace: true, minifyCSS: true, minifyJS: true })) .pipe(gulp.dest(".")); }); return merge(tasks); }); gulp.task("clean", function () { var files = bundleconfig.map(function (bundle) { return bundle.outputFileName; }); return del(files); }); gulp.task("watch", function () { gulp.watch('./Layout/Less/*.less', ['less']); getBundles(regex.js).forEach(function (bundle) { gulp.watch(bundle.inputFiles, ["min:js"]); }); getBundles(regex.css).forEach(function (bundle) { gulp.watch(bundle.inputFiles, ["min:css"]); }); getBundles(regex.html).forEach(function (bundle) { gulp.watch(bundle.inputFiles, ["min:html"]); }); }); function getBundles(regexPattern) { return bundleconfig.filter(function (bundle) { return regexPattern.test(bundle.outputFileName); }); } 然后执行 然后再文件夹中可查看到,把所有的css、js自动压缩到了min里面。 此致,gulp构建 算是初步完成,后面还有很多可以优化的地方,具体可以参考: Gulp API文档 我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 什么是 localStorage? 在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。 ocalStorage的优势与局限 localStorage的优势 1、localStorage拓展了cookie的4K限制 2、localStorage会可以将第一次请求的数据直接存储到本地,这个相当于一个5M大小的针对于前端页面的数据库,相比于cookie可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的 localStorage的局限 1、浏览器的大小不统一,并且在IE8以上的IE版本才支持localStorage这个属性 2、目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换 3、localStorage在浏览器的隐私模式下面是不可读取的 4、localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡 5、localStorage不能被爬虫抓取到 localStorage与sessionStorage的唯一一点区别就是localStorage属于永久性存储,而sessionStorage属于当会话结束的时候,sessionStorage中的键值对会被清空 这里我们以localStorage来分析 localStorage的使用 localStorage的浏览器支持情况: 这里要特别声明一下,如果是使用IE浏览器的话,那么就要UserData来作为存储,这里主要讲解的是localStorage的内容,所以userData不做过多的解释,而且以博主个人的看法,也是没有必要去学习UserData的使用来的,因为目前的IE6/IE7属于淘汰的位置上,而且在如今的很多页面开发都会涉及到HTML5\CSS3等新兴的技术,所以在使用上面一般我们不会去对其进行兼容 首先在使用localStorage的时候,我们需要判断浏览器是否支持localStorage这个属性 if(!window.localStorage){ console.log("浏览器支持localstorage"); return false; }else{ //主逻辑业务 } localStorage的写入有三种方法,如下: if(!window.localStorage){ console.log("浏览器支持localstorage"); return false; }else{ var storage=window.localStorage; //写入a字段 storage["a"]=1; //写入b字段 storage.a=1; //写入c字段 storage.setItem("c",3); console.log(typeof storage["a"]); console.log(typeof storage["b"]); console.log(typeof storage["c"]); } 运行后的结果如下: 这里要特别说明一下localStorage的使用也是遵循同源策略的,所以不同的网站直接是不能共用相同的localStorage 最后在控制台上面打印出来的结果是: 不知道各位读者有没有注意到,刚刚存储进去的是int类型,但是打印出来却是string类型,这个与localStorage本身的特点有关,localStorage只支持string类型的存储。 localStorage的读取 if(!window.localStorage){ alert("浏览器支持localstorage"); }else{ var storage=window.localStorage; //写入a字段 storage["a"]=1; //写入b字段 storage.a=1; //写入c字段 storage.setItem("c",3); console.log(typeof storage["a"]); console.log(typeof storage["b"]); console.log(typeof storage["c"]); //第一种方法读取 var a=storage.a; console.log(a); //第二种方法读取 var b=storage["b"]; console.log(b); //第三种方法读取 var c=storage.getItem("c"); console.log(c); } 这里面是三种对localStorage的读取,其中官方推荐的是getItem\setItem这两种方法对其进行存取,不要问我这个为什么,因为这个我也不知道 我之前说过localStorage就是相当于一个前端的数据库的东西,数据库主要是增删查改这四个步骤,这里的读取和写入就相当于增、查的这两个步骤 下面我们就来说一说localStorage的删、改这两个步骤 改这个步骤比较好理解,思路跟重新更改全局变量的值一样,这里我们就以一个为例来简单的说明一下 if(!window.localStorage){ alert("浏览器支持localstorage"); }else{ var storage=window.localStorage; //写入a字段 storage["a"]=1; //写入b字段 storage.b=1; //写入c字段 storage.setItem("c",3); console.log(storage.a); // console.log(typeof storage["a"]); // console.log(typeof storage["b"]); // console.log(typeof storage["c"]); /*分割线*/ storage.a=4; console.log(storage.a); } 这个在控制台上面我们就可以看到已经a键已经被更改为4了 localStorage的删除 1、将localStorage的所有内容清除 var storage=window.localStorage; storage.a=1; storage.setItem("c",3); console.log(storage); storage.clear(); console.log(storage); 2、 将localStorage中的某个键值对删除 var storage=window.localStorage; storage.a=1; storage.setItem("c",3); console.log(storage); storage.removeItem("a"); console.log(storage.a); 控制台查看结果 localStorage的键获取 var storage=window.localStorage; storage.a=1; storage.setItem("c",3); for(var i=0;i<storage.length;i++){ var key=storage.key(i); console.log(key); } 使用key()方法,向其中出入索引即可获取对应的键 localStorage其他注意事项 一般我们会将JSON存入localStorage中,但是在localStorage会自动将localStorage转换成为字符串形式 这个时候我们可以使用JSON.stringify()这个方法,来将JSON转换成为JSON字符串 示例: if(!window.localStorage){ alert("浏览器支持localstorage"); }else{ var storage=window.localStorage; var data={ name:'xiecanyong', sex:'man', hobby:'program' }; var d=JSON.stringify(data); storage.setItem("data",d); console.log(storage.data); } 读取之后要将JSON字符串转换成为JSON对象,使用JSON.parse()方法 var storage=window.localStorage; var data={ name:'xiecanyong', sex:'man', hobby:'program' }; var d=JSON.stringify(data); storage.setItem("data",d); //将JSON字符串转换成为JSON对象输出 var json=storage.getItem("data"); var jsonObj=JSON.parse(json); console.log(typeof jsonObj); 打印出来是Object对象 另外还有一点要注意的是,其他类型读取出来也要进行转换 实战 项目中有这样一个页面,页面中有一个头像,点击小头像需要调用API 得到一个大头像。如果每次点击头像都去请求api,那如果有1000个、10000个用户点击 小头像,那对与服务器来说,是一个不小的压力。所以此处,我们可以通过上面讲到的localStorage来将第一次请求到的大头像暂时“保存起来”。 var personalImg = $("#PersonalId").val();var showpersonal = (new Function("", "return " + Storage.get(personalImg)))();//将获取到的Json字符串 序列化成对象 在点击小头像前,我们需要判断当前是否存在我们已经设置过的Storage if (Storage.get(personalImg) == null) { $.get("/Home/GetUserBigImg?id=" + $("#PersonInfo_InternalId").val(), function(data) { if (data.imageBase64 != null) { $("#UserbigImage").attr('src', 'data:image/jpg;base64,' + data.imageBase64);//将得到的结果替换到标签的src上去 var date = new Date(); date.setMinutes(date.getMinutes() + 2);//设置2分钟过期时间 var obj = {Img: data.imageBase64, TimeSpan: date }; obj = JSON.stringify(obj); //转化为JSON字符串 Storage.set(personalImg, obj);//设置storage值 } }); } 点击小头像后运行结果如下: 本地缓存后 ,第二次点击应进入else中,代码如下: var dateOld = new Date(showpersonal.TimeSpan);//获取设置的过期时间 var dateNow = new Date(); if (dateOld - dateNow < 0) {//比较两个时间差 localStorage.removeItem(personalImg);//清除当前localStorage personal.main.redirectToShow();//重新进入获取Storage方法 return; } console.log(Storage.get(personalImg)); console.log(showpersonal); $("#UserbigImage").attr('src', 'data:image/jpg;base64,' + showpersonal.Img);//设置头像 完整代码如下: $("#lodingImg").css({ "display": "block" }); var personalImg = $("#PersonalId").val(); var showpersonal = (new Function("", "return " + Storage.get(personalImg)))();//将获取到的Json字符串 序列化成对象 if (Storage.get(personalImg) == null) { $.get("/Home/GetUserBigImg?id=" + $("#PersonInfo_InternalId").val(), function (data) { if (data.imageBase64 != null) { //console.log(data.imageBase64); $("#UserbigImage").attr('src', 'data:image/jpg;base64,' + data.imageBase64); $("#lodingImg").css({ "display": "none" }); // debugger; var date = new Date(); date.setMinutes(date.getMinutes() + 2); var obj = {Img: data.imageBase64, TimeSpan: date }; obj = JSON.stringify(obj); //转化为JSON字符串 Storage.set(personalImg, obj); } }); } else { var dateOld = new Date(showpersonal.TimeSpan); var dateNow = new Date(); if (dateOld - dateNow < 0) { localStorage.removeItem(personalImg);//清除当前localStorage personal.main.redirectToShow(); return; } console.log(Storage.get(personalImg)); console.log(showpersonal); $("#UserbigImage").attr('src', 'data:image/jpg;base64,' + showpersonal.Img); $("#lodingImg").css({ "display": "none" }); } layer.open({ type: 1, title: false, closeBtn: 1, area: '300px', skin: 'layui-layer-nobg', //没有背景色 shadeClose: true, content: $('#ImgBig'), end: function () { $(".layui-layer-shade").css({ 'z-index': "", 'background-color': "", 'opacity': "", 'width': '', 'height': '', 'top': '0', 'left': '0' }); $(".layui-layer-shade").removeClass("layui-layer-shade"); } }); 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 对于缓存我们都已经很熟悉了,缓存分为很多种,浏览器缓存、试图缓存、服务器缓存、数据库缓存等等一些,那今天我们先介绍一下视图缓存和MemoryCache内存缓存的概念和用法: 视图缓存 在老的版本的MVC里面,有一种可以缓存视图的特性(OutputCache),可以保持同一个参数的请求,在N段时间内,直接从mvc的缓存中读取,不去走视图的逻辑。 //老版本的.NET 做法 [OutputCache(Duration =20)]//设置过期时间为20秒 public ActionResult ExampleCacheAction() { var time=DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); ViewBag.time= time; return View(); } 在Asp.Net core 2.1中,官方文档上称:响应缓存可减少客户端或代理对 web 服务器的请求数。 响应缓存还可减少量工作的 web 服务器执行程序生成响应。 响应缓存由标头,指定你希望客户端、 代理和缓存响应的中间件如何控制。 在Asp.Net Core 2.1 中,没有了OutputCache,换成了ResponseCache,ResponseCache必须带一个参数:Duration 单位为秒,最少设置一秒钟 //.NET Core2.1做法 [ResponseCache(Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); } 然后再浏览器请求这个视图 在浏览器的响应头的Cache-Control 中出现max-age=5, Http协议对此的解释是 客户端将不会接受其保留时间大于指定的秒数的响应。 示例: max-age=60 (60 秒), max-age=2592000 (1 个月) 如果在浏览器中禁用缓存,那么ResponseCache不会有任何效果 Vary过滤 [ResponseCache(VaryByHeader = "User-Agent", Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); } 关于vary在Http响应头的作用就是:告诉缓存服务器或者CDN,我还是同一个浏览器的请求,你给我缓存就行了,如果你换个浏览器去请求,那么vary的值肯定为空,那么缓存服务器就会认为你是一个新的请求,就会去读取最新的数据给浏览器 参考资料:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 禁用缓存(NoStore 和 Location.None) 在Http中 :no-store,请求和响应的信息都不应该被存储在对方的磁盘系统中 [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); } ResponseCacheLocation.None是在Cache-Control设置一个no-cache属性,让浏览器不缓存当前这个URL 缓存配置(CacheProfiles) 在一个正常的项目中,肯定有很多个控制器,但是不可能每个控制器的缓存策略都一样,这时候,我们就需要一个缓存的配置来灵活应对这个问题在mvc的服务注入的时候,我们可以在option里面注入进我们的缓存策略 services.AddMvc(option=> { option.CacheProfiles.Add("test1", new CacheProfile() { Duration = 5 }); option.CacheProfiles.Add("test2", new CacheProfile() { Location = ResponseCacheLocation.None, NoStore = true }); }); 然后我们在使用的时候,直接使用配置策略的名称就好了 [ResponseCache(CacheProfileName = "test1")] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); } 这样我们就能和之前在特性后边配置一样了,这是视图缓存,下面我们就来看看MemoryCache 是个什么东东 MemoryCache 如果回到老版本的.NET,说到内存缓存大家可能立马想到了HttpRuntime.Cache,它位于System.Web命名空间下,但是在ASP.NET Core中System.Web已经不复存在。今儿个就简单的聊聊如何在ASP.NET Core中使用内存缓存 有几个问题我们需要先进行了解: 1.什么时候需要用到缓存? 一般将经常访问但是又不是经常改变的数据放进缓存是再好不过了,这样可以明显提高应用程序的性能。 2.缓存的好处? 建议http://www.baidu.com 使用 不同于 ASP.NET Web 窗体和 ASP.NET MVC,ASP.NET Core 没有内置的 Cache 对象,可以拿来在控制器里面直接使用。 这里,内存缓存时通过依赖注入来启用的,因此第一步就是在 Startup 类中注册内存缓存的服务。如此,就得打开 Startup 类然后定位到 ConfigureServices() 方法,像下面这样修改 ConfigureServices() 方法: ①首先需要在ConfigureServices中注册缓存服务 services.AddMemoryCache() 为了向你的应用程序加入内存缓存能力,你需要在服务集合上调用 AddMemoryCache() 方法。采用这种办法就可以让一个内存缓存(它是一个 IMemoryCache 对象)的默认实现可以被注入到控制器中去。 ②在下面的代码中从Home控制器的构造函中获取IMemoryCache实例(内存缓存使用依赖注入来注入缓存对象) private readonly IMemoryCache _memoryCache; public HomeController(IMemoryCache memoryCache) { _memoryCache = memoryCache; } 如你所见,上述代码声明了一个 ImemoryCache 的私有变量。该变量会被构造器中被赋值。构造器会通过 DI(依赖注入)接收到缓存参数,然后被存储在本地变量总,提供后续使用。 ③关于缓存的使用常用的就是Set Get Remove,一般有以下几种做法可以参考: ⑴可以使用 Set() 方法来在缓存中存东西 等你有了这个 IMemoryCache 对象,就可以读取或者向它写入数据了。向缓存写入数据项是相当直接的 public IActionResult Index() { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); return View(); } 上述代码在 Index() 这个 action 中设置了一个缓存项。这是通过使用 IMemoryCache 的 Set<T>() 来完成的。Set() 方法的第一个参数是键名,用来标识该数据项。第二个参数是键的取值。在此例中,我们存储一个字符串的键和一个字符串的值,而你也可以存储其它类型 (原生以及自定义的类型) 的键值对。 ⑵可以使用 Get 方法来从缓存中获取到一个数据项 等你向缓存中添加好了数据,也许会想要在应用程序的其它地方去获取到该数据,可以用 Get() 来做到。如下代码会告诉你如何来做这件事情。 public IActionResult Show() { string timestamp = _memoryCache.Get<string>("timestamp"); return View("Show",timestamp); } 上述代码从 HomeController 的另外一个action(Show)那里获取到了一个缓存的数据项。Get() 方法会指定数据项的类型以及它的键名。如果该数据项存在的话,就会被返回并且被赋值给 timestamp 这个字符串变量。然后这个 timestamp 的值就会被传递给 Show 视图。 Show 视图只是简单地输出了 timestamp 的值,如下所示: <h1>TimeStamp : @Model</h1> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2> 如果你观察前面的示例,会发现每次你导航至 /Home/Index 的时候, 都会有一个新的 timestamp 被赋值给了缓存项。这是因为我们并没有对此进行检查,规定只有在数据项不存在的时候才赋值。许多时候你都会想要这样做的。这里有两种办法可以在 Index() 这个 action 里面来做这样的检查。我们把两种办法都在下面列了出来 可以使用 TryGet() 来检查缓存中是否存在特定的键值 //first way if (string.IsNullOrEmpty (_memoryCache.Get<string>("timestamp"))) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); } //second way if (!_memoryCache.TryGetValue<string> ("timestamp", out string timestamp)) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); } 第一种办法使用了你早先用过的同一个 Get() 方法,这一次它被拿来跟 if 块一起用。如果 Get() 不能在缓存中找到指定的数据项,IsNullOrEmpty() 就会返回 true。而只有这时候 Set() 才会被调用,一次来添加数据项。 第二种办法更加优雅一点。它使用 TryGet() 方法来获取一个数据项。TryGet() 方法会返回一个布尔值来指明数据项有没有被找到。实际的数据项可以使用一个输出参数拉取出来。如果 TryGet() 返回false,Set() 就会被用来添加数据。 如果不存在的话,可以使用 GetOrCreate() 来添加一项 有时你需要从缓存中检索现有项。如果该项目不存在,则希望添加该项。这两个任务 - 如果它存在获取值,否则创建之 - 可以使用 GetOrCreate() 方法来实现。修改后的 Show() 方法展示了如何实现的 public IActionResult Show() { string timestamp = cache.GetOrCreate<string> ("timestamp", entry => { return DateTime.Now.ToString(); }); return View("Show",timestamp); } Show() 动作现在使用 GetOrCreate() 方法。 GetOrCreate() 方法将检查时间戳的键值是否存在。如果是,现有值将被赋值给局部变量。否则,将根据第二个参数中指定的逻辑创建一个新条目并将其添加到缓存中。 在缓存的数据项上面设置绝对和滚动的过期时间 一个缓存项只要被添加到缓存就会一直存储,除非它被明确地使用 Remove() 从缓存中移除。你也可以在一个缓存项上面设置一个绝对和滚动的过期时间。一个绝对的过期设置意味着该缓存项会在严格指定的日期和时间点被移除,而滚动过期设置则意味着它在给定的一段时间量处于空闲状态(也就是没人去访问)之后被移除。 为了能在一个缓存项上面设置这两种过期策略,你要用到 MemoryCacheEntryOptions 对象。如下代码向你展示了如何去使用。 MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options); 上述代码来自于修改过的 Index() action,它创建了一个 MemoryCacheEntryOptions 的对象,然后将它的 AbsoluteExpiration 属性设置为从此刻到一分钟之后的一个 DateTime 值,它还将 SlidingExpiration 属性设置为一分钟。这些值都指定了该缓存项会在一分钟之后从缓存移除,不管其是否会被访问。此外,如果该缓存项如初持续空闲了有一分钟,它也会被从缓存中移除。 等你将 AbsoluteExpiration 和 SlidingExpiration 的值设置后, Set() 方法就可以被用来将一个数据项添加到缓存。这一次 MemoryCacheEntryOptions 对象会被作为第三个参数传递给 Set() 方法。 当缓存项会被移除时,可以连接回调 有时你会想要在缓存项从缓存中被移除时收到通知。可能会有多种原因需要从缓存中移除数据项。例如,因为明确地执行了 Remove() 方法而移除了一个缓存项, 也有可能是因为它的 AbsoluteExpiration 和 SlidingExpiration 值已经到期而被移除,诸如此类的原因。 为了能知道项目是何时从缓存移除的,你需要编写一个缓存函数。如下代码向你展示了如何去做这件事情 MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration =DateTime.Now.AddMinutes(1);options.SlidingExpiration = TimeSpan.FromMinutes(1); options.RegisterPostEvictionCallback(MyCallback, this); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options); private static void MyCallback(object key, object value,EvictionReason reason, object state) { var message = $"Cache entry was removed : {reason}"; ((HomeController)state). _memoryCache.Set("callbackMessage", message); } 请仔细观察这段代码。 MyCallback() 是 HomeController 类里面的一个私有静态函数,它有四个参数。前面两个参数表示刚刚删除的缓存项的键和值,第三个参数表示的是该数据项被删除的原因。EvictionReason 是一个枚举类型,它维护者各种可能的删除原因,如过期,删除以及替换。在回调函数的内部,我们会基于删除的原因构造一个字符串消息。我们想要将此消息设置成另外一个缓存项。这样做的话就需要访问 HomeController 的缓存对象,此时状态参数就可以排上用场了。使用状态对象,你可以对 HomeController 的缓存对象进行控制,并使用 Set() 增加一个 callbackMessage 缓存项。你可以通过 Show() 这个 action 来访问到 callbackMessage,如下所示: public IActionResult Show() { string timestamp = cache.Get<string>("timestamp"); ViewData["callbackMessage"] = _memoryCache.Get<string>("callbackMessage"); return View("Show",timestamp); } 最后就可以在 Show 视图中显示出来了: <h1>TimeStamp : @Model</h1> <h3>@ViewData["callbackMessage"]</h3> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2> 一些建议,像上面提到的设置缓存数据项的过期时间那块,如果一个项目中所有的缓存过期时间是一致的,我们可以有更简单的做法,而不是每个地方都去写一堆这个: MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1); 可以在头部定义一个共有的 readonly MemoryCacheEntryOptions _options = Cache.GetMemoryCacheEntryOptions(); Cache是自定义的一个缓存类,其中GetMemoryCacheEntryOptions就是获取当前缓存设置项的,代码如下: public static MemoryCacheEntryOptions GetMemoryCacheEntryOptions() { var options = new MemoryCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")), SlidingExpiration = TimeSpan.FromMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")) }; return options; } 这里面我们将过期时间放到配置文件中,这里读取配置文件可以参考前面的博客:https://www.cnblogs.com/zhangxiaoyong/p/9411036.html 这样,我们的写法就可以简写很多,当然,刚才提到了是使用的 IMemoryCache 的 Set<T>() 来完成的,所以,这里我们不仅可以传String,还可以按需传递,比如: var homeCache = _memoryCache.Get<Task<HomeInfo>>("HomeCache");//获取HomeCache if (homeCache == null)//判断是否存在 { homeCache = _dadaServices.GetHomeData();//调用API获取数据 _memoryCache.Set<Task<HomeInfo>>("HomeCache", homeCache, _options);//存放HomeCache,传入data数据和设置的数据项 } return View("~/Views/Home/Index.cshtml", homeCache.Result);//返回Cache 到这里,你已经大概知道了MemoryCache 的简单使用方式,剩下的就自行研究吧~ 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 在之前的.NET 里,我们可以很容易的使用Session读取值。那今天我们来看看 如何在.NET Core中读取Session值呢? Session 使用Session之前,我们需要到Startup.cs中配置我们的服务如下: ①在ConfigureServices中加入: services.AddSession(); ②在Configure中注入Sessio服务,如下: //注册Session服务 app.UseSession(); ③使用, 假设我们在HomeController.cs中,会有个个人中心Action,每次进来需要判断是否带Id,我们可以这样: public void GetSession(string Id) { if (HttpContext.Session.GetString("UserStuats") == null) { HttpContext.Session.SetString("UserStuats", "yes"); } if (HttpContext.Session.GetString("UserId") == null) { HttpContext.Session.SetString("UserId", Id); } } 抽象出一个通用方法判断是否当前这个id是否有值,然后进这个Action的时候调用: GetSession(当前用户Id); 然后我们假设有个返回首页的动作,此时,首页中也是需要获取是否用户已经存在。这个时候,我们就可以去通过Session来判断当前是否用户已经进入: ViewBag.UserId = HttpContext.Session.GetString("UserStuats") ?? "no"; 这里很清楚,我们在首页中判断ViewBag.UserId是yes还是no就能很清楚的知道 当前是否已经有用户进入。 移除Session我们可以用: HttpContext.Session.Remove("UserStuats"); 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 每个项目都会需要使用到日志功能,这对于项目上线后 出现的bug异常,能及时定位和便于后期错误分析。那我们今天来看看在.NET Core中如何使用NLog日志。 NLog 什么是NLog呢? NLog是一个基于.NET平台编写的类库,我们可以使用NLog在应用程序中添加极为完善的跟踪调试代码。NLog是一个简单灵活的.NET日志记录类库。通过使用NLog,我们可以在任何一种.NET语言中输出带有上下文的(contextual information)调试诊断信息,根据喜好配置其表现样式之后发送到一个或多个输出目标(target)中。NLog的API非常类似于log4net,且配置方式非常简单。NLog使用路由表(routing table)进行配置,这样就让NLog的配置文件非常容易阅读,并便于今后维护。NLog遵从BSD license,即允许商业应用且完全开放源代码。任何人都可以免费使用并对其进行测试,然后通过邮件列表反馈问题以及建议。NLog支持.NET、C/C++以及COM interop API,因此我们的程序、组件、包括用C++/COM 编写的遗留模块都可以通过同一个路由引擎将信息发送至NLog中。简单来说Nlog就是用来记录项目日志的组件 使用步骤 ①在NuGet中安装:NLog.Config和 NLog.Extensions.Logging ②配置Configure如下 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddNLog();//添加NLog } ③打开项目bin/debug目录,找到 ④将里面内容替换成: <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Warn" internalLogFile="internal-nlog.txt"> <!--define various log targets--> <targets> <!--write logs to file--> <target xsi:type="File" name="allfile" fileName="nlog-all-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}|${message} ${exception}" /> <target xsi:type="File" name="ownFile-web" fileName="nlog-my-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}|${message} ${exception}" /> <target xsi:type="Null" name="blackhole" /> </targets> <rules> <!--All logs, including from Microsoft--> <logger name="*" minlevel="Trace" writeTo="allfile" /> <!--Skip Microsoft logs and so log only own logs--> <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" /> <logger name="*" minlevel="Trace" writeTo="ownFile-web" /> </rules> </nlog> ⑤在HomeController中添加DI注入 private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } ⑥测试效果 _logger.LogError("我是错误显示"); _logger.LogDebug("我是调试信息"); _logger.LogInformation("我是提示信息"); 执行后,查看bin/debug/netcoreapp2.0下面的nlog-my-2018-08-12.log文件即可看到输出日志: 关于Nlog日志的就先介绍到这儿,这只是一个基础入门讲解,更多内容可以参考:Nlog官网 异常处理 对于全局异常处理,通常有几种方式,我们这里介绍一个常见的做法: ①我们在web下新建一个文件夹ErrorHand ②在此文件夹中新建一个ErrorHandlingMiddleware类如下 public class ErrorHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ErrorHandlingMiddleware> _logger; public ErrorHandlingMiddleware(RequestDelegate next,ILogger<ErrorHandlingMiddleware> logger) { this._next = next; _logger = logger; } public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception e) { var statusCode = context.Response.StatusCode; if (e is ArgumentException) { statusCode = 200; } await HandleExceptionAsync(context, statusCode, e.Message); } finally { var statusCode = context.Response.StatusCode; var msg = ""; if (statusCode != 200) { _logger.LogError(context.Request.GetAbsoluteUri()+"\r\n"+statusCode.ToString()); } if (!string.IsNullOrEmpty(msg)) { await HandleExceptionAsync(context, statusCode, msg); } } } private static Task HandleExceptionAsync(HttpContext context, int statusCode, string msg) { var data = new {code = statusCode.ToString(), is_success = false, msg = msg}; var result = JsonConvert.SerializeObject(new {data = data}); context.Response.ContentType = "application/json;charset=utf-8"; return context.Response.WriteAsync(result); } } 这里类内容比较简单,就不多赘述了,此时,我们全局异常类已经编写好了,但是,还无法正常使用,我们需要在Startup中配置一下 ③我们需要在Configure方法中加入: //全局错误 app.UseMiddleware(typeof(ErrorHandlingMiddleware)); 这个地方就是绑定了我们自定义的错误类。 注:.netCore中 提供了两个我们可以跳转到错误页面,一个是404的,一个是500的,同样在Configure方法中加入: app.UseExceptionHandler("/Home/NothingFound"); app.UseStatusCodePagesWithReExecute("/Home/NothingFound"); 这样当有对应异常出现的时候,就可以跳转到自己的错误页。然后配合NLog可以查看到相应输出日志。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 本篇幅会专门记录在工作中实际碰到的问题场景,和一些比较好的实现方法作为汇总,可以供各位借鉴和参考,当然 本人入行不深,能力有限,仅供各位借鉴和参考。欢迎补充 技巧一:引入其他项目类库文件 做项目大家都知道会有远程请求API的情况,现在假设做API的项目你能接触到并且Git下来。那么继续往下看。 将API项目中用到的Dto引入自己项目。这样方便我们将请求到的json字符串直接序列化成dto,然后再转成我们自己需要的ViewModel,具体怎么转呢,往下看: 技巧二:使用Extension 使用Extension,具体怎么用,我们来用一个实际例子说一下吧。比如,我们现在要请求一个所有俱乐部的api。首先我们定义一个ViewModel用于存放俱乐部信息,Model如下: ①定义ViewModel public class ClubBase { /// <summary> /// 主键Id /// </summary> public long Id { get; set; } /// <summary> /// 俱乐部名称 /// </summary> public string Name { get; set; } /// <summary> /// 俱乐部描述 /// </summary> public string Description { get; set; } /// <summary> /// 创始人 /// </summary> public string Creator { get; set; } /// <summary> /// 创建年份 /// </summary> public int Year { get; set; } /// <summary> /// 其他信息 /// </summary> public string Contactor { get; set; } /// <summary> /// 手机号 /// </summary> public string Phone { get; set; } /// <summary> /// 地址 例如 中国/深圳 /// </summary> public string Address { get; set; } /// <summary> /// 日期 /// </summary> public DateTime CreationDate { get; set; } } View Code ②编写接口 /// <summary> /// 所有接口信息 /// </summary> public interface IDataServices { /// <summary> /// 获取所有俱乐部 /// </summary> /// <returns></returns> Task<IList<ClubBase>> GetClubs(); } ③实现Server接口 /// <summary> /// 接口实现类 动态请求api获取相应数据 /// </summary> public class DataServices: IDataServices { private readonly IDataHttpServers _dataHttpServers; public DataServices(IDataHttpServers dataHttpServers) { _dataHttpServers = dataHttpServers; } public async Task<IList<ClubBase>> GetClubs() { var clubsModelList = await _dataHttpServers.GetClubs(); return clubsModelList; } } ④编写Http请求接口 public interface IDataHttpServers { /// <summary> /// 获取所有俱乐部 /// </summary> /// <returns></returns> Task<IList<ClubBase>> GetClubs(); } ⑤实现此接口 /// <summary> /// 返回俱乐部列表 /// </summary> /// <returns></returns> public async Task<IList<ClubBase>> GetClubs() { return await Task.Run(() => GetClubApi()); } /// <summary> /// 获取所有俱乐部API /// </summary> /// <returns></returns> public List<ClubBase> GetClubApi() { var list = HttpHelper.GetApi<string>("getclub"); List<ClubBase> viewClubList = new List<ClubBase>(); try { List<ClubInfoDto> apiClubList = JsonConvert.DeserializeObject<List<ClubInfoDto>>(list); foreach (var item in apiClubList) { //调用拓展方法解耦dto于ViewModel的赋值操作 var club = item.TranslateToClubBaseViewModel(); viewClubList.Add(club); } } catch (Exception e) { //请求接口api异常,异常描述 list } return viewClubList; } 重头戏也就是此方法了,其中 可以将请求api单独抽离出来写成泛型方法,此处是这样抽离的: /// <summary> /// HTTPclient 泛型抽象类 /// </summary> public static class HttpHelper { public static T GetApi<T>(string apiName, string pragm = "") { var client = new RestSharpClient($"{SiteConfig.GetSite("Url")}"); var request = client.Execute(string.IsNullOrEmpty(pragm) ? new RestRequest($"{SiteConfig.GetSite($"{apiName}")}", Method.GET) : new RestRequest($"{SiteConfig.GetSite($"{apiName}")}/{pragm}", Method.GET)); if (request.StatusCode != HttpStatusCode.OK) { return (T)Convert.ChangeType(request.ErrorMessage, typeof(T)); } T result = (T)Convert.ChangeType(request.Content, typeof(T)); return result; } } 这里我请求API是使用的RestSharp提供的请求方法。具体可以看这儿 RestSharp ,需要NuGet安装RestSharp 这里我稍微贴一下 大概常用的一些接口方法: 1)接口 /// <summary> /// API请求执行者接口 /// </summary> public interface IRestSharp { /// <summary> /// 同步执行方法 /// </summary> /// <param name="request"></param> /// <returns></returns> IRestResponse Execute(IRestRequest request); /// <summary> /// 同步执行方法 /// </summary> /// <typeparam name="T">返回值</typeparam> /// <param name="request">请求参数</param> /// <returns></returns> T Execute<T>(IRestRequest request) where T : new(); /// <summary> /// 异步执行方法 /// </summary> /// <param name="request">请求参数</param> /// <param name="callback"></param> /// <returns></returns> RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback); /// <summary> /// 异步执行方法 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="request"></param> /// <param name="callback"></param> /// <returns></returns> RestRequestAsyncHandle ExecuteAsync<T>(IRestRequest request, Action<IRestResponse<T>> callback) where T : new(); } View Code 2)实现 /// <summary> /// Rest接口执行者 /// </summary> public class RestSharpClient : IRestSharp { /// <summary> /// 请求客户端 /// </summary> private RestClient client; /// <summary> /// 接口基地址 格式:http://www.xxx.com/ /// </summary> private string BaseUrl { get; set; } /// <summary> /// 默认的时间参数格式 /// </summary> private string DefaultDateParameterFormat { get; set; } /// <summary> /// 默认验证器 /// </summary> private IAuthenticator DefaultAuthenticator { get; set; } /// <summary> /// 构造函数 /// </summary> /// <param name="baseUrl"></param> /// <param name="authenticator"></param> public RestSharpClient(string baseUrl, IAuthenticator authenticator = null) { BaseUrl = baseUrl; client = new RestClient(BaseUrl); DefaultAuthenticator = authenticator; //默认时间显示格式 DefaultDateParameterFormat = "yyyy-MM-dd HH:mm:ss"; //默认校验器 if (DefaultAuthenticator != null) { client.Authenticator = DefaultAuthenticator; } } /// <summary> /// 通用执行方法 /// </summary> /// <param name="request">请求参数</param> /// <remarks> /// 调用实例: /// var client = new RestSharpClient("http://localhost:82/"); /// var result = client.Execute(new RestRequest("api/values", Method.GET)); /// var content = result.Content;//返回的字符串数据 /// </remarks> /// <returns></returns> public IRestResponse Execute(IRestRequest request) { request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; var response = client.Execute(request); return response; } /// <summary> /// 同步执行方法 /// </summary> /// <typeparam name="T">返回的泛型对象</typeparam> /// <param name="request">请求参数</param> /// <remarks> /// var client = new RestSharpClient("http://localhost:82/"); /// var result = client.Execute<List<string>>(new RestRequest("api/values", Method.GET)); /// </remarks> /// <returns></returns> public T Execute<T>(IRestRequest request) where T : new() { request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; var response = client.Execute<T>(request); return response.Data; } /// <summary> /// 异步执行方法 /// </summary> /// <param name="request">请求参数</param> /// <param name="callback">回调函数</param> /// <remarks> /// 调用实例: /// var client = new RestSharpClient("http://localhost:62981/"); /// client.ExecuteAsync<List<string>>(new RestRequest("api/values", Method.GET), result => /// { /// var content = result.Content;//返回的字符串数据 /// }); /// </remarks> /// <returns></returns> public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback) { request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; return client.ExecuteAsync(request, callback); } /// <summary> /// 异步执行方法 /// </summary> /// <typeparam name="T">返回的泛型对象</typeparam> /// <param name="request">请求参数</param> /// <param name="callback">回调函数</param> /// <remarks> /// 调用实例: /// var client = new RestSharpClient("http://localhost:62981/"); /// client.ExecuteAsync<List<string>>(new RestRequest("api/values", Method.GET), result => /// { /// if (result.StatusCode != HttpStatusCode.OK) /// { /// return; /// } /// var data = result.Data;//返回数据 /// }); /// </remarks> /// <returns></returns> public RestRequestAsyncHandle ExecuteAsync<T>(IRestRequest request, Action<IRestResponse<T>> callback) where T : new() { request.DateFormat = string.IsNullOrEmpty(request.DateFormat) ? DefaultDateParameterFormat : request.DateFormat; return client.ExecuteAsync<T>(request, callback); } } View Code 好了,我们接着上面说,大家可以看到,当我们通过请求api获取到数据之后得到的是下面这个Json字符串如下: 为什么我们需要引入api的dto而不是直接使用ViewModel来赋值我们需要的呢,这里有两个好处,第一个,我们可以快速完成json字符串到对象的转变,第二个,我们可以使用我们下面说的一个小技巧快速过滤出我们想组装的ViewModel即可。 注意下我们的这个方法TranslateToClubBaseViewModel ,是我们用于操作dto和ViewModel的主要方法 /// <summary> /// 此方法用于将Dto类型 数据 赋值到需要操作的ViewModel上 /// </summary> /// <param name="clubInfoDto">dto数据集</param> /// <returns></returns> public static ClubBase TranslateToClubBaseViewModel(this ClubInfoDto clubInfoDto) { ClubBase club = new ClubBase() { #region 实体按需赋值 Id = clubInfoDto.Id, Address = clubInfoDto.Address, Contactor = clubInfoDto.Contactor, Creator = clubInfoDto.Creator, Description = clubInfoDto.Description, Name = clubInfoDto.Name, Phone = clubInfoDto.Phone, Year = clubInfoDto.Year #endregion }; return club; } 这样操作的好处,我们就不用维护具体的业务层,专注于ViewModel上面。达到了解耦效果。 RestSharp发送Post请求 假设我们项目中需要自定义支持所有页面的浏览记录,需要记录再数据库每个页面的详细浏览情况,而不是采用第三方的统计,我们可以这样实现。(假设此处采用的是前后端分离,API的方式读写数据库)①我们在IDataServices接口类中新建一个接口SendBrowseRecord /// <summary> /// 发送浏览记录 /// </summary> /// <param name="id">用户id,若无则为0</param> /// <param name="url">请求页面</param> /// <param name="alias">别名</param> /// <returns></returns> Task<string> SendBrowseRecord(int id, string url, string alias); ②在DataServices中实现此接口 public async Task<string> SendBrowseRecord(int id, string url, string alias) { return await _dataHttpServers.SendBrowseRecord(id, url, alias); } ③此处的_dataHttpServers是单独封装起来的访问API的接口,内容和IDataServices一样,但需要注意 在DataServices构造方法中需要注入进来 private readonly IDataHttpServers _dataHttpServers; public DataServices(IDataHttpServers dataHttpServers) { _dataHttpServers = dataHttpServers; } ④实现Http的访问SendBrowseRecord方法 public string SendBrowseRecordApi(int id, string url, string alias) { try { return HttpHelper.PostApi<string>(id, url, alias); } catch (Exception e) { //请求接口api异常,异常描述 list _logger.LogError(e.Message); } return ""; } public async Task<string> SendBrowseRecord(int id, string url, string alias) { return await Task.Run(() => SendBrowseRecordApi(id, url, alias)); } 因为每个页面都需要去调用这个PostApi,所以此处我将PostApi<T>抽象出一个泛型方法进来,这样不管哪个页面都是调用这个。抽象出来的Post方法如下: public static T PostApi<T>(int id, string url, string alias) { var client = new RestClient($"{SiteConfig.GetSite("Url")}api/pageviews"); IRestRequest queest = new RestRequest(); queest.Method = Method.POST; queest.AddHeader("Accept", "application/json"); queest.RequestFormat = DataFormat.Json; queest.AddBody(new { userid = id, Url = url, alias = alias }); // uses JsonSerializer var result = client.Execute(queest); if (result.StatusCode != HttpStatusCode.OK) { return (T)Convert.ChangeType(result.ErrorMessage, typeof(T)); } T request = (T)Convert.ChangeType(result.Content, typeof(T)); return request; } 此处的Body是发送一个Json对象。这样,我们就把RestSharp的Post调用实现了。这样就ok啦 未完待续。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 .net core来势已不可阻挡。既然挡不了,那我们就顺应它。了解它并学习它。今天我们就来看看和之前.net版本的配置文件读取方式有何异同,这里不在赘述.NET Core 基础知识。 ps:更新版,更新了多种方式实现读取配置文件信息,各位看官结合自己实际情况选择合适的读取方式即可。 实现方式一 我们先来看下初始的Json文件是怎样的: { "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } }, "Test": { "One": 123, "Two": "456", "Three": "789", "Content": { "cone": 111, "ctwo": "潇十一郎" } } } 读取Json配置文件信息,我们需要安装Microsoft.Extensions.Configuration.Json 包,如下: 然后再 调用AddJsonFile把Json配置的Provider添加到ConfigurationBuilder中,实现如下: 我们将Configuration 属性改成 public static IConfiguration Configuration { get; set; } 然后再ConfigureServices 中将Json配置的Provider添加到ConfigurationBuilder中 var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json"); Configuration = builder.Build(); services.AddSingleton<IConfiguration>(Configuration);//配置IConfiguration的依赖 然后配置完,我们就可以读取了,当然,如果是在其他地方调用,就需要通过构造函数注入IConfiguration 读取方式一:使用key读取,如下: private readonly IConfiguration _configuration; //构造注入 public HomeController(IConfiguration configuration) { _configuration = configuration; } public IActionResult Index() { //方式一 通过 Key值获取 var one = _configuration["Test:One"]; //123 var conw = _configuration["Test:Content:ctwo"]; //潇十一郎 } 读取方式二:使用GetValue<T>,需要安装Microsoft.Extensions.Configuration.Binder包,读取如下: private readonly IConfiguration _configuration; //构造注入 public HomeController(IConfiguration configuration) { _configuration = configuration; } public IActionResult Index() { //方式二 通过GetValue<T>(string key)方式获取 第一个是直接获取key 第二个若查找不到则指定默认值 var two = _configuration.GetValue<int>("Test:One"); //123 var three = _configuration.GetValue<string>("Test:Three"); //789 var ctwo = _configuration.GetValue<string>("Test:Content:ctwo"); //潇十一郎 var four = _configuration.GetValue<string>("Test:four", "我是默认值"); //我是默认值 } 特别说明一下:GetValue的泛型形式有两个重载,一个是GetValue("key"),另一个是可以指定默认值的GetValue("key",defaultValue).如果key的配置不存在,第一种结果为default(T),第二种结果为指定的默认值. 上述两个读取方式调试图如下: 实现方式二 注:需要NuGet引入:Microsoft.Extensions.Options.ConfigurationExtensions ①我们再配置文件appsettings.json中 新增自定义API Json如下: { "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } }, "API": { "Url": "http://localhost:8080/", "getclub": "api/club" } } ②然后我们定义一个静态类,再类中申明一个IConfigurationSection 类型变量 private static IConfigurationSection _appSection = null; ③写一个AppSetting静态方法获取到配置的Value项,代码如下: public static string AppSetting(string key) { string str = string.Empty; if (_appSection.GetSection(key) != null) { str = _appSection.GetSection(key).Value; } return str; } ④需要设置IConfigurationSection初始值,如下: public static void SetAppSetting(IConfigurationSection section) { _appSection = section; } ⑤然后写一个根据不同Json项读取出对应的值即可: public static string GetSite(string apiName) { return AppSetting(apiName); } ⑥有了以上几个步骤,基本上读取代码已经全部写完,剩下最后一个最重要的步骤,将要读取的Json文件配置到Startup.cs的Configure方法中,如下: 这样,我们就可以很轻松的获取到我们想要的配置项了,整段CS代码如下: /// <summary> /// 配置信息读取模型 /// </summary> public static class SiteConfig { private static IConfigurationSection _appSection = null; /// <summary> /// API域名地址 /// </summary> public static string AppSetting(string key) { string str = string.Empty; if (_appSection.GetSection(key) != null) { str = _appSection.GetSection(key).Value; } return str; } public static void SetAppSetting(IConfigurationSection section) { _appSection = section; } public static string GetSite(string apiName) { return AppSetting(apiName); } } 最后 ,我们来跑一下演示效果如下: 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 (四)微信公众号开发之网页授权获取用户基本信息 (五)微信公众号开发之网页中及时获取当前用户Openid及注意事项 (六)微信公众号开发之扫码支付 (七)微信公众号开发之公众号支付 (八)微信公众号开发之现金红包 今天,我们接着讲微信支付的系列教程,前面,我们讲了这个微信红包和扫码支付。现在,我们讲讲这个公众号支付。公众号支付的应用环境常见的用户通过公众号,然后再通过公众号里面的菜单链接,进入公众号的商城,然后在里面完成购买和支付功能,我们可以看看官方对这个公众号支付的场景的解释,链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1,通过这个官方的解释,那我们大概清楚这个公众号的用途了,下面,我就说说,做这个公众号支付的准备工作有哪些了。 1、下载微信web开发者工具,工具的使用方式,也看链接,地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140&token=&lang=zh_CN 2、配置“微信支付”环境,如下图: 3、授权获取用户信息,如下图: 下面开始,一步一步往下走。 一、我们先开发程序,首先,新建一个MVC工程(asp.net的话,官方给的demo就是asp.net的,可以下载来参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1),名为:微信支付之公众号支付,如下图: 然后右键项目,我们修改一下属性,如下图: 然后我们再把程序自动生成的HomeController.cs和View里面的删掉,再新建一个HomeController.cs和添加View,代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Web.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } } } View代码: @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> </div> </body> </html> 嗯,没错,目前还是空的,现在我们开始写前台,代码如下(我先贴上代码,后续再解释为啥这么做,因为如果一步步的写下去,按照前面两个的篇幅来,我觉得都可以开课了,所以,我先上代码,然后再一步步解释。): @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>电表充值服务</title> <link href="~/Scripts/jquery-easyui-1.4.5/themes/bootstrap/easyui.css" rel="stylesheet" /> <link href="~/Scripts/jquery-easyui-1.4.5/themes/mobile.css" rel="stylesheet" /> <link href="~/Scripts/jquery-easyui-1.4.5/themes/icon.css" rel="stylesheet" /> <style type="text/css"> body{ margin:0; padding:0; } .logo { width: 100%; height: 70px; background: url(/Images/EleLogo.png) 0 0 no-repeat; background-size: 100% 100%; padding: 0; margin: 0; } .line { width: 100%; float: left; height: auto; text-align: center; margin-top: 10px; } .lineText { width: 100%; float: left; height: auto; text-indent: 5%; text-align: left; font-size: x-large; margin: 0; } .function { height: 60pt; line-height: 60pt; width: 45%; float: left; border-radius: 10px; background-color: #990000; margin-left: 8pt; } .title { font-family: "微软雅黑"; font-size: x-large; color: white; } a { text-decoration: none; color: white; } input { vertical-align: central; } label { vertical-align: central; } .lbBlock { border: 1px solid #808080; background-color: grey; width: 90%; margin-left: 5%; font-size: x-large; border-radius: 10px; text-align: left; text-indent: 10pt; height: 30pt; padding-top: 5pt; } .btn { width: 90%; height: 35pt; font-size: x-large; background-color: #990000; color: white; background: url(/Images/red.png) 0 0 repeat; border: none; border-radius: 10px; margin: 10px 0 0 0; } .input { height: 30pt; width: 90%; font-size: x-large; border-radius: 10px; margin: 0; padding: 0; } </style> </head> <body> <div class="logo"> </div> <form id="ChargeForm"> <div class="line"> <div class="lineText"> 充值金额: </div> </div> <div class="line"> <input type="number" id="ChargeVal" name="ChargeVal" class="input" placeholder="单位:元" /> </div> </form> <div class="line"> <input type="button" class="btn" value="立即充值" onclick="fCharge()" style="margin-top: 20px;" /> </div> <div class="line"> <input type="button" id="btnHome" class="btn" value="返回主页" onclick="fBackHome()" /> </div> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.min.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.easyui.min.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/jquery.easyui.mobile.js"></script> <script src="~/Scripts/jquery-easyui-1.4.5/easyloader.js"></script> <script type="text/javascript"> $(function () { var vCode = getQueryString("code"); if (vCode != "" && vCode != null) { //alert(vCode); $.ajax({ type: 'post', data: { code: vCode }, url: '/Home/getWxInfo', success: function (sjson) { //alert(sjson); //var vData = JSON.stringify(sjson); //alert(vData); $.messager.show({ title: '提示', msg: '欢迎您来到微信端充值中心。' }); } }) } else { $.ajax({ type: 'post', url: '/Home/getCode', success: function (sjson) { //alert(sjson); location.href = sjson; } }) } }) //获取url的参数 function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); if (r != null) return unescape(r[2]); return null; } //初始化微信支付环境 function fCharge() { if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } } else { fPostCharge(); } } //提交充值数据 function fPostCharge() { var vChargeVal = $("#ChargeVal").val(); vChargeVal = parseFloat(vChargeVal); if (vChargeVal > 0) { $.messager.progress({ title: "", msg: "正在调用微信支付接口,请稍后..." }); $.ajax({ type: "post", data: "totalfee=" + vChargeVal, url: "/Home/MeterRecharge", success: function (json) { $.messager.progress('close');//记得关闭 //var json = eval("(" + msg + ")");//转换后的JSON对象 onBridgeReady(json); }, error: function () { $.messager.progress('close');//记得关闭 $.messager.alert("提示", '调用微信支付模块失败,请稍后再试。', 'info') } }) } else { alert("房间名或者充值金额不可以为空或者为负数,请确认后再试.") } } //调用微信支付模块 function onBridgeReady(json) { WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": json.appId, //公众号名称,由商户传入 "timeStamp": json.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr": json.nonceStr, //随机串 "package": json.packageValue, "signType": "MD5", //微信签名方式: "paySign": json.paySign //微信签名 }, function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { //alert("支付成功,请稍后查询余额,如有疑问,请联系管理员."); fAlreadyPay(); } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } function fBackHome() { location.href = "/"; } </script> </body> </html> 后台代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using Web.Models; using WxPayAPI; namespace Web.Controllers { public class HomeController : Controller { JsApiPay jsApiPay = new JsApiPay(); // GET: Home public ActionResult Index() { if (Session["openid"] == null) { try { //调用【网页授权获取用户信息】接口获取用户的openid和access_token GetOpenidAndAccessToken(); } catch (Exception ex) { //Response.Write(ex.ToString()); //throw; } } return View(); } /** * * 网页授权获取用户基本信息的全部过程 * 详情请参看网页授权获取用户基本信息:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html * 第一步:利用url跳转获取code * 第二步:利用code去获取openid和access_token * */ public void GetOpenidAndAccessToken() { if (Session["code"] != null) { //获取code码,以获取openid和access_token string code = Session["code"].ToString(); Log.Debug(this.GetType().ToString(), "Get code : " + code); jsApiPay.GetOpenidAndAccessTokenFromCode(code); } else { //构造网页授权获取code的URL string host = Request.Url.Host; string path = Request.Path; string redirect_uri = HttpUtility.UrlEncode("http://" + host + path); //string redirect_uri = HttpUtility.UrlEncode("http://gzh.lmx.ren"); WxPayData data = new WxPayData(); data.SetValue("appid", WxPayConfig.APPID); data.SetValue("redirect_uri", redirect_uri); data.SetValue("response_type", "code"); data.SetValue("scope", "snsapi_base"); data.SetValue("state", "STATE" + "#wechat_redirect"); string url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + data.ToUrl(); Log.Debug(this.GetType().ToString(), "Will Redirect to URL : " + url); Session["url"] = url; } } /// <summary> /// 获取code /// </summary> /// <returns></returns> [HttpPost] public ActionResult getCode() { object objResult = ""; if (Session["url"] != null) { objResult = Session["url"].ToString(); } else { objResult = "url为空。"; } return Json(objResult); } /// <summary> /// 通过code换取网页授权access_token和openid的返回数据 /// </summary> /// <returns></returns> [HttpPost] public ActionResult getWxInfo() { object objResult = ""; string strCode = Request.Form["code"]; if (Session["access_token"] == null || Session["openid"] == null) { jsApiPay.GetOpenidAndAccessTokenFromCode(strCode); } string strAccess_Token = Session["access_token"].ToString(); string strOpenid = Session["openid"].ToString(); objResult = new { openid = strOpenid, access_token = strAccess_Token }; return Json(objResult); } /// <summary> /// 充值 /// </summary> /// <returns></returns> [HttpPost] public ActionResult MeterRecharge() { object objResult = ""; string strTotal_fee = Request.Form["totalfee"]; string strFee = (double.Parse(strTotal_fee) * 100).ToString(); //若传递了相关参数,则调统一下单接口,获得后续相关接口的入口参数 jsApiPay.openid = Session["openid"].ToString(); jsApiPay.total_fee = int.Parse(strFee); //JSAPI支付预处理 try { string strBody = "南宫萧尘微信支付";//商品描述 WxPayData unifiedOrderResult = jsApiPay.GetUnifiedOrderResult(strBody); WxPayData wxJsApiParam = jsApiPay.GetJsApiParameters();//获取H5调起JS API参数,注意,这里引用了官方的demo的方法,由于原方法是返回string的,所以,要对原方法改为下面的代码,代码在下一段 ModelForOrder aOrder = new ModelForOrder() { appId = wxJsApiParam.GetValue("appId").ToString(), nonceStr = wxJsApiParam.GetValue("nonceStr").ToString(), packageValue = wxJsApiParam.GetValue("package").ToString(), paySign = wxJsApiParam.GetValue("paySign").ToString(), timeStamp = wxJsApiParam.GetValue("timeStamp").ToString(), msg = "成功下单,正在接入微信支付." }; objResult = aOrder; } catch (Exception ex) { ModelForOrder aOrder = new ModelForOrder() { appId = "", nonceStr = "", packageValue = "", paySign = "", timeStamp = "", msg = "下单失败,请重试,多次失败,请联系管理员." }; objResult = aOrder; } return Json(objResult); } } } 这里就是上面修改了的代码,童鞋们请注意 /** * * 从统一下单成功返回的数据中获取微信浏览器调起jsapi支付所需的参数, * 微信浏览器调起JSAPI时的输入参数格式如下: * { * "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入 * "timeStamp":" 1395712654", //时间戳,自1970年以来的秒数 * "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串 * "package" : "prepay_id=u802345jgfjsdfgsdg888", * "signType" : "MD5", //微信签名方式: * "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 * } * @return string 微信浏览器调起JSAPI时的输入参数,json格式可以直接做参数用 * 更详细的说明请参考网页端调起支付API:http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7 * */ public WxPayData GetJsApiParameters() { Log.Debug(this.GetType().ToString(), "JsApiPay::GetJsApiParam is processing..."); WxPayData jsApiParam = new WxPayData(); jsApiParam.SetValue("appId", unifiedOrderResult.GetValue("appid")); jsApiParam.SetValue("timeStamp", WxPayApi.GenerateTimeStamp()); jsApiParam.SetValue("nonceStr", WxPayApi.GenerateNonceStr()); jsApiParam.SetValue("package", "prepay_id=" + unifiedOrderResult.GetValue("prepay_id")); jsApiParam.SetValue("signType", "MD5"); jsApiParam.SetValue("paySign", jsApiParam.MakeSign()); string parameters = jsApiParam.ToJson(); Log.Debug(this.GetType().ToString(), "Get jsApiParam : " + parameters); return jsApiParam; } ModelForOrder类的代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace Web.Models { public class ModelForOrder { public string appId { get; set; } public string timeStamp { get; set; } public string nonceStr { get; set; } public string packageValue { get; set; } public string paySign { get; set; } public string msg { get; set; } } } 还有一个地方需要注意,修改一下的就是这里WxLib/business/JsApiPay.cs,如下图: 最后,把程序发布出来,这次咱们把Web发布在http://gzh.lmx.ren上 ,然后再把接口权限,改为这样的,如下图: 注意,这里面的域名和上面我们发布的域名要一致。 除此以外,我们还需要改这里: 就是一定要授权这里,否则,支付的时候,会提示其他错误,具体,我就不测试了。 另外,这里其实已经完成了这个公众号的支付的流程了,但是,我们页面上,会友善的提醒(其实不友善,提示是红色的,在头部,提示别输入密码什么),这是因为,我们还没把咱们这个http://gzh.lmx.ren域名设置为安全域名,设置之后,就不会在提示了。设置方法如下图: 在这里面加入咱们的域名,就完美了。。。 我的代码都尽量精简,多余的,我都会丢掉,就是为了避免混淆视听。如果代码里面,有写的不清楚的,可以私信问我,或群里来问我,群号在文章末端。 现在,我开始一一解释我上面的做法。 首先,在后端,页面加载的时候,他会先执行 public ActionResult Index() { if (Session["openid"] == null) { try { //调用【网页授权获取用户信息】接口获取用户的openid和access_token GetOpenidAndAccessToken(); } catch (Exception ex) { //Response.Write(ex.ToString()); //throw; } } return View(); } 这里面就是为了获取用户的Openid和Access_token,这个用途很大,还有就是,我们通过代码可以知道,我们通过这个方法,可以获取到微信的一些相关信息,获取完了之后,他会返回到我们的页面上来,url就存在一个session里面,如下: Session["url"] = url; 接着,在前端: 当页面加载完毕之后,会执行以下JS方法,如下: $(function () { var vCode = getQueryString("code"); if (vCode != "" && vCode != null) { //alert(vCode); $.ajax({ type: 'post', data: { code: vCode }, url: '/Home/getWxInfo', success: function (sjson) { //alert(sjson); //var vData = JSON.stringify(sjson); //alert(vData); $.messager.show({ title: '提示', msg: '欢迎您来到微信端充值中心。' }); } }) } else { $.ajax({ type: 'post', url: '/Home/getCode', success: function (sjson) { //alert(sjson); location.href = sjson; } }) } }) 他会先获取浏览器的url,然后获取code,就是一般url后面的xxx.com?code=xxx,这里面就是首先判断有无code,如果没有code,则,我们去后台请求这个code。为什么请求这个code呢?我们来看这个方法: getWxInfo,如下图: /// <summary> /// 获取code /// </summary> /// <returns></returns> [HttpPost] public ActionResult getCode() { object objResult = ""; if (Session["url"] != null) { objResult = Session["url"].ToString(); } else { objResult = "url为空。"; } return Json(objResult); } 他就会返回url到前端,前端通过js去访问那个网址,那个网址就是微信端获取到我们的信息之后,给我们按照规则再返回一个url,这rul就是我们后面需要后去的code的url。这个code对我们至关重要,因为后面我们要做跟支付有关的工作,都用到的。有了code,我们才能拿到openid和access_token。具体看代码逻辑也可以明了。 好,走到这一步,我们已经知道openid和access_token了,这个时候,我们就负责处理前端的东西。 前端,我就一个金额输入框,然后一个提交,实际应用中,我们肯定还需要传入商品的参数,我这里面就不写那些多余的了,后续你们自己加进去就可以了。这里面在点击提交的时候,会调用微信的环境,看下面的代码: //初始化微信支付环境 function fCharge() { if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } } else { fPostCharge(); } } 他会初始化一下环境,如果初始化成功,代表,这个页面是在微信客户端里面运行的,那么我们就给他运行我们真正的充值代码提交,所以,就会执行:fPostCharge(); 提交之后,就会进入后台,后台需要组织我们前台需要用到的参数,其中包括如下: /// <summary> /// 充值 /// </summary> /// <returns></returns> [HttpPost] public ActionResult MeterRecharge() { object objResult = ""; string strTotal_fee = Request.Form["totalfee"]; string strFee = (double.Parse(strTotal_fee) * 100).ToString(); //若传递了相关参数,则调统一下单接口,获得后续相关接口的入口参数 jsApiPay.openid = Session["openid"].ToString(); jsApiPay.total_fee = int.Parse(strFee); //JSAPI支付预处理 try { string strBody = "南宫萧尘微信支付";//商品描述 WxPayData unifiedOrderResult = jsApiPay.GetUnifiedOrderResult(strBody); WxPayData wxJsApiParam = jsApiPay.GetJsApiParameters();//获取H5调起JS API参数 ModelForOrder aOrder = new ModelForOrder() { appId = wxJsApiParam.GetValue("appId").ToString(), nonceStr = wxJsApiParam.GetValue("nonceStr").ToString(), packageValue = wxJsApiParam.GetValue("package").ToString(), paySign = wxJsApiParam.GetValue("paySign").ToString(), timeStamp = wxJsApiParam.GetValue("timeStamp").ToString(), msg = "成功下单,正在接入微信支付." }; objResult = aOrder; } catch (Exception ex) { ModelForOrder aOrder = new ModelForOrder() { appId = "", nonceStr = "", packageValue = "", paySign = "", timeStamp = "", msg = "下单失败,请重试,多次失败,请联系管理员." }; objResult = aOrder; } return Json(objResult); } 我们主要需要提供的就是这个类ModelForOrder 里面的参数,然后再把这些参数返回给前台调用,如下: //调用微信支付模块 function onBridgeReady(json) { WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": json.appId, //公众号名称,由商户传入 "timeStamp": json.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr": json.nonceStr, //随机串 "package": json.packageValue, "signType": "MD5", //微信签名方式: "paySign": json.paySign //微信签名 }, function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { //alert("支付成功,请稍后查询余额,如有疑问,请联系管理员."); fAlreadyPay(); } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } 这样,他就会弹出一个微信支付的窗口,如下: 然后我们确认付款之后,是否付款成功,如果付款成功,我们在执行: fAlreadyPay(); 这个方法已经被我删掉了,用途是用于,我们收到用户的款之后,我们需要同步一些数据到我们的数据库里面去,所以,该怎么操作,自己自行修改了。 原文地址:http://www.cnblogs.com/nangong/p/50ab3c60f0d1ae5b551373cf96cd060d.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前情提要 有时候我们会在朋友圈看到如下两种转发情况:一种是前面带缩略图的 ,一种是无缩略图的,当然有缩略图的不管是从用户体验,还是网站推广运营方都是更优的选择。 那我们看看微信分享朋友圈缩略图是 怎么一回事呢 注:微信6.5.5版本后,微信调整了分享规则。以前的没有通过认证公众号jssdk注入分享的都不是官方认可的分享。 必要前提:① 所打开的分享网页 域名必须经过备案(备案过的二级域名也行) ②公众号后台基本配置里面获取AppID和AppSecret 以添加服务器IP到白名单 显示如下: 代码实现 在HTML的Body下 加入如下代码: <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script> var dataForWeixin = { appId: '@ViewBag.appid', url: '@ViewBag.url', jsapiTicket:'@ViewBag.jsapiTicket', title: '转发标题', imgUrl: '服务器上需要显示的图片路径', timestamp: '@ViewBag.timestamp', nonceStr: '@ViewBag.nonceStr', signature: '@ViewBag.signature', jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage'], callback: function () { } }; wx.config({ debug: false, appId: dataForWeixin.appId, timestamp: dataForWeixin.timestamp, nonceStr: dataForWeixin.nonceStr, signature: dataForWeixin.signature, jsApiList: dataForWeixin.jsApiList }); </script> <div style="height:0px;overflow:hidden;"> <img src="服务器上需要显示的图片路径" /> </div> 在控制器中加入一个 获取微信分享接口的参数如下(再程序入口调用此方法即可) /// <summary> /// 获取微信分享接口参数 /// </summary> public void GetWX() { string app_id = ConfigHelper.AppId; string AppSecret = ConfigHelper.AppSecret; writeLog.WriteLogs("app_id,AppSecret:" + app_id + AppSecret + ""); Wx_helper jssdk = new Wx_helper(app_id, AppSecret); Hashtable ht = jssdk.getSignPackage(); // 遍历哈希表 foreach (DictionaryEntry de in ht) { if (de.Key.ToString() == "appId") { ViewBag.appid = de.Value.ToString(); } if (de.Key.ToString() == "nonceStr") { ViewBag.nonceStr = de.Value.ToString(); } if (de.Key.ToString() == "timestamp") { ViewBag.timestamp = de.Value.ToString(); } if (de.Key.ToString() == "url") { ViewBag.url = de.Value.ToString(); } if (de.Key.ToString() == "signature") { ViewBag.signature = de.Value.ToString(); } if (de.Key.ToString() == "jsapiTicket") { ViewBag.jsapiTicket = de.Value.ToString(); } } } 上述代码中 Wx_helper(微信接口类)如下: /// <summary> /// 微信接口类 /// </summary> public class Wx_helper : DBBase { private string appId; private string appSecret; private DataTable DT; public Wx_helper(string appId, string appSecret) { this.appId = appId; this.appSecret = appSecret; } //得到数据包,返回使用页面 public System.Collections.Hashtable getSignPackage() { string jsapiTicket = getJsApiTicket(); string url_req = HttpContext.Current.Request.Url.ToString(); //当前网页的URL string pageurl = HttpContext.Current.Request.Url.AbsoluteUri; writeLog.WriteLogs("当前网页的URL:" + pageurl + ""); //string url = "http://" + HttpContext.Current.Request.ServerVariables["Http_Host"] + HttpContext.Current.Request.ApplicationPath; string timestamp = Convert.ToString(ConvertDateTimeInt(DateTime.Now)); string nonceStr = createNonceStr(); // 这里参数的顺序要按照 key 值 ASCII 码升序排序 string rawstring = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + pageurl + ""; writeLog.WriteLogs("rawstring:" + rawstring + ""); string signature = SHA1_Hash(rawstring); System.Collections.Hashtable signPackage = new System.Collections.Hashtable(); signPackage.Add("appId", appId); signPackage.Add("nonceStr", nonceStr); signPackage.Add("timestamp", timestamp); signPackage.Add("url", pageurl); signPackage.Add("signature", signature); signPackage.Add("jsapiTicket", jsapiTicket); return signPackage; } //创建随机字符串 private string createNonceStr() { int length = 16; string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; string str = ""; Random rad = new Random(); for (int i = 0; i < length; i++) { str += chars.Substring(rad.Next(0, chars.Length - 1), 1); } return str; } //SHA1哈希加密算法 public string SHA1_Hash(string str_sha1_in) { SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] bytes_sha1_in = System.Text.UTF8Encoding.Default.GetBytes(str_sha1_in); byte[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in); string str_sha1_out = BitConverter.ToString(bytes_sha1_out); str_sha1_out = str_sha1_out.Replace("-", "").ToLower(); return str_sha1_out; } //得到ticket 如果文件里时间 超时则重新获取 private string getJsApiTicket() { //这里我从数据库读取 string strSql = "select jsapi_ticket,ticket_expires,add_time from dt_weixin_jsapiticket where ID=1"; DataSet ds = sqlhelp.ExecuteDataSet(strSql); DT = ds.Tables[0]; int expire_time = Convert.ToInt32(DT.Rows[0]["ticket_expires"]); string ticket = DT.Rows[0]["jsapi_ticket"].ToString(); string error = string.Empty; string accessToken = new WXCRMComm().GetAccessToken(out error); //获取系统的全局token writeLog.WriteLogs("获取系统的全局token :" + accessToken + "," + error + ""); if (string.IsNullOrEmpty(error)) { writeLog.WriteLogs("GetAccessToken:" + error + ""); //计算时间判断是否过期 TimeSpan ts = DateTime.Now - DateTime.Parse(DT.Rows[0]["add_time"].ToString()); double chajunSecond = ts.TotalSeconds; if (chajunSecond >= 1200) { writeLog.WriteLogs("jsapiticket计算时间判断是否过期:已过期"); string url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi"; Jsapi api = JsonConvert.DeserializeObject<Jsapi>(httpGet(url)); ticket = api.ticket; if (ticket != "") { expire_time = ConvertDateTimeInt(DateTime.Now) + 1200; //存入数据库操作 strSql = " update dt_weixin_jsapiticket set jsapi_ticket='" + ticket + "',ticket_expires='" + expire_time + "',add_time='" + DateTime.Now + "' where ID=1"; sqlhelp.ExecuteNonQuery(strSql); } } writeLog.WriteLogs("jsapiticket计算时间判断是否过期:没过期"); } return ticket; } /// <summary> /// 将c# DateTime时间格式转换为Unix时间戳格式 /// </summary> /// <param name="time">时间</param> /// <returns>double</returns> public int ConvertDateTimeInt(System.DateTime time) { int intResult = 0; System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); intResult = Convert.ToInt32((time - startTime).TotalSeconds); return intResult; } //发起一个http请球,返回值 private string httpGet(string url) { try { WebClient MyWebClient = new WebClient(); MyWebClient.Credentials = CredentialCache.DefaultCredentials;//获取或设置用于向Internet资源的请求进行身份验证的网络凭据 Byte[] pageData = MyWebClient.DownloadData(url); //从指定网站下载数据 string pageHtml = System.Text.Encoding.Default.GetString(pageData); //如果获取网站页面采用的是GB2312,则使用这句 return pageHtml; } catch (WebException webEx) { Console.WriteLine(webEx.Message.ToString()); return null; } } #region 创建Json序列化 及反序列化类目 // /// <summary> /// 创建JSon类 保存文件 jsapi_ticket.json /// </summary> public class JSTicket { public string jsapi_ticket { get; set; } public double expire_time { get; set; } } /// <summary> /// 创建 JSon类 保存文件 access_token.json /// </summary> public class AccToken { public string access_token { get; set; } public double expires_in { get; set; } } /// <summary> /// 创建从微信返回结果的一个类 用于获取ticket /// </summary> public class Jsapi { public int errcode { get; set; } public string errmsg { get; set; } public string ticket { get; set; } public string expires_in { get; set; } } #endregion } 上述代码中负责获取和刷新的 WXCRMComm 类如下: /// <summary> /// 负责获取或刷新AccessToken /// </summary> public class WXCRMComm { public WXCRMComm() { } private string appid; private string appsecret; BLLweixin_access_token tokenBLL = new BLL.BLLweixin_access_token(); //账户AccessToken 此处根据自己项目单独特别处理 BLLweixin_account accountBLL = new BLL.BLLweixin_account(); //公众平台账户 此处根据自己项目单独特别处理 /// <summary> /// 及时获得access_token值 /// access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒, /// 重复获取将导致上次获取的access_token失效。 /// 每日限额获取access_token.我们将access_token保存到数据库里,间隔时间为20分钟,从微信公众平台获得一次。 /// </summary> public string GetAccessToken(out string error) { string access_token = string.Empty; error = string.Empty; try { Model.weixin_account accountModel = accountBLL.GetModel(); //公众平台账户信息 if (accountModel == null || string.IsNullOrEmpty(accountModel.appid) || string.IsNullOrEmpty(accountModel.appsecret)) { error = "AppId或者AppSecret未填写,请在补全信息!"; return string.Empty; } //没有找到该账户则获取AccessToKen写入存储1200秒 if (!tokenBLL.Exists()) { var result = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(accountModel.appid, accountModel.appsecret); access_token = result.access_token; tokenBLL.Add(access_token); return access_token; } //获取公众账户的实体 Model.weixin_access_token tokenModel = tokenBLL.GetModel(); //计算时间判断是否过期 TimeSpan ts = DateTime.Now - tokenModel.add_time; double chajunSecond = ts.TotalSeconds; if (string.IsNullOrEmpty(tokenModel.access_token) || chajunSecond >= tokenModel.expires_in) { writeLog.WriteLogs("GetAccessToken:重新修改"); //从微信平台重新获得AccessToken var result = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(accountModel.appid, accountModel.appsecret); access_token = result.access_token; //更新到数据库里的AccessToken tokenModel.access_token = access_token; tokenModel.add_time = DateTime.Now; bool ret=tokenBLL.Update(tokenModel); writeLog.WriteLogs("GetAccessToken:重新修改"+ ret + ""); } else { writeLog.WriteLogs("GetAccessToken:获取旧的"); access_token = tokenModel.access_token; } } catch (Exception ex) { error = "获取AccessToken出错:" + ex.Message; } return access_token; } /// <summary> ///【强制刷新】access_token值 /// access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒, /// 重复获取将导致上次获取的access_token失效。 /// 每日限额获取access_token.我们将access_token保存到数据库里,间隔时间为20分钟,从微信公众平台获得一次。 /// </summary> /// <returns></returns> public string FlushAccessToken(out string error) { string access_token = string.Empty; error = string.Empty; try { Model.weixin_account accountModel = accountBLL.GetModel(); //公众平台账户信息 if (string.IsNullOrEmpty(accountModel.appid) || string.IsNullOrEmpty(accountModel.appsecret)) { error = "AppId或者AppSecret未填写,请在补全信息!"; return ""; } var result = Senparc.Weixin.MP.CommonAPIs.CommonApi.GetToken(accountModel.appid, accountModel.appsecret); access_token = result.access_token; //没有找到该账户则获取AccessToKen写入存储1200秒 if (!tokenBLL.Exists()) { tokenBLL.Add(access_token); } else { //获取公众账户的实体 Model.weixin_access_token tokenModel = tokenBLL.GetModel(); //更新到数据库里的AccessToken tokenModel.access_token = access_token; tokenModel.add_time = DateTime.Now; tokenBLL.Update(tokenModel); } } catch (Exception ex) { error = "获得AccessToken出错:" + ex.Message; } return access_token; } /// <summary> /// 获得所有关注用户的openid字符串(别的方法调用此方法) /// </summary> private IList<string> BaseUserOpenId(out string error) { IList<string> ret = new List<string>(); string access_token = GetAccessToken(out error); if (error != "") { return null; } Senparc.Weixin.MP.AdvancedAPIs.User.OpenIdResultJson openidJson = Senparc.Weixin.MP.AdvancedAPIs.UserApi.Get(access_token, string.Empty); if (openidJson.count == openidJson.total) { ret = openidJson.data.openid; } else { GetNextUserOpenId(openidJson.next_openid, ret); } return ret; } /// <summary> /// (基础方法)获得所有关注用户的openid字符串(递归算法) /// </summary> private void GetNextUserOpenId(string nexOpenid, IList<string> openidList) { string err = string.Empty; string access_token = GetAccessToken(out err); Senparc.Weixin.MP.AdvancedAPIs.User.OpenIdResultJson openidJson = Senparc.Weixin.MP.AdvancedAPIs.UserApi.Get(access_token, nexOpenid); if (openidJson == null || openidJson.count <= 0) { return; } else { for (int i = 0; i < openidJson.data.openid.Count; i++) { openidList.Add(openidJson.data.openid[i]); } GetNextUserOpenId(openidJson.next_openid, openidList); } } #region 消息群发处理=================================== /// <summary> /// 上传永久素材 /// </summary> public string UploadForeverMedia(string imgFullPath, out string error) { string accessToken = GetAccessToken(out error); if (!string.IsNullOrEmpty(error)) { return string.Empty; } var result = Senparc.Weixin.MP.AdvancedAPIs.MediaApi.UploadForeverMedia(accessToken, imgFullPath); if (result.errcode == 0) { return result.media_id; } error = result.errmsg; return string.Empty; } /// <summary> /// 删除永久素材 /// </summary> public bool DeleteForeverMedia(string mediaId, out string error) { string accessToken = GetAccessToken(out error); if (!string.IsNullOrEmpty(error)) { return false; } var result = Senparc.Weixin.MP.AdvancedAPIs.MediaApi.DeleteForeverMedia(accessToken, mediaId); if (result.errcode != 0) { error = result.errmsg; return false; } error = string.Empty; return true; } /// <summary> /// 群发消息 /// </summary> public bool SendGroupMessageByGroupId(List<Senparc.Weixin.MP.AdvancedAPIs.GroupMessage.NewsModel> ls, out string error) { string accessToken = GetAccessToken(out error); //新增素材 var result1 = Senparc.Weixin.MP.AdvancedAPIs.MediaApi.UploadNews(accessToken, 10000, ls.ToArray()); if (result1.errcode != 0) { error = result1.errmsg; return false; } //群发消息 var result2 = Senparc.Weixin.MP.AdvancedAPIs.GroupMessageApi.SendGroupMessageByGroupId(accessToken, "0", result1.media_id, Senparc.Weixin.MP.GroupMessageType.mpnews, true); if (result2.errcode != 0) { error = result2.errmsg; return false; } error = string.Empty; return true; } #endregion } 另外,微信接口类 继承 了 DBBase ,里面只有连接数据库对象,这个可以根据自己项目特点和需要处理 这里只做演示: public class DBBase { protected static string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; public SqlHelper sqlhelp = new SqlHelper(connectionString); } 上述代码中SqlHelper 通用帮助类 也贴一下吧,仅供参考 /// <summary> /// SqlHelper操作类 /// </summary> public sealed partial class SqlHelper { /// <summary> /// 批量操作每批次记录数 /// </summary> public static int BatchSize = 2000; /// <summary> /// 超时时间 /// </summary> public static int CommandTimeOut = 600; /// <summary> ///初始化SqlHelper实例 /// </summary> /// <param name="connectionString">数据库连接字符串</param> public SqlHelper(string connectionString) { this.ConnectionString = connectionString; } /// <summary> /// 数据库连接字符串 /// </summary> public string ConnectionString { get; set; } #region 实例方法 #region ExecuteNonQuery /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public int ExecuteNonQuery(string commandText, params SqlParameter[] parms) { return ExecuteNonQuery(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public int ExecuteNonQuery(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteNonQuery(ConnectionString, commandType, commandText, parms); } #endregion ExecuteNonQuery #region ExecuteScalar /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <typeparam name="T">返回对象类型</typeparam> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public T ExecuteScalar<T>(string commandText, params SqlParameter[] parms) { return ExecuteScalar<T>(ConnectionString, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public object ExecuteScalar(string commandText, params SqlParameter[] parms) { return ExecuteScalar(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public object ExecuteScalar(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteScalar(ConnectionString, commandType, commandText, parms); } #endregion ExecuteScalar #region ExecuteDataReader /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private SqlDataReader ExecuteDataReader(string commandText, params SqlParameter[] parms) { return ExecuteDataReader(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private SqlDataReader ExecuteDataReader(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataReader(ConnectionString, commandType, commandText, parms); } #endregion #region ExecuteDataRow /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行</returns> public DataRow ExecuteDataRow(string commandText, params SqlParameter[] parms) { return ExecuteDataRow(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行</returns> public DataRow ExecuteDataRow(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataRow(ConnectionString, commandType, commandText, parms); } #endregion ExecuteDataRow #region ExecuteDataTable /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public DataTable ExecuteDataTable(string commandText, params SqlParameter[] parms) { return ExecuteDataTable(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public DataTable ExecuteDataTable(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(ConnectionString, commandType, commandText, parms).Tables[0]; } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="sql">SQL语句</param> /// <param name="order">排序SQL,如"ORDER BY ID DESC"</param> /// <param name="pageSize">每页记录数</param> /// <param name="pageIndex">页索引</param> /// <param name="parms">查询参数</param> /// <param name="query">查询SQL</param> /// <returns></returns> public DataTable ExecutePageDataTable(string sql, string order, int pageSize, int pageIndex, SqlParameter[] parms = null, string query = null, string cte = null) { return ExecutePageDataTable(sql, order, pageSize, pageIndex, parms, query, cte); } #endregion ExecuteDataTable #region ExecuteDataSet /// <summary> /// 执行查询语句,返回DataSet /// </summary> /// <param name="SQLString">查询语句</param> /// <returns>DataSet</returns> public DataSet ExecuteDataSet(string SQLString) { return ExecuteDataSet(SQLString, ConnectionString); } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public DataSet ExecuteDataSet(string commandText, params SqlParameter[] parms) { return ExecuteDataSet(ConnectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public DataSet ExecuteDataSet(CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(ConnectionString, commandType, commandText, parms); } public object GetSingle(string SQLString, params SqlParameter[] cmdParms) { return GetSingle(SQLString, ConnectionString, cmdParms); } #endregion ExecuteDataSet #region 批量操作 /// <summary> /// 大批量数据插入 /// </summary> /// <param name="table">数据表</param> public void BulkInsert(DataTable table) { BulkInsert(ConnectionString, table); } /// <summary> /// 使用MySqlDataAdapter批量更新数据 /// </summary> /// <param name="table">数据表</param> public void BatchUpdate(DataTable table) { BatchUpdate(ConnectionString, table); } /// <summary> /// 分批次批量删除数据 /// </summary> /// <param name="sql">SQL语句</param> /// <param name="batchSize">每批次删除记录行数</param> /// <param name="interval">批次执行间隔(秒)</param> public void BatchDelete(string sql, int batchSize = 1000, int interval = 1) { BatchDelete(ConnectionString, sql, batchSize, interval); } /// <summary> /// 分批次批量更新数据 /// </summary> /// <param name="sql">SQL语句</param> /// <param name="batchSize">每批次更新记录行数</param> /// <param name="interval">批次执行间隔(秒)</param> public void BatchUpdate(string sql, int batchSize = 1000, int interval = 1) { BatchUpdate(ConnectionString, sql, batchSize, interval); } #endregion 批量操作 #endregion 实例方法 #region 静态方法 public static object GetSingle(string SQLString, string connectionString, params SqlParameter[] cmdParms) { using (SqlConnection connection = new SqlConnection(connectionString)) { using (SqlCommand cmd = new SqlCommand()) { try { PrepareCommand(cmd, connection, null, SQLString, cmdParms); object obj = cmd.ExecuteScalar(); cmd.Parameters.Clear(); if ((Object.Equals(obj, null)) || (Object.Equals(obj, System.DBNull.Value))) { return null; } else { return obj; } } catch (System.Data.SqlClient.SqlException e) { throw e; } } } } private static void PrepareCommand(SqlCommand cmd, SqlConnection conn, SqlTransaction trans, string cmdText, SqlParameter[] cmdParms) { if (conn.State != ConnectionState.Open) conn.Open(); cmd.Connection = conn; cmd.CommandText = cmdText; if (trans != null) cmd.Transaction = trans; cmd.CommandType = CommandType.Text;//cmdType; if (cmdParms != null) { foreach (SqlParameter parameter in cmdParms) { if ((parameter.Direction == ParameterDirection.InputOutput || parameter.Direction == ParameterDirection.Input) && (parameter.Value == null)) { parameter.Value = DBNull.Value; } cmd.Parameters.Add(parameter); } } } private static void PrepareCommand(SqlCommand command, SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, SqlParameter[] parms) { if (connection.State != ConnectionState.Open) connection.Open(); command.Connection = connection; command.CommandTimeout = CommandTimeOut; // 设置命令文本(存储过程名或SQL语句) command.CommandText = commandText; // 分配事务 if (transaction != null) { command.Transaction = transaction; } // 设置命令类型. command.CommandType = commandType; if (parms != null && parms.Length > 0) { //预处理SqlParameter参数数组,将为NULL的参数赋值为DBNull.Value; foreach (SqlParameter parameter in parms) { if ((parameter.Direction == ParameterDirection.InputOutput || parameter.Direction == ParameterDirection.Input) && (parameter.Value == null)) { parameter.Value = DBNull.Value; } } command.Parameters.AddRange(parms); } } #region ExecuteNonQuery /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public static int ExecuteNonQuery(string connectionString, string commandText, params SqlParameter[] parms) { using (SqlConnection connection = new SqlConnection(connectionString)) { return ExecuteNonQuery(connection, CommandType.Text, commandText, parms); } } /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public static int ExecuteNonQuery(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { using (SqlConnection connection = new SqlConnection(connectionString)) { return ExecuteNonQuery(connection, commandType, commandText, parms); } } /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public static int ExecuteNonQuery(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteNonQuery(connection, null, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> public static int ExecuteNonQuery(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteNonQuery(transaction.Connection, transaction, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回影响的行数 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回影响的行数</returns> private static int ExecuteNonQuery(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { SqlCommand command = new SqlCommand(); PrepareCommand(command, connection, transaction, commandType, commandText, parms); int retval = command.ExecuteNonQuery(); command.Parameters.Clear(); return retval; } #endregion ExecuteNonQuery #region ExecuteScalar /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <typeparam name="T">返回对象类型</typeparam> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public static T ExecuteScalar<T>(string connectionString, string commandText, params SqlParameter[] parms) { object result = ExecuteScalar(connectionString, commandText, parms); if (result != null) { return (T)Convert.ChangeType(result, typeof(T)); ; } return default(T); } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public static object ExecuteScalar(string connectionString, string commandText, params SqlParameter[] parms) { using (SqlConnection connection = new SqlConnection(connectionString)) { return ExecuteScalar(connection, CommandType.Text, commandText, parms); } } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public static object ExecuteScalar(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { using (SqlConnection connection = new SqlConnection(connectionString)) { return ExecuteScalar(connection, commandType, commandText, parms); } } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public static object ExecuteScalar(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteScalar(connection, null, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> public static object ExecuteScalar(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteScalar(transaction.Connection, transaction, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集中的第一行第一列 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一行第一列</returns> private static object ExecuteScalar(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { SqlCommand command = new SqlCommand(); PrepareCommand(command, connection, transaction, commandType, commandText, parms); object retval = command.ExecuteScalar(); command.Parameters.Clear(); return retval; } #endregion ExecuteScalar #region ExecuteDataReader /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private static SqlDataReader ExecuteDataReader(string connectionString, string commandText, params SqlParameter[] parms) { SqlConnection connection = new SqlConnection(connectionString); return ExecuteDataReader(connection, null, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private static SqlDataReader ExecuteDataReader(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { SqlConnection connection = new SqlConnection(connectionString); return ExecuteDataReader(connection, null, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private static SqlDataReader ExecuteDataReader(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataReader(connection, null, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private static SqlDataReader ExecuteDataReader(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataReader(transaction.Connection, transaction, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回只读数据集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回只读数据集</returns> private static SqlDataReader ExecuteDataReader(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { SqlCommand command = new SqlCommand(); PrepareCommand(command, connection, transaction, commandType, commandText, parms); return command.ExecuteReader(CommandBehavior.CloseConnection); } #endregion #region ExecuteDataRow /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>,返回结果集中的第一行</returns> public static DataRow ExecuteDataRow(string connectionString, string commandText, params SqlParameter[] parms) { DataTable dt = ExecuteDataTable(connectionString, CommandType.Text, commandText, parms); return dt.Rows.Count > 0 ? dt.Rows[0] : null; } /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>,返回结果集中的第一行</returns> public static DataRow ExecuteDataRow(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { DataTable dt = ExecuteDataTable(connectionString, commandType, commandText, parms); return dt.Rows.Count > 0 ? dt.Rows[0] : null; } /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>,返回结果集中的第一行</returns> public static DataRow ExecuteDataRow(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { DataTable dt = ExecuteDataTable(connection, commandType, commandText, parms); return dt.Rows.Count > 0 ? dt.Rows[0] : null; } /// <summary> /// 执行SQL语句,返回结果集中的第一行 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>,返回结果集中的第一行</returns> public static DataRow ExecuteDataRow(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { DataTable dt = ExecuteDataTable(transaction, commandType, commandText, parms); return dt.Rows.Count > 0 ? dt.Rows[0] : null; } #endregion ExecuteDataRow #region ExecuteDataTable /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public static DataTable ExecuteDataTable(string connectionString, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(connectionString, CommandType.Text, commandText, parms).Tables[0]; } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public static DataTable ExecuteDataTable(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(connectionString, commandType, commandText, parms).Tables[0]; } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public static DataTable ExecuteDataTable(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(connection, commandType, commandText, parms).Tables[0]; } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集中的第一个数据表</returns> public static DataTable ExecuteDataTable(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(transaction, commandType, commandText, parms).Tables[0]; } /// <summary> /// 获取空表结构 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="tableName">数据表名称</param> /// <returns>返回结果集中的第一个数据表</returns> public static DataTable ExecuteEmptyDataTable(string connectionString, string tableName) { return ExecuteDataSet(connectionString, CommandType.Text, string.Format("select * from {0} where 1=-1", tableName)).Tables[0]; } /// <summary> /// 执行SQL语句,返回结果集中的第一个数据表 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="sql">SQL语句</param> /// <param name="order">排序SQL,如"ORDER BY ID DESC"</param> /// <param name="pageSize">每页记录数</param> /// <param name="pageIndex">页索引</param> /// <param name="parms">查询参数</param> /// <param name="query">查询SQL</param> /// <param name="cte">CTE表达式</param> /// <returns></returns> public static DataTable ExecutePageDataTable(string connectionString, string sql, string order, int pageSize, int pageIndex, SqlParameter[] parms = null, string query = null, string cte = null) { string psql = string.Format(@" {3} SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY {1}) RowNumber,* FROM ( {0} ) t WHERE 1 = 1 {2} ) t WHERE RowNumber BETWEEN @RowNumber_Begin AND @RowNumber_End", sql, order, query, cte); List<SqlParameter> paramlist = new List<SqlParameter>() { new SqlParameter("@RowNumber_Begin", SqlDbType.Int){ Value = (pageIndex - 1) * pageSize + 1 }, new SqlParameter("@RowNumber_End", SqlDbType.Int){ Value = pageIndex * pageSize } }; if (parms != null) paramlist.AddRange(parms); return ExecuteDataTable(connectionString, psql, paramlist.ToArray()); } #endregion ExecuteDataTable #region ExecuteDataSet /// <summary> /// 执行查询语句,返回DataSet /// </summary> /// <param name="SQLString">查询语句</param> /// <returns>DataSet</returns> public static DataSet ExecuteDataSet(string SQLString, string connectionString) { using (SqlConnection connection = new SqlConnection(connectionString)) { DataSet ds = new DataSet(); try { connection.Open(); SqlDataAdapter command = new SqlDataAdapter(SQLString, connection); command.Fill(ds, "ds"); } catch (System.Data.SqlClient.SqlException ex) { throw new Exception(ex.Message); } return ds; } } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandText">SQL语句</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public static DataSet ExecuteDataSet(string connectionString, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(connectionString, CommandType.Text, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public static DataSet ExecuteDataSet(string connectionString, CommandType commandType, string commandText, params SqlParameter[] parms) { using (SqlConnection connection = new SqlConnection(connectionString)) { return ExecuteDataSet(connection, commandType, commandText, parms); } } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public static DataSet ExecuteDataSet(SqlConnection connection, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(connection, null, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> public static DataSet ExecuteDataSet(SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { return ExecuteDataSet(transaction.Connection, transaction, commandType, commandText, parms); } /// <summary> /// 执行SQL语句,返回结果集 /// </summary> /// <param name="connection">数据库连接</param> /// <param name="transaction">事务</param> /// <param name="commandType">命令类型(存储过程,命令文本, 其它.)</param> /// <param name="commandText">SQL语句或存储过程名称</param> /// <param name="parms">查询参数</param> /// <returns>返回结果集</returns> private static DataSet ExecuteDataSet(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, params SqlParameter[] parms) { SqlCommand command = new SqlCommand(); PrepareCommand(command, connection, transaction, commandType, commandText, parms); SqlDataAdapter adapter = new SqlDataAdapter(command); DataSet ds = new DataSet(); adapter.Fill(ds); if (commandText.IndexOf("@") > 0) { commandText = commandText.ToLower(); int index = commandText.IndexOf("where "); if (index < 0) { index = commandText.IndexOf("\nwhere"); } if (index > 0) { ds.ExtendedProperties.Add("SQL", commandText.Substring(0, index - 1)); //将获取的语句保存在表的一个附属数组里,方便更新时生成CommandBuilder } else { ds.ExtendedProperties.Add("SQL", commandText); //将获取的语句保存在表的一个附属数组里,方便更新时生成CommandBuilder } } else { ds.ExtendedProperties.Add("SQL", commandText); //将获取的语句保存在表的一个附属数组里,方便更新时生成CommandBuilder } foreach (DataTable dt in ds.Tables) { dt.ExtendedProperties.Add("SQL", ds.ExtendedProperties["SQL"]); } command.Parameters.Clear(); return ds; } #endregion ExecuteDataSet #region 批量操作 /// <summary> /// 大批量数据插入 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="table">数据表</param> public static void BulkInsert(string connectionString, DataTable table) { if (string.IsNullOrEmpty(table.TableName)) throw new Exception("DataTable.TableName属性不能为空"); using (SqlBulkCopy bulk = new SqlBulkCopy(connectionString)) { bulk.BatchSize = BatchSize; bulk.BulkCopyTimeout = CommandTimeOut; bulk.DestinationTableName = table.TableName; foreach (DataColumn col in table.Columns) { bulk.ColumnMappings.Add(col.ColumnName, col.ColumnName); } bulk.WriteToServer(table); bulk.Close(); } } /// <summary> /// 使用MySqlDataAdapter批量更新数据 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="table">数据表</param> public static void BatchUpdate(string connectionString, DataTable table) { SqlConnection connection = new SqlConnection(connectionString); SqlCommand command = connection.CreateCommand(); command.CommandTimeout = CommandTimeOut; command.CommandType = CommandType.Text; SqlDataAdapter adapter = new SqlDataAdapter(command); SqlCommandBuilder commandBulider = new SqlCommandBuilder(adapter); commandBulider.ConflictOption = ConflictOption.OverwriteChanges; SqlTransaction transaction = null; try { connection.Open(); transaction = connection.BeginTransaction(); //设置批量更新的每次处理条数 adapter.UpdateBatchSize = BatchSize; //设置事物 adapter.SelectCommand.Transaction = transaction; if (table.ExtendedProperties["SQL"] != null) { adapter.SelectCommand.CommandText = table.ExtendedProperties["SQL"].ToString(); } adapter.Update(table); transaction.Commit();/////提交事务 } catch (SqlException ex) { if (transaction != null) transaction.Rollback(); throw ex; } finally { connection.Close(); connection.Dispose(); } } /// <summary> /// 分批次批量删除数据 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="sql">SQL语句</param> /// <param name="batchSize">每批次更新记录行数</param> /// <param name="interval">批次执行间隔(秒)</param> public static void BatchDelete(string connectionString, string sql, int batchSize = 1000, int interval = 1) { sql = sql.ToLower(); if (batchSize < 1000) batchSize = 1000; if (interval < 1) interval = 1; while (ExecuteScalar(connectionString, sql.Replace("delete", "select top 1 1")) != null) { ExecuteNonQuery(connectionString, CommandType.Text, sql.Replace("delete", string.Format("delete top ({0})", batchSize))); System.Threading.Thread.Sleep(interval * 1000); } } /// <summary> /// 分批次批量更新数据 /// </summary> /// <param name="connectionString">数据库连接字符串</param> /// <param name="sql">SQL语句</param> /// <param name="batchSize">每批次更新记录行数</param> /// <param name="interval">批次执行间隔(秒)</param> public static void BatchUpdate(string connectionString, string sql, int batchSize = 1000, int interval = 1) { if (batchSize < 1000) batchSize = 1000; if (interval < 1) interval = 1; string existsSql = Regex.Replace(sql, @"[\w\s.=,']*from", "select top 1 1 from", RegexOptions.IgnoreCase); existsSql = Regex.Replace(existsSql, @"set[\w\s.=,']* where", "where", RegexOptions.IgnoreCase); existsSql = Regex.Replace(existsSql, @"update", "select top 1 1 from", RegexOptions.IgnoreCase); while (ExecuteScalar<int>(connectionString, existsSql) != 0) { ExecuteNonQuery(connectionString, CommandType.Text, Regex.Replace(sql, "update", string.Format("update top ({0})", batchSize), RegexOptions.IgnoreCase)); System.Threading.Thread.Sleep(interval * 1000); } } #endregion 批量操作 #endregion 静态方法 } View Code 最后 在原项目数据库需要新建三张数据表:分别记录公众号信息、access_token信息以及分享信息(dt_weixin_access_token、dt_weixin_account、dt_weixin_jsapiticket) 数据表结构如下: USE [数据库名称] GO /****** Object: Table [dbo].[dt_weixin_access_token] Script Date: 2018-04-07 10:49:17 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[dt_weixin_access_token]( [id] [int] IDENTITY(1,1) NOT NULL, [account_id] [int] NULL, [access_token] [nvarchar](1000) NULL, [expires_in] [int] NULL CONSTRAINT [DF__dt_weixin__expir__233F2673] DEFAULT ((0)), [count] [int] NULL CONSTRAINT [DF__dt_weixin__count__24334AAC] DEFAULT ((0)), [add_time] [datetime] NULL CONSTRAINT [DF__dt_weixin__add_t__25276EE5] DEFAULT (getdate()), CONSTRAINT [PK_DT_WEIXIN_ACCESS_TOKEN] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: Table [dbo].[dt_weixin_account] Script Date: 2018-04-07 10:49:17 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[dt_weixin_account]( [id] [int] IDENTITY(1,1) NOT NULL, [name] [nvarchar](100) NULL, [originalid] [nvarchar](50) NULL, [wxcode] [nvarchar](50) NULL, [token] [nvarchar](300) NULL, [appid] [nvarchar](100) NULL, [appsecret] [nvarchar](150) NULL, [is_push] [tinyint] NULL CONSTRAINT [DF__dt_weixin__is_pu__19B5BC39] DEFAULT ((0)), [sort_id] [int] NULL CONSTRAINT [DF__dt_weixin__sort___1AA9E072] DEFAULT ((99)), [add_time] [datetime] NULL CONSTRAINT [DF__dt_weixin__add_t__1B9E04AB] DEFAULT (getdate()), CONSTRAINT [PK_DT_WEIXIN_ACCOUNT] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: Table [dbo].[dt_weixin_jsapiticket] Script Date: 2018-04-07 10:49:17 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[dt_weixin_jsapiticket]( [id] [int] NOT NULL, [jsapi_ticket] [varchar](500) NULL, [ticket_expires] [varchar](500) NULL, [add_time] [datetime] NULL CONSTRAINT [DF_dt_weixin_jsapiticket_add_time] DEFAULT (getdate()), CONSTRAINT [PK_dt_weixin_jsapiticket] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO SET ANSI_PADDING OFF GO SET IDENTITY_INSERT [dbo].[dt_weixin_access_token] ON GO INSERT [dbo].[dt_weixin_access_token] ([id], [account_id], [access_token], [expires_in], [count], [add_time]) VALUES (1, 1, N'8_NX5IydeBm6ZEnUAzyXNKDIVJEJy-hzTbTaXd7w-q51P96XDcmFP2OYnJRyJ7rBAt9peJ-C5ad8RPkIDe8Vsm_LCdmOvyrVfWuotnTpCngXZFUVdbgCqOH03LBmWGMg69HRCp5ActsK2o269hUQJaAHAHDW', 1200, 1, CAST(N'2018-04-06 22:23:14.870' AS DateTime)) GO SET IDENTITY_INSERT [dbo].[dt_weixin_access_token] OFF GO SET IDENTITY_INSERT [dbo].[dt_weixin_account] ON GO INSERT [dbo].[dt_weixin_account] ([id], [name], [originalid], [wxcode], [token], [appid], [appsecret], [is_push], [sort_id], [add_time]) VALUES (1, N'WBC', N'gh_f43eded4a607', N'szsanfang', NULL, N'AppId', N'appSecret', 0, 99, CAST(N'2018-04-03 14:51:19.310' AS DateTime)) GO SET IDENTITY_INSERT [dbo].[dt_weixin_account] OFF GO INSERT [dbo].[dt_weixin_jsapiticket] ([id], [jsapi_ticket], [ticket_expires], [add_time]) VALUES (1, N'kgt8ON7yVITDhtdwci0qea8xYacVzuqZEqxNGwp-1WE0DTSI2wkMf1-e__jGosg516Xz2u9M-xsbXOd4eZBcMw', N'1523025795', CAST(N'2018-04-06 22:23:15.000' AS DateTime)) GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'自增ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'公众账户ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'account_id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'access_token值' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'access_token' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'有效期(秒)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'expires_in' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'总数' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'count' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'创建时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token', @level2type=N'COLUMN',@level2name=N'add_time' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'公众平台access_token存储' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_access_token' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'自增ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'公众号名称' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'name' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'公众号原始ID' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'originalid' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'微信号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'wxcode' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'令牌必须与微信平台对应' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'token' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'appid(仅用于高级接口)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'appid' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'appsecret(仅用于高级接口)' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'appsecret' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否支持网站内容推送' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'is_push' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'排序号' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'sort_id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'添加时间' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account', @level2type=N'COLUMN',@level2name=N'add_time' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'微信公众平台账户' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'dt_weixin_account' GO View Code 本地写完代码 可以放到绑定过域名的 服务器 进行日志打印调试,看看各项数据是否正常拿到。 最后实现转发 效果如下: 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
连载篇提前看 物流一站式查询之TrackingMore篇 物流一站式查询之顺丰接口篇 物流一站式查询之快递100 前言 前两篇我们已经讲了TrackingMore和顺丰接口的场景应用和对接示例,本篇,将会对项目中如何使用快递100物流平台进行物流信息跟踪的对接进行一个全面详细的讲解。同样,我们会分为申请接入、物流信息订阅、物流消息推送、物流消息查询等几个步骤分别讲解。各位看官,请拿好你们的板凳和瓜子。我们要开始了, 接口申请 进入快递100官网https://www.kuaidi100.com/ 在快递接口(API)菜单中,我们可以看到接口申请这个菜单,点击进去会有免费版和企业版两种功能和资费介绍。 这里我们选择企业版,点击企业版,会要求填一些基本信息,我们按要求填完然后点击提交申请即可。 申请之后,有客服会通过联系方式联系所填号码,然后进行需求确认。这时候我们可以注册一个快递100的账号 注册完之后就可以进行登录了,登录之后在主页面,就可以看到再快递100平台中的账户余额情况,以及下面表格给出的可用的产品、服务等开通的状态(具体开通流程和客服取得联系之后他会协助你完成的),如下图所示 开通各个接口是需要快递100 那边进行审核的,审核通过之后会发邮件出来,附件里面会带有一些接口文档,截图如下: 其中 较为重要的是审核结果中会有一个分配好的Key密匙,请求接口的时候需要用到。所以需要妥善保管。 首先来看一下快递100 给出的订阅和推送的流向示意图 物流订阅 拿到接口文档相关秘籍和查阅请求调用流程图之后,我们便可以进行开发了,下面是物流订阅的相关示例步骤: 2.1订阅请求 发起方:本服务用户,即贵公司 地址:http://www.kuaidi100.com/poll 通信协议:HTTP 请求类型:POST 字符集:utf-8 请求内容: schema= json/xml (或者xml,选择json则推送也是json,选择xml则推送也是xml,默认是json) param=body Body格式(json): { "company":"yuantong", //订阅的快递公司的编码,一律用小写字母,见章五《快递公司编码》 "number":"12345678", //订阅的快递单号,单号的最大长度是32个字符 "from":"广东省深圳市南山区", //出发地城市 "to":"北京市朝阳区", //目的地城市,到达目的地后会加大监控频率 "key":"*********", //授权码,签订合同后发放 "parameters":{ "callbackurl":" http://www.您的域名.com/kuaidi?callbackid=...", //回调地址 "salt":"any string", //签名用随机字符串(可选) "resultv2":"1" //添加此字段表示开通行政区域解析功能(仅对开通签收状态服务用户有效),见章3.1《推送请求》 } } Body格式(xml): <?xml version='1.0' encoding='UTF-8'?> <orderRequest> <company>yuantong</company> //订阅的快递公司的编码,一律用小写字母,见章五《快递公司编码》 <number>123123123123</number> //订阅的快递单号 <from>北京市朝阳区东直门外大街</from> //出发地城市 <to>广东省深圳市南山区科技园</to> //目的地城市,到达目的地后会加大监控频率 <key>**********</key> //授权码,签订合同后发放 <parameters> <callbackurl>http://www.你的域名.com/kuaidi/push?XXXXX=ZZZZ</callbackurl> //回调地址 <salt>any string</salt> //签名用随机字符串(可选) <resultv2>1</ resultv2> //添加此字段表示开通行政区域解析功能(仅对开通签收状态服务用户有效),见章3.1《推送请求》 </parameters> </orderRequest> ①根据订阅请求文档编写请求方法,首先我们编写两个类,一个是快递订阅信息类,另一个是快递100返回信息类 /// <summary> /// 快递订阅信息 /// </summary> public class KuaiDiInput { /// <summary> /// 快递单号 /// </summary> public string ExpressNumber; /// <summary> /// 快递公司编号 /// </summary> public string CompanyNumber; /// <summary> /// 收货地址 /// </summary> public string Address; /// <summary> /// 回调接口Url /// </summary> public string CallBackUrl; } View Code /// <summary> /// 快递100查询公司返回信息 /// </summary> public class KuaiDiOut { /// <summary> /// 快递公司编码 /// </summary> public string comCode; /// <summary> /// 内部字段 /// </summary> public string Id; /// <summary> /// 内部字段 /// </summary> public string noCount; /// <summary> /// 内部字段 /// </summary> public string noPre; /// <summary> /// 内部字段 /// </summary> public string startTime; } View Code ②编写快递100订阅接口请求类 KuaiDi100,此类中包含的请求方法如下 /// <summary> /// 订阅快递单号 /// </summary> /// <param name="input"></param> /// <returns></returns> public static OperationResult Subscibe(KuaiDiInput input) { WebClient client = new WebClient(); NameValueCollection postVars = new NameValueCollection(); String param = ""; param += "{"; param += "\"company\":\"" + input.CompanyNumber + "\","; param += "\"number\":\"" + input.ExpressNumber + "\","; param += "\"from\":\"\","; param += "\"to\":\"" + TrimAddress(input.Address) + "\","; param += "\"key\":\"" + ConfigHelper.GetKey("SendKey") + "\","; param += "\"parameters\":{\"callbackurl\":\"" + input.CallBackUrl + "\"}"; param += "}"; postVars.Add("schema", "json"); postVars.Add("param", param); byte[] byRemoteInfo = client.UploadValues("http://www.kuaidi100.com/poll", "POST", postVars); string output = Encoding.UTF8.GetString(byRemoteInfo).ToLower(); if (GetValueFromJson(output, "result") != "true") { string message = GetValueFromJson(output, "message"); if (message.IndexOf("重复订阅") < 0) { return new OperationResult(message); } } return new OperationResult(); } View Code 方法的返回值是自定义的一个 OperationResult类,当前框架业务操作结果 此类返回操作成功或者操作失败,类部分内容如下: /// <summary> /// 框架业务操作结果 /// </summary> public class OperationResult { /// <summary> /// 默认操作成功 /// </summary> public OperationResult() { IsSuccess = true; } /// <summary> /// 以操作失败信息实例操作结果 /// </summary> /// <param name="failMessage">操作失败信息</param> public OperationResult(string failMessage) { IsSuccess = false; FailMessage = failMessage; } } View Code 另外请求方法中的GetValueFromJson 方法是表示从Json字符串中取出某个值,再前两篇文章中我们也提到过这个方法,这里再贴一下: /// <summary> /// 从json字符串中获取字段值 /// </summary> /// <param name="json">json字符串</param> /// <param name="field">要解析出值的字段</param> /// <returns></returns> private static string GetValueFromJson(string json, string field) { int start = json.IndexOf(field + "\":"); start += field.Length + 2; int end = json.IndexOf(",", start); if (end < 0) { end = json.IndexOf("}", start); } return json.Substring(start, end - start).Trim('"'); } View Code 另外,请求方法中的地址我们使用了一个方法去除地址中的特殊字符,方法如下“: /// <summary> /// 去除地址中的特殊字符 /// </summary> /// <param name="address">地址</param> /// <returns></returns> private static string TrimAddress(string address) { if (String.IsNullOrEmpty(address)) { return address; } return address.Replace("'", "").Replace("\\", "").Replace("/", "").Replace("\"", "").Replace("\n", "").Replace("\r", "").Replace("\t", ""); } 这样,我们请求方法就完成了,现在只剩下调用,一般项目中可以写一个服务,定时查询数据库,根据一些特定条件筛选,然后进行接口调用。调用示例如下: OperationResult result = KuaiDi100.Subscibe(new KuaiDiInput() { ExpressNumber = item.ExpressNumber, Address = item.Address, CompanyNumber = item.CompanyNumber, CallBackUrl = "此处是回调地址" }); if (result.IsSuccess) { //订阅成功,记录一些状态 }else { //订阅失败,记录一些状态 } 订阅返回的接口示例我们看一下 2.2订阅返回 由快递100直接通过订阅请求的response返回。 返回格式(json): { "result":"true", "returnCode":"200", "message":"提交成功" } 返回格式(xml): <?xml version='1.0' encoding='UTF-8'?> <orderResponse> <result>true</result> <returnCode>200</returnCode> <message>订阅成功</message> </orderResponse> result: "true"表示成功,false表示失败 returnCode: 200: 提交成功 701: 拒绝订阅的快递公司 700: 订阅方的订阅数据存在错误(如不支持的快递公司、单号为空、单号超长等) 600: 您不是合法的订阅者(即授权Key出错) 500: 服务器错误(即快递100的服务器出理间隙或临时性异常,有时如果因为不按规范提交请求,比如快递公司参数写错等,也会报此错误) 501:重复订阅 订阅成功后,我们再快递100后台是可以看到订阅记录的,如下图 物流推送 那推送就比较好处理了,推送即是 当物流信息有发生变化时,快递100 就会向请求订阅时所填的回调地址,发送此条变化的所有物流信息。推送请求示例如下(body太长了,这里就不贴出来了) 3.1推送请求 发起方: 快递100 请求地址: 在订阅请求中提供(即回调servlet的互联网地址) 通信协议: HTTP 请求类型: POST 字符集: utf-8 请求内容: param=body或者param=body&sign=signvalue 查看了推送请求之后,我们根据快递100的推送请求,我们先写出相关的返回结果模型ModPushRequest /// <summary> /// 快递100传回快递结果模型 /// </summary> public class ModPushRequest { /// <summary> /// 监控状态相关消息,如:3天查询无记录,60天无变化 /// </summary> public string message { set; get; } /// <summary> /// 包括got、sending、check三个状态,由于意义不大,已弃用,请忽略 /// </summary> public string billstatus { set; get; } /// <summary> /// 监控状态:polling:监控中,shutdown:结束,abort:中止,updateall:重新推送。 /// 其中当快递单为已签收时status=shutdown,当message为“3天查询无记录”或“60天无变化时”status= abort ,对于stuatus=abort的状度,需要增加额外的处理逻辑,详见本节最后的说明 /// </summary> public string status { set; get; } /// <summary> /// 最新查询结果,全量,倒序(即时间最新的在最前) /// </summary> public ModLastResult lastResult { set; get; } } View Code 其中 ModLastResult是 快递100传回快递状态模型如下 /// <summary> /// 快递100传回快递状态模型 /// </summary> public class ModLastResult { /// <summary> /// 消息体,请忽略 /// </summary> public string message { set; get; } /// <summary> /// 快递单当前签收状态,包括0在途中、1已揽收、2疑难、3已签收、4退签、5同城派送中、6退回、7转单等7个状态 /// </summary> public string state { set; get; } /// <summary> /// 快递单明细状态标记,暂未实现,请忽略 /// </summary> public string condition { set; get; } /// <summary> /// 否签收标记,明细状态请参考state字段 /// </summary> public string ischeck { set; get; } /// <summary> /// 快递公司编码,一律用小写字母 /// </summary> public string com { set; get; } /// <summary> /// 快递单号 /// </summary> public string nu { set; get; } /// <summary> /// 通讯状态,请忽略 /// </summary> public string status { set; get; } /// <summary> /// 快递物流信息,时间到序 /// </summary> public List<ModData> data { set; get; } } View Code 其中 ModData 是物流信息 如下 /// <summary> /// 快递100传回快递记录模型 /// </summary> public class ModData { /// <summary> /// 快递物流内容 /// </summary> public string context { set; get; } /// <summary> /// 快递原始时间 /// </summary> public string time { set; get; } /// <summary> /// 快递格式化时间 /// </summary> public string ftime { set; get; } } 有了推送模型,那此时我们要做的就是接收快递100的请求数据即可,代码如下: /// <summary> /// 更新订单快递信息 /// </summary> /// <param name="form"></param> /// <returns></returns> public ActionResult UpdateMessage(FormCollection form) { try { string json = form.Get("param"); JavaScriptSerializer serializer = new JavaScriptSerializer(); //将Json字符串转换为ModPushRequest类型对象 ModPushRequest pustRequest = serializer.Deserialize<ModPushRequest>(json); //TODO } catch{} } 以上我们就将收到的数据成功转换成了我们的模型数据,剩下的就是具体业务需求了,这个可以根据实际项目情况,做不同的处理。例如:TODO可以 先拿到返回的快递单号 string number = pustRequest.lastResult.nu; 然后可以去查询数据库中找到此单号的订单信息。再循环物流信息 string senderMessage = ""; string lastMessage = ""; foreach (ModData item in pustRequest.lastResult.data) { //所有物流信息 senderMessage += item.ftime + " " + item.context + "<br/>"; } if (pustRequest.lastResult.data.Count > 0) { //最近一条物流信息 lastMessage = pustRequest.lastResult.data[0].ftime + " " + pustRequest.lastResult.data[0].context; } 然后可以再根据不同的物流返回状态做不同的操作 // 快递单当前签收状态,包括0在途中、1已揽收、2疑难、3已签收、4退签、5同城派送中、6退回、7转单等7个状态 if (pustRequest.lastResult.state == "3") { //TODO } else if (pustRequest.lastResult.state == "4" || pustRequest.lastResult.state == "6") { //TODO } 温馨提示,设计订单表的时候,可以多加三个字段,一个字段是订阅时间,一个是物流平台推送回来的时候推送时间,再一个是物流平台推送过来时的推送状态。可以记录用于后面物流数据分析。 物流消息查询 5、整体使用流程: 第一步,后台创建链接,调用:http://www.kuaidi100.com/applyurl?key=[]&com=[]&nu=[] ,调用后系统会返回一个url地址,如:http://www.kuaidi100.com/kuaidiresult?id=23 。 第二步:在要显示结果的页面添加一个iframe标签,将上述结果url地址传入该iframe标签的的src值,即可在该页面查看到结果(如果要实现系统自动地将结果url传入iframe标签的src,请参考下面第五章),iframe代码示范: <iframe name="kuaidi100" src="结果url地址" width="600" height="380" marginwidth="0" marginheight="0" hspace="0" vspace="0" frameborder="0" scrolling="no"></iframe> 使用场景,项目中的订单,我们一定不是每次都直接去调接口查询,那样太慢,效率也不高,只有部分,数据表中物流信息没有且平台订单不为空的时候,查询物流信息时,我们需要去实时调用查询接口,将数据返回出来。根据整体使用流程介绍,我们很容知道,是通过返回html绑定再iframe上实现的。实现如下: 前台页面(传入快递公司对应的公司编码和物流单号) ajaxGetContent("@Url.Action("ViewExpress")" + "?companyNumber=" + company + "&expressNumber=" + number, function (data) { var d = dialog({ title: '物流最新跟踪', content: data, okValue: '确定', ok: true, }); d.show(obj); }); Action public ActionResult ViewExpress(string expressNumber, string companyNumber) { #region 快递100 获取方式 string url = "http://www.kuaidi100.com/applyurl?key=我是key&com=" + companyNumber + "&nu=" + expressNumber; //初始化获取HTML动作 GetHtmlFromUrl getHtml = new GetHtmlFromUrl(url); //获取请求的url中html字符串 getHtml.StartWork(); //数据绑定 ViewData.Model = getHtml.ResultHtml; #endregion } 其中初始化html方法如下: /// <summary> /// 初始化一个获取html动作 /// </summary> /// <param name="requestUrl">要请求的url地址</param> public GetHtmlFromUrl(string requestUrl) { ArgumentChecker.ThrowExceptionWhenStringIsNullOrEmpty(requestUrl, "requestUrl"); if (requestUrl.IndexOf("//") < 0) { requestUrl = "http://" + requestUrl; } RequestUrl = requestUrl; } 获取请求url地址的html字符串方法如下: /// <summary> /// 开始获取请求url地址的html字符串 /// </summary> public void StartWork() { WebRequest request = WebRequest.Create(RequestUrl); using (WebResponse response = request.GetResponse()) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, System.Text.Encoding.GetEncoding("UTF-8"))) { ResultHtml = reader.ReadToEnd(); } } } } 返回的ViewExpress视图如下 @model string <iframe name="kuaidi100" src="@Model" width="600" height="380" marginwidth="0" marginheight="0" hspace="0" vspace="0" frameborder="0" scrolling="no"></iframe> 这样我们就按照文档中所说的将返回html呈现出来了 返回结果说明: 提交请求后,快递100会给您返回一个可以看到结果的url地址,如:http://www.kuaidi100.com/kuaidiresult?id=23 ,您直接访问或用iframe页调用该url(调用方法见后面第四章),即可以看到结果。效果: 特别提醒: 因为EMS、顺丰和申通偶尔会不稳定, 不稳定时会先显示验证码 (如下图所示),所以请勿直接将这个页面直接解析成JSON等形式,否则会出错 至此,我们在通过快递100平台 完成了快递订阅、接收了快递消息的推送以及我们自主通过快递100接口查询最新的物流信息。 到这里,三篇关于快递接口的文章已经全部结束,文中给出的只是根据官方文档要求,编写的部分代码,可能不尽完善,能够优化的地方还有很多,大家能借鉴的地方借鉴一下就好~ End! 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
连载篇提前看 物流一站式查询之TrackingMore篇 物流一站式查询之顺丰接口篇 物流一站式查询之快递100篇 前情提要 本篇内容承接上篇《物流包裹一站式查询(TrackingMore) 文末所说,顺丰物流关闭了对第三方的物流接口,导致众多第三方物流平台查询不到顺丰快递的物流信息。但是问题终归是要解决滴,别家不行,咱就直接用顺丰自家的。 原本网上找顺丰物流信息查询发现顺丰开放平台 看了下介绍,因为也是顺丰的平台,也没多想,看到流程还是比较清晰的。 本来想找在线客服咨询下,结果发现在线客服有的只是一个群号,而且还不能加人了,于是乎就按照接入流程开始操作,本地都开发的差不多了,后来意外联系到一个顺丰的IT人员,通过他得知,顺丰物流信息接口已经转到另一个部门和平台操作了,这个开放平台已经几乎没有人维护了。于是再他的协助下,我得到了最新的对接文档。按找新文档,之前的开发的全部得重写,请求接口不一样,数据传输和接收方式不一样,由开放平台的Json格式到现在用XML传输。这里贴一下接入规范文档的目录 顺便提一下 顺丰路由查询接口 就是 查询物流信息的接口,不过再顺丰平台使用此接口有个前提条件,就是必须是顺丰的月结用户。登陆 顺丰平台 可以查看到基本信息 注:①不是顺丰月结卡 用户 或者企业,不能接入路由查询 ② 不是通过顺丰接口下单的运单号,不能接入路由推送接口,换而言之,如果是通过顺丰大客户发货系统或者其他方式进行的打单获取到的快递单号,无法对此单进行订阅推送操作。 开发篇 看完基本流程和接入规范之后,就可以按照文档规范进行编码。因为目前我只用到了标红的三个接口,所以接下来对这三个接口一一讲解。(注开发之前本机IP需要得到官方授权,不然会请求会返回IP未授权) 下单接口 1.1. 功能描述 下订单接口根据客户需要,可提供以下三个功能: 1) 客户系统向顺丰下发订单。 2) 为订单分配运单号。 3) 筛单(可选,具体商务沟通中双方约定,由顺丰内部为客户配置)。 此接口也用于路由推送注册。客户的顺丰运单号不是通过此下订单接口获取,但却需要获取BSP的路由推送时,需要通过此接口对相应的顺丰运单进行注册以使用BSP的路由推送接口。 按照接入文档所说 接口通信协议支持WEBSERVICE及HTTP/POST协议,以下我是采用HTTP/POST协议 开发 其中 密匙生成规则: 先把XML报文与checkword前后连接。 把连接后的字符串做MD5编码。 把MD5编码后的数据进行Base64编码,此时编码后的字符串即为校验码。 元素的请求和响应内容字段和描述比较多,这里就不一一贴出来了,文末会提供接口文档下载地址。 ① 编写下单操作实体类 #region 下单操作实体 public class OrderService { /// <summary> /// 订单号 /// </summary> public string orderid { get; set; } /// <summary> /// 运单号 顺丰运单号,一个订单只能有一个母单号,如果是子母单的情况,以半角逗号分隔,主单号在第一个位置,如 “755123456789,001123456789,002123456789” , /// 对于路由推送注册,此字段为必填。 /// </summary> public string mailno { get; set; } /// <summary> /// 寄件方公司名称,如果需要 生成电子运单,则为必填。 /// </summary> public string j_company { get; set; } /// <summary> /// 寄件方联系人,如果需要生成电子运单,则为必填。 /// </summary> public string j_contact { get; set; } /// <summary> /// 寄件方联系电话,如果需要生成电子运单,则为必填。 /// </summary> public string j_tel { get; set; } /// <summary> /// 寄件方手机 /// </summary> public string j_mobile { get; set; } /// <summary> /// 寄件方详细地址 /// </summary> public string j_address { get; set; } /// <summary> /// 到件方公司名称 /// </summary> public string d_company { get; set; } /// <summary> /// 收件方联系人 /// </summary> public string d_contact { get; set; } /// <summary> /// 收件人联系电话 /// </summary> public string d_tel { get; set; } /// <summary> /// 收件人手机 /// </summary> public string d_mobile { get; set; } /// <summary> /// 收件人详细地址 /// </summary> public string d_address { get; set; } /// <summary> /// 包裹数(1个包裹对应一个运单号) /// </summary> public int parcel_quantity { get; set; } /// <summary> /// 快件产品类别(只有再商务上与顺丰约定的类别方可使用) /// </summary> public string express_type { get; set; } /// <summary> /// 顺丰月结卡号 /// </summary> public string custid { get; set; } /// <summary> /// 备注 /// </summary> public string remark { get; set; } /// <summary> /// 订单元素 /// </summary> public OrderCargo OrderCargos { get; set; } } public class OrderCargo { /// <summary> /// 货物名称 /// </summary> public string name { get; set; } } #endregion View Code ② 定义三个全局属性,因为再几个请求我们都会使用到这三个 //开发环境URL(文档中有提供) private static readonly string Posturl = "http://bsp-ois.sit.sf-express.com:9080/bsp-ois/sfexpressService"; //开发环境编码(文档中有提供) private static readonly string Bianma = "" + ConfigHelper.GetKey("bianma") + ""; //开发环境密匙(文档中有提供) private static readonly string Checkword = "" + ConfigHelper.GetKey("checkword") + ""; ③ 下单操作方法 /// <summary> /// 下单操作方法 /// </summary> /// <param name="model">下单操作实体</param> /// <returns> </returns> public static string GetHttpBack(OrderService model) { //得到下单XML请求体 var xml = Getxml(model); //生成密匙 var pass = Convert.ToBase64String(MD5(xml + Checkword)); //下单请求 var str = GethttpBack(Posturl, "xml=" + xml + "&verifyCode=" + pass); return str; } 下单XML请求体如下 /// <summary> /// 构建下单XML请求体 /// </summary> /// <param name="model">下单操作实体</param> /// <returns></returns> private static string Getxml(OrderService model) { string[] xmls = { "<Request service='OrderService' lang='zh-CN'>", "<Head>" + ConfigHelper.GetKey("bianma") + "</Head>", "<Body>", "<Order", "orderid='" + model.orderid + "'", "j_company='" + model.j_company + "'", "j_contact='" + model.j_contact + "'", "j_tel='" + model.j_tel + "'", "j_mobile='" + model.j_mobile + "'", "j_address='" + model.j_address + "'", "d_company='" + model.d_company + "'", "d_contact='" + model.d_contact + "' ", "d_tel='" + model.d_tel + "'", "d_mobile='" + model.d_mobile + "'", "d_address='" + model.d_address + "'", "parcel_quantity='" + model.parcel_quantity + "'", "express_type='" + model.express_type + "'", "custid='" + model.custid + "'", "remark='" + model.remark + "'>", "<Cargo name='" + model.OrderCargos.name + "'></Cargo>", "</Order>", "</Body>", "</Request>" }; var xml = ""; foreach (var s in xmls) if (xml == "") xml = s; else xml += "\r\n" + s; return xml; } View Code MD5编码方法如下 private static byte[] MD5(string str) { var result = Encoding.UTF8.GetBytes(str); MD5 md5 = new MD5CryptoServiceProvider(); var output = md5.ComputeHash(result); return output; } 下单请求方法如下 因为下单成功会返回订单号和运单号 以及筛单结果,所以我们先定义一个返回的响应报文模型容器 public class SfOrderResponse { /// <summary> /// 订单号 /// </summary> public string orderid { get; set; } /// <summary> /// 运单号 /// </summary> public string mailno { get; set; } /// <summary> /// 筛单结果 1 人工确认 2 可收派 3 不可以收派 /// </summary> public string filter_result { get; set; } } View Code private static string GethttpBack(string add, string post) { var we = new WebClient(); var sMessage = ""; #region 下单 try { SfOrderResponse sfresponse; if (post != "") { //编码,尤其是汉字,事先要看下抓取网页的编码方式 var postData = Encoding.UTF8.GetBytes(post); we.Headers.Clear(); //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可 we.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); sMessage = Encoding.UTF8.GetString(we.UploadData(add, "POST", postData)); //读取XML资源中的指定节点内容 if (Convert.ToString(GetNodeValue(sMessage, "Head")) == "ERR") sMessage = XElement.Parse(sMessage).Value; else sfresponse = new SfOrderResponse { //获取xml中orderid、mailno、destcode等节点值 orderid = GetXmlNodeValue(sMessage, "OrderResponse", "orderid"), mailno = GetXmlNodeValue(sMessage, "OrderResponse", "mailno"), filter_result = GetXmlNodeValue(sMessage, "OrderResponse", "filter_result") }; } else { sMessage = Encoding.UTF8.GetString(we.DownloadData(add)); if (Convert.ToString(GetNodeValue(sMessage, "Head")) == "ERR") sMessage = XElement.Parse(sMessage).Value; else sfresponse = new SfOrderResponse { //获取xml中orderid、mailno、destcode等节点值 orderid = GetXmlNodeValue(sMessage, "OrderResponse", "orderid"), mailno = GetXmlNodeValue(sMessage, "OrderResponse", "mailno"), filter_result = GetXmlNodeValue(sMessage, "OrderResponse", "destcode") }; } } catch { if (sMessage.IndexOf("8001") > 0) sMessage = "IP未授权"; } #endregion //释放资源 we.Dispose(); return sMessage; } View Code 其中读取XML资源中指定节点内容的方法如下 /// <summary> /// 读取XML资源中的指定节点内容 /// </summary> /// <param name="source">XML资源</param> /// <param name="nodeName">节点名称</param> /// <returns>节点内容</returns> public static object GetNodeValue(string source, string nodeName) { if (source == null || nodeName == null || source == "" || nodeName == "" || source.Length < nodeName.Length * 2) return null; var start = source.IndexOf("<" + nodeName + ">") + nodeName.Length + 2; var end = source.IndexOf("</" + nodeName + ">"); if (start == -1 || end == -1) return null; if (start >= end) return null; return source.Substring(start, end - start); } View Code 获取XML任意节点中某个属性值的方法如下 /// <summary> /// 获取xml任意节点中某个属性值 /// </summary> /// <param name="strXml">xml</param> /// <param name="strNodeName">节点名称</param> /// <param name="strValueName">属性名称</param> /// <returns></returns> public static string GetXmlNodeValue(string strXml, string strNodeName, string strValueName) { try { var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(strXml); var xNode = xmlDoc.SelectSingleNode("//" + strNodeName + ""); var strValue = xNode.Attributes[strValueName].Value; return strValue; } catch (Exception ex) { return ex.Message; } } View Code 这样我们就大概写完了下单的请求逻辑代码,现在我们可以写一条测试数据进行测试: SfExpress.GetHttpBack(new OrderService() { orderid = ConfigHelper.GetKey("orderID"), j_company = "深圳市*******有限公司", j_contact = "潇十一郎", j_mobile = "123456789", j_address = "这是发货地址", d_company = "收件公司名称", d_contact = "收件人", d_tel = "13237157517", d_mobile = "78946561", d_address = "收货地址", parcel_quantity = 1, express_type = "1", custid = "9999999999", remark = "下单测试", OrderCargos = new OrderCargo() { name = "显示屏" } }); 请求过程全过程如下(若每个订单号只能下单一次,若重复下单则会返回Err 重复下单,下面演示中,第一次演示的是重复下单,后来修改了订单号重新跑了一次,就返回了OK,顺利拿到了运单号) 路由查询 有了上一个下单作为铺垫,那我们路由查询就比较好处理了。 1.1. 功能描述 客户可通过此接口查询顺丰运单路由,BSP会在响应XML报文返回当时点要求的全部路由节点信息。 此路由查询接口支持两类查询方式: 1) 根据顺丰运单号查询:查询请求中提供接入编码与运单号,BSP将验证接入编码与所有请求运单号的归属关系,系统只返回具有正确归属关系的运单路由信息。 2) 根据客户订单号查询:查询请求中提供接入编码与订单号,BSP将验证接入编码与所有请求订单号的归属关系,对于归属关系正确的订单号,找到对应的运单号,然后返回订单对应运单号的路由信息。适用于通过BSP下单的客户订单。 ①编写请求对应的模型容器 /// <summary> /// 路由请求实体 /// </summary> public class RotueSehachService { /// <summary> /// 查询号类别 1运单号查询 2 订单号查询 /// </summary> public int tracking_type { get; set; } = 1; /// <summary> /// 查询号 tracking_type=1 则此值是运单号 tracking_type为1=2 此值是订单号 /// </summary> public string tracking_number { get; set; } /// <summary> /// 1 标准路由查询 2定制路由查询 /// </summary> public int method_type { get; set; } = 1; } View Code ②根据响应报文编写对应模型容器 public class RouteResponse { public string mailno { get; set; } public Route Route { get; set; } public string failMessage { get; set; } = string.Empty; } public class Route { public string accept_time { get; set; } public string accept_address { get; set; } public string remark { get; set; } public string opcode { get; set; } } ③路由查询 public static List<RouteResponse> GetHttpRotueSheach(RotueSehachService model) { //返回路由查询XML请求体 var xml = GetRoutexml(model); //MD5编码 var pass = Convert.ToBase64String(MD5(xml + Checkword)); var strData = "xml=" + xml + "&verifyCode=" + pass; var result = GetRouteack(Posturl, "xml=" + xml + "&verifyCode=" + pass); return result; } 其中,构建查询XML请求体方法如下 /// <summary> /// 构建路由查询XML请求体 /// </summary> /// <param name="model">路由查询请求模型</param> /// <returns></returns> private static string GetRoutexml(RotueSehachService model) { string[] xmls = { "<Request service='RouteService' lang='zh-CN'>", "<Head>" + ConfigHelper.GetKey("bianma") + "</Head>", "<Body>", "<RouteRequest", "tracking_type='" + model.tracking_type + "'", "method_type='" + model.method_type + "'", "tracking_number='" + model.tracking_number + "'/>", "</Body>", "</Request>" }; var xml = ""; foreach (var s in xmls) if (xml == "") xml = s; else xml += "\r\n" + s; return xml; } View Code MD5方法下单请求中已经贴出,其中GetRouteack请求方法如下 /// <summary> /// 路由查询 /// </summary> /// <param name="add">URL地址</param> /// <param name="post">编码后的请求体</param> /// <returns></returns> private static List<RouteResponse> GetRouteack(string add, string post) { var client = new WebClient(); var sXml = ""; var rutoelist = new List<RouteResponse>(); try { //编码,尤其是汉字,事先要看下抓取网页的编码方式 var postData = Encoding.UTF8.GetBytes(post); client.Headers.Clear(); //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可 client.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); //sXml = Encoding.UTF8.GetString(client.UploadData(add, "POST", postData)); sXml = "<?xml version='1.0' encoding='UTF-8'?><Response service='RouteService'><Head>OK</Head><Body>" + "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 已达到武汉航空总站' accept_time='2018-01-19 06:50:55' accept_address='武汉市' opcode='50'/></RouteResponse>" + "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 快递正在发往武汉航空总站' accept_time='2018-01-18 18:18:55' accept_address='深圳市' opcode='50'/></RouteResponse>" + "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 快递达到深圳中转站' accept_time='2018-01-18 15:12:55' accept_address='深圳市' opcode='50'/></RouteResponse>" + "<RouteResponse mailno='444032636081'><Route remark='顺丰速运 已收取快件' accept_time='2018-01-18 10:10:55' accept_address='深圳市' opcode='50'/></RouteResponse>" + "</Body></Response>"; //判断返回响应体是否是ERR if (Convert.ToString(GetNodeValue(sXml, "Head")) == "ERR") { sXml = XElement.Parse(sXml).Value; rutoelist.Add(new RouteResponse {failMessage = XElement.Parse(sXml).Value}); } //将xml字符串转换为XML文档 var xmlDoc = XDocument.Parse(sXml); //获取 文档中子代元素 RouteResponse 的所有集合 var nodelist = xmlDoc.Descendants("RouteResponse"); if (nodelist != null) { //获取源集合中每个元素和子元素的集合 var pieceareas = nodelist.Elements(); foreach (var item in pieceareas) //循环添加到路由响应容器集合中 rutoelist.Add(new RouteResponse { //获取XML mailno 属性值 mailno = GetXmlNodeValue(sXml, "RouteResponse", "mailno"), Route = new Route { opcode = item.Attribute("opcode").Value, accept_address = item.Attribute("accept_address").Value, accept_time = item.Attribute("accept_time").Value, remark = item.Attribute("remark").Value } }); } } catch (Exception e) { client.Dispose(); rutoelist.Add(new RouteResponse {failMessage = XElement.Parse(sXml).Value}); return rutoelist; } client.Dispose(); return rutoelist; } View Code 因为开发测试环境,没有真实返回路由数据,所以我这里构造了几个返回数据,写到这里,我们路由查询的大部分逻辑已经完成,剩下就是调用和获取到路由返回集合,循环拼接输出我们想要的物流信息,具体操作可参考如下 #region 物流查询 var message = ""; //顺丰单独处理 if (companyNumber == "sf-express") { #region 获取顺丰物流信息 //先进行路由查询,若返回未下单则再进行下单操作。 List<RouteResponse> result = SfExpress.GetHttpRotueSheach(new RotueSehachService() { tracking_number = ConfigHelper.GetKey("orderID") }); if (result.Count >0) { //循环返回的路由集合 foreach (var item in result) { //判断错误信息不为空 if (item.failMessage == string.Empty) { //将所有路由信息 按时间+路由地址拼接 message += item.Route.accept_time + " " + item.Route.accept_address + "\r\n <br/>"; } else { message += item.failMessage + "\r\n <br/> "; } } } else { return Content("暂无物流信息,请稍后重试"); } #endregion 请求全过程如下 不知道大家留意看没,再请求成功后往集合里面添加了第一条数据后,我返到上面查看了下收到的数据,有一个opcode=50 的,顺便说下,这个opcode是返回的路由操作码,顺丰有个专门的文档记录这些返回操作码,那现在问题就来了,只是返回这个操作码,我们并不能马上知道操作码对应的文字描述。拿到操作码去官方文档上一个一个对,也不是我们程序员的思维。所以,此时我们应该想办法解决,当我们收到这个请求码的时候,就自动获取它的描述,这样就一目了然,方便我们后面操作。 单独处理路由操作码 其实改起来也不麻烦,我们需要把之前定义的Route 模型稍微改下 让它继承我们定义的操作码和对应的操作码描述类即可 代码如下: public class Route: SfErrorEntity { public string accept_time { get; set; } public string accept_address { get; set; } public string remark { get; set; } // public string opcode { get; set; } } 在SfErrorEntity 中,我们再去处理操作码和文字描述的问题。首先我们需要找到操作码,操作码模样如下 我们可以再项目中新建一个资源文件,名字叫SFcode.resx,然后添加资源我们选择添加文本文件叫 sfResourse 接下来我们把文档中的操作码和文字描述 复制进去 最后一步 就是编写SfErrorEntity 类如下 public class SfErrorEntity { /// <summary> /// 定义一个字典存放操作码和对应描述 /// </summary> private static Dictionary<int, string> _errorDic; /// <summary> /// 定一个操作码集合 /// </summary> public static Dictionary<int, string> ErrList { get { //获取时先判断字典中是否存在数据,若存在则直接返回 if (_errorDic != null && _errorDic.Count > 0) return _errorDic; //不存在,首先实例化当前操作字典 _errorDic = new Dictionary<int, string>(); //读取资源文件中的文本文件 先按行分隔 var temp = SFCode.sfResourse.Split(new char[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries); //拿到所有行,再按空格分隔 foreach (var result in temp.Select(m => m.Split('\t'))) { //将对应的操作码和描述添加到当前字典中 _errorDic.Add(int.Parse(result[0]), result[1]); } return _errorDic; } } /// <summary> /// 存放操作码描述 /// </summary> public string ErrDescription { get; set; } /// <summary> /// 操作码 /// </summary> private int _opcode; public int Opcode { get { return _opcode; } set { //写入操作码时,就拿当前写入的值和操作码集合中的数据做比较,同时返回对应的操作码描述 _opcode = value; ErrDescription = ErrList.FirstOrDefault(m => m.Key == value).Value; } } } View Code 最后的效果如下: 路由推送 上文中也提到过,只有是通过顺丰接口下的订单,才能走路由推送这个接口,还是先看一下关于路由推送的描述 1.1. 功能描述 该接口用于当路由信息生产后向客户主动推送要求的顺丰运单路由信息。推送方式为增量推送,对于同一个顺丰运单的同一个路由节点,不重复推送。 客户需提供一个符合以下规范的HTTP URL,以接收BSP推送的信息。 1) 请求方法为:“application/x-www-form-urlencoded; charset=UTF-8”。 Key:content 2) 信息以URL编码(字符集为UTF-8)的XML格式,通过HTTP POST方式推送给客户。 3) 客户在接收到信息后,需要先对其进行URL解码,得到相应的XML。 4) 在客户处理XML信息后,向BSP返回响应XML报文,响应XML报文结果只能为OK/ERR(参见XML报文说明),BSP将重新推送此次交易的所有信息。 路由推送整体和上面两个类似,这个路有推送返回更简单,只有OK,或者Err。操作代码如下 ①定义推送模型容器 /// <summary> /// 路由推送模型 /// </summary> public class RoutePushService:SfErrorEntity { /// <summary> /// 路由节点信息编号,每一个id 代表一条不同的路由节点信息 /// </summary> public int id { get; set; } /// <summary> /// 顺丰运单号 /// </summary> public string mailno { get; set; } /// <summary> /// 客户订单号 /// </summary> public string orderid { get; set; } /// <summary> /// 路由节点产生的时间,格式:YYYY-MM-DDHH24:MM:SS /// </summary> public DateTime acceptTime { get; set; } /// <summary> /// 路由节点发生的城市 /// </summary> public string acceptAddress { get; set; } = "深圳"; /// <summary> /// 路由节点具体描述 /// </summary> public string remark { get; set; } = "上门收件"; /// <summary> /// 路由节点操作码 /// </summary> // public string opCode { get; set; } = "50"; } View Code #region 路由推送 public static string RoutePushService(RoutePushService model) { var xml = GetPushxml(model); var pass = Convert.ToBase64String(MD5(xml + Checkword)); var strData = "xml=" + xml + "&verifyCode=" + pass; var strHeaders = "Content-Type: application/x-www-form-urlencoded\r\n"; var bytePost = Encoding.UTF8.GetBytes(strData); var byteHeaders = Encoding.UTF8.GetBytes(strHeaders); var str = GetPushBack(Posturl, "xml=" + xml + "&verifyCode=" + pass); return str; } /// <summary> /// 构建路由推送XML /// </summary> /// <param name="model">推送容器</param> /// <returns></returns> private static string GetPushxml(RoutePushService model) { string[] xmls = { "<Request service='RouteService' lang='zh-CN'>", "<Body>", "<WaybillRoute", "id='" + model.id + "'", "mailno='" + model.mailno + "'", "acceptTime='" + model.acceptTime.ToString("yyyy-MM-dd HH:mm:ss") + "'", "acceptAddress='" + model.acceptAddress + "'", "remark='" + model.remark + "'", "opCode='50'/>", "orderid='" + model.orderid + "'/>", "</Body>", "</Request>" }; var xml = ""; foreach (var s in xmls) if (xml == "") xml = s; else xml += "\r\n" + s; return xml; } /// <summary> /// 推送 /// </summary> /// <param name="add">URL</param> /// <param name="post">数据</param> /// <returns></returns> private static string GetPushBack(string add, string post) { var pusthclient = new WebClient(); var sXml = ""; try { //编码,尤其是汉字,事先要看下抓取网页的编码方式 var postData = Encoding.UTF8.GetBytes(post); pusthclient.Headers.Clear(); //采取POST方式必须加的header,如果改为GET方式的话就去掉这句话即可 pusthclient.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); sXml = Encoding.UTF8.GetString(pusthclient.UploadData(add, "POST", postData)); //获取Head是否是ERR,若不是,直接返回OK if (Convert.ToString(GetNodeValue(sXml, "Head")) == "ERR") sXml = XElement.Parse(sXml).Value; else sXml = "OK"; } catch (Exception e) { sXml = e.Message; } pusthclient.Dispose(); return sXml; } #endregion 这个路由推送 ,可以写再服务里面,例如:客户下单了,是代发货状态,那么可以筛选出来,写一个服务,调用这个推送的接口,返回OK,则往数据库一个标示字段注明订阅成功,否则反。 有了路由推送,然后就是回调了,推送成功之后,顺丰会根据当前物流单,若有物流信息发生改变,则会主动推向提前约定好的回调地址上,此时只需要再回调方法中做一些业务上的处理即可。 做完以上的开发,剩下的就是给顺丰那边接口对接人员发邮件申请进入联调测试阶段,联调测试通过之后再那那边签协议接入正式环境投入使用。 注:联调本机开发环境即可,无需部署线上。 关于顺丰接口大概就这么多。下一篇 关于快递100 物流接口详解。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
连载篇提前看 物流一站式查询之TrackingMore篇 物流一站式查询之顺丰接口篇 物流一站式查询之快递100篇 快递查询接口 目前提供快递查询的接口平台有: Trackingmore 快递100 快递网 不同接口的区别: (1)Trackingmore支持380家快递公司,其中有55家为国内的快递,其余325家为国际快递。具体的价格为0.6分钱/单号左右,新注册用户可以免费测试12小时。 (2)快递100属于在国内做得比较早的平台,可以申请每天最多2000次的API调用,但需要给快递100做一个友链。超过2000次收费,每次0.06~0.1元不等。 (3)快递网可以申请每天最多500次的API调用,但同样需要做一个友链。超过部分,每次0.05元。 快递API的应用场景与用途 1. 最常见的应用场景如下: (1)电商网站:例如B2C、团购、B2B、批发分销站、C2C、本地生活交易等网站。 (2)管理系统:订单处理平台、订货平台、发货平台、分销系统、渠道管理系统、客户管理系统、ERP等。 2. 快递API的用途如下: (1)让顾客登录网站后,直接在“我的订单”页面内就能看到订单的物流状态。 (2)自动筛选出“已签收”、“疑难件”等状态的单号,减轻物流跟单人员的压力。 (3)改变订单的状态和交易流程,例如单号变为“已签收”,就能让订单变为可以确认退换货等。 (4)评估选择快递公司,根据“已签收”的运单数,可以算出销售人员的业绩,且便于应对货到付款的结算。 (5)邮件、短信提醒用户运单的最新状态,可以安抚用户,也可以利用邮件短信二次营销。 对接示例 这里以Trackingmore为例,不同的接口的对接方式比较类似,都需要注册,并生成自己的API key。以下以Trackingmore的实时查询API为例。 接口支持的消息接收方式为HTTP POST 请求方法的编码格式为 utf-8 请求body部分的参数的数据格式为json 格式 接口参数 接口请求地址 http://api.trackingmore.com/v2/trackings/realtime 请求头部信息参数 参数名称 类型 说明 是否必须 Content-Type: application/json 定义请求头部的数据格式 是 Trackingmore-Api-Key: string Trackingmore 后台获取的API 是 请求body参数说明 参数说明 类型 说明 是否必须 tracking_number string 查询快递的快递单号 是 carrier_code string trackingmore定义的快递商简码,比如china ems 就是china-ema 是 carrier_code 参数是trackingmore 自己定义的快递商家的简码,具体的可以在这里查看。 还有需要注意的就是body部分这两个参数需要时json数据格式。大概样子就是这样的 1 { 2 "tracking_number": "LK664578623CN", 3 "carrier_code": "china-ems" 4 } 返回参数定义 参数名称 参数类型 参数说明 是否一定要返回该项值 code 数字 返回码 成功返回200,失败有其他队列的错误码 type string 接口类型 成功返回Success message string 返回信息说明 成功返回Succes,失败返回队列的错误信息 data json 查询到的物流信息 成功返回物流信息,失败返回空 其他的状态响应简码可以在这里看到。 这里把Trackingmore平台状态响应简码简单封装了下,代码如下: /// <summary> /// 典型的服务器响应 状态枚举 /// </summary> public enum TrackMoreServerResponse { [Display(Name = "请求已成功 (一些 API 调用可能会相反返回 201)")] OK = 200, [Display(Name = "请求成功,已创建了资源")] Created = 201, [Display(Name = "请求已成功,但超过了数量限制")] CreatedFail = 202, [Display(Name = "身份验证失败或用户没有所请求的操作的权限")] Unauthorized = 401, [Display(Name = "无效的 API 密钥。请确保自己的申请API key的账户与调用API的服务器同时在国内或国外。即如果你的服务器在国外,你需要FQ登录trackingmore网站进而获取API key")] UnauthorizedAPIInvalid = 4001, [Display(Name = "API 密钥已被删除")] UnauthorizedAPIDelete = 4002, [Display(Name = "请求不能理解或缺少必需的参数")] BadRequest = 4012, [Display(Name = "Tracking_number 是必需的")] BadRequest1 = 4013, [Display(Name = "Tracking_number 的值是无效的")] BadRequestInvalid = 4014, [Display(Name = "Carrier_code的值是无效的")] BadRequest3 = 4015, [Display(Name = "跟踪已存在")] BadRequest4 = 4016, [Display(Name = "跟踪并不存在")] BadRequest5 = 4017, [Display(Name = "由于过载风险此功能需要自定义激活。与 service@trackingmore.org 联系更多的信息。")] BadRequest6 = 4018, [Display(Name = "数量限制一次 200")] BadRequest7 = 4020, [Display(Name = "你的余额不够,所以你不能调用API请求数据")] BadRequest8 = 4021, [Display(Name = "请求已成功,但响应为空")] BadRequest9 = 4031, [Display(Name = "无法检测到快递")] NoContent = 4032, [Display(Name = "付款要求")] PaymentRequired = 402, [Display(Name = "访问被拒绝")] Forbidden = 403, [Display(Name = "找不到资源")] NotFound = 404, [Display(Name = "指定的资源不支持请求的方法")] MethodNotAllowed = 405, [Display(Name = "由于冲突,无法完成请求")] Conflict = 409, [Display(Name = "超过 API 限制。请求暂停,等待两分钟,然后再试")] TooManyRequests = 429, [Display(Name = "内部异常")] ServerError = 500, [Display(Name = "服务是临时不可用 (例如预定的平台维护)稍后再试")] ServiceUnavailable = 503, } View Code 然后我们可以获取枚举描述值来快速知道返回了什么状态,而不用去官网查状态码的意思。这里也把获取枚举描述值的代码贴出来如下: /// <summary> /// 枚举辅助类 /// </summary> public static class EnmHelper { /// <summary> /// 获取枚举值的描述 /// </summary> /// <param name="sourceValue"></param> /// <returns></returns> public static string GetEnmName(this Enum sourceValue) { DisplayAttribute[] attributes = null; if (sourceValue != null) { FieldInfo field = sourceValue.GetType().GetField(sourceValue.ToString()); if (field != null) { attributes =field .GetCustomAttributes(typeof(DisplayAttribute), false) as DisplayAttribute[]; } } if (attributes==null|| attributes.Length < 1) return sourceValue.ToString(); return attributes[0].Name; } /// <summary> /// 获取枚举集合列表 /// </summary> /// <param name="enmType"></param> /// <returns></returns> public static List<ModItem> GetEnmList(this Type enmType) { List<EnmItem> result = new List<EnmItem>(); Array array= Enum.GetValues(enmType); foreach (var item in array) { DisplayAttribute[] attributes = (DisplayAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(DisplayAttribute), false); EnmItem enmItem = new EnmItem(); if (attributes.Length > 0) { enmItem.Name = attributes[0].Name; enmItem.Value = Convert.ToInt32(item).ToString(); enmItem.OrderIndex = attributes[0].GetOrder().GetValueOrDefault(0); result.Add(enmItem); } } return result.OrderBy(x=>x.OrderIndex).Select(x=>x as ModItem).ToList(); } /// <summary> /// 把字符串转换为枚举 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source"></param> /// <returns></returns> public static T ConvertToEnm<T>(this string source) { return (T) ConvertToEnm(source,typeof(T)); } /// <summary> /// 把字符串转换为枚举 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source"></param> /// <returns></returns> public static object ConvertToEnm(this string source,Type enmType) { ModItem mod = enmType.GetEnmList().FirstOrDefault(x => x.Name == source); if (mod != null) { source = mod.Value; } return Enum.Parse(enmType, source); } } class EnmItem:ModItem { public int OrderIndex { set; get; } } View Code 一个枚举帮助类,其中ModItem类是自定义的一个类,里面就两个属性,Name、Value。 public class ModItem { public string Name { set; get; } public string Value { set; get; } } 像一般的调用接口获取物流信息,官网也有提供调用示例,点我去看示例 完整的物流订阅、查询状态有更新触发回调url 示例 现在假设你已经在More平台注册过,并生成创建了一个API Key。 订阅篇 ①创建一个接口的访问类(此类官方已提供) /// <summary> /// TrackingMore 每次发送必须带有Key 否则请求无效 /// </summary> public class TrackingMoreAPI { private string ApiKey = ConfigHelper.GetKey("APIKey"); public string getOrderTracesByJson(string requestData, string urlStr, string method) { string result = null; if (method.Equals("post")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/post"; string RelUrl = ReqURL + urlStr; result = sendPost(ReqURL, requestData, "POST"); } else if (method.Equals("get")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/get"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "GET"); } else if (method.Equals("batch")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/batch"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "POST"); } else if (method.Equals("codeNumberGet")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "GET"); } else if (method.Equals("codeNumberPut")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "PUT"); } else if (method.Equals("codeNumberDel")) { string ReqURL = "http://api.trackingmore.com/v2/trackings"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "DELETE"); } else if (method.Equals("realtime")) { string ReqURL = "http://api.trackingmore.com/v2/trackings/realtime"; string RelUrl = ReqURL + urlStr; //Console.WriteLine("RelUrl:" + RelUrl); result = sendPost(RelUrl, requestData, "POST"); } return result; } private string sendPost(string url, string requestData, string method) { string result = ""; byte[] byteData = null; if (requestData != null) { byteData = Encoding.GetEncoding("UTF-8").GetBytes(requestData.ToString()); } try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.ContentType = "application/x-www-form-urlencoded"; request.Timeout = 30 * 1000; request.Method = method; request.Headers["Trackingmore-Api-Key"] = ApiKey; if (byteData != null) { Stream stream = request.GetRequestStream(); stream.Write(byteData, 0, byteData.Length); stream.Flush(); stream.Close(); } HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream backStream = response.GetResponseStream(); StreamReader sr = new StreamReader(backStream, Encoding.GetEncoding("UTF-8")); result = sr.ReadToEnd(); sr.Close(); backStream.Close(); response.Close(); request.Abort(); } catch (Exception ex) { result = ex.Message; } return result; } } View Code ②创建一个TrackMoreModel模型类用于传输快递单号和快递公司编码 public class TrackMoreModel { /// <summary> /// 快递单号 /// </summary> public string tracking_number { get; set; } /// <summary> /// 快递公司编码 /// </summary> public string carrier_code { get; set; } } View Code ③创建一个TrackMore快递信息跟踪类,里面可以包含你需要的一些访问接口的方法,如:创建快递单项目,查询单个物流信息、创建多个物流项目,查询多个物流信息、实时查询物流信息等。这里以创建单个快递单项目为例 /// <summary> /// 快递跟踪信息 /// </summary> public class TrackMore { /// <summary> /// 创建快递单号项目 /// </summary> /// <param name="input"></param> /// <returns></returns> public static OperationResult TrackExpressSubscibe(TrackMoreModel model) { #region 创建物流跟踪信息 string urlstr = null; string requestdata = "{\"tracking_number\": \"" + model.tracking_number + "\",\"carrier_code\":\"" + model.carrier_code + "\"}"; string result = new TrackingMoreAPI().getOrderTracesByJson(requestdata, urlstr, "post"); var code = TarckMoreExpressCompany.GetValueFromJson(result, "code"); if ( code!="200") { string message = EnmHelper.GetEnmName((TrackMoreServerResponse)int.Parse(code)); if (!message.Equals("跟踪已存在")) { return new OperationResult(message); } } #endregion return new OperationResult(); } } View Code 其中GetValueFromJson 方法作用是从json字符串中获取字段值 ,在这里方法也贴一下: /// <summary> /// 从json字符串中获取字段值 /// </summary> /// <param name="json">json字符串</param> /// <param name="field">要解析出值的字段</param> /// <returns></returns> public static string GetValueFromJson(string json, string field) { int start = json.IndexOf(field + "\":"); start += field.Length + 2; int end = json.IndexOf(",", start); if (end < 0) { end = json.IndexOf("}", start); } return json.Substring(start, end - start).Trim('"'); } View Code OperationResult 类是自定义的业务操作结果类,可以根据项目需要自定义添加,这里我贴三个出来 /// <summary> /// 默认操作成功 /// </summary> public OperationResult() { IsSuccess = true; } /// <summary> /// 以操作失败信息实例操作结果 /// </summary> /// <param name="failMessage">操作失败信息</param> public OperationResult(string failMessage) { IsSuccess = false; FailMessage = failMessage; } /// <summary> /// 业务操作是否成功 /// </summary> public bool IsSuccess { set; get; } ④有了跟踪物流信息类,那剩下的就是调用了 ,调用比较简单,主要是根绝项目业务 去做一些操作,比如从数据库查询订单,过滤一些条件,快递没有订阅的,排除掉一些订单类型,有些充值啊这些不需要快递,查询到的集合 可以循环去创建物流单项目,调用的代码就一句, OperationResult result = TrackMore.TrackExpressSubscibe(new TrackMoreModel() { tracking_number =快递单号, carrier_code =快递公司简码) }); 然后可以根据返回的结果做一些其他操作 if (result.IsSuccess) { //TODO 比如 更新数据库此条订单物流订阅状态,订阅时间等 } else { //TODO 比如 日志记录失败原因 } 回调更新物流信息 上面只是再More物流平台创建了此条物流信息记录,但是我们并不知道当前这个快递状态是如何,也不能每次都去快递平台查询,所以,回调的作用就凸显出来了,物流平台每次只要有物流更新的时候,会访问到你再平台上设定的回调URL。 此时,你只用接收发过来的数据(包括meta请求头状态,data 数据 verifyInfo 签名部分),具体代码如下: ⑴先再More平台设置回调地址 当然官方也提供了回调测试的 页面 在填回调地址的下面 点击 format of inbound webhooks 即可进入,界面如下图所示: ⑵设置完了回调地址,那我们就可以进行编码了,首先我们要对返回的数据 进行分析,上面也提到了 Body返回的主体部分是Json格式的,其实这里有一个坑,就是提交那边是form,但是接收是的数据类型是Json的 关于这种请求数据类型区别和使用,可以看这篇博客: ASP.NET MVC学习系列(二)-WebAPI请求 ok,既然是Json格式数据,那我们就把返回的Json格式数据转成实体模型。这里有几种方式推荐,①将官方示例中的json全部复制下来,然后随便百度一个Json转实体,不过我更推荐第二种,② 号称宇宙最强IED 的VS也帮我们实现Json转实体的快捷操作,具体操作方式如下: 有了模型容器,我们就可以来接收数据了 创建一个和回调URL一样的方法名称UpdateTrack /// <summary> /// TrackingMore 回调 同步物流信息 /// </summary> /// <param name="form"></param> /// <returns></returns> public ActionResult UpdateTrack([FromBody]Root dataJson) { var code = dataJson.meta.code;//请求状态 var expressNumber = dataJson.data.tracking_number;//快递单号 var status = dataJson.data.status;//物流状态 var trackinfo = dataJson.data.origin_info.trackinfo;//物流信息集合 var expressMessage = ""; //所有物流信息 foreach (var item in dataJson.data.origin_info.trackinfo) { expressMessage += item.Date + " " + item.StatusDescription + "<br/>"; } var track = dataJson.data.origin_info.trackinfo; var LastExpressMessage = track.Count > 0 ? track[0].Date + " " + track[0].StatusDescription : "";//最近一条物流信息 //TODO 比如:先根据传过来的快递单号查寻 项目的订单表 确认是否有此订单,再根据数据表中的订单状态 //看是否需要修改一些其他信息,主要就是拿到第一条物流信息和所有的物流信息,也可以再数据表中记录此时这个快递单 回调的时间 和返回的状态 用于后期数据分析 } 注:此方法接受回调使用了FormBody ,快递100平台接收回调是FormCollection即可。 外部调用 所谓外部调用,就是再自己项目中,根据不同单号 借助物流平台查询快递实时信息。物流平台对此操作也有讲解,这里简单提下。 官方给出的效果如下: 实现步骤 ①将下面这句引用代码放在标签开始之前的位置 <script type="text/javascript" src="//cdn.trackingmore.com/plugins/v1/pluginsCss.js?time=20170913"></script><script type="text/javascript" src="//cdn.trackingmore.com/plugins/v1/plugins.min.js"></script> ②将下面代码放在 页面与标签之间的位置 <div style="width:600px;margin-left:0px;text-align:left;"> <form id="trackingmoreId" role="form" action="//track.trackingmore.com" method="get" onsubmit="return false"> <div class="search-box"> <div class="input-box"> <input id="button_express_code" type="hidden" value="" name="button_express_code"><input style="border-color: #428bca;" type="text" autocomplete="off" maxlength="26" placeholder="请输入快递单号" id="button_tracking_number" class="inp-metro" name="button_tracking_number"> <button style="background-color: #428bca" class="button-query" type="submit" onclick="return doTrack()" id="query">查询</button> </div> </div> <input type="hidden" name="lang" value="cn" /> </form> <div id="TRNum"></div> </div> <script type="text/javascript"> function doTrack() { var num=document.getElementById("button_tracking_number").value; var expCode=document.getElementById("button_express_code").value; var width = document.getElementById("query").parentNode.offsetWidth; TRACKINGMORE.trackMynumber({ TR_ElementId:"TRNum", //必须,指定悬浮位置的元素ID。 TR_Width:width, //可选,指定查询结果宽度,最小宽度为600px,默认撑满容器。 TR_Height:600, //可选,指定查询结果高度,最大高度为800px,默认撑满容器。 TR_ExpressCode:expCode, //可选,指定运输商,默认为自动识别。 TR_Lang:"cn", //可选,指定UI语言,默认根据浏览器自动识别。 TR_Num:num //必须,指定要查询的单号。 }); return false; } </script> 温馨提示:不要短时间内频繁调用 More平台会检测当前IP,过短调用会提示I异常 需要输入验证码 最终效果展示如下: PS:这种每次点击订单请求快递数据并不是最优的做法。因为上面我们也有提到过快递订阅这个功能,其实,最佳的做法是 订阅之后,快递平台推送到回调地址的最新物流信息,我们可以将这些物流信息保存再对应订单的快递物流字段里面。这样前台取的时候可以先判断 物流信息字段是否为空,如果不为空 就直接取物流信息字段,这样既可以减少网络流量消耗,也可以加快系统反应时间,带来更好的用户体验。如果不喜欢用官方自带的这个显示框,也可以改成Bootstrap的样式框显示,具体做法如下: //查看物流(订单id,this,物流信息,快递单号,快递公司简码) function viewSenderMessage(id, obj, message, number, company) { debugger; if (!isNullEmptyUndefined(message)) { var d = dialog({ title: '物流最新跟踪', content: "<div style='line-height:20px;'>" + message + "</div>", okValue: '确定', ok: true, }); d.show(obj); } else if (!isNullEmptyUndefined(number) && !isNullEmptyUndefined(company)) { // doTrack(number, company); ajaxGetContent("@Url.Action("ViewExpress")" + "?companyNumber=" + company + "&expressNumber=" + number, function (data) { var d = dialog({ title: '物流最新跟踪', content: data, okValue: '确定', ok: true, }); d.show(obj); }); } else { var d = dialog({ title: '物流最新跟踪', content: "无物流跟踪信息", okValue: '确定', ok: true, }); d.show(obj); } } 按照逻辑,如果物流信息为空 此时我们就需要从Action ViewExpress中获取单个快递信息。ViewExpress代码如下: public ActionResult ViewExpress(string expressNumber, string companyNumber) { string urlstr = null; string requestdata = "carrier_code=" + companyNumber + "&tracking_number=" + expressNumber; //此处是获取单个物流信息 采用get请求,method传入codeNumberGet string result = new TrackingMoreAPI().getOrderTracesByJson(requestdata, urlstr, "codeNumberGet"); if (!result.Contains("code")) return Content("暂无物流信息,请稍后重试"); var code = TarckMoreExpressCompany.GetValueFromJson(result, "code"); if (code != "200") { message = EnmHelper.GetEnmName((TrackMoreServerResponse)int.Parse(code)); //这里多加一层判断 4031表示请求成功,但是响应为空,意思就是 TrackMore平台上还没有这个快递单数据,所以要再次调之前创建物流项目这个接口 if (code == "4031") { OperationResult resMessage = TrackMore.TrackExpressSubscibe(new TrackMoreModel() { carrier_code = companyNumber, tracking_number = expressNumber }); if (resMessage.IsSuccess) { return ViewExpress(expressNumber, companyNumber); } else { message = resMessage.FailMessage; } } } else { Root root = JsonConvert.DeserializeObject<Root>(result); if (root.Data.origin_info.trackinfo.Count == 0) message = "暂无物流信息,请稍后重试"; //此处将返回的物流信息以 时间+空格+物流消息 格式循环输出 foreach (var item in root.Data.origin_info.trackinfo) { message += item.Date + " " + item.StatusDescription + "\r\n <br/>"; } } return Content(message); } 获取单个物流信息采用的是get请求,所以再发送http请求的时候不能再用post 的流方式,需要多加一个get请求的,代码如下: public string HttpGet(string Url, string postDataStr, string method) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr); request.Method = "GET"; request.ContentType = "application/x-www-form-urlencoded"; request.Timeout = 30 * 1000; request.Method = method; request.Headers["Trackingmore-Api-Key"] = ApiKey; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return retString; } 最终效果展示 注:顺丰即日取消了对外第三方接口,故不能通过第三方物流平台获取到顺丰的物流信息,需要和顺丰开放平台(目前据顺丰IT人员透露,此平台已无人维护,已物流服务已转至顺丰官方的企业服务平台)对接 才能查到最新顺丰的物流信息 顺丰的接口对接请转场至下文《物流一站式查询之顺丰接口篇》 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信。什么是实时通信的Web呢?就是让客户端(Web页面)和服务器端可以互相通知消息及调用方法,当然这是实时操作的。 没有Gif我会告诉你是什么效果? SignalR1.1运行Demo: SignalR2.2.2运行Demo: SignalR2.2.2运行Demo2: 具体实现 文章已加密请点我入群获取Demo 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
下面列举了100多个国内常用API接口,并按照 笔记、出行、词典、电商、地图、电影、即时通讯、开发者网站、快递查询、旅游、社交、视频、天气、团队协作、图片与图像处理、外卖、消息推送、音乐、云、语义识别、语音识别、杂志、综合 进行了如下分类。 笔记 OneNote - OneNote支持获取,复制,创建,更新,导入与导出笔记,支持为笔记添加多媒体内容,管理权限等。提供SDK和Demo。 为知笔记 - 为知笔记Windows客户端开放了大量的API,其中绝大部分,都通过COM提供,可以在javascript, C#, C++, Delphi等语言中使用。接口通过IDL(Interface description language)语言描述。 印象笔记 - 印象笔记提供了ActionScript 3, Android, C++, Windows, iOS, Java, JavaScript, OS X, Perl, PHP, Python, Ruby等平台的SDK和完整的API参考文档,可通过API进行认证,笔记,笔记本,附件,搜索,同步等操作,同时提供企业版和本地API。 有道云笔记 - 有道云笔记提供了Android SDK,同时Open API允许进行授权,用户,笔记本,笔记,分享,附件等方面的操作。 出行 滴滴 - 滴滴提供了iOS和Android SDK, 可实现拉起滴滴叫车等方面的操作。 神州专车 - 神州专车提供了API模式和H5模式两种接入模式,允许进行基础信息,订单,支付,充值,用户,发票,代金券,企业等方面的操作。 Uber - Uber提供了Android和iOS的SDK,允许进行乘客,行程体验,司机,派送次数等四大方面的操作。 词典 百度翻译 - 百度翻译支持多种语言互相翻译,包含PHP, JS, Python, C, Java版Demo。 必应词典 - 微软翻译API支持文字和语音两种类型,支持多种语言互相翻译,提供C#版本Demo。 必应词典(非官方) - 支持单词和语句翻译。 #非官方 金山词霸 - 金山词霸支持简单的翻译操作。 金山词霸(非官方) - 金山词霸允许进行简单的翻译操作。 #非官方 扇贝 - 扇贝提供了完整的API,允许进行用户,查询,添加学习记录,忘记单词,例句,笔记等方面的操作。 译云翻译 - 译云支持进行中英互译,后续会支持更多的语言。 有道词典 - 有道词典允许进行简单的翻译操作。 有道词典(非官方) - 允许进行简单的翻译操作。 #非官方 电商 当当 - 当当允许商家用户和网站接入授权,可进行商品,订单,图片,问答,店铺和促销等方面的操作。 京东 - 京东提供了Java, PHP, .net的SDK,授权后可进行多种操作。 苏宁开放服务 - 苏宁提供了Java, PHP, .Net, Python版本的SDK,授权后可进行多种操作。 淘宝开放平台 - 淘宝提供了Java, .Net, PHP, Python版本的SDK,授权后提供多种操作。 亚马逊 - 亚马逊提供多种语言版本的SDK,授权后允许多种操作。 地图 百度地图 - 百度地图提供了Android, iOS版本的SDK和JavaScript API,可进行定位、地图、数据、出行、鹰眼轨迹和分析服务。 高德地图 - 高德地图提供了JavaScript和web服务API,Android和iOS SDK,支持地图,定位,搜索,路线规划,导航和室内地图等。 腾讯地图 - 腾讯地图提供了JavaScript API,Android和iOS SDK,支持定位,地图,地点搜索,路线和导航等。 天地图 - 天地图提供了H5 API和JavaScript API等web API,同时提供了Android和iOS SDK,支持基础地图服务,图层管理,地图覆盖物,地图工具,地名搜索和出行规划服务。 图吧地图 - 图吧提供了JavaScript和Flash API,Android和iOS SDK,支持定位,地址解析,位置标注,位置截图,路线规划,周边查询,兴趣点搜索和在线导航。 电影 豆瓣电影 - 豆瓣电影支持电影条目,影人条目,搜索和榜单等。 豆瓣电影(非官方) - 获取最近热映电影、短评、影评、图片等。 #非官方 猫眼电影(非官方) - 支持查询首页电影列表,电影详情(含评论),本地影院和影院详情,选座。 #非官方 Time时光(非官方) - 支持获取时光网网站数据。 #非官方 V电影(非官方) - 支持获取V电影网站的数据。 #非官方 即时通讯 环信 - 支持Android, iOS, WebIM, Linux, REST集成,支持多种消息类型。 融云 - 支持Android, iOS, Web, 游戏集成,支持多种消息类型。 网易云信 - 支持IM实时通讯,实时音视频,教学白班,专线电话,短信,聊天室,提供iOS, Android, Windows和Web SDK。 腾讯云通信IM - 提供iOS, Android, Windows和Web SDK,支持多种消息类型。 开发者网站 Coding - 授权后可访问coding.net网站的内容。 干货集中营 - 提供妹子图和Android, iOS, 前端,拓展资源等内容。 diycode - 授权后可访问diycode网站的内容。 开源中国 - 授权后可访问开源中国网站的内容。 Laravel China - 授权后可访问 Laravel China 网站的内容。 Ruby China - 授权后可访问Ruby China网站的内容。 V2EX - 可访问V2EX网站的内容。 快递查询 Trackingmore - Trackingmore目前支持400多家国内外快递商,免费版有使用次数限制。 快递100 - 快递100支持300家国内国际快递,免费版有使用次数限制。 快递100(非官方) - 快递100支持300家国内国际快递。 #非官方 旅游 12306(非官方) - 支持获取12306火车票票数、票价查询。 #非官方 去哪儿 - 支持获取去哪儿网的内容。 途牛 - 支持途牛网的内容,仅开放给供应商系统。 途牛火车票(非官方) - 支持获取途牛火车票票数、票价查询。 #非官方 携程 - 支持携程网的内容。 艺龙 - 支持获取产品数据,完成用户的预订,进行订单查询、更改或取消。提供在线工具,以及H5, Java, C#, PHP, Ruby版本的Demo。 社交 钉钉 - 支持免登,企业通讯录,服务窗,钉盘,地图,会话,DING,电话,音频,扫码,支付,分享等服务,提供SDK和Demo,PC版UI规范,调试工具和钉钉UI组件库。 豆瓣 - 支持图书,电影,音乐,同城,广播,用户,日记,相册,线上活动,论坛,回复和我去等功能,提供豆瓣组件,豆瓣标示和Demo。 开心网 - 支持用户信息,登录授权,好友,传播应用,支付,分享内容,消息,交互,开心网应用等内容,提供SDK,开源插件和标示素材。 QQ互联 - 支持用户资料,QQ会员信息,空间相册,腾讯微博资料,分享到腾讯微博,微博好友信息,财付通信息等内容,提供SDK, Demo, 以及设计资源。 微博 - 支持粉丝服务,微博,评论,用户,关系,账号,收藏,搜索,提醒,短链,公共服务,位置服务,地理信息,地图引擎,支付以及OAuth2.0授权等内容,提供微博标示及SDK。 微信 - 支持移动应用,网站应用,公众账号,公众号第三方平台等内容,提供SDK, Demo, 以及设计资源。 视频 爱奇艺 - 支持弹幕,全色彩播放器,高清码流,视频托管,播放爱奇艺视频,应用分发,IOCP等内容。 Bilibili(非官方) - 支持登录,我的信息,番剧专题,视频/专题收藏、关注,番剧,弹幕等。 #非官方 Bilibili(非官方) - 支持获取Bilibili网站数据。#非官方 乐视 - 支持标准直播,标准点播,视频发行平台,移动直播等内容,提供SDK下载。 内涵段子(非官方) - 支持获取内涵段子中大部分模块信息。 #非官方 搜狐视频 - 支持一二级内容获取,内容分类获取,视频详情信息,专辑详情信息,分级列表获取,关键词搜索等内容。 土豆 - 支持视频模块,豆单模块,影视库模块,用户模块,转帖模块,字段定义模块等内容。 优酷 - 支持内容输出,视频搜索,智能推荐,用户登录,用户互动,用户信息,视频上传至优酷,视频互动等内容,提供SDK。 天气 彩云天气 - 支持全球天气数据,两种空气质量数据,天气预报,实况天气,独家降水预报,独家空气质量预报,六种天气数据,四种生活指数数据等内容,部分功能收费。 和风天气 - 支持7-10天预报,实况天气,每小时预报,生活指数,灾害预警,景点天气,历史天气,城市查询等内容,仅国内数据免费。 魅族天气(非官方) - 支持获取魅族天气。 #非官方 小米天气(非官方) - 支持获取小米天气数据。 #非官方 心知天气 - 支持天气实况,逐日预报和历史,24小时逐小时预报,过去24小时天气历史记录,气象灾害预警,空气质量实况与城市排行,逐日和逐小时空气质量预报,过去24小时空气质量历史记录,生活指数,农历、节气、生肖,机动车尾号限行,日出日落,月初月落和月像,城市搜索等内容,仅国内数据免费。 中央天气预报(非官方) - 支持获取中央天气预报数据。 #非官方 团队协作 Teambition - 支持详细的文档说明,部分平台提供demo。 图片与图像处理 别样网 - 无版权免费大尺寸图片共享平台。 Bing每日壁纸(非官方) - 支持图片URL和图片描述,可获取不同地区的数据。 #非官方 Camera360 - 支持全帧率直播美白滤镜,提供SDK和Demo。 嗨图 - 支持图片标注,仅提供iOS版本SDK。 名片全能王 - 支持精准识别几十种语言的名片,自动切边并美化名片图像,自动返回识别结果,提供多种版本SDK,收费。 pixabay - 在所有的图像和视频Pixabay释放自由版权下创作共用CC0。你可以下载、修改、分发,并使用它们在任何你喜欢的任何东西,即使在商业应用程序中使用它们。不需要归属权。 企业证件识别 - 支持身份证,驾驶证,护照等,收费。 扫描全能王 - 支持图像智能剪裁,五种图像增强模式,手动调节图像细节,自动返回扫描结果等,提供iOS与Android版本SDK,收费。 我知图 - 支持相似图像搜索,图像识别匹配,图像识别关键词推荐,重复图片探测等内容。 银行卡|信用卡识别 - 提供SDK和API,收费。 外卖 百度外卖 - 支持商户,菜品,商品,订单和基础数据等内容,提供SDK和Demo。 大众点评 - 支持商户,团购,在线预定,商品点评,数据统计,元数据等内容。 饿了么 - 支持查询,预定,订单,其他订单,数据推送,支付,评价,活动,账户同步,数据落地同步等内容。 美团外卖 - 支持门店,配送范围,菜品,药品,订单,订单推送等内容。 消息推送 百度云推送 - 支持iOS, Android和服务器端,支持推送,统计,组管理等Rest API接口。服务器端支持Java, Python, PHP, REST API。提供所支持各语言版本的SDK。 华为推送 - 支持Android,提供SDK。 极光 - 支持Android, iOS, WindowsPhone, 服务器端REST API, 提供Java, Python, PHP, Ruby, C#, Node.js等版本的SDK。 LeanCloud - 支持Android, iOS, WindowsPhone和Web网页推送,使用云引擎和JavaScript创建推送,使用REST API推送消息。提供Objectvie-C(开放源码), JavaScript(开放源码), Android, Unity, .Net, WindowsPhone, Java(开放源码), Python(开放源码), PHP(开放源码), C++(开放源码), Swift(开放源码)版本SDK。同时提供Demo。 腾讯信鸽 - 支持iOS和Android平台,服务器端采用Rest API, 同时服务器端支持Java, PHP, Python等语言并提供SDK。 小米 - 支持Android和iOS平台,服务器端支持Java, Python并提供SDK。 友盟 - 支持Android和iOS平台,服务器端支持PHP, Java, Python并提供SDK。 音乐 百度音乐(非官方) - 支持频道歌曲列表,专辑的歌曲列表,歌曲的详细信息,歌手专辑信息,搜索,歌手的所有歌曲,排行榜,所有专辑,所有歌手,歌手的专辑列表,歌手信息,歌词搜索,歌曲文件详细信息。 #非官方 豆瓣音乐 - 支持音乐信息,评论信息,标签信息,搜索音乐,某个音乐中标记最多的标签,发表、修改、删除评论,用户对音乐的所有标签等内容。 考拉FM - 支持获取指定分类下列表和内容,搜索指定关键字内容,专辑/电台/直播详情,指定专辑下列表,指定电台播单,分类下专辑TOP50,指定期(碎片)所在专辑最新分页功能,分类下全部直播计划,版本升级接口,排行榜,精选,传统电台列表/详情/地区等。 酷狗音乐(非官方) - 支持搜索,各种排行榜,歌手专辑信息,下载和获取播放地址。 #非官方 企鹅FM - 支持获取电台分类列表,电台分类下的专辑信息列表,专辑下节目信息列表,电台节目播放链接,搜索关键字相关主播/专辑/节目,主播名下专辑,特定时间段内新增主播/更新的专辑/新增的专辑等。 QQ音乐(非官方) - 支持歌曲榜单,歌曲/歌词地址,歌曲图片。 #非官方 蜻蜓FM - 支持OAuth2.0授权,音频数据中心,分类,点播,直播,临时直播,排行榜,搜索,内容更新状态,主播,此刻,专题,活动等内容。 网易云音乐(非官方) - 支持获取用户歌单,歌单详情,歌曲URL。 #非官方 喜马拉雅FM - 支持Android和iOS平台,并提供相应的SDK和Demo,具体支持内容请下载相关文件查看。 云 阿里云 - 支持弹性计算,数据库,存储与CDN,网络,应用服务,域名与网站等类别的内容,并提供了相关SDK。 百度云 - 支持计算和网络,存储和CDN,数据库,安全和管理,数据分析,智能多媒体服务,物联网服务,人工智能,应用服务,网站服务,数字营销服务等内容,并提供相关的SDK。 Bmob - 支持云数据库,容器服务,消息推送,文件存储,短信验证码,及时通讯,云端逻辑,定时任务,地理位置等。 LeanCloud - 支持云存储,数据分析,用户关系,实时通讯,消息推送,移动统计等。 七牛云 - 支持对象存储,融合CDN,直播云,数据处理等。 腾讯云 - 支持计算,网络,存储与CDN,数据库,安全服务,监控与管理,域名服务,视频服务,大数据与AI等内容,提供相关SDK。 野狗 - 支持实时数据同步,实时视频通话,及时通讯,短信,身份认证等。 语义识别 BosonNLP玻森 - 支持REST API并提供Python SDK。 腾讯文智 - 支持词法类,句法类,篇章类,下载类API,目前平台能识别类别囊括了求职招聘、影视、音乐、健康养生、财经、广告推广、犯罪、政治等90多个类别,且算法支持快速迭代更新已有类别及增加新类别。提供Python SDK。 语音识别 百度语音 - 支持全平台REST API, 离线在线融合模式,深度语义解析,场景识别定制,自定义上传语料、训练模型,基础服务永久免费。提供相应SDK和Demo应用。 搜狗语音云开放平台 - 支持在线/离线语音识别,在线听歌识曲,离线语音合成等内容。提供相应平台SDK。 讯飞开放平台 - 支持语音听写/转写,在线/离线命令词识别,语音唤醒等内容,平台支持广泛,提供相应SDK。 杂志 豆瓣一刻(非官方) - 支持获取指定日期文章列表,栏目总览,推荐作者,作者信息,作者更多文章信息,栏目文章列表及翻页,文章评论及热门评论列表。 #非官方 开眼(非官方) - 支持获取未登录状态下开眼精选、发现、关注信息。 #非官方 One一个(非官方) - 支持获取首页图片,文章,音乐及电影。 #非官方 图虫(非官方) - 支持获取图虫 app 所有信息。 #非官方 一席(非官方) - 支持获取一席主页、演讲、讲者、枝桠等内容 #非官方 知乎日报(非官方) - 支持获取界面启动图像,软件版本查询,最新消息,消息内容获取与离线下载,过往消息,新闻额外消息,新闻对应长/短评论查看,主题日报列表,主题日报内容,热门消息,栏目总览,栏目具体消息,新闻的推荐者,某个专栏之前的新闻,Editor的主页等。 #非官方 知乎专栏(非官方) - 支持获取指定专栏的信息,指定专栏的文章列表,指定的文章内容,评论列表,点赞信息。 #非官方 综合 阿凡达数据 - 支持金融股票,充值认证,便民类,新闻文章,医药交通,科教文艺,创意数据,及时通讯等内容。 阿里大于 - 支持验证码,短信通知,语音通知,流量钱包充值,私密专线,群发助手等内容。 APiX - 支持基础征信数据,信用分析服务,支付缴费接口等数据,部分免费。 百度API STORE - 支持多种类型数据,提供SDK。 HaoService - 支持多种类型数据。 聚合数据 - 支持多种类型数据,部分免费。 通联数据 - 提供金融类数据,支持免费试用。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 相信很多人对枚举并不陌生,枚举可以很方便和直观的管理一组特定值。如果我们在页面上直接输出我们希望匹配的汉语意思或则其他满足我们需求的语句就更好了,当然,通常小伙伴们都会再页面上if(enum==1) “我是一个枚举”或者switch(enum)这种方式解决。 那今天我们就来介绍一种更优雅的解决方法 开整 先定义一个枚举类 enum StatusEnum { [Description("修改")] Update = 1, [Description("新增")] Insert = 2, [Description("删除")] Delete = 3 } Description是属性特性的意思。记住即可大家要记住,所有的特性类必须继承自 Attribute,所以,我们自定义一个特性类 /// <summary> /// 备注特性 /// </summary> public class RemarkAttribute : Attribute { /// <summary> /// 备注 /// </summary> public string Remark { get; set; } public RemarkAttribute(string remark) { this.Remark = remark; } } 有了这个特性类之后呢,我们还需要一个枚举扩展类 /// <summary> /// 枚举扩展类 /// </summary> public static class EnumExtension { /// <summary> /// 获取枚举的备注信息 /// </summary> /// <param name="em"></param> /// <returns></returns> public static string GetRemark(this Enum value) { FieldInfo fi = value.GetType().GetField(value.ToString()); if (fi == null) { return value.ToString(); } object[] attributes = fi.GetCustomAttributes(typeof(RemarkAttribute), false); if (attributes.Length > 0) { return ((RemarkAttribute)attributes[0]).Remark; } else { return value.ToString(); } } public static string GetEnumDescription(this Enum value) { FieldInfo fi = value.GetType().GetField(value.ToString()); DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes.Length > 0) { return attributes[0].Description; } else { return value.ToString(); } } } 需要引入命名空间: using System.Collections.Generic;using System.ComponentModel; 有了这个枚举扩展类,我们就可以直接使用了 Console.WriteLine((int)StatusEnum.Insert);//输出原有枚举值 Console.WriteLine(StatusEnum.Insert.GetRemark());//获取枚举备注信息 Console.WriteLine(StatusEnum.Insert.GetEnumDescription());//获取枚举特性值 调试过程 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。 Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,主要负责创建自己的唯一实例。 class Singleton { private static Singleton instance; //构造方法让其private,这就堵死了外界利用new创建此类实例的可能 private Singleton() { } //此方法是获得本类实例的唯一全局访问点 public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 客户端代码 static void Main(string[] args) { Singleton s1 = Singleton.GetInstance(); Singleton s2 = Singleton.GetInstance(); if (s1 == s2)//比较两次实例化后对象的结果是实例相同 { Console.WriteLine("两个对象是相同的实例"); } Console.Read(); } 多线程时的单例 class Singleton { private static Singleton instance; //程序运行时创建一个只读的进程辅助对象 private static readonly object syncRoot = new object(); //构造方法让其private,这就堵死了外界利用new创建此类实例的可能 private Singleton() { } //此方法是获得本类实例的唯一全局访问点 public static Singleton GetInstance() { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } return instance; } } lock是锁的意思,再同一时刻加了锁的那一部分,只有一个线程可以进入。 但是这样做并不是最好的,程序每次进来都会lock,会比较影响性能,所以我们可以使用双重锁定。 class Singleton { private static Singleton instance; //程序运行时创建一个只读的进程辅助对象 private static readonly object syncRoot = new object(); //构造方法让其private,这就堵死了外界利用new创建此类实例的可能 private Singleton() { } //此方法是获得本类实例的唯一全局访问点 public static Singleton GetInstance() { //先判断实例是否存在,存在直接返回,不存在再进入锁 if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } } 再lock外层增加了一个实例是否存在的判断,看起来程序多用了一个无用判断,其实不然,最外层判断是判断实例是否存在,如果不存在,就加锁创建,如果没有里面一层判断,那当多线程使用时,可能同时两个线程越过第一重判断,进而多次实例化,这并不是我们想要的结果。所以,解决多线程单例的推荐方式是双重锁 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 我们先来看一段基本的数据访问代码,以‘新增用户’和得到用户为例,假设只有ID和Name两个字段,其余省略。 class User { private int _id; public int ID { get { return _id; } set { _id = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } } SqlserverUser类-用于操作User表 public class SqlserverUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } } 客户端代码 static void Main(string[] args) { User user = new User(); SqlserverUser su = new SqlserverUser();//此处与SQL Server耦合 su.Insert(user);//插入用户 su.GetUser(1);//得到ID为1的用户 Console.Read(); } 这里和Sql Server数据库耦合,不能做到灵活的更换数据库,如果下次要换成Mysql或者其他数据库,就非常麻烦了。这里我们可以改进下程序,使用工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。 IUser接口,用于客户端访问,解除于具体数据库访问的耦合 interface IUser { void Insert(User user); User GetUser(int id); } SqlserverUser类,用于访问SQL Server的User public class SqlserverUser:IUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } } AccessUser类,用于访问Access的User class AccessUser : IUser { public void Insert(User user) { Console.WriteLine("在Sql Server中给User表增加一条记录"); } public User GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到User表一条记录"); return null; } } IFactory 接口,定义一个创建访问User表对象的抽象工厂接口 /// <summary> /// 创建访问User表对象的抽象工厂接口 /// </summary> interface IFactory { IUser CreateUser(); } SqlServerFactory类,实现IFactory接口,实例化SqlserverUser /// <summary> /// 实现IFactory接口,实例化SqlserverUser /// </summary> class SqlServerFactory : IFactory { public IUser CreateUser() { return new SqlserverUser(); } } AccessFactory类,实现IFactory接口,实例化AccessUser /// <summary> /// 实现IFactory接口,实例化AccessUser /// </summary> /// <returns></returns> class AccessFactory : IFactory { public IUser CreateUser() { return new AccessUser(); } } 客户端代码 static void Main(string[] args) { User user = new User(); //若要改成Access数据库,只需要将本剧改成 IFactory factory = new AccessFactory(); IFactory factory = new SqlServerFactory(); IUser iu = factory.CreateUser(); iu.Insert(user); iu.GetUser(1); Console.Read(); } 程序到这里依然还存在问题,虽然我们把业务逻辑和数据访问解耦了,但是如果我们数据此时新增其他的表,比如部门表(Department),此时程序应该怎样才会更灵活呢?思考五秒。。。。。 代码结构图如下 IDepartment接口,用于客户端访问,解除于具体数据库访问的耦合 interface IDepartment { void Insert(IDepartment department); Department GetDepartment(int id); } SqlserverDepartment类,用于访问SQL server 的Department. class SqlserverDepartment:IDepartment { public void Insert(Department user) { Console.WriteLine("在Sql Server中给Department表增加一条记录"); } public Department GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到Department表一条记录"); return null; } } AccessDepartment类,用于访问Access的Department class AccessDepartment : IDepartment { public void Insert(Department user) { Console.WriteLine("在Sql Server中给Department表增加一条记录"); } public Department GetUser(int user) { Console.WriteLine("在Sql Server中根据ID得到Department表一条记录"); return null; } } IFactory接口,定义一个创建访问Department表对象的抽象工厂接口。 /// <summary> /// 创建访问表对象的抽象工厂接口 /// </summary> interface IFactory { IUser CreateUser(); IDepartment CreateDepartment();//增加接口方法 } SqlServerFactory类,实现IFactory接口,实例化SqlServerDepartment和SqlServerUser /// <summary> /// 实现IFactory接口,实例化SqlserverUser /// </summary> class SqlServerFactory : IFactory { public IUser CreateUser() { return new SqlserverUser(); } /// <summary> /// 新增SqlserverDepartment工厂 /// </summary> /// <returns></returns> public IDepartment CreateDepartment() { return new SqlserverDepartment(); } } AccessFactory类,实现了IFactory接口,实例化User和Department /// <summary> /// 实现IFactory接口,实例化AccessUser /// </summary> /// <returns></returns> class AccessFactory : IFactory { public IUser CreateUser() { return new AccessUser(); } /// <summary> /// 新增OleDBDepartment工厂 /// </summary> /// <returns></returns> public IDepartment CreateDepartment() { return new AccessDepartment(); } } 客户端代码 static void Main(string[] args) { User user = new User(); Department dept = new Department(); //只需确定实例化哪一个数据库访问对象给factory //IFactory factory = new SqlServerFactory(); IFactory factory = new AccessFactory(); //则此时已于具体的数据库访问接触了依赖 IUser iu = factory.CreateUser(); iu.Insert(user); iu.GetUser(1); //则此时已于具体的数据库访问接触了依赖 IDepartment id = factory.CreateDepartment(); id.Insert(dept); id.GetDepartment(1); Console.Read(); } 此时,我们会发现数据库中有很多个表,和SQL Server和Access又是两大不同的分类,所以解决这种涉及到多产品系列的问题,我们可以使用抽象工厂模式。 抽象工厂模式 AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为他们都有可能有两种不同的实现。就上面的例子来说就是User和Department,而ProductA1,ProductA2和ProductB1,ProductB2就是对两个抽象产品的具体分类实现 ,比如ProductA1可以理解是SqlserverUser,而ProductB1是AccessUser。 IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法,而ConcreateFactory1和ConcreateFactory2就是具体的工厂了,就像 SqlserverFactory和AccessFactory一样。 这样做优点和缺点? 好处:易于交换产品系列,由于是具体工厂类,例如IFactory factory=new AccessFactory,再一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂边的非常容易,它只需要改变具体工厂即可使用不同的产品配置。 第二大好处是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。 缺点:很明显,我们新增一个表改动的地方很多,接口、工厂类,具体实现,这太糟糕了 用简单工厂来改进抽象工厂 去除IFactory/SqlserverFactory和AccessFactory三个类,取而代之的是DataAccess类,用一个简单工厂模式来实现 class DataAccess { private static readonly string db = "Sqlserver"; //private static readonly string db = "Access"; public static IUser CreateUser() { IUser result = null; switch (db) { case "Sqlserver": result = new SqlserverUser(); break; case "Access": result = new AccessUser(); break; } return result; } public static IDepartment CreateUser() { IDepartment result = null; switch (db) { case "Sqlserver": result = new SqlserverDepartment(); break; case "Access": result = new AccessDepartment(); break; } return result; } } 由于db的实现设置,所以swtich中可以根据选择实例化出相应的对象 static void Main(string[] args) { User user = new User(); Department dept = new Department(); //直接得到实际的数据库访问实例,而不存在任何依赖 IUser iu = DataAccess.CreateUser(); iu.Insert(user); iu.GetUser(1); IDepartment id = DataAccess.CreateDepartment(); id.Insert(dept); id.GetDepartment(1); Console.Read(); } 这里用简单工厂来实现了,我们抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,客户端没有出现任何一个Sqlserver 和Access的字样,达到了解耦的目的。 不过此时还不是最完美的,因为我们需要增加Oracle的话,现在需要在DataAccess类中每个方法的swicth中加case了。 反射+抽象工厂 上述问题的关键在于我们如何去解决switch的问题,可以使用依赖注入(Dependency Injection),本来依赖注入需要专门的IoC容器提供,比如:Spring.NET,显然这个程序不需要这么麻烦。 程序引用:using System.Reflection 就可以使用反射帮我们克服抽象工厂模式的先天不足了。 DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory. class DataAccess { private static readonly string AssemblyName = "程序集名称"; private static readonly string db = "Sqlserver";//数据库名称,可以替换成Access public static IUser CreateUser() { string className = AssemblyName + "." + db + "User"; return (IUser)Assembly.Load(AssemblyName).CreateInstance(className); } public static IDepartment CreateDepartment() { string className = AssemblyName + "." + db + "User"; return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className); } } 用反射+配置文件实现数据库访问 最后得到执行结果: 总结:所有简单工厂的地方,都可以考虑用反射技术取出swtich或if,接触分支判断带来的耦合。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 1、需求 需求很简单,就是在C#开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。 解决方案 2.1、简单原理说明 使用列队先缓存到内存,然后我们一直有个线程再从列队中写到磁盘上,这样就可以高速高性能的写日志了。因为速度慢的地方我们分离出来了,也就是说程序在把日志扔给列队后,程序的日志部分就算完成了,后面操作磁盘耗时的部分程序是不需要关心的,由另一个线程操作。 俗话说,鱼和熊掌不可兼得,这样会有一个问题,就是如果日志已经到列队了这个时候程序崩溃或者电脑断电都会导致日志部分丢失,但是有些地方为了高性能的写日志,是否可以忽略一些情况,请各位根据情况而定。 2.2、示例图 关键代码部分 这里写日志的部分LZ选用了比较常用的log4net,当然也可以选择其他的日志组件,比如nlog等等。 3.1、日志至列队部分 第一步我们首先需要把日志放到列队中,然后才能从列队中写到磁盘上。 public void EnqueueMessage(string message, FlashLogLevel level, Exception ex = null) { if ((level == FlashLogLevel.Debug && _log.IsDebugEnabled) || (level == FlashLogLevel.Error && _log.IsErrorEnabled) || (level == FlashLogLevel.Fatal && _log.IsFatalEnabled) || (level == FlashLogLevel.Info && _log.IsInfoEnabled) || (level == FlashLogLevel.Warn && _log.IsWarnEnabled)) { _que.Enqueue(new FlashLogMessage { Message = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") + "]\r\n" + message, Level = level, Exception = ex }); // 通知线程往磁盘中写日志 _mre.Set(); } } _log是log4net日志组件的ILog,其中包含了写日志,判断日志等级等功能,代码开始部分的if判断就是判断等级和现在的日志等级做对比,看是否需要写入列队,这样可以有效的提高日志的性能。 其中的_que是ConcurrentQueue列队。_mre是ManualResetEvent信号,ManualResetEvent是用来通知线程列队中有新的日志,可以从列队中写入磁盘了。当从列队中写完日志后,重新设置信号,在等待下次有新的日志到来。 3.2、列队到磁盘 从列队到磁盘我们需要有一个线程从列队写入磁盘,也就是说我们在程序启动时就要加载这个线程,比如asp.net中就要在global中的Application_Start中加载。 /// <summary> /// 另一个线程记录日志,只在程序初始化时调用一次 /// </summary> public void Register() { Thread t = new Thread(new ThreadStart(WriteLog)); t.IsBackground = false; t.Start(); } /// <summary> /// 从队列中写日志至磁盘 /// </summary> private void WriteLog() { while (true) { // 等待信号通知 _mre.WaitOne(); FlashLogMessage msg; // 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容 while (_que.Count > 0 && _que.TryDequeue(out msg)) { // 判断日志等级,然后写日志 switch (msg.Level) { case FlashLogLevel.Debug: _log.Debug(msg.Message, msg.Exception); break; case FlashLogLevel.Info: _log.Info(msg.Message, msg.Exception); break; case FlashLogLevel.Error: _log.Error(msg.Message, msg.Exception); break; case FlashLogLevel.Warn: _log.Warn(msg.Message, msg.Exception); break; case FlashLogLevel.Fatal: _log.Fatal(msg.Message, msg.Exception); break; } } // 重新设置信号 _mre.Reset(); Thread.Sleep(1); } } 完整代码 using log4net; using log4net.Config; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Emrys.FlashLog { public sealed class FlashLogger { /// <summary> /// 记录消息Queue /// </summary> private readonly ConcurrentQueue<FlashLogMessage> _que; /// <summary> /// 信号 /// </summary> private readonly ManualResetEvent _mre; /// <summary> /// 日志 /// </summary> private readonly ILog _log; /// <summary> /// 日志 /// </summary> private static FlashLogger _flashLog = new FlashLogger(); private FlashLogger() { var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config")); if (!configFile.Exists) { throw new Exception("未配置log4net配置文件!"); } // 设置日志配置文件路径 XmlConfigurator.Configure(configFile); _que = new ConcurrentQueue<FlashLogMessage>(); _mre = new ManualResetEvent(false); _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); } /// <summary> /// 实现单例 /// </summary> /// <returns></returns> public static FlashLogger Instance() { return _flashLog; } /// <summary> /// 另一个线程记录日志,只在程序初始化时调用一次 /// </summary> public void Register() { Thread t = new Thread(new ThreadStart(WriteLog)); t.IsBackground = false; t.Start(); } /// <summary> /// 从队列中写日志至磁盘 /// </summary> private void WriteLog() { while (true) { // 等待信号通知 _mre.WaitOne(); FlashLogMessage msg; // 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容 while (_que.Count > 0 && _que.TryDequeue(out msg)) { // 判断日志等级,然后写日志 switch (msg.Level) { case FlashLogLevel.Debug: _log.Debug(msg.Message, msg.Exception); break; case FlashLogLevel.Info: _log.Info(msg.Message, msg.Exception); break; case FlashLogLevel.Error: _log.Error(msg.Message, msg.Exception); break; case FlashLogLevel.Warn: _log.Warn(msg.Message, msg.Exception); break; case FlashLogLevel.Fatal: _log.Fatal(msg.Message, msg.Exception); break; } } // 重新设置信号 _mre.Reset(); Thread.Sleep(1); } } /// <summary> /// 写日志 /// </summary> /// <param name="message">日志文本</param> /// <param name="level">等级</param> /// <param name="ex">Exception</param> public void EnqueueMessage(string message, FlashLogLevel level, Exception ex = null) { if ((level == FlashLogLevel.Debug && _log.IsDebugEnabled) || (level == FlashLogLevel.Error && _log.IsErrorEnabled) || (level == FlashLogLevel.Fatal && _log.IsFatalEnabled) || (level == FlashLogLevel.Info && _log.IsInfoEnabled) || (level == FlashLogLevel.Warn && _log.IsWarnEnabled)) { _que.Enqueue(new FlashLogMessage { Message = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") + "]\r\n" + message, Level = level, Exception = ex }); // 通知线程往磁盘中写日志 _mre.Set(); } } public static void Debug(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Debug, ex); } public static void Error(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Error, ex); } public static void Fatal(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Fatal, ex); } public static void Info(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Info, ex); } public static void Warn(string msg, Exception ex = null) { Instance().EnqueueMessage(msg, FlashLogLevel.Warn, ex); } } /// <summary> /// 日志等级 /// </summary> public enum FlashLogLevel { Debug, Info, Error, Warn, Fatal } /// <summary> /// 日志内容 /// </summary> public class FlashLogMessage { public string Message { get; set; } public FlashLogLevel Level { get; set; } public Exception Exception { get; set; } } } View Code 性能对比和应用 4.1、性能对比 经过测试发现 使用原始的log4net写入日志100000条数据需要:19104毫秒。 同样数据使用列队方式只需要251毫秒。 应用 4.2.1、需要在程序启动时注册,如asp.net 程序中在Global.asax中的Application_Start注册。 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); FlashLogger.Instance().Register(); } } 4.2.2、在需要写入日志的地方直接调用FlashLogger的静态方法即可。 FlashLogger.Debug("Debug"); FlashLogger.Debug("Debug", new Exception("testexception")); FlashLogger.Info("Info"); FlashLogger.Fatal("Fatal"); FlashLogger.Error("Error"); FlashLogger.Warn("Warn", new Exception("testexception")); 代码开源 前去 Git下载 本文为转载文章,原文地址:http://www.cnblogs.com/emrys5/p/flashlog.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 回顾上篇的设计模式之-简单工厂模式 我们可以从中发现一些问题。 先看看以计算器为例的简单工厂的结构图如下: 那此时我们换成工厂模式呢?我们先看看工厂的结构图: 承接上篇计算器为例,我们需要新建一个工厂接口 /// <summary> /// 构建一个工厂接口 /// </summary> interface IFactory { Operaion CreateOperation(); } 然后呢,为加减乘法各建一个具体的工厂实现接口 //然后加减乘法各建一个具体的工厂实现接口 class AddFactory : IFactory { public Operaion CreateOperation() { return new OpertionAdd(); } } class SubFactory : IFactory { public Operaion CreateOperation() { return new OperationSub(); } } class MulFactory : IFactory { public Operaion CreateOperation() { return new OperationMul(); } } class DivFactory : IFactory { public Operaion CreateOperation() { return new OperationDiv(); } } 客户端调用 static void Main(string[] args) { try { Console.Write("请输入数字A:"); string strNumberA = Console.ReadLine(); Console.Write("请选择运算符号(+、-、*、/):"); string strOperate = Console.ReadLine(); Console.Write("请输入数字B:"); string strNumberB = Console.ReadLine(); //调用工厂进行计算 IFactory operFactory = new AddFactory(); Operaion oper = operFactory.CreateOperation(); oper.NumberA =double.Parse(strNumberA); oper.NumberB =double.Parse(strNumberB); //返回计算结果 var strResult = oper.GetResult(); Console.WriteLine("结果是:" + strResult); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine("您的输入有错:" + ex.Message); } } Why?为什么要这么写呢?上一篇的简单工厂已经很容易就实现了这个效果,为什么还要新加这个工厂来“复杂”化这个问题呢? 其实不然,简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了具体产品的依赖。 就像计算器,客户端不用管该用哪个类的实例,只需要把"+"给工厂,工厂自动就给出了相应的实例,客户端只要去做运算就可以了。但问题也在这里,如果要加一个‘求M数的N次方’功能,我们是一定要给运算工厂类的方法里加‘Case’分支条件的,既是修改了原有的类。这样就违背了我们开放-封闭原则。于是工厂方法就来了 这样的话,我们要增加‘求M数的N次方’的功能时,就不需要更改原有的工厂类了,只需要增加此功能的运算类和相应的工厂类就可以了。这就完全符合开放-封闭原则。 巩固 以大学生学习‘雷锋’好人好事为例。首先是雷锋类:拥有扫地、洗衣、买米等方法 //雷锋类 class LeiFeng { public void Sweep() { Console.WriteLine("扫地"); } public void Wash() { Console.WriteLine("洗衣"); } public void BuyRice() { Console.WriteLine("买米"); } } 然后‘学雷锋大学生’类继承‘雷锋’类 /// <summary> /// 学雷锋的大学生,继承‘雷锋’ /// </summary> class Undergraduate : LeiFeng { } 然后客户端实现 LeiFeng xueleifeng = new Undergraduate(); xueleifeng.BuyRice(); xueleifeng.Sweep(); xueleifeng.Wash(); 有一个问题,如果有三个人去学‘雷锋做好事,该怎么写呢?’ 难道是写成? LeiFeng xueleifeng = new Undergraduate(); xueleifeng.BuyRice(); LeiFeng xueleifeng2 = new Undergraduate(); xueleifeng.Sweep(); LeiFeng xueleifeng3 = new Undergraduate(); xueleifeng.Wash(); 此时,就非常不合适了,老人不需要知道是谁来做好事,他只需要知道学雷锋的人来帮忙就可以ile,这样写我们需要更改多个实例化的地方。我们应该增加‘社区志愿者’类 /// <summary> /// 社区志愿者 /// </summary> class Volunteer : LeiFeng { } 再写简单工厂类 /// <summary> /// 简单雷锋工厂 /// </summary> class SimpleFactory { public static LeiFeng CreateLeiFeng(string type) { LeiFeng result = null; switch (type) { case "学雷锋的大学生": result = new Undergraduate(); break; case "社区志愿者": result=new Volunteer(); break; } return result; } } 客户端的代码,如果要换,就只需要换‘学雷锋的大学生为‘社区志愿者’’ //简单工厂模式 LeiFeng studentA = SimpleFactory.CreateLeiFeng("学雷锋的大学生"); studentA.BuyRice(); LeiFeng studentB = SimpleFactory.CreateLeiFeng("学雷锋的大学生"); studentA.Sweep(); LeiFeng studentC = SimpleFactory.CreateLeiFeng("学雷锋的大学生"); studentA.Wash(); 好,此时我们就发现,在任何实例化的时候写出这个工厂的代码,这里有重复,就出现了坏的味道,此时,我们再用工厂模式写一遍。 /// <summary> /// 雷锋工厂 /// </summary> interface IFactory { LeiFeng CreateLeiFeng(); } /// <summary> /// 学雷锋的大学生工厂 /// </summary> class UndergraduateFacotry : IFactory { public LeiFeng CreateLeiFeng() { return new Undergraduate(); } } /// <summary> /// 社区志愿者工厂 /// </summary> class VolunteerFactory : IFactory { public LeiFeng CreateLeiFeng() { return new Volunteer(); } } 客户端调用的时候只需要如下就可以了 //工厂方法模式 IFactory factory = new UndergraduateFacotry();//要换成‘社区志愿者’,修改这里即可 LeiFeng student = factory.CreateLeiFeng(); student.BuyRice(); student.Sweep(); student.Wash(); 小结 工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点。 但以上并不是最佳做法,利用‘反射’可以解决避免分支判断的问题。 未完待更新。。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 为了理解和学习简单工厂模式,我们先看一段简单计算器的代码 class Program { static void Main(string[] args) { Console.Write("请输入数字A:"); string A = Console.ReadLine(); Console.Write("请选择运算符号(+、-、*、/):"); string B = Console.ReadLine(); Console.Write("请输入数字B:"); string C = Console.ReadLine(); string D = ""; if (B == "+") D = Convert.ToString(Convert.ToDouble(A) + Convert.ToDouble(C)); if (B == "-") D = Convert.ToString(Convert.ToDouble(A) - Convert.ToDouble(C)); if (B == "*") D = Convert.ToString(Convert.ToDouble(A) * Convert.ToDouble(C)); if (B == "/") D = Convert.ToString(Convert.ToDouble(A) / Convert.ToDouble(C)); Console.WriteLine("结果是:" + D); Console.ReadKey(); } } 以上代码存在几点明显问题 ①A、B、C、D这样的命名非常不规范,真实项目中应该避免使用 ②if判断分支,让计算机多做了三次无用功 ③除数的时候如果用户输入了非正数及符号,没有相关处理。 根据上述三点问题进行优化后的代码如下: class Program { static void Main(string[] args) { try { Console.Write("请输入数字A:"); string strNumberA = Console.ReadLine(); Console.Write("请选择运算符号(+、-、*、/):"); string strOperate = Console.ReadLine(); Console.Write("请输入数字B:"); string strNumberB = Console.ReadLine(); string strResult = ""; switch (strOperate) { case "+": strResult = Convert.ToString(Convert.ToDouble(strNumberA) + Convert.ToDouble(strNumberB)); break; case "-": strResult = Convert.ToString(Convert.ToDouble(strNumberA) - Convert.ToDouble(strNumberB)); break; case "*": strResult = Convert.ToString(Convert.ToDouble(strNumberA) * Convert.ToDouble(strNumberB)); break; case "/": if (strNumberB != "0") strResult = Convert.ToString(Convert.ToDouble(strNumberA) / Convert.ToDouble(strNumberB)); else strResult = "除数不能为0"; break; } Console.WriteLine("结果是:" + strResult); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine("您的输入有错:" + ex.Message); } } } 就上述代码而言,的确实现了简单计算器的功能,但是我们进一步思考这段代码,如果此时 需要新做一个计算器呢?估计有很多小伙伴灵光一现,复制粘贴大法。我们回想下古代活字印刷术的出现,对于印刷的巨大帮助。 ⑴ 要改内容,只需要改动要改的文字,此为可维护; ⑵这些字并非用完这次就无用了,以后的印刷中可以重复使用,此乃可复用; ⑶若此版面内容要加字,只需要另刻字加入即可,这是可扩展。 ⑷字的排列可能是竖排,可能是横排,此时只需要将活字移动就可做到满足排列需求,此是灵活性好。 而再活字印刷术出现之前,以上四点特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本后,此版已无任何可再利用价值。 面向对象的好处 业务的封装(让业务逻辑与界面逻辑分开,降低耦合度) 根据上述思考,我们将运算单独封装一个运算类 Operation /// <summary> /// 运算类 /// </summary> public class Operaion { /// <summary> /// 计算方法 /// </summary> /// <param name="numberA">运算数</param> /// <param name="numberB">运算数</param> /// <param name="operate">符号</param> /// <returns>返回值</returns> public static double GetResult(double numberA, double numberB, string operate) { double result = 0d; switch (operate) { case "+": result = numberA + numberB; break; case "-": result = numberA - numberB; break; case "*": result = numberA * numberB; break; case "/": result = numberA / numberB; break; } return result; } } 客户端调用的时候调用此方法即可: class Program { static void Main(string[] args) { try { Console.Write("请输入数字A:"); string strNumberA = Console.ReadLine(); Console.Write("请选择运算符号(+、-、*、/):"); string strOperate = Console.ReadLine(); Console.Write("请输入数字B:"); string strNumberB = Console.ReadLine(); string strResult = ""; //调用运算类中计算方法 strResult = Convert.ToString(Operaion.GetResult(Convert.ToDouble(strNumberA), Convert.ToDouble(strNumberB), strOperate)); Console.WriteLine("结果是:" + strResult); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine("您的输入有错:" + ex.Message); } } } 上述代码实现了业务逻辑与界面的分离,但是还没有真正运用到程序的精髓所在:封装、继承、多态,带着思考和疑问,我们做出进一步修改如下: /// <summary> /// 运算类 /// </summary> public class Operaion { private double _numberA = 0; private double _numberB = 0; public double NumberA { get { return _numberA; } set { _numberA = value; } } public double NumberB { get { return _numberB; } set { _numberB = value; } } public virtual double GetResult() { double result = 0; return result; } } /// <summary> /// 加法类,继承运算类 /// </summary> class OpertionAdd : Operaion { public override double GetResult() { double result = 0; result = NumberA + NumberB; return result; } } /// <summary> /// 减法类,继承运算类 /// </summary> class OperationSub : Operaion { public override double GetResult() { double result = 0; result = NumberA - NumberB; return result; } } /// <summary> /// 乘法类,继承运算类 /// </summary> class OperationMul : Operaion { public override double GetResult() { double result = 0; result = NumberA * NumberB; return result; } } /// <summary> ///除法类,继承运算类 /// </summary> class OperationDiv : Operaion { public override double GetResult() { double result = 0; if (NumberB == 0) throw new Exception("除数不能为0."); result = NumberA / NumberB; return result; } } 上述代码写完,此时就有一个疑问,如何让计算器知道我们希望用哪一个算法呢? 下面我们看看简单运算工厂类 public class OperationFactory { public static Operaion createOperate(string operate) { Operaion oper = null; switch (operate) { case "+": oper = new OpertionAdd(); break; case "-": oper = new OperationSub(); break; case "*": oper = new OperationMul(); break; case "/": oper = new OperationDiv(); break; } return oper; } } 有了运算工厂类,我们只需要输入运算符号,工厂就能实例化适合的对象,通过多态,返回父类的方法实现了计算器的结果,客户端代码如下: class Program { static void Main(string[] args) { try { Console.Write("请输入数字A:"); string strNumberA = Console.ReadLine(); Console.Write("请选择运算符号(+、-、*、/):"); string strOperate = Console.ReadLine(); Console.Write("请输入数字B:"); string strNumberB = Console.ReadLine(); //调用工厂类进行计算 Operaion oper; oper = OperationFactory.createOperate(strOperate); oper.NumberA =double.Parse(strNumberA); oper.NumberB =double.Parse(strNumberB); //返回计算结果 var strResult = oper.GetResult(); Console.WriteLine("结果是:" + strResult); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine("您的输入有错:" + ex.Message); } } } 这样,不管我们是控制台程序、Windows程序,Web程序或者手机程序,都可以使用这段代码来实现计算器的功能,如果有一天我们需要更改加法运算,我们改OperationAdd就可以了,如果需要增加其他复杂运算,比如平方根、立方根,我们只需要增加相应的运算子类和运算类工厂,再switch中增加分支即可。 UML类图 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 此篇讲到的是图片上传功能,每个网站必定会有这样类似的功能,上传文件、上传图片等等。那么接下来,看看我们EF+uploadfile+ftp如何玩转上传图片吧 效果预览 具体实现 一个简单数据库 只有一个主键Id,一个身份证正面路径和一个身份证背面路径三个字段。 首先呢,我们把实体类新建好如下: public class ImageModel:BaseEntity { /// <summary> /// 用户Id /// </summary> public int ID { get; set; } /// <summary> ///身份证正面相对路径 /// </summary> public string IDProofFront { get; set; } /// <summary> ///身份证背面相对路径 /// </summary> public string IDProofBack { get; set; } } 其中 我们将身份信息实体继承自BaseEntity,我们看看BaseEntity里面是什么东东,代码如下: public abstract partial class BaseEntity { public override bool Equals(object obj) { return Equals(obj as BaseEntity); } private Type GetUnproxiedType() { return GetType(); } public virtual bool Equals(BaseEntity other) { if (other == null) return false; if (ReferenceEquals(this, other)) return true; return false; } public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(BaseEntity x, BaseEntity y) { return Equals(x, y); } public static bool operator !=(BaseEntity x, BaseEntity y) { return !(x == y); } } 这里,我们将BaseEntity定义成一个抽象类,里面包含一些静态方法和重载方法 ======================回到HTML======= 我们先回过头来讲页面,上面演示的是一个很简单的单页面,HTML代码如下: <form enctype="multipart/form-data" id="form" action="/Home/UpLoadImage" method="post"> <div class="full_w" style="margin-top: 100px; margin-left: 30%; width: 800px;"> <div class="h_title">&nbsp;<b>用户上传的文件</b></div> <div class="entry"> 步骤: <span class="red" style="color: red">(上传资料必须是bmp,gif,jpg,jpeg,png类型,不能大于2M)</span> <ol> <li>先按『选择』键选择上传文件;</li> <li>按『上传』键上传文件;</li> <li>按『保存』键保存文件;</li> </ol> </div> <div class="entry"> <div class="sep"></div> </div> <div class="entry"> <div id="wrapper" style="text-align: center; position: relative;"> <div class="form-group"> <input id="uploadfile" type="file" value="浏览..." class="file" name="FileName" data-upload-url="#" style="position: absolute; top: 0; right: 0; min-width: 100%; min-height: 100%; text-align: right; opacity: 0; background: none repeat scroll 0 0 transparent; cursor: inherit; display: block;" /> </div> </div> </div> <table> <tbody> <tr> <td class="entry">身份证正面</td> <td> @if (Model == null || Model.ID == null || string.IsNullOrEmpty(Model.IDProofFront)) { <a href="javascript:void(0);" target="_blank" class="winView"> <img style="border: none; width: 150px; height: 100px" src="/img/noupload.png" /> </a> } else { <a href="@(ViewBag.pathSrc + Model.IDProofFront)" target="_blank"class="winView" > <img style="border: none; width: 150px; height: 100px" src="@(ViewBag.pathSrc + Model.IDProofFront)" /> </a> } @Html.HiddenFor(m => m.IDProofFront) @Html.HiddenFor(m => m.ID) </td> <td> <a href="javascript:void(0)" class="easyui-linkbutton btnFinleUP" data-op="1" data-type="image">上传</a> </td> </tr> <tr> <td class="entry">身份证背面</td> <span id="lblinfosi" style="color: Green"></span> <td> @if (Model == null || Model.ID == null || string.IsNullOrEmpty(Model.IDProofBack)) { <a href="javascript:void(0);" target="_blank" class="winView"> <img style="border: none; width: 150px; height: 100px" src="/img/noupload.png" /> </a> } else { <a href="@(ViewBag.pathSrc + Model.IDProofBack)" target="_blank" class="winView" > <img style="border: none; width: 150px; height: 100px" src="@(ViewBag.pathSrc + Model.IDProofBack)" /> </a> } @Html.HiddenFor(m => m.IDProofBack) </td> <td> <a href="javascript:void(0)" class="easyui-linkbutton btnFinleUP" data-op="2" data-type="image">上传</a> </td> </tr> </tbody> </table> <div class="entry"> <button class="button" name="btnSaveAll" value="保存" id="btnSaveAll" style="height: 30px; width: 80px; text-align: center;">保存</button> <a href="/Home/Index" style="height: 30px; text-align: center; width: 80px; background: #ffffff; border: 1px solid #DCDCDC; border-radius: 2px; color: #444444; cursor: pointer; display: inline-block; font: 700 11px Tahoma, Arial, sans-serif; margin-right: 10px; padding: 7px 12px 7px 12px; position: relative; text-decoration: none; text-shadow: 0px 1px 0px #FFFFFF;">返回</a> </div> </div> </form> 现在我们看页面将会毫无样式,所以我们先引用下样式,这里引用了bootstrap 和一个 style2.css ,引入样式后 界面如下: 其中,关于选择框是用了一个js单独封装起来了,是代码中的zh.js,代码如下: /*! * FileInput Chinese Translations * * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or * any HTML markup tags in the messages must not be converted or translated. * * @see http://github.com/kartik-v/bootstrap-fileinput * @author kangqf <kangqingfei@gmail.com> * * NOTE: this file must be saved in UTF-8 encoding. */ (function ($) { "use strict"; $.fn.fileinputLocales['zh'] = { fileSingle: '文件', filePlural: '个文件', browseLabel: '选择 &hellip;', removeLabel: '移除', removeTitle: '清除选中文件', cancelLabel: '取消', cancelTitle: '取消进行中的上传', uploadLabel: '上传', uploadTitle: '上传选中文件', msgNo: '没有', msgNoFilesSelected: '', msgCancelled: '取消', msgZoomModalHeading: '详细预览', msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.', msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.', msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ', msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.', msgFileNotFound: '文件 "{name}" 未找到!', msgFileSecured: '安全限制,为了防止读取文件 "{name}".', msgFileNotReadable: '文件 "{name}" 不可读.', msgFilePreviewAborted: '取消 "{name}" 的预览.', msgFilePreviewError: '读取 "{name}" 时出现了一个错误.', msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".', msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.', msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.', msgFileTypes: { 'image': 'image', 'html': 'HTML', 'text': 'text', 'video': 'video', 'audio': 'audio', 'flash': 'flash', 'pdf': 'PDF', 'object': 'object' }, msgUploadAborted: '该文件上传被中止', msgUploadThreshold: 'Processing...', msgUploadEmpty: 'No valid data available for upload.', msgValidationError: '验证错误', msgLoading: '加载第 {index} 文件 共 {files} &hellip;', msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.', msgSelected: '{n} {files} 选中', msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.', msgImageWidthSmall: '宽度的图像文件的"{name}"的必须是至少{size}像素.', msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.', msgImageWidthLarge: '宽度的图像文件"{name}"不能超过{size}像素.', msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.', msgImageResizeError: '无法获取的图像尺寸调整。', msgImageResizeException: '错误而调整图像大小。<pre>{errors}</pre>', msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!', msgAjaxProgressError: '{operation} failed', ajaxOperations: { deleteThumb: 'file delete', uploadThumb: 'single file upload', uploadBatch: 'batch file upload', uploadExtra: 'form data upload' }, dropZoneTitle: '拖拽文件到这里 &hellip;<br>支持多文件同时上传', dropZoneClickTitle: '<br>(或点击{files}按钮选择文件)', fileActionSettings: { removeTitle: '删除文件', uploadTitle: '上传文件', zoomTitle: '查看详情', dragTitle: '移动 / 重置', indicatorNewTitle: '没有上传', indicatorSuccessTitle: '上传', indicatorErrorTitle: '上传错误', indicatorLoadingTitle: '上传 ...' }, previewZoomButtonTitles: { prev: '预览上一个文件', next: '预览下一个文件', toggleheader: '缩放', fullscreen: '全屏', borderless: '无边界模式', close: '关闭当前预览' } }; })(window.jQuery); View Code 好了,界面大概就整成这样子,现在需要我们实现功能了。首先用JS将上传控件初始化一下: //上传控件初始化 function initFileUpload() { $("#uploadfile").fileinput({ //uploadExtraData: { kvId: '10' }, language: 'zh', //设置语言 showUpload: false, //是否显示上传按钮 uploadAsync: true, //默认异步上传 showRemove: false, autoReplace: true, maxFileCount: 1, maxFileSize: 10240, dropZoneTitle: '拖拽文件到这里 &hellip;<br>仅限.pdf, .jpg, .jpeg, .gif', enctype: 'multipart/form-data', fileActionSettings: { uploadClass: "hidden", zoomClass: "hidden", removeClass: "hidden" }, allowedFileExtensions: ['jpg', 'png', 'gif', 'pdf'],//接收的文件后缀 msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!", uploadUrl: "/FileUpload/FileUpLoad", //上传的地址 }).on("filebatchselected", function (event, files) { $(".file-preview-success").remove(); }) $("#uploadfile").on("fileuploaded", function (event, data, previewId, index) { console.log("accountData 初始化后 FileOpt" + accountData); console.log("accountData.FileOpt " + accountData.FileOpt); var obj = data.response; if (obj.Status != 500 && obj.Data != undefined) { var src = accountData.PathSrc + obj.Data.FileName; showName = obj.Data.FileName; //alert(showName); var pId = accountData.PId; var fileObj = undefined; var field = ""; var isPdf = false; debugger; if (accountData.FileOpt == 1) { fileObj = $("#IDProofFront"); //$("#PersonInfo_OldIDFile").val(obj.Data.FileName); field = "IDProofFront"; fileObj.val(obj.Data.FileName); } else if (accountData.FileOpt == 2) { fileObj = $("#IDProofBack"); field = "IDProofBack"; $("#IDProofBack").val(showName); fileObj.val(obj.Data.FileName); } //fileObj = $("#IDProofFront"); //$("#IDProofFront").val(obj.Data.FileName); //field = "IDProofFront"; //fileObj.val(obj.Data.FileName); fileObj.prev().attr("href", src); src = isPdf == true ? "/Content/images/PDF.png" : src; fileObj.prev().find("img").attr("src", src); } else { console.error(obj.Data); } }); $('#uploadfile').on('filesuccessremove', function (event, id) { }); $('#uploadfile').on('fileerror', function (event, data, msg) { }); //上传 $(".btnFinleUP").click(function () { var fileName = $("#uploadfile").val(); var obj = document.getElementById("uploadfile"); var type = $(this).attr("data-op"); //alert("当前点击的type是:" + type); var fileType = $(this).attr("data-type"); var files = $("#uploadfile").fileinput("getFileStack"); if (files.length == 0) { layer.msg('请选择要上传的文件', function () { }); return; } var array = fileType.split(","); var selectType = files[0].type.toLowerCase(); var falg = false; for (var i = 0; i < array.length; i++) { if (selectType.indexOf(array[i]) == -1) { falg = false; } else falg = true; if (falg) break; } if (!falg) { layer.msg('只能选择' + fileType + ' 类型的文件', function () { }); return; } accountData.FileOpt = type; $("#uploadfile").fileinput("upload"); }); } View Code 然后再 加载页面的时候调用这个初始化即可 $(function () { initFileUpload(); }) FTP上传操作 注意,再initFileUpload方法中 上传了图片,会自动访问uploadUrl 这个url地址,存放图片,我们先看看这个action如何通过ftp上传指定服务器的。 FileUpLoad方法如下 /// <summary> /// 通过ftp上传指定服务器 /// </summary> /// <returns></returns> public ActionResult FileUpLoad() { bool flag = false; string msg = string.Empty; int size = Convert.ToInt16(_fileSize) * 1024 * 1024; try { Dictionary<string, string> fileDict = new Dictionary<string, string>(); for (int i = 0; i < Request.Files.Count; i++) { HttpPostedFileBase file = Request.Files[i]; string extension = Path.GetExtension(file.FileName); string[] fileExtensions = _fileExtension.Split(';'); if (fileExtensions.Any(o => o.Equals(extension, StringComparison.OrdinalIgnoreCase))) { if (file.ContentLength <= size) { string fileName = string.Format("{0}_{1}", DateTime.Now.ToString("yyyyMMddHHmmssfff"), Path.GetFileName(file.FileName)); if (file.ContentLength <= 10 * 1024 * 1024) { byte[] buffer = new byte[file.ContentLength]; file.InputStream.Read(buffer, 0, file.ContentLength); flag = FileUpLoad(buffer, file.FileName, out fileName, out msg); } else//图片压缩有问题>> { var stream = ImageHelper.GetPicThumbnail(file.InputStream, 40); byte[] buffer = new byte[stream.Length]; stream.Read(buffer, 0, (int)stream.Length); flag = FileUpLoad(buffer, file.FileName, out fileName, out msg); } fileDict.Add(Request.Files.AllKeys[i], fileName); } else { msg = string.Format("上传文件不能大于{0}M", _fileSize); } } else { msg = string.Format("上传的文件类型不正确"); } } return Json(new { Result = "0", MSG = "" + msg, Data = fileDict }); } catch (Exception ex) { return Json(new { Result = "0", MSG = "网络异常,请稍后再试" }); } } View Code 其中 _fileExtension _filePath _fileSize 是分别从配置文件中读取出来的如下: private static string _fileExtension = ConfigurationManager.AppSettings["FileType"]; private readonly string _filePath = ConfigurationManager.AppSettings["UploadPath"]; private readonly string _fileSize = ConfigurationManager.AppSettings["FileSizem"]; 方法中有一个 FileUpLoad 上传文件的方法 如下: /// <summary> /// 上传文件 /// </summary> /// <param name="fileBytes"></param> /// <param name="originalName"></param> /// <param name="msg"></param> /// <returns></returns> protected bool FileUpLoad(byte[] fileBytes, string originalName, out string newFileName, out string msg) { msg = ""; newFileName = ""; try { FTPUpFile ftp = new FTPUpFile(); newFileName = ftp.UpFile(fileBytes, originalName); if (string.IsNullOrEmpty(newFileName)) { msg = "上传文件时出错!"; return false; } return true; } catch (Exception ex) { msg = ex.Message; return false; } } 其中 FTPUpFile 是一个ftp上传文件帮助类,大家可以直接照搬 ,代码如下: /// <summary> /// FTP上传文件 /// </summary> public class FTPUpFile { string Filetype = ConfigurationManager.AppSettings["FileType"]; string ipaddress = ConfigurationManager.AppSettings["IPaddress"]; string Username = ConfigurationManager.AppSettings["UserName"]; string Password = ConfigurationManager.AppSettings["Password"]; /// <summary> /// FTP上传文件 /// </summary> /// <param name="filename">上传文件路径</param> /// <param name="ftpServerIP">FTP服务器的IP和端口</param> /// <param name="ftpPath">FTP服务器下的哪个目录</param> /// <param name="ftpUserID">FTP用户名</param> /// <param name="ftpPassword">FTP密码</param> public bool Upload(string filename, string ftpServerIP, string ftpPath, string ftpUserID, string ftpPassword) { FileInfo fileInf = new FileInfo(filename); string uri = "ftp://" + ftpServerIP + "/" + ftpPath + "/" + fileInf.Name; try { FtpWebRequest reqFTP = (FtpWebRequest)FtpWebRequest.Create(new Uri(uri)); // ftp用户名和密码 reqFTP.Credentials = new NetworkCredential(ftpUserID, ftpPassword); reqFTP.KeepAlive = false; // 指定执行什么命令 reqFTP.Method = WebRequestMethods.Ftp.UploadFile; // 指定数据传输类型 reqFTP.UseBinary = true; // 上传文件时通知服务器文件的大小 reqFTP.ContentLength = fileInf.Length; //this.Invoke(InitUProgress, fileInf.Length); // 缓冲大小设置为2kb int buffLength = 4096; byte[] buff = new byte[buffLength]; int contentLen; // 打开一个文件流 (System.IO.FileStream) 去读上传的文件 FileStream fs = fileInf.OpenRead(); // 把上传的文件写入流 Stream strm = reqFTP.GetRequestStream(); contentLen = fs.Read(buff, 0, buffLength); while (contentLen != 0) { strm.Write(buff, 0, contentLen); contentLen = fs.Read(buff, 0, buffLength); } // 关闭两个流 strm.Close(); strm.Dispose(); fs.Close(); fs.Dispose(); return true; } catch (Exception ex) { return false; } } /// <summary> /// 新建目录 /// </summary> /// <param name="ftpPath"></param> /// <param name="dirName"></param> public void MakeDir(string ftpPath, string dirName, string username, string password) { try { //实例化FTP连接 FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpPath + dirName)); // ftp用户名和密码 request.Credentials = new NetworkCredential(username, password); // 默认为true,连接不会被关闭 request.KeepAlive = false; //指定FTP操作类型为创建目录 request.Method = WebRequestMethods.Ftp.MakeDirectory; //获取FTP服务器的响应 FtpWebResponse response = (FtpWebResponse)request.GetResponse(); response.Close(); } catch (Exception ex) { //Respons } } /// <summary> /// 删除指定文件 /// </summary> /// <param name="ftpPath"></param> /// <param name="dirName"></param> /// <param name="username"></param> /// <param name="password"></param> public void DeleteFile(string ftpPath, string username, string password) { try { // string uri = "ftp://" + ftpServerIP + "/" + ftpPath + "/" + fileInf.Name; //ftpPath = "ftp://192.168.1.111:2005/2012-12-05/20121206O5CATICE.docx"; //password = "111"; //username = "yuanluluoli"; //实例化FTP连接 FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpPath)); request.Method = WebRequestMethods.Ftp.DeleteFile; // ftp用户名和密码 request.Credentials = new NetworkCredential(username, password); // 默认为true,连接不会被关闭 request.KeepAlive = false; //获取FTP服务器的响应 FtpWebResponse response = (FtpWebResponse)request.GetResponse(); response.Close(); } catch (Exception ex) { //Respons } } /// <summary> /// 检查目录是否存在 /// </summary> /// <param name="ftpPath">要检查的目录的路径</param> /// <param name="dirName">要检查的目录名</param> /// <returns>存在返回true,否则false</returns> public bool CheckDirectoryExist(string ftpPath, string dirName, string username, string password) { bool result = false; try { //实例化FTP连接 FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpPath)); // ftp用户名和密码 request.Credentials = new NetworkCredential(username, password); request.KeepAlive = false; //指定FTP操作类型为创建目录 request.Method = WebRequestMethods.Ftp.ListDirectoryDetails; //获取FTP服务器的响应 FtpWebResponse response = (FtpWebResponse)request.GetResponse(); StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default); StringBuilder str = new StringBuilder(); string line = sr.ReadLine(); while (line != null) { str.Append(line); str.Append("|"); line = sr.ReadLine(); } string[] datas = str.ToString().Split('|'); for (int i = 0; i < datas.Length; i++) { if (datas[i].Contains("<DIR>")) { int index = datas[i].IndexOf("<DIR>"); string name = datas[i].Substring(index + 5).Trim(); if (name == dirName) { result = true; break; } } } sr.Close(); sr.Dispose(); response.Close(); } catch (Exception) { return false; } return result; } /// <summary> /// 上传文件 /// </summary> /// <param name="buffer">文件的Byte数组</param> /// <param name="originalName">文件原始名字(带后缀名)</param> /// <param name="perStr">新文件名的前缀</param> /// <returns></returns> public string UpFile(byte[] buffer, string originalName, string perStr = "") { if (buffer == null || buffer.Length <= 0 || string.IsNullOrEmpty(originalName)) throw new ArgumentException("参数错误!"); string filePathstr = string.Empty; string filepathsql = null; try { string pathstr = perStr + DateTime.Now.ToString().Replace("/", "").Replace("-", "").Replace(":", "").Replace(" ", ""); string rodumlist = GeneralHelper.GetMixPwd(10);//10位随机数 filePathstr = "~/File/" + pathstr + rodumlist + Path.GetExtension(originalName); //Stream sr = upfile.PostedFile.InputStream; //byte[] file = new byte[sr.Length]; //sr.Read(file, 0, file.Length); StreamWriter sw = new StreamWriter(HttpContext.Current.Server.MapPath(filePathstr)); sw.BaseStream.Write(buffer, 0, buffer.Length); sw.Flush(); sw.Close(); // file.SaveAs(HttpContext.Current.Server.MapPath(filePathstr));//把文件上传到服务器的绝对路径上 bool check; string ftpPath = DateTime.Now.ToString("yyyy-MM-dd"); string uri = @"ftp://" + ipaddress + "/"; //检查是否存在此目录文件夹 if (CheckDirectoryExist(uri, ftpPath, Username, Password)) { //存在此文件夹就直接上传 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } else { MakeDir(uri, ftpPath, Username, Password);//创建 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } //成功就更新 if (check) { filepathsql = ftpPath + "/" + pathstr + rodumlist + Path.GetExtension(originalName); } //检查是否存在此文件 if (File.Exists(HttpContext.Current.Server.MapPath(filePathstr))) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); } return filepathsql; } catch (Exception ex) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); throw ex; } } /// <summary> /// 上传文件 /// 不修改名字及后缀名 /// </summary> /// <param name="originalFilePath">上传文件的绝对路径</param> /// <returns></returns> public string UpFile(string originalFilePath) { if (string.IsNullOrEmpty(originalFilePath)) throw new ArgumentException("参数错误!"); string filepathsql = null; try { //检查是否存在此文件 if (!File.Exists(originalFilePath)) throw new Exception("文件不存在!"); //Stream sr = upfile.PostedFile.InputStream; //byte[] file = new byte[sr.Length]; //sr.Read(file, 0, file.Length); // file.SaveAs(HttpContext.Current.Server.MapPath(filePathstr));//把文件上传到服务器的绝对路径上 bool check; string ftpPath = DateTime.Now.ToString("yyyy-MM-dd"); string uri = @"ftp://" + ipaddress + "/"; //检查是否存在此目录文件夹 if (CheckDirectoryExist(uri, ftpPath, Username, Password)) { //存在此文件夹就直接上传 check = Upload(originalFilePath, ipaddress, ftpPath, Username, Password); } else { MakeDir(uri, ftpPath, Username, Password);//创建 check = Upload(originalFilePath, ipaddress, ftpPath, Username, Password); } //成功就更新 if (check) { filepathsql = ftpPath + "/" + Path.GetFileName(originalFilePath); } //检查是否存在此文件 if (File.Exists(originalFilePath)) { File.Delete(originalFilePath); } return filepathsql; } catch (Exception ex) { //File.Delete(originalFilePath); throw ex; } } public string Ftp_Up(HtmlInputFile upfile) { //Encrypt En = new Encrypt(); string filePathstr = string.Empty; string filepathsql = null; try { string pathstr = DateTime.Now.ToString().Replace("/", "").Replace("-", "").Replace(":", "").Replace(" ", ""); string rodumlist = GeneralHelper.GetMixPwd(10);//10位随机数 filePathstr = "~/File/" + pathstr + rodumlist + Path.GetExtension(upfile.PostedFile.FileName); Stream sr = upfile.PostedFile.InputStream; byte[] file = new byte[sr.Length]; sr.Read(file, 0, file.Length); StreamWriter sw = new StreamWriter(HttpContext.Current.Server.MapPath(filePathstr)); sw.BaseStream.Write(file, 0, file.Length); sw.Flush(); sw.Close(); sr.Flush(); sr.Close(); // file.SaveAs(HttpContext.Current.Server.MapPath(filePathstr));//把文件上传到服务器的绝对路径上 bool check; string ftpPath = DateTime.Now.ToString("yyyy-MM-dd"); string uri = @"ftp://" + ipaddress + "/"; //检查是否存在此目录文件夹 if (CheckDirectoryExist(uri, ftpPath, Username, Password)) { //存在此文件夹就直接上传 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } else { MakeDir(uri, ftpPath, Username, Password);//创建 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } //成功就更新 if (check) { filepathsql = ftpPath + "/" + pathstr + rodumlist + Path.GetExtension(upfile.PostedFile.FileName); } //检查是否存在此文件 if (File.Exists(HttpContext.Current.Server.MapPath(filePathstr))) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); } return filepathsql; } catch (Exception) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); return filepathsql; // Response.Write("<script>alert(" + ex.Message + ");</script>"); } } /// <summary> /// 上传 /// </summary> /// <param name="file"></param> /// <returns></returns> public string Ftp_Up(HttpPostedFileBase postedFile) { string filePathstr = string.Empty; string filepathsql = null; try { string pathstr = DateTime.Now.ToString("yyyyMMddHHmmss"); string rodumlist = GeneralHelper.GetMixPwd(10);//10位随机数 string filename = System.IO.Path.GetFileName(postedFile.FileName); string eExtension = Path.GetExtension(filename); string strLocation = HttpContext.Current.Server.MapPath("~/File/"); filePathstr = strLocation + pathstr + rodumlist + eExtension; postedFile.SaveAs(filePathstr); bool check; string ftpPath = DateTime.Now.ToString("yyyy-MM-dd"); string uri = @"ftp://" + ipaddress + "/"; //检查是否存在此目录文件夹 if (CheckDirectoryExist(uri, ftpPath, Username, Password)) { //存在此文件夹就直接上传 check = Upload(filePathstr, ipaddress, ftpPath, Username, Password); } else { MakeDir(uri, ftpPath, Username, Password);//创建 check = Upload(filePathstr, ipaddress, ftpPath, Username, Password); } //成功就更新 if (check) { filepathsql = ftpPath + "/" + pathstr + rodumlist + eExtension; } //检查是否存在此文件 if (File.Exists(filePathstr)) { File.Delete(filePathstr); } return filepathsql; } catch (Exception ex) { //检查是否存在此文件 if (File.Exists(filePathstr)) { File.Delete(filePathstr); } return ""; // Response.Write("<script>alert(" + ex.Message + ");</script>"); } } /// <summary> /// FTP下载文件在服务器目录 /// </summary> /// <param name="pathname">本地保存目录路径和文件名称</param> /// <param name="filename">FTP目录路径和文件名称</param> /// <returns></returns> public bool FileDown(string pathname, string filename) { string uri = "ftp://" + ipaddress + "/" + filename; string FileName = pathname;//本地保存目录 //创建一个文件流 FileStream fs = null; Stream responseStream = null; try { //创建一个与FTP服务器联系的FtpWebRequest对象 FtpWebRequest request = (FtpWebRequest)WebRequest.Create(new Uri(uri)); //连接登录FTP服务器 request.Credentials = new NetworkCredential(Username, Password); request.KeepAlive = false; //设置请求的方法是FTP文件下载 request.Method = WebRequestMethods.Ftp.DownloadFile; //获取一个请求响应对象 FtpWebResponse response = (FtpWebResponse)request.GetResponse(); //获取请求的响应流 responseStream = response.GetResponseStream(); //判断本地文件是否存在,如果存在,则打开和重写本地文件 if (File.Exists(FileName)) fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite); //判断本地文件是否存在,如果不存在,则创建本地文件 else { fs = File.Create(FileName); } if (fs != null) { int buffer_count = 65536; byte[] buffer = new byte[buffer_count]; int size = 0; while ((size = responseStream.Read(buffer, 0, buffer_count)) > 0) { fs.Write(buffer, 0, size); } fs.Flush(); fs.Close(); responseStream.Close(); } return true; } catch (Exception ex) { return false; } finally { if (fs != null) fs.Close(); if (responseStream != null) responseStream.Close(); } } /// <summary> /// 保存和上传图片 /// </summary> /// <param name="imgtwo">需要上传图片</param> /// <param name="date"></param> /// <returns>文件路径</returns> public string SaveUploadImg(Bitmap imgtwo) { string filePathstr = string.Empty; string filepathsql = null; try { string pathstr = DateTime.Now.ToString().Replace("/", "").Replace("-", "").Replace(":", "").Replace(" ", ""); string rodumlist = GeneralHelper.GetMixPwd(10);//10位随机数 filePathstr = "~/File/" + pathstr + rodumlist + ".jpg"; imgtwo.Save(HttpContext.Current.Server.MapPath(filePathstr));//把文件上传到服务器的绝对路径上 bool check; string ftpPath = DateTime.Now.ToString("yyyy-MM-dd"); string uri = @"ftp://" + ipaddress + "/"; //检查是否存在此目录文件夹 if (CheckDirectoryExist(uri, ftpPath, Username, Password)) { //存在此文件夹就直接上传 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } else { MakeDir(uri, ftpPath, Username, Password);//创建 check = Upload(HttpContext.Current.Server.MapPath(filePathstr), ipaddress, ftpPath, Username, Password); } //成功就更新 if (check) { filepathsql = ftpPath + "/" + pathstr + rodumlist + ".jpg"; } //检查是否存在此文件 if (File.Exists(HttpContext.Current.Server.MapPath(filePathstr))) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); } imgtwo.Dispose(); return filepathsql; } catch (Exception ex) { File.Delete(HttpContext.Current.Server.MapPath(filePathstr)); return filepathsql; } } #region /// <summary> /// 文件大小 /// </summary> public bool _File_Length(int ContentLength) { bool length = false; int FileLen = ContentLength; if (FileLen > 2048 * 1024 == false)//不能超过2M { length = true; } return length; } #endregion //用来获取文件类型 public bool File_PastFileName(string fileName) { //bmp, doc, docx, gif, jpg, jpeg, pdf, png, tif, tiff bool isnot = true; string ext = Path.GetExtension(fileName); string[] type = Filetype.Split(';'); for (int i = 0; i < type.Length; i++) { if (type[i].ToLower() == ext.ToLower()) { isnot = false; break; } } return isnot; } } View Code 值得注意的是:帮助类中也从配置文件中读取了存放地址、文件类型、用户名、密码等必要信息。这个大家可以自己再配置文件中配置,配置好了如下所示: <add key="FileSizem" value="10"></add> <add key="FileType" value=".bmp;.gif;.jpg;.jpeg;.png;.pdf" /> <!---本地测试--> <add key="IPaddress" value="路径" /> <!--FTP上传文件的帐号--> <add key="UserName" value="账号" /> <!--FTP上传文件的密码--> <add key="Password" value="密码" /> <!--后台显示图片地址--> <add key="PathSrc" value="路径" /> 还有一个类是图片帮助类,代码如下: public class ImageHelper { /// <summary> /// 图片压缩 /// </summary> /// <param name="sFile">原图路径</param> /// <param name="dFile">保存路径</param> /// <param name="flag">压缩质量(数字越小压缩率越高) 1-100</param> /// <param name="dWidth">宽度</param> /// <param name="dHeight">高度</param> /// <returns></returns> public static bool SavePicThumbnail(string sFile, string dFile, int flag, int dWidth = 0, int dHeight = 0) { System.Drawing.Image iSource = System.Drawing.Image.FromFile(sFile); return SavePicThumbnail(dFile, flag, iSource, dWidth, dHeight); } /// <summary> /// 图片压缩 /// </summary> /// <param name="sFile">原图流</param> /// <param name="dFile">保存路径</param> /// <param name="flag">压缩质量(数字越小压缩率越高) 1-100</param> /// <param name="dWidth">宽度</param> /// <param name="dHeight">高度</param> /// <returns></returns> public static bool SavePicThumbnail(Stream stream, string dFile, int flag, int dWidth = 0, int dHeight = 0) { System.Drawing.Image iSource = System.Drawing.Image.FromStream(stream); return SavePicThumbnail(dFile, flag, iSource, dWidth, dHeight); } #region GetPicThumbnail public static Stream GetPicThumbnail(Stream stream ,int flag, int dWidth = 0, int dHeight = 0) { System.Drawing.Image iSource = System.Drawing.Image.FromStream(stream); ImageFormat tFormat = iSource.RawFormat; int sW = 0, sH = 0; if (dHeight == 0 && dWidth == 0) { sW = iSource.Width; sH = iSource.Height; } else if (dWidth != 0) { sW = dWidth; sH = iSource.Height * dWidth / iSource.Width; } else if (dHeight != 0) { sH = dHeight; sW = iSource.Width * dHeight / iSource.Height; } Bitmap ob = new Bitmap(sW, sH); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle(0, 0, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代码为保存图片时,设置压缩质量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//设置压缩的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; MemoryStream ms = new MemoryStream(); try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int x = 0; x < arrayICI.Length; x++) { if (arrayICI[x].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[x]; break; } } if (jpegICIinfo != null) { ob.Save(ms, jpegICIinfo, ep);//dFile是压缩后的新路径 } else { ob.Save(ms, tFormat); } return stream; } catch { return null; } finally { iSource.Dispose(); ob.Dispose(); } } public static bool SavePicThumbnail(string dFile, int flag, System.Drawing.Image iSource, int dWidth = 0, int dHeight = 0) { ImageFormat tFormat = iSource.RawFormat; int sW = 0, sH = 0; if (dHeight == 0 && dWidth == 0) { sW = iSource.Width; sH = iSource.Height; } else if (dWidth != 0) { sW = dWidth; sH = iSource.Height * dWidth / iSource.Width; } else if (dHeight != 0) { sH = dHeight; sW = iSource.Width * dHeight / iSource.Height; } Bitmap ob = new Bitmap(sW, sH); Graphics g = Graphics.FromImage(ob); g.Clear(Color.WhiteSmoke); g.CompositingQuality = CompositingQuality.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality; g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.DrawImage(iSource, new Rectangle(0, 0, sW, sH), 0, 0, iSource.Width, iSource.Height, GraphicsUnit.Pixel); g.Dispose(); //以下代码为保存图片时,设置压缩质量 EncoderParameters ep = new EncoderParameters(); long[] qy = new long[1]; qy[0] = flag;//设置压缩的比例1-100 EncoderParameter eParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qy); ep.Param[0] = eParam; try { ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo jpegICIinfo = null; for (int x = 0; x < arrayICI.Length; x++) { if (arrayICI[x].FormatDescription.Equals("JPEG")) { jpegICIinfo = arrayICI[x]; break; } } if (jpegICIinfo != null) { ob.Save(dFile, jpegICIinfo, ep);//dFile是压缩后的新路径 } else { ob.Save(dFile, tFormat); } return true; } catch { return false; } finally { iSource.Dispose(); ob.Dispose(); } } #endregion } View Code 然后我们通过在fileuploaded的回调函数中绑定url即可显示上传后的图片,效果如下图: 不过这个只是上传到ftp服务器上了,并没有保存到数据库,现在我们要做的就是通过EF的方式将上传的图片保存到数据库。 保存图片 首先我们要建一个操作的接口基类,并且还要约束成BaseEntity,代码如下 public interface IRepository<T> where T : BaseEntity { /// <summary> /// 根据过滤条件,获取记录 /// </summary> /// <param name="exp"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <returns></returns> IQueryable<T> Find(Expression<Func<T, bool>> exp = null, bool isNoTracking = true); /// <summary> /// 根据过滤条件,获取记录 /// </summary> /// <param name="whereLambda"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <param name="values"></param> /// <returns></returns> IQueryable<T> Find(string whereLambda = null, bool isNoTracking = true, params object[] values); IQueryable<TEntity> OtherTable<TEntity>() where TEntity : BaseEntity; IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity; /// <summary> /// 判断记录是否存在 /// </summary> /// <param name="exp"></param> /// <returns></returns> bool IsExist(Expression<Func<T, bool>> exp); /// <summary> /// 查找单个(如果没找到则返回为NULL) /// </summary> /// <param name="exp"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <returns></returns> T FindSingle(Expression<Func<T, bool>> exp, bool isNoTracking = true); /// <summary> /// 得到分页记录 /// </summary> /// <param name="pageIndex">The pageindex.</param> /// <param name="pageSize">The pagesize.</param> /// <param name="total">总条数</param> /// <param name="exp">条件谓词</param> /// <param name="orderBy">排序,格式如:"Id"/"Id descending"</param> IQueryable<T> Find(int pageIndex, int pageSize, out int total, Expression<Func<T, bool>> exp = null, string orderBy = ""); /// <summary> /// 得到分页记录 /// </summary> /// <param name="pageIndex">The pageindex.</param> /// <param name="pageSize">The pagesize.</param> /// <param name="total">总条数</param> /// <param name="whereLambda">条件谓词</param> /// <param name="orderBy">排序,格式如:"Id"/"Id descending"</param> IQueryable<T> Find(int pageIndex, int pageSize, out int total, string whereLambda = "", string orderBy = "", params object[] values); /// <summary> /// 根据过滤条件获取记录数 /// </summary> int GetCount(Expression<Func<T, bool>> exp = null); /// <summary> /// 添加实体 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> int Add(T entity, bool isComit = true); /// <summary> /// 批量添加 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> int Adds(List<T> entitis, bool isComit = true); /// <summary> /// 更新实体(会更新实体的所有属性) /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> int Update(T entity, bool isComit = true); /// <summary> /// 删除实体 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> int Delete(T entity, bool isComit = true); /// <summary> /// 实现按需要只更新部分更新 /// <para>如:Update(u =>u.Id==1,u =>new User{Name="ok"});</para> /// </summary> /// <param name="where">The where.</param> /// <param name="entity">The entity.</param> int Update(Expression<Func<T, bool>> where, Expression<Func<T, T>> entity); /// <summary> /// 批量按条件删除 /// </summary> /// <param name="exp"></param> int Delete(Expression<Func<T, bool>> exp); /// <summary> /// 对数据库执行给定的 DDL/DML 命令。 /// </summary> /// <param name="sql">sql</param> /// <param name="parameters">参数</param> /// <returns></returns> int ExecuteSqlCommand(string sql, params SqlParameter[] parameters); /// <summary> /// 执行SQL查询语句 /// </summary> /// <param name="sql"></param> /// <returns></returns> DbRawSqlQuery<int> ExecuteSqlQuery(string sql); /// <summary> /// 执行原始的sql查询 /// </summary> /// <typeparam name="TElement">返回的泛型类型</typeparam> /// <param name="sql">sql</param> /// <param name="parameters">参数</param> /// <returns></returns> IList<TElement> SqlQuery<TElement>(string sql, params SqlParameter[] parameters); /// <summary> /// 开启一个事务 /// </summary> /// <param name="fun"></param> bool BeginTransaction(Func<bool> fun); /// <summary> /// 执行SQL语句或存储过程 /// 返回Datatable数据集 /// </summary> /// <param name="sql">SQL语句或存储过程 例如:exec usp_procedure</param> /// <param name="parameters">参数列表</param> /// <returns></returns> DataTable SqlQueryForDataTable(string sql, DbParameter[] parameters); /// <summary> /// 返回Datatable数据集 /// </summary> /// <param name="proName">存储过程名</param> /// <param name="parameters">参数列表</param> /// <returns></returns> [Obsolete("此方法已过时,请改用SqlQueryForDataTable")] DataTable ExecuteForDataTable(string proName, IDataParameter[] parameters); } View Code 然后需要实现这个基类接口,代码如下: public class BaseRepository<T> : IRepository<T> where T : BaseEntity { private DbContext Context { get { DbContext db = (DbContext)CallContext.GetData("DbContext"); if (db == null) { db = new DbContext(); // db.Database.Log = o => LoggingHelper.Instance.Logging(LogLevel.Debug, o); CallContext.SetData("DbContext", db); } return db; } } /// <summary> /// 根据过滤条件,获取记录 /// </summary> /// <param name="exp"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <returns></returns> public IQueryable<T> Find(Expression<Func<T, bool>> exp = null, bool isNoTracking = true) { return Filter(exp, isNoTracking); } /// <summary> /// 根据过滤条件,获取记录 /// </summary> /// <param name="whereLambda"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <param name="values"></param> /// <returns></returns> public IQueryable<T> Find(string whereLambda = null, bool isNoTracking = true, params object[] values) { return Filter(whereLambda, isNoTracking, values); } /// <summary> /// 判断记录是否存在 /// </summary> /// <param name="exp"></param> /// <returns></returns> public bool IsExist(Expression<Func<T, bool>> exp) { return Context.Set<T>().Any(exp); } /// <summary> /// 查找单个(如果没找到则返回为NULL) /// </summary> /// <param name="exp"></param> /// <param name="isNoTracking">(默认不跟踪实体状态)使用NoTracking的查询会在性能方面得到改善</param> /// <returns></returns> public T FindSingle(Expression<Func<T, bool>> exp, bool isNoTracking = true) { return Filter(exp, isNoTracking).FirstOrDefault(); } /// <summary> /// 得到分页记录 /// </summary> /// <param name="pageIndex">The pageindex.</param> /// <param name="pageSize">The pagesize.</param> /// <param name="total">总条数</param> /// <param name="exp">条件谓词</param> /// <param name="orderBy">排序,格式如:"Id"/"Id descending"</param> public IQueryable<T> Find(int pageIndex, int pageSize, out int total, Expression<Func<T, bool>> exp = null, string orderBy = "") { if (pageIndex < 1) pageIndex = 1; var query = Filter(exp); if (!string.IsNullOrEmpty(orderBy)) query = query.OrderBy(orderBy); total = query.Count(); ///return query.Skip(pageSize * (pageIndex - 1)).Take(pageSize); return null; } /// <summary> /// 得到分页记录 /// </summary> /// <param name="pageIndex">The pageindex.</param> /// <param name="pageSize">The pagesize.</param> /// <param name="total">总条数</param> /// <param name="whereLambda">条件谓词</param> /// <param name="orderBy">排序,格式如:"Id"/"Id descending"</param> public IQueryable<T> Find(int pageIndex, int pageSize, out int total, string whereLambda = "", string orderBy = "", params object[] values) { if (pageIndex < 1) pageIndex = 1; var query = Filter(whereLambda); if (string.IsNullOrEmpty(orderBy)) query = query.OrderBy(orderBy); total = query.Count(); // return query.Skip(pageSize * (pageIndex - 1)).Take(pageSize); return null; } /// <summary> /// 根据过滤条件获取记录数 /// </summary> public int GetCount(Expression<Func<T, bool>> exp = null) { return Filter(exp).Count(); } /// <summary> /// 添加书体 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> public int Add(T entity, bool isComit = true) { Context.Entry<T>(entity).State = System.Data.Entity.EntityState.Added; return isComit ? Context.SaveChanges() : 0; } /// <summary> /// 批量添加 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> public int Adds(List<T> entitis, bool isComit = true) { foreach (T item in entitis) { Context.Entry<T>(item).State = System.Data.Entity.EntityState.Added; } return isComit ? Context.SaveChanges() : 0; } /// <summary> /// 更新实体(会更新实体的所有属性) /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> public int Update(T entity, bool isComit = true) { Context.Entry(entity).State = System.Data.Entity.EntityState.Modified; return isComit ? Context.SaveChanges() : 0; } /// <summary> /// 删除实体 /// </summary> /// <param name="entity">The entities.</param> /// <param name="isComit">是否提交(true)</param> /// <returns></returns> public int Delete(T entity, bool isComit = true) { Context.Set<T>().Remove(entity); return isComit ? Context.SaveChanges() : 0; } /// <summary> /// 实现按需要只更新部分更新 /// <para>如:Update(u =>u.Id==1,u =>new User{Name="ok"});</para> /// </summary> /// <param name="where">The where.</param> /// <param name="entity">The entity.</param> public int Update(Expression<Func<T, bool>> where, Expression<Func<T, T>> entity) { return Context.Set<T>().Where(where).Update(entity); } /// <summary> /// 批量按条件删除 /// </summary> /// <param name="exp"></param> public int Delete(Expression<Func<T, bool>> exp) { return Context.Set<T>().Where(exp).Delete(); } /// <summary> /// 对数据库执行给定的 DDL/DML 命令。 /// </summary> /// <param name="sql">sql</param> /// <param name="parameters">参数</param> /// <returns></returns> public int ExecuteSqlCommand(string sql, params SqlParameter[] parameters) { return Context.Database.ExecuteSqlCommand(sql, parameters); } /// <summary> /// 执行原始的sql查询 /// </summary> /// <typeparam name="TElement">返回的泛型类型</typeparam> /// <param name="sql">sql</param> /// <param name="parameters">参数</param> /// <returns></returns> public IList<TElement> SqlQuery<TElement>(string sql, params SqlParameter[] parameters) { return Context.Database.SqlQuery<TElement>(sql, parameters).ToList(); } public bool BeginTransaction(Func<bool> fun) { using (var trans = Context.Database.BeginTransaction()) { try { var result = fun(); trans.Commit(); return result; } catch (Exception) { trans.Rollback(); return false; } } } private IQueryable<T> Filter(Expression<Func<T, bool>> exp = null, bool isNoTracking = true) { var dbSet = Context.Set<T>().AsQueryable(); if (exp != null) dbSet = dbSet.Where(exp); if (isNoTracking) dbSet = dbSet.AsNoTracking(); return dbSet; } private IQueryable<T> Filter(string whereLambda = null, bool isNoTracking = true, params object[] values) { var dbSet = Context.Set<T>().AsQueryable(); if (whereLambda != null) dbSet = dbSet.Where(whereLambda, values); if (isNoTracking) dbSet = dbSet.AsNoTracking(); return dbSet; } public IQueryable<T> Table { get { return Find(whereLambda: null); } } public IQueryable<TEntity> OtherTable<TEntity>() where TEntity : BaseEntity { return Context.Set<TEntity>().AsNoTracking().AsQueryable(); } public IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity { return Context.Set<TEntity>(); } /// <summary> /// 执行SQL查询语句 /// </summary> /// <param name="sql"></param> /// <returns></returns> public DbRawSqlQuery<int> ExecuteSqlQuery(string sql) { try { return Context.Database.SqlQuery<int>(sql); } catch (Exception ex) { throw ex; } } /// <summary> /// 执行SQL语句或存储过程 /// 返回Datatable数据集 /// </summary> /// <param name="sql">SQL语句或存储过程 例如:exec usp_procedure</param> /// <param name="parameters">参数列表</param> /// <returns></returns> public System.Data.DataTable SqlQueryForDataTable(string sql, params System.Data.Common.DbParameter[] parameters) { SqlConnection conn = new System.Data.SqlClient.SqlConnection(); try { conn = (SqlConnection)Context.Database.Connection; if (conn.State != ConnectionState.Open) { conn.Open(); } SqlCommand cmd = new SqlCommand(); StringBuilder sb = new StringBuilder(sql); if (parameters != null && parameters.Length > 0) { if (sql.StartsWith("exec ", StringComparison.OrdinalIgnoreCase)) sb.AppendFormat(" {0}", string.Join(",", parameters.Select(o => o.ParameterName).ToArray())); foreach (var item in parameters) { cmd.Parameters.Add(item); } } cmd.Connection = conn; cmd.CommandText = sb.ToString(); SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable table = new DataTable(); adapter.Fill(table); conn.Close();//连接需要关闭 return table; } catch (Exception ex) { throw ex; } finally { if (conn.State == ConnectionState.Open) conn.Close(); } } /// <summary> /// 返回Datatable数据集 /// </summary> /// <param name="proName">存储过程名</param> /// <param name="parameters">参数列表</param> /// <returns></returns> [Obsolete("此方法已过时,请改用SqlQueryForDataTable")] public System.Data.DataTable ExecuteForDataTable(string proName, IDataParameter[] parameters) { try { SqlConnection conn = new System.Data.SqlClient.SqlConnection(); conn.ConnectionString = Context.Database.Connection.ConnectionString; if (conn.State != ConnectionState.Open) { conn.Open(); } SqlCommand cmd = new SqlCommand(proName, conn); cmd.CommandType = CommandType.StoredProcedure; if (parameters != null && parameters.Length > 0) { foreach (var item in parameters) { cmd.Parameters.Add(item); } } SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataTable table = new DataTable(); adapter.Fill(table); return table; } catch (Exception ex) { throw ex; } } } View Code 注意,我们在这个实现类中定义了一个私有方法,指明了数据库访问的上下文DbContext DbContext类如下: public class DbContext : System.Data.Entity.DbContext { static DbContext() { //Database.SetInitializer(new CreateDatabaseIfNotExists<PersonalDbContext>()); } public DbContext() : base("Name=DbContext") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !String.IsNullOrEmpty(type.Namespace)) .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); foreach (var type in typesToRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } } } View Code 我们将Name=DbContext 意思是去寻找webconfig中具有相同名称的值 ,所以,我们在配置文件中配置该项如下: 再configuration节点下面新建 <connectionStrings> <add name="DbContext" providerName="System.Data.SqlClient" connectionString="Server=.;Database=Test;Uid=sa;Pwd=sa;"/> </connectionStrings> 忘了说,这里对实现类中的一些扩展方法做了延伸,新建一个DynamicQueryable类,代码如下: public static class DynamicQueryable { public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values) { return (IQueryable<T>)Where((IQueryable)source, predicate, values); } public static IQueryable Where(this IQueryable source, string predicate, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (predicate == null) throw new ArgumentNullException("predicate"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); } public static IQueryable Select(this IQueryable source, string selector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (selector == null) throw new ArgumentNullException("selector"); LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Select", new Type[] { source.ElementType, lambda.Body.Type }, source.Expression, Expression.Quote(lambda))); } public static IQueryable<dynamic> Select<T>(this IQueryable<T> source, string selector, params object[] values) { return (IQueryable<dynamic>)Select((IQueryable)source, selector, values); } public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) { return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values); } public static IQueryable<T> ThenBy<T>(this IQueryable<T> source, string ordering, params object[] values) { return (IQueryable<T>)ThenBy((IQueryable)source, ordering, values); } public static IQueryable ThenBy(this IQueryable source, string ordering, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (ordering == null) throw new ArgumentNullException("ordering"); ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(source.ElementType, "") }; ExpressionParser parser = new ExpressionParser(parameters, ordering, values); IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; string methodAsc = "ThenBy"; string methodDesc = "ThenByDescending"; foreach (DynamicOrdering o in orderings) { queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new Type[] { source.ElementType, o.Selector.Type }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); } return source.Provider.CreateQuery(queryExpr); } public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (ordering == null) throw new ArgumentNullException("ordering"); ParameterExpression[] parameters = new ParameterExpression[] { Expression.Parameter(source.ElementType, "") }; ExpressionParser parser = new ExpressionParser(parameters, ordering, values); IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering(); Expression queryExpr = source.Expression; string methodAsc = "OrderBy"; string methodDesc = "OrderByDescending"; foreach (DynamicOrdering o in orderings) { queryExpr = Expression.Call( typeof(Queryable), o.Ascending ? methodAsc : methodDesc, new Type[] { source.ElementType, o.Selector.Type }, queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters))); methodAsc = "ThenBy"; methodDesc = "ThenByDescending"; } return source.Provider.CreateQuery(queryExpr); } public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName, bool ascending) where T : class { Type type = typeof(T); PropertyInfo property = type.GetProperty(propertyName); if (property == null) throw new ArgumentException("propertyName", "Not Exist"); ParameterExpression param = Expression.Parameter(type, "p"); Expression propertyAccessExpression = Expression.MakeMemberAccess(param, property); LambdaExpression orderByExpression = Expression.Lambda(propertyAccessExpression, param); string methodName = ascending ? "OrderBy" : "OrderByDescending"; MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName, new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExpression)); return source.Provider.CreateQuery<T>(resultExp); } public static IQueryable Take(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException("source"); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Take", new Type[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable Skip(this IQueryable source, int count) { if (source == null) throw new ArgumentNullException("source"); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "Skip", new Type[] { source.ElementType }, source.Expression, Expression.Constant(count))); } public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values) { if (source == null) throw new ArgumentNullException("source"); if (keySelector == null) throw new ArgumentNullException("keySelector"); if (elementSelector == null) throw new ArgumentNullException("elementSelector"); LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values); LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values); return source.Provider.CreateQuery( Expression.Call( typeof(Queryable), "GroupBy", new Type[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type }, source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda))); } public static bool Any(this IQueryable source) { if (source == null) throw new ArgumentNullException("source"); return (bool)source.Provider.Execute( Expression.Call( typeof(Queryable), "Any", new Type[] { source.ElementType }, source.Expression)); } public static int Count(this IQueryable source) { if (source == null) throw new ArgumentNullException("source"); return (int)source.Provider.Execute( Expression.Call( typeof(Queryable), "Count", new Type[] { source.ElementType }, source.Expression)); } } public abstract class DynamicClass { public override string ToString() { PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); StringBuilder sb = new StringBuilder(); sb.Append("{"); for (int i = 0; i < props.Length; i++) { if (i > 0) sb.Append(", "); sb.Append(props[i].Name); sb.Append("="); sb.Append(props[i].GetValue(this, null)); } sb.Append("}"); return sb.ToString(); } } public class DynamicProperty { string name; Type type; public DynamicProperty(string name, Type type) { if (name == null) throw new ArgumentNullException("name"); if (type == null) throw new ArgumentNullException("type"); this.name = name; this.type = type; } public string Name { get { return name; } } public Type Type { get { return type; } } } public static class DynamicExpression { public static Expression Parse(Type resultType, string expression, params object[] values) { ExpressionParser parser = new ExpressionParser(null, expression, values); return parser.Parse(resultType); } public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, params object[] values) { return ParseLambda(new ParameterExpression[] { Expression.Parameter(itType, "") }, resultType, expression, values); } public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values) { ExpressionParser parser = new ExpressionParser(parameters, expression, values); return Expression.Lambda(parser.Parse(resultType), parameters); } public static Expression<Func<T, S>> ParseLambda<T, S>(string expression, params object[] values) { return (Expression<Func<T, S>>)ParseLambda(typeof(T), typeof(S), expression, values); } public static Type CreateClass(params DynamicProperty[] properties) { return ClassFactory.Instance.GetDynamicClass(properties); } public static Type CreateClass(IEnumerable<DynamicProperty> properties) { return ClassFactory.Instance.GetDynamicClass(properties); } } internal class DynamicOrdering { public Expression Selector; public bool Ascending; } internal class Signature : IEquatable<Signature> { public DynamicProperty[] properties; public int hashCode; public Signature(IEnumerable<DynamicProperty> properties) { this.properties = properties.ToArray(); hashCode = 0; foreach (DynamicProperty p in properties) { hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode(); } } public override int GetHashCode() { return hashCode; } public override bool Equals(object obj) { return obj is Signature ? Equals((Signature)obj) : false; } public bool Equals(Signature other) { if (properties.Length != other.properties.Length) return false; for (int i = 0; i < properties.Length; i++) { if (properties[i].Name != other.properties[i].Name || properties[i].Type != other.properties[i].Type) return false; } return true; } } internal class ClassFactory { public static readonly ClassFactory Instance = new ClassFactory(); static ClassFactory() { } // Trigger lazy initialization of static fields ModuleBuilder module; Dictionary<Signature, Type> classes; int classCount; ReaderWriterLock rwLock; private ClassFactory() { AssemblyName name = new AssemblyName("DynamicClasses"); AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); #if ENABLE_LINQ_PARTIAL_TRUST new ReflectionPermission(PermissionState.Unrestricted).Assert(); #endif try { module = assembly.DefineDynamicModule("Module"); } finally { #if ENABLE_LINQ_PARTIAL_TRUST PermissionSet.RevertAssert(); #endif } classes = new Dictionary<Signature, Type>(); rwLock = new ReaderWriterLock(); } public Type GetDynamicClass(IEnumerable<DynamicProperty> properties) { rwLock.AcquireReaderLock(Timeout.Infinite); try { Signature signature = new Signature(properties); Type type; if (!classes.TryGetValue(signature, out type)) { type = CreateDynamicClass(signature.properties); classes.Add(signature, type); } return type; } finally { rwLock.ReleaseReaderLock(); } } Type CreateDynamicClass(DynamicProperty[] properties) { LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite); try { string typeName = "DynamicClass" + (classCount + 1); #if ENABLE_LINQ_PARTIAL_TRUST new ReflectionPermission(PermissionState.Unrestricted).Assert(); #endif try { TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, typeof(DynamicClass)); FieldInfo[] fields = GenerateProperties(tb, properties); GenerateEquals(tb, fields); GenerateGetHashCode(tb, fields); Type result = tb.CreateType(); classCount++; return result; } finally { #if ENABLE_LINQ_PARTIAL_TRUST PermissionSet.RevertAssert(); #endif } } finally { rwLock.DowngradeFromWriterLock(ref cookie); } } FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties) { FieldInfo[] fields = new FieldBuilder[properties.Length]; for (int i = 0; i < properties.Length; i++) { DynamicProperty dp = properties[i]; FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private); PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null); MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, dp.Type, Type.EmptyTypes); ILGenerator genGet = mbGet.GetILGenerator(); genGet.Emit(OpCodes.Ldarg_0); genGet.Emit(OpCodes.Ldfld, fb); genGet.Emit(OpCodes.Ret); MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new Type[] { dp.Type }); ILGenerator genSet = mbSet.GetILGenerator(); genSet.Emit(OpCodes.Ldarg_0); genSet.Emit(OpCodes.Ldarg_1); genSet.Emit(OpCodes.Stfld, fb); genSet.Emit(OpCodes.Ret); pb.SetGetMethod(mbGet); pb.SetSetMethod(mbSet); fields[i] = fb; } return fields; } void GenerateEquals(TypeBuilder tb, FieldInfo[] fields) { MethodBuilder mb = tb.DefineMethod("Equals", MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, typeof(bool), new Type[] { typeof(object) }); ILGenerator gen = mb.GetILGenerator(); LocalBuilder other = gen.DeclareLocal(tb); Label next = gen.DefineLabel(); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Isinst, tb); gen.Emit(OpCodes.Stloc, other); gen.Emit(OpCodes.Ldloc, other); gen.Emit(OpCodes.Brtrue_S, next); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ret); gen.MarkLabel(next); foreach (FieldInfo field in fields) { Type ft = field.FieldType; Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); next = gen.DefineLabel(); gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, field); gen.Emit(OpCodes.Ldloc, other); gen.Emit(OpCodes.Ldfld, field); gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { ft, ft }), null); gen.Emit(OpCodes.Brtrue_S, next); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Ret); gen.MarkLabel(next); } gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Ret); } void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) { MethodBuilder mb = tb.DefineMethod("GetHashCode", MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, typeof(int), Type.EmptyTypes); ILGenerator gen = mb.GetILGenerator(); gen.Emit(OpCodes.Ldc_I4_0); foreach (FieldInfo field in fields) { Type ft = field.FieldType; Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, field); gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null); gen.Emit(OpCodes.Xor); } gen.Emit(OpCodes.Ret); } } public sealed class ParseException : Exception { int position; public ParseException(string message, int position) : base(message) { this.position = position; } public int Position { get { return position; } } public override string ToString() { return string.Format(Res.ParseExceptionFormat, Message, position); } } internal class ExpressionParser { struct Token { public TokenId id; public string text; public int pos; } enum TokenId { Unknown, End, Identifier, StringLiteral, IntegerLiteral, RealLiteral, Exclamation, Percent, Amphersand, OpenParen, CloseParen, Asterisk, Plus, Comma, Minus, Dot, Slash, Colon, LessThan, Equal, GreaterThan, Question, OpenBracket, CloseBracket, Bar, ExclamationEqual, DoubleAmphersand, LessThanEqual, LessGreater, DoubleEqual, GreaterThanEqual, DoubleBar } interface ILogicalSignatures { void F(bool x, bool y); void F(bool? x, bool? y); } interface IArithmeticSignatures { void F(int x, int y); void F(uint x, uint y); void F(long x, long y); void F(ulong x, ulong y); void F(float x, float y); void F(double x, double y); void F(decimal x, decimal y); void F(int? x, int? y); void F(uint? x, uint? y); void F(long? x, long? y); void F(ulong? x, ulong? y); void F(float? x, float? y); void F(double? x, double? y); void F(decimal? x, decimal? y); } interface IRelationalSignatures : IArithmeticSignatures { void F(string x, string y); void F(char x, char y); void F(DateTime x, DateTime y); void F(TimeSpan x, TimeSpan y); void F(char? x, char? y); void F(DateTime? x, DateTime? y); void F(TimeSpan? x, TimeSpan? y); } interface IEqualitySignatures : IRelationalSignatures { void F(bool x, bool y); void F(bool? x, bool? y); } interface IAddSignatures : IArithmeticSignatures { void F(DateTime x, TimeSpan y); void F(TimeSpan x, TimeSpan y); void F(DateTime? x, TimeSpan? y); void F(TimeSpan? x, TimeSpan? y); } interface ISubtractSignatures : IAddSignatures { void F(DateTime x, DateTime y); void F(DateTime? x, DateTime? y); } interface INegationSignatures { void F(int x); void F(long x); void F(float x); void F(double x); void F(decimal x); void F(int? x); void F(long? x); void F(float? x); void F(double? x); void F(decimal? x); } interface INotSignatures { void F(bool x); void F(bool? x); } interface IEnumerableSignatures { void Where(bool predicate); void Any(); void Any(bool predicate); void All(bool predicate); void Count(); void Count(bool predicate); void Min(object selector); void Max(object selector); void Sum(int selector); void Sum(int? selector); void Sum(long selector); void Sum(long? selector); void Sum(float selector); void Sum(float? selector); void Sum(double selector); void Sum(double? selector); void Sum(decimal selector); void Sum(decimal? selector); void Average(int selector); void Average(int? selector); void Average(long selector); void Average(long? selector); void Average(float selector); void Average(float? selector); void Average(double selector); void Average(double? selector); void Average(decimal selector); void Average(decimal? selector); } static readonly Type[] predefinedTypes = { typeof(Object), typeof(Boolean), typeof(Char), typeof(String), typeof(SByte), typeof(Byte), typeof(Int16), typeof(UInt16), typeof(Int32), typeof(UInt32), typeof(Int64), typeof(UInt64), typeof(Single), typeof(Double), typeof(Decimal), typeof(DateTime), typeof(TimeSpan), typeof(Guid), typeof(Math), typeof(Convert) }; static readonly Expression trueLiteral = Expression.Constant(true); static readonly Expression falseLiteral = Expression.Constant(false); static readonly Expression nullLiteral = Expression.Constant(null); static readonly string keywordIt = "it"; static readonly string keywordIif = "iif"; static readonly string keywordNew = "new"; static Dictionary<string, object> keywords; Dictionary<string, object> symbols; IDictionary<string, object> externals; Dictionary<Expression, string> literals; ParameterExpression it; string text; int textPos; int textLen; char ch; Token token; public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values) { if (expression == null) throw new ArgumentNullException("expression"); if (keywords == null) keywords = CreateKeywords(); symbols = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); literals = new Dictionary<Expression, string>(); if (parameters != null) ProcessParameters(parameters); if (values != null) ProcessValues(values); text = expression; textLen = text.Length; SetTextPos(0); NextToken(); } void ProcessParameters(ParameterExpression[] parameters) { foreach (ParameterExpression pe in parameters) if (!String.IsNullOrEmpty(pe.Name)) AddSymbol(pe.Name, pe); if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name)) it = parameters[0]; } void ProcessValues(object[] values) { for (int i = 0; i < values.Length; i++) { object value = values[i]; if (i == values.Length - 1 && value is IDictionary<string, object>) { externals = (IDictionary<string, object>)value; } else { AddSymbol("@" + i.ToString(System.Globalization.CultureInfo.InvariantCulture), value); } } } void AddSymbol(string name, object value) { if (symbols.ContainsKey(name)) throw ParseError(Res.DuplicateIdentifier, name); symbols.Add(name, value); } public Expression Parse(Type resultType) { int exprPos = token.pos; Expression expr = ParseExpression(); if (resultType != null) if ((expr = PromoteExpression(expr, resultType, true)) == null) throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType)); ValidateToken(TokenId.End, Res.SyntaxError); return expr; } #pragma warning disable 0219 public IEnumerable<DynamicOrdering> ParseOrdering() { List<DynamicOrdering> orderings = new List<DynamicOrdering>(); while (true) { Expression expr = ParseExpression(); bool ascending = true; if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) { NextToken(); } else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) { NextToken(); ascending = false; } orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending }); if (token.id != TokenId.Comma) break; NextToken(); } ValidateToken(TokenId.End, Res.SyntaxError); return orderings; } #pragma warning restore 0219 // ?: operator Expression ParseExpression() { int errorPos = token.pos; Expression expr = ParseLogicalOr(); if (token.id == TokenId.Question) { NextToken(); Expression expr1 = ParseExpression(); ValidateToken(TokenId.Colon, Res.ColonExpected); NextToken(); Expression expr2 = ParseExpression(); expr = GenerateConditional(expr, expr1, expr2, errorPos); } return expr; } // ||, or operator Expression ParseLogicalOr() { Expression left = ParseLogicalAnd(); while (token.id == TokenId.DoubleBar || TokenIdentifierIs("or")) { Token op = token; NextToken(); Expression right = ParseLogicalAnd(); CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); left = Expression.OrElse(left, right); } return left; } // &&, and operator Expression ParseLogicalAnd() { Expression left = ParseComparison(); while (token.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) { Token op = token; NextToken(); Expression right = ParseComparison(); CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); left = Expression.AndAlso(left, right); } return left; } // =, ==, !=, <>, >, >=, <, <= operators Expression ParseComparison() { Expression left = ParseAdditive(); while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual || token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater || token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual || token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual) { Token op = token; NextToken(); Expression right = ParseAdditive(); bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) { if (left.Type.IsAssignableFrom(right.Type)) { right = Expression.Convert(right, left.Type); } else if (right.Type.IsAssignableFrom(left.Type)) { left = Expression.Convert(left, right.Type); } else { throw IncompatibleOperandsError(op.text, left, right, op.pos); } } } else if (IsEnumType(left.Type) || IsEnumType(right.Type)) { if (left.Type != right.Type) { Expression e; if ((e = PromoteExpression(right, left.Type, true)) != null) { right = e; } else if ((e = PromoteExpression(left, right.Type, true)) != null) { left = e; } else { throw IncompatibleOperandsError(op.text, left, right, op.pos); } } } else { CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.text, ref left, ref right, op.pos); } switch (op.id) { case TokenId.Equal: case TokenId.DoubleEqual: left = GenerateEqual(left, right); break; case TokenId.ExclamationEqual: case TokenId.LessGreater: left = GenerateNotEqual(left, right); break; case TokenId.GreaterThan: left = GenerateGreaterThan(left, right); break; case TokenId.GreaterThanEqual: left = GenerateGreaterThanEqual(left, right); break; case TokenId.LessThan: left = GenerateLessThan(left, right); break; case TokenId.LessThanEqual: left = GenerateLessThanEqual(left, right); break; } } return left; } // +, -, & operators Expression ParseAdditive() { Expression left = ParseMultiplicative(); while (token.id == TokenId.Plus || token.id == TokenId.Minus || token.id == TokenId.Amphersand) { Token op = token; NextToken(); Expression right = ParseMultiplicative(); switch (op.id) { case TokenId.Plus: if (left.Type == typeof(string) || right.Type == typeof(string)) goto case TokenId.Amphersand; CheckAndPromoteOperands(typeof(IAddSignatures), op.text, ref left, ref right, op.pos); left = GenerateAdd(left, right); break; case TokenId.Minus: CheckAndPromoteOperands(typeof(ISubtractSignatures), op.text, ref left, ref right, op.pos); left = GenerateSubtract(left, right); break; case TokenId.Amphersand: left = GenerateStringConcat(left, right); break; } } return left; } // *, /, %, mod operators Expression ParseMultiplicative() { Expression left = ParseUnary(); while (token.id == TokenId.Asterisk || token.id == TokenId.Slash || token.id == TokenId.Percent || TokenIdentifierIs("mod")) { Token op = token; NextToken(); Expression right = ParseUnary(); CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos); switch (op.id) { case TokenId.Asterisk: left = Expression.Multiply(left, right); break; case TokenId.Slash: left = Expression.Divide(left, right); break; case TokenId.Percent: case TokenId.Identifier: left = Expression.Modulo(left, right); break; } } return left; } // -, !, not unary operators Expression ParseUnary() { if (token.id == TokenId.Minus || token.id == TokenId.Exclamation || TokenIdentifierIs("not")) { Token op = token; NextToken(); if (op.id == TokenId.Minus && (token.id == TokenId.IntegerLiteral || token.id == TokenId.RealLiteral)) { token.text = "-" + token.text; token.pos = op.pos; return ParsePrimary(); } Expression expr = ParseUnary(); if (op.id == TokenId.Minus) { CheckAndPromoteOperand(typeof(INegationSignatures), op.text, ref expr, op.pos); expr = Expression.Negate(expr); } else { CheckAndPromoteOperand(typeof(INotSignatures), op.text, ref expr, op.pos); expr = Expression.Not(expr); } return expr; } return ParsePrimary(); } Expression ParsePrimary() { Expression expr = ParsePrimaryStart(); while (true) { if (token.id == TokenId.Dot) { NextToken(); expr = ParseMemberAccess(null, expr); } else if (token.id == TokenId.OpenBracket) { expr = ParseElementAccess(expr); } else { break; } } return expr; } Expression ParsePrimaryStart() { switch (token.id) { case TokenId.Identifier: return ParseIdentifier(); case TokenId.StringLiteral: return ParseStringLiteral(); case TokenId.IntegerLiteral: return ParseIntegerLiteral(); case TokenId.RealLiteral: return ParseRealLiteral(); case TokenId.OpenParen: return ParseParenExpression(); default: throw ParseError(Res.ExpressionExpected); } } Expression ParseStringLiteral() { ValidateToken(TokenId.StringLiteral); char quote = token.text[0]; string s = token.text.Substring(1, token.text.Length - 2); int start = 0; while (true) { int i = s.IndexOf(quote, start); if (i < 0) break; s = s.Remove(i, 1); start = i + 1; } //if (quote == '\'') { // if (s.Length != 1) // throw ParseError(Res.InvalidCharacterLiteral); // NextToken(); // return CreateLiteral(s[0], s); //} NextToken(); return CreateLiteral(s, s); } Expression ParseIntegerLiteral() { ValidateToken(TokenId.IntegerLiteral); string text = token.text; if (text[0] != '-') { ulong value; if (!UInt64.TryParse(text, out value)) throw ParseError(Res.InvalidIntegerLiteral, text); NextToken(); if (value <= (ulong)Int32.MaxValue) return CreateLiteral((int)value, text); if (value <= (ulong)UInt32.MaxValue) return CreateLiteral((uint)value, text); if (value <= (ulong)Int64.MaxValue) return CreateLiteral((long)value, text); return CreateLiteral(value, text); } else { long value; if (!Int64.TryParse(text, out value)) throw ParseError(Res.InvalidIntegerLiteral, text); NextToken(); if (value >= Int32.MinValue && value <= Int32.MaxValue) return CreateLiteral((int)value, text); return CreateLiteral(value, text); } } Expression ParseRealLiteral() { ValidateToken(TokenId.RealLiteral); string text = token.text; object value = null; char last = text[text.Length - 1]; if (last == 'F' || last == 'f') { float f; if (Single.TryParse(text.Substring(0, text.Length - 1), out f)) value = f; } else { double d; if (Double.TryParse(text, out d)) value = d; } if (value == null) throw ParseError(Res.InvalidRealLiteral, text); NextToken(); return CreateLiteral(value, text); } Expression CreateLiteral(object value, string text) { ConstantExpression expr = Expression.Constant(value); literals.Add(expr, text); return expr; } Expression ParseParenExpression() { ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); NextToken(); Expression e = ParseExpression(); ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); NextToken(); return e; } Expression ParseIdentifier() { ValidateToken(TokenId.Identifier); object value; if (keywords.TryGetValue(token.text, out value)) { if (value is Type) return ParseTypeAccess((Type)value); if (value == (object)keywordIt) return ParseIt(); if (value == (object)keywordIif) return ParseIif(); if (value == (object)keywordNew) return ParseNew(); NextToken(); return (Expression)value; } if (symbols.TryGetValue(token.text, out value) || externals != null && externals.TryGetValue(token.text, out value)) { Expression expr = value as Expression; if (expr == null) { expr = Expression.Constant(value); } else { LambdaExpression lambda = expr as LambdaExpression; if (lambda != null) return ParseLambdaInvocation(lambda); } NextToken(); return expr; } if (it != null) return ParseMemberAccess(null, it); throw ParseError(Res.UnknownIdentifier, token.text); } Expression ParseIt() { if (it == null) throw ParseError(Res.NoItInScope); NextToken(); return it; } Expression ParseIif() { int errorPos = token.pos; NextToken(); Expression[] args = ParseArgumentList(); if (args.Length != 3) throw ParseError(errorPos, Res.IifRequiresThreeArgs); return GenerateConditional(args[0], args[1], args[2], errorPos); } Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos) { if (test.Type != typeof(bool)) throw ParseError(errorPos, Res.FirstExprMustBeBool); if (expr1.Type != expr2.Type) { Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; if (expr1as2 != null && expr2as1 == null) { expr1 = expr1as2; } else if (expr2as1 != null && expr1as2 == null) { expr2 = expr2as1; } else { string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null"; string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null"; if (expr1as2 != null && expr2as1 != null) throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); } } return Expression.Condition(test, expr1, expr2); } Expression ParseNew() { NextToken(); ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); NextToken(); List<DynamicProperty> properties = new List<DynamicProperty>(); List<Expression> expressions = new List<Expression>(); while (true) { int exprPos = token.pos; Expression expr = ParseExpression(); string propName; if (TokenIdentifierIs("as")) { NextToken(); propName = GetIdentifier(); NextToken(); } else { MemberExpression me = expr as MemberExpression; if (me == null) throw ParseError(exprPos, Res.MissingAsClause); propName = me.Member.Name; } expressions.Add(expr); properties.Add(new DynamicProperty(propName, expr.Type)); if (token.id != TokenId.Comma) break; NextToken(); } ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); NextToken(); Type type = DynamicExpression.CreateClass(properties); MemberBinding[] bindings = new MemberBinding[properties.Count]; for (int i = 0; i < bindings.Length; i++) bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); return Expression.MemberInit(Expression.New(type), bindings); } Expression ParseLambdaInvocation(LambdaExpression lambda) { int errorPos = token.pos; NextToken(); Expression[] args = ParseArgumentList(); MethodBase method; if (FindMethod(lambda.Type, "Invoke", false, args, out method) != 1) throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); return Expression.Invoke(lambda, args); } Expression ParseTypeAccess(Type type) { int errorPos = token.pos; NextToken(); if (token.id == TokenId.Question) { if (!type.IsValueType || IsNullableType(type)) throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type)); type = typeof(Nullable<>).MakeGenericType(type); NextToken(); } if (token.id == TokenId.OpenParen) { Expression[] args = ParseArgumentList(); MethodBase method; switch (FindBestMethod(type.GetConstructors(), args, out method)) { case 0: if (args.Length == 1) return GenerateConversion(args[0], type, errorPos); throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type)); case 1: return Expression.New((ConstructorInfo)method, args); default: throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type)); } } ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected); NextToken(); return ParseMemberAccess(type, null); } Expression GenerateConversion(Expression expr, Type type, int errorPos) { Type exprType = expr.Type; if (exprType == type) return expr; if (exprType.IsValueType && type.IsValueType) { if ((IsNullableType(exprType) || IsNullableType(type)) && GetNonNullableType(exprType) == GetNonNullableType(type)) return Expression.Convert(expr, type); if ((IsNumericType(exprType) || IsEnumType(exprType)) && (IsNumericType(type)) || IsEnumType(type)) return Expression.ConvertChecked(expr, type); } if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || exprType.IsInterface || type.IsInterface) return Expression.Convert(expr, type); throw ParseError(errorPos, Res.CannotConvertValue, GetTypeName(exprType), GetTypeName(type)); } Expression ParseMemberAccess(Type type, Expression instance) { if (instance != null) type = instance.Type; int errorPos = token.pos; string id = GetIdentifier(); NextToken(); if (token.id == TokenId.OpenParen) { if (instance != null && type != typeof(string)) { Type enumerableType = FindGenericType(typeof(IEnumerable<>), type); if (enumerableType != null) { Type elementType = enumerableType.GetGenericArguments()[0]; return ParseAggregate(instance, elementType, id, errorPos); } } Expression[] args = ParseArgumentList(); MethodBase mb; switch (FindMethod(type, id, instance == null, args, out mb)) { case 0: throw ParseError(errorPos, Res.NoApplicableMethod, id, GetTypeName(type)); case 1: MethodInfo method = (MethodInfo)mb; if (!IsPredefinedType(method.DeclaringType)) throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); if (method.ReturnType == typeof(void)) throw ParseError(errorPos, Res.MethodIsVoid, id, GetTypeName(method.DeclaringType)); return Expression.Call(instance, (MethodInfo)method, args); default: throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, GetTypeName(type)); } } else { MemberInfo member = FindPropertyOrField(type, id, instance == null); if (member == null) throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type)); return member is PropertyInfo ? Expression.Property(instance, (PropertyInfo)member) : Expression.Field(instance, (FieldInfo)member); } } static Type FindGenericType(Type generic, Type type) { while (type != null && type != typeof(object)) { if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type; if (generic.IsInterface) { foreach (Type intfType in type.GetInterfaces()) { Type found = FindGenericType(generic, intfType); if (found != null) return found; } } type = type.BaseType; } return null; } Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) { ParameterExpression outerIt = it; ParameterExpression innerIt = Expression.Parameter(elementType, ""); it = innerIt; Expression[] args = ParseArgumentList(); it = outerIt; MethodBase signature; if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); Type[] typeArgs; if (signature.Name == "Min" || signature.Name == "Max") { typeArgs = new Type[] { elementType, args[0].Type }; } else { typeArgs = new Type[] { elementType }; } if (args.Length == 0) { args = new Expression[] { instance }; } else { args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; } return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); } Expression[] ParseArgumentList() { ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); NextToken(); Expression[] args = token.id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); NextToken(); return args; } Expression[] ParseArguments() { List<Expression> argList = new List<Expression>(); while (true) { argList.Add(ParseExpression()); if (token.id != TokenId.Comma) break; NextToken(); } return argList.ToArray(); } Expression ParseElementAccess(Expression expr) { int errorPos = token.pos; ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); NextToken(); Expression[] args = ParseArguments(); ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); NextToken(); if (expr.Type.IsArray) { if (expr.Type.GetArrayRank() != 1 || args.Length != 1) throw ParseError(errorPos, Res.CannotIndexMultiDimArray); Expression index = PromoteExpression(args[0], typeof(int), true); if (index == null) throw ParseError(errorPos, Res.InvalidIndex); return Expression.ArrayIndex(expr, index); } else { MethodBase mb; switch (FindIndexer(expr.Type, args, out mb)) { case 0: throw ParseError(errorPos, Res.NoApplicableIndexer, GetTypeName(expr.Type)); case 1: return Expression.Call(expr, (MethodInfo)mb, args); default: throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, GetTypeName(expr.Type)); } } } static bool IsPredefinedType(Type type) { foreach (Type t in predefinedTypes) if (t == type) return true; return false; } static bool IsNullableType(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } static Type GetNonNullableType(Type type) { return IsNullableType(type) ? type.GetGenericArguments()[0] : type; } static string GetTypeName(Type type) { Type baseType = GetNonNullableType(type); string s = baseType.Name; if (type != baseType) s += '?'; return s; } static bool IsNumericType(Type type) { return GetNumericTypeKind(type) != 0; } static bool IsSignedIntegralType(Type type) { return GetNumericTypeKind(type) == 2; } static bool IsUnsignedIntegralType(Type type) { return GetNumericTypeKind(type) == 3; } static int GetNumericTypeKind(Type type) { type = GetNonNullableType(type); if (type.IsEnum) return 0; switch (Type.GetTypeCode(type)) { case TypeCode.Char: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return 1; case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: return 2; case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: return 3; default: return 0; } } static bool IsEnumType(Type type) { return GetNonNullableType(type).IsEnum; } void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) { Expression[] args = new Expression[] { expr }; MethodBase method; if (FindMethod(signatures, "F", false, args, out method) != 1) throw ParseError(errorPos, Res.IncompatibleOperand, opName, GetTypeName(args[0].Type)); expr = args[0]; } void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) { Expression[] args = new Expression[] { left, right }; MethodBase method; if (FindMethod(signatures, "F", false, args, out method) != 1) throw IncompatibleOperandsError(opName, left, right, errorPos); left = args[0]; right = args[1]; } Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int pos) { return ParseError(pos, Res.IncompatibleOperands, opName, GetTypeName(left.Type), GetTypeName(right.Type)); } MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) { BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); foreach (Type t in SelfAndBaseTypes(type)) { MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, Type.FilterNameIgnoreCase, memberName); if (members.Length != 0) return members[0]; } return null; } int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) { BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); foreach (Type t in SelfAndBaseTypes(type)) { MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName); int count = FindBestMethod(members.Cast<MethodBase>(), args, out method); if (count != 0) return count; } method = null; return 0; } int FindIndexer(Type type, Expression[] args, out MethodBase method) { foreach (Type t in SelfAndBaseTypes(type)) { MemberInfo[] members = t.GetDefaultMembers(); if (members.Length != 0) { IEnumerable<MethodBase> methods = members. OfType<PropertyInfo>(). Select(p => (MethodBase)p.GetGetMethod()). Where(m => m != null); int count = FindBestMethod(methods, args, out method); if (count != 0) return count; } } method = null; return 0; } static IEnumerable<Type> SelfAndBaseTypes(Type type) { if (type.IsInterface) { List<Type> types = new List<Type>(); AddInterface(types, type); return types; } return SelfAndBaseClasses(type); } static IEnumerable<Type> SelfAndBaseClasses(Type type) { while (type != null) { yield return type; type = type.BaseType; } } static void AddInterface(List<Type> types, Type type) { if (!types.Contains(type)) { types.Add(type); foreach (Type t in type.GetInterfaces()) AddInterface(types, t); } } class MethodData { public MethodBase MethodBase; public ParameterInfo[] Parameters; public Expression[] Args; } int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method) { MethodData[] applicable = methods. Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). Where(m => IsApplicable(m, args)). ToArray(); if (applicable.Length > 1) { applicable = applicable. Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))). ToArray(); } if (applicable.Length == 1) { MethodData md = applicable[0]; for (int i = 0; i < args.Length; i++) args[i] = md.Args[i]; method = md.MethodBase; } else { method = null; } return applicable.Length; } bool IsApplicable(MethodData method, Expression[] args) { if (method.Parameters.Length != args.Length) return false; Expression[] promotedArgs = new Expression[args.Length]; for (int i = 0; i < args.Length; i++) { ParameterInfo pi = method.Parameters[i]; if (pi.IsOut) return false; Expression promoted = PromoteExpression(args[i], pi.ParameterType, false); if (promoted == null) return false; promotedArgs[i] = promoted; } method.Args = promotedArgs; return true; } Expression PromoteExpression(Expression expr, Type type, bool exact) { if (expr.Type == type) return expr; if (expr is ConstantExpression) { ConstantExpression ce = (ConstantExpression)expr; if (ce == nullLiteral) { if (!type.IsValueType || IsNullableType(type)) return Expression.Constant(null, type); } else { string text; if (literals.TryGetValue(ce, out text)) { Type target = GetNonNullableType(type); Object value = null; switch (Type.GetTypeCode(ce.Type)) { case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: value = ParseNumber(text, target); break; case TypeCode.Double: if (target == typeof(decimal)) value = ParseNumber(text, target); break; case TypeCode.String: value = ParseEnum(text, target); break; } if (value != null) return Expression.Constant(value, type); } } } if (IsCompatibleWith(expr.Type, type)) { if (type.IsValueType || exact) return Expression.Convert(expr, type); return expr; } return null; } static object ParseNumber(string text, Type type) { switch (Type.GetTypeCode(GetNonNullableType(type))) { case TypeCode.SByte: sbyte sb; if (sbyte.TryParse(text, out sb)) return sb; break; case TypeCode.Byte: byte b; if (byte.TryParse(text, out b)) return b; break; case TypeCode.Int16: short s; if (short.TryParse(text, out s)) return s; break; case TypeCode.UInt16: ushort us; if (ushort.TryParse(text, out us)) return us; break; case TypeCode.Int32: int i; if (int.TryParse(text, out i)) return i; break; case TypeCode.UInt32: uint ui; if (uint.TryParse(text, out ui)) return ui; break; case TypeCode.Int64: long l; if (long.TryParse(text, out l)) return l; break; case TypeCode.UInt64: ulong ul; if (ulong.TryParse(text, out ul)) return ul; break; case TypeCode.Single: float f; if (float.TryParse(text, out f)) return f; break; case TypeCode.Double: double d; if (double.TryParse(text, out d)) return d; break; case TypeCode.Decimal: decimal e; if (decimal.TryParse(text, out e)) return e; break; } return null; } static object ParseEnum(string name, Type type) { if (type.IsEnum) { MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, Type.FilterNameIgnoreCase, name); if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null); } return null; } static bool IsCompatibleWith(Type source, Type target) { if (source == target) return true; if (!target.IsValueType) return target.IsAssignableFrom(source); Type st = GetNonNullableType(source); Type tt = GetNonNullableType(target); if (st != source && tt == target) return false; TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); switch (sc) { case TypeCode.SByte: switch (tc) { case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Byte: switch (tc) { case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int16: switch (tc) { case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.UInt16: switch (tc) { case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int32: switch (tc) { case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.UInt32: switch (tc) { case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Int64: switch (tc) { case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.UInt64: switch (tc) { case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return true; } break; case TypeCode.Single: switch (tc) { case TypeCode.Single: case TypeCode.Double: return true; } break; default: if (st == tt) return true; break; } return false; } static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) { bool better = false; for (int i = 0; i < args.Length; i++) { int c = CompareConversions(args[i].Type, m1.Parameters[i].ParameterType, m2.Parameters[i].ParameterType); if (c < 0) return false; if (c > 0) better = true; } return better; } // Return 1 if s -> t1 is a better conversion than s -> t2 // Return -1 if s -> t2 is a better conversion than s -> t1 // Return 0 if neither conversion is better static int CompareConversions(Type s, Type t1, Type t2) { if (t1 == t2) return 0; if (s == t1) return 1; if (s == t2) return -1; bool t1t2 = IsCompatibleWith(t1, t2); bool t2t1 = IsCompatibleWith(t2, t1); if (t1t2 && !t2t1) return 1; if (t2t1 && !t1t2) return -1; if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; return 0; } Expression GenerateEqual(Expression left, Expression right) { return Expression.Equal(left, right); } Expression GenerateNotEqual(Expression left, Expression right) { return Expression.NotEqual(left, right); } Expression GenerateGreaterThan(Expression left, Expression right) { if (left.Type == typeof(string)) { return Expression.GreaterThan( GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0) ); } return Expression.GreaterThan(left, right); } Expression GenerateGreaterThanEqual(Expression left, Expression right) { if (left.Type == typeof(string)) { return Expression.GreaterThanOrEqual( GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0) ); } return Expression.GreaterThanOrEqual(left, right); } Expression GenerateLessThan(Expression left, Expression right) { if (left.Type == typeof(string)) { return Expression.LessThan( GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0) ); } return Expression.LessThan(left, right); } Expression GenerateLessThanEqual(Expression left, Expression right) { if (left.Type == typeof(string)) { return Expression.LessThanOrEqual( GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0) ); } return Expression.LessThanOrEqual(left, right); } Expression GenerateAdd(Expression left, Expression right) { if (left.Type == typeof(string) && right.Type == typeof(string)) { return GenerateStaticMethodCall("Concat", left, right); } return Expression.Add(left, right); } Expression GenerateSubtract(Expression left, Expression right) { return Expression.Subtract(left, right); } Expression GenerateStringConcat(Expression left, Expression right) { return Expression.Call( null, typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }), new[] { left, right }); } MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) { return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); } Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) { return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); } void SetTextPos(int pos) { textPos = pos; ch = textPos < textLen ? text[textPos] : '\0'; } void NextChar() { if (textPos < textLen) textPos++; ch = textPos < textLen ? text[textPos] : '\0'; } void NextToken() { while (Char.IsWhiteSpace(ch)) NextChar(); TokenId t; int tokenPos = textPos; switch (ch) { case '!': NextChar(); if (ch == '=') { NextChar(); t = TokenId.ExclamationEqual; } else { t = TokenId.Exclamation; } break; case '%': NextChar(); t = TokenId.Percent; break; case '&': NextChar(); if (ch == '&') { NextChar(); t = TokenId.DoubleAmphersand; } else { t = TokenId.Amphersand; } break; case '(': NextChar(); t = TokenId.OpenParen; break; case ')': NextChar(); t = TokenId.CloseParen; break; case '*': NextChar(); t = TokenId.Asterisk; break; case '+': NextChar(); t = TokenId.Plus; break; case ',': NextChar(); t = TokenId.Comma; break; case '-': NextChar(); t = TokenId.Minus; break; case '.': NextChar(); t = TokenId.Dot; break; case '/': NextChar(); t = TokenId.Slash; break; case ':': NextChar(); t = TokenId.Colon; break; case '<': NextChar(); if (ch == '=') { NextChar(); t = TokenId.LessThanEqual; } else if (ch == '>') { NextChar(); t = TokenId.LessGreater; } else { t = TokenId.LessThan; } break; case '=': NextChar(); if (ch == '=') { NextChar(); t = TokenId.DoubleEqual; } else { t = TokenId.Equal; } break; case '>': NextChar(); if (ch == '=') { NextChar(); t = TokenId.GreaterThanEqual; } else { t = TokenId.GreaterThan; } break; case '?': NextChar(); t = TokenId.Question; break; case '[': NextChar(); t = TokenId.OpenBracket; break; case ']': NextChar(); t = TokenId.CloseBracket; break; case '|': NextChar(); if (ch == '|') { NextChar(); t = TokenId.DoubleBar; } else { t = TokenId.Bar; } break; case '"': case '\'': char quote = ch; do { NextChar(); while (textPos < textLen && ch != quote) NextChar(); if (textPos == textLen) throw ParseError(textPos, Res.UnterminatedStringLiteral); NextChar(); } while (ch == quote); t = TokenId.StringLiteral; break; default: if (Char.IsLetter(ch) || ch == '@' || ch == '_') { do { NextChar(); } while (Char.IsLetterOrDigit(ch) || ch == '_'); t = TokenId.Identifier; break; } if (Char.IsDigit(ch)) { t = TokenId.IntegerLiteral; do { NextChar(); } while (Char.IsDigit(ch)); if (ch == '.') { t = TokenId.RealLiteral; NextChar(); ValidateDigit(); do { NextChar(); } while (Char.IsDigit(ch)); } if (ch == 'E' || ch == 'e') { t = TokenId.RealLiteral; NextChar(); if (ch == '+' || ch == '-') NextChar(); ValidateDigit(); do { NextChar(); } while (Char.IsDigit(ch)); } if (ch == 'F' || ch == 'f') NextChar(); break; } if (textPos == textLen) { t = TokenId.End; break; } throw ParseError(textPos, Res.InvalidCharacter, ch); } token.id = t; token.text = text.Substring(tokenPos, textPos - tokenPos); token.pos = tokenPos; } bool TokenIdentifierIs(string id) { return token.id == TokenId.Identifier && String.Equals(id, token.text, StringComparison.OrdinalIgnoreCase); } string GetIdentifier() { ValidateToken(TokenId.Identifier, Res.IdentifierExpected); string id = token.text; if (id.Length > 1 && id[0] == '@') id = id.Substring(1); return id; } void ValidateDigit() { if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected); } void ValidateToken(TokenId t, string errorMessage) { if (token.id != t) throw ParseError(errorMessage); } void ValidateToken(TokenId t) { if (token.id != t) throw ParseError(Res.SyntaxError); } Exception ParseError(string format, params object[] args) { return ParseError(token.pos, format, args); } Exception ParseError(int pos, string format, params object[] args) { return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos); } static Dictionary<string, object> CreateKeywords() { Dictionary<string, object> d = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); d.Add("true", trueLiteral); d.Add("false", falseLiteral); d.Add("null", nullLiteral); d.Add(keywordIt, keywordIt); d.Add(keywordIif, keywordIif); d.Add(keywordNew, keywordNew); foreach (Type type in predefinedTypes) d.Add(type.Name, type); return d; } } static class Res { public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once"; public const string ExpressionTypeMismatch = "Expression of type '{0}' expected"; public const string ExpressionExpected = "Expression expected"; public const string InvalidCharacterLiteral = "Character literal must contain exactly one character"; public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; public const string InvalidRealLiteral = "Invalid real literal '{0}'"; public const string UnknownIdentifier = "Unknown identifier '{0}'"; public const string NoItInScope = "No 'it' is in scope"; public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'"; public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other"; public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; public const string MissingAsClause = "Expression is missing an 'as' clause"; public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression"; public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form"; public const string NoMatchingConstructor = "No matching constructor in type '{0}'"; public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor"; public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'"; public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'"; public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'"; public const string NoApplicableAggregate = "No applicable aggregate method '{0}' exists"; public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported"; public const string InvalidIndex = "Array index must be an integer expression"; public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'"; public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; public const string UnterminatedStringLiteral = "Unterminated string literal"; public const string InvalidCharacter = "Syntax error '{0}'"; public const string DigitExpected = "Digit expected"; public const string SyntaxError = "Syntax error"; public const string TokenExpected = "{0} expected"; public const string ParseExceptionFormat = "{0} (at index {1})"; public const string ColonExpected = "':' expected"; public const string OpenParenExpected = "'(' expected"; public const string CloseParenOrOperatorExpected = "')' or operator expected"; public const string CloseParenOrCommaExpected = "')' or ',' expected"; public const string DotOrOpenParenExpected = "'.' or '(' expected"; public const string OpenBracketExpected = "'[' expected"; public const string CloseBracketOrCommaExpected = "']' or ',' expected"; public const string IdentifierExpected = "Identifier expected"; } View Code 此时,我们需要将我们的实体和数据库字段映射对应起来,新建一个ImageMap类代码如下: public class ImageMap : EntityTypeConfiguration<ImageModel> { public ImageMap() { // Primary Key this.HasKey(t => t.ID); // Properties this.Property(t => t.IDProofFront) .HasMaxLength(100); this.Property(t => t.IDProofBack) .HasMaxLength(100); // Table & Column Mappings this.ToTable("ImageModel"); this.Property(t => t.ID).HasColumnName("ID"); this.Property(t => t.IDProofFront).HasColumnName("IDProofFront"); this.Property(t => t.IDProofBack).HasColumnName("IDProofBack"); } } 其中ToTable就是指明数据库表名, 那么如何将上传的图片保存更新到数据库呢? 接下来我们新建一个接口类IResourcesImage 并继承操作基类 IRepository<ImageModel> 定义一个上传身份信息的规则如下: bool UpdateIDProof(string IDProofFront, string IDProofBack, int pId); 接下来我们新建一个ResourcesImage 实现上述接口。代码如下 public ResourcesImage() { } /// <summary> /// 上传身份信息采用此种方式 /// </summary> /// <param name="IDProofBack"></param> /// <param name="IDProofBack"></param> /// <param name="pId"></param> /// <returns></returns> public bool UpdateIDProof(string IDProofFront, string IDProofBack, int pId) { int flag = 0; if (IDProofFront != "" && IDProofFront != null) { flag = this.Update(m => m.ID == pId, u => new ImageModel { IDProofFront = IDProofFront }); if (flag == 1) { if (IDProofBack != "" && IDProofBack != null) flag = this.Update(m => m.ID == pId, u => new ImageModel { IDProofBack = IDProofBack }); } } else { if (IDProofBack != "" && IDProofBack != null) flag = this.Update(m => m.ID == pId, u => new ImageModel { IDProofBack = IDProofBack }); } return flag == 0 ? false : true; } 我们在中间层做一下这个操作: private readonly IResourcesImage _resourcesImage; public CodeBLL() { this._resourcesImage = new ResourcesImage(); } /// <summary> /// 根据字段更新用户的文件资料信息 /// </summary> /// <param name="fileNameField">字段</param> /// <param name="fileNameValue">字段值</param> /// <param name="pId"></param> /// <returns></returns> public bool UpdateFileName(string IDProofFront, string IDProofBack, int pId) { bool flag = false; flag = _resourcesImage.UpdateIDProof(IDProofFront, IDProofBack, pId); return flag; } 这样做其实并不科学,需要手动实例化这种仓储操作,科学的方式可以使用IOC(控制反转). 中间层做好之后,我们只需要在HomeController中调用此方法即可,代码如下: 至此,我们就实现了本地通过ftp方式上传图片代码,并将图片以相对路径保存在数据库中,数据库存放格式如下: Demo下载 点我下载Demo Git下载 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 之前一直很少接触多线程这块。这次项目中刚好用到了网络编程TCP这块,做一个服务端,需要使用到多线程,所以记录下过程。希望可以帮到自己的同时能给别人带来一点点收获~ 关于TCP的介绍就不多讲,神马经典的三次握手、四次握手,可以参考下面几篇博客学习了解: TCP三次握手扫盲 效果预览 客户端是一个门禁设备,主要是向服务端发送实时数据(200ms)。服务端解析出进出人数并打印显示。 实现步骤 因为主要是在服务器上监听各设备的连接请求以及回应并打印出入人数,所以界面我设计成这样: 可以在窗体事件中绑定本地IP,代码如下: //获取本地的IP地址 string AddressIP = string.Empty; foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList) { if (_IPAddress.AddressFamily.ToString() == "InterNetwork") { AddressIP = _IPAddress.ToString(); } } //给IP控件赋值 txtIp.Text = AddressIP; 首先我们需要定义几个全局变量 Thread threadWatch = null; // 负责监听客户端连接请求的 线程; Socket socketWatch = null;Dictionary<string, Socket> dict = new Dictionary<string, Socket>();//存放套接字Dictionary<string, Thread> dictThread = new Dictionary<string, Thread>();//存放线程 然后可以开始我们的点击事件启动服务啦 首先我们创建负责监听的套接字,用到了 System.Net.Socket 下的寻址方案AddressFamily ,然后后面跟套接字类型,最后是支持的协议。 在Bind绑定后,我们创建了负责监听的线程。代码如下: // 创建负责监听的套接字,注意其中的参数; socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 获得文本框中的IP对象; IPAddress address = IPAddress.Parse(txtIp.Text.Trim()); // 创建包含ip和端口号的网络节点对象; IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text.Trim())); try { // 将负责监听的套接字绑定到唯一的ip和端口上; socketWatch.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); socketWatch.Bind(endPoint); } catch (SocketException se) { MessageBox.Show("异常:" + se.Message); return; } // 设置监听队列的长度; socketWatch.Listen(10000); // 创建负责监听的线程; threadWatch = new Thread(WatchConnecting); threadWatch.IsBackground = true; threadWatch.Start(); ShowMsg("服务器启动监听成功!"); 其中 WatchConnecting方法是负责监听新客户端请求的 相信图片中注释已经很详细了,主要是监听到有客户端的连接请求后,开辟一个新线程用来接收客户端发来的数据,有一点比较重要就是在Start方法中传递了当前socket对象 /// <summary> /// 监听客户端请求的方法; /// </summary> void WatchConnecting() { ShowMsg("新客户端连接成功!"); while (true) // 持续不断的监听客户端的连接请求; { // 开始监听客户端连接请求,Accept方法会阻断当前的线程; Socket sokConnection = socketWatch.Accept(); // 一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字; var ssss = sokConnection.RemoteEndPoint.ToString().Split(':'); //查找ListBox集合中是否包含此IP开头的项,找到为0,找不到为-1 if (lbOnline.FindString(ssss[0]) >= 0) { lbOnline.Items.Remove(sokConnection.RemoteEndPoint.ToString()); } else { lbOnline.Items.Add(sokConnection.RemoteEndPoint.ToString()); } // 将与客户端连接的 套接字 对象添加到集合中; dict.Add(sokConnection.RemoteEndPoint.ToString(), sokConnection); Thread thr = new Thread(RecMsg); thr.IsBackground = true; thr.Start(sokConnection); dictThread.Add(sokConnection.RemoteEndPoint.ToString(), thr); // 将新建的线程 添加 到线程的集合中去。 } } 其中接收数据 RecMsg方法如下: 解释如图,一目了然,代码如下 void RecMsg(object sokConnectionparn) { Socket sokClient = sokConnectionparn as Socket; while (true) { // 定义一个缓存区; byte[] arrMsgRec = new byte[1024]; // 将接受到的数据存入到输入 arrMsgRec中; int length = -1; try { length = sokClient.Receive(arrMsgRec); // 接收数据,并返回数据的长度; if (length > 0) { //主业务 } else { // 从 通信套接字 集合中删除被中断连接的通信套接字; dict.Remove(sokClient.RemoteEndPoint.ToString()); // 从通信线程集合中删除被中断连接的通信线程对象; dictThread.Remove(sokClient.RemoteEndPoint.ToString()); // 从列表中移除被中断的连接IP lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); ShowMsg("" + sokClient.RemoteEndPoint.ToString() + "断开连接\r\n"); //log.log("遇见异常"+se.Message); break; } } catch (SocketException se) { // 从 通信套接字 集合中删除被中断连接的通信套接字; dict.Remove(sokClient.RemoteEndPoint.ToString()); // 从通信线程集合中删除被中断连接的通信线程对象; dictThread.Remove(sokClient.RemoteEndPoint.ToString()); // 从列表中移除被中断的连接IP lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); ShowMsg("" + sokClient.RemoteEndPoint.ToString() + "断开,异常消息:" + se.Message + "\r\n"); //log.log("遇见异常"+se.Message); break; } catch (Exception e) { // 从 通信套接字 集合中删除被中断连接的通信套接字; dict.Remove(sokClient.RemoteEndPoint.ToString()); // 从通信线程集合中删除被中断连接的通信线程对象; dictThread.Remove(sokClient.RemoteEndPoint.ToString()); // 从列表中移除被中断的连接IP lbOnline.Items.Remove(sokClient.RemoteEndPoint.ToString()); ShowMsg("异常消息:" + e.Message + "\r\n"); // log.log("遇见异常" + e.Message); break; } } } 其中那个ShowMsg方法主要是在窗体中打印当前接收情况和一些异常情况,方法如下: void ShowMsg(string str) { if (!BPS_Help.ChangeByte(txtMsg.Text, 2000)) { txtMsg.Text = ""; txtMsg.AppendText(str + "\r\n"); } else { txtMsg.AppendText(str + "\r\n"); } } 其中用到了一个方法判断ChangeByte ,如果文本长度超过2000个字节,就清空再重新赋值。具体实现如下: /// <summary> /// 判断文本框混合输入长度 /// </summary> /// <param name="str">要判断的字符串</param> /// <param name="i">长度</param> /// <returns></returns> public static bool ChangeByte(string str, int i) { byte[] b = Encoding.Default.GetBytes(str); int m = b.Length; if (m < i) { return true; } else { return false; } } 心得体会:其实整个流程并不复杂,但我遇到一个问题是,客户端每200毫秒发一次连接过来后,服务端会报一个远程主机已经强制关闭连接,开始我以为是我这边服务器线程间的问题或者是阻塞神马的,后来和客户端联调才发现问题,原来是服务器回应客户端心跳包的长度有问题,服务端定义的是1024字节,但是客户端只接受32字节的心跳包回应才会正确解析~所以,对接协议要沟通清楚,沟通清楚,沟通清楚,重要的事情说说三遍 还有几个点值得注意 1,有时候会遇到窗体间的控件访问异常,需要这样处理 Control.CheckForIllegalCrossThreadCalls = false; 2 多线程调试比较麻烦,可以采用打印日志的方式,例如: 具体实现可以参考我的另一篇博客:点我跳转 3 ,接收解析客户端数据的时候,要注意大小端的问题,比如下面这个第9位和第8位如果解出来和实际不相符,可以把两边颠倒一下。 public int Get_ch2In(byte[] data) { var ch2In = (data[9] << 8) | data[8]; return ch2In; } 4 在接收到客户端数据的时候,有些地方要注意转换成十六进制再看结果是否正确 public int Get_ch3In(byte[] data) { int ch3In = 0; for (int i = 12; i < 14; i++) { ch3In = int.Parse(ch3In + BPS_Help.HexOf(data[i])); } return ch3In; } 上面这个方法在对data[i]进行了十六进制的转换,转换方法如下: /// <summary> /// 转换成十六进制数 /// </summary> /// <param name="AscNum"></param> /// <returns></returns> public static string HexOf(int AscNum) { string TStr; if (AscNum > 255) { AscNum = AscNum % 256; } TStr = AscNum.ToString("X"); if (TStr.Length == 1) { TStr = "0" + TStr; } return TStr; } 5 还有个可以了解的是将数组转换成结构,参考代码如下: /// <summary> /// Byte数组转结构体 /// </summary> /// <param name="bytes">byte数组</param> /// <param name="type">结构体类型</param> /// <returns>转换后的结构体</returns> public static object BytesToStuct(byte[] bytes, Type type) { //得到结构体的大小 int size = Marshal.SizeOf(type); //byte数组长度小于结构体的大小 if (size > bytes.Length) { return null; } IntPtr structPtr = Marshal.AllocHGlobal(size); Marshal.Copy(bytes, 0, structPtr, size); object obj = Marshal.PtrToStructure(structPtr, type); //释放内存空间 Marshal.FreeHGlobal(structPtr); return obj; } 调用方法如下,注意,此处的package的结构应该和协议中客户端发送的数据结构一致才能转换 如协议中是这样的定义的话: 那在代码中就可以这样定义一个package结构体 /// <summary> /// 数据包结构体 /// </summary> [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Package { /// <summary> /// 确定为命令包的标识 /// </summary> public int commandFlag; /// <summary> /// 命令 /// </summary> public int command; /// <summary> ///数据长度(数据段不包括包头) /// </summary> public int dataLength; /// <summary> /// 通道编号 /// </summary> public short channelNo; /// <summary> /// 块编号 /// </summary> public short blockNo; /// <summary> /// 开始标记 /// </summary> public int startFlag; /// <summary> /// 结束标记0x0D0A为结束符 /// </summary> public int finishFlag; /// <summary> /// 校验码 /// </summary> public int checksum; /// <summary> /// 保留 char数组,SizeConst表示数组个数,在转成 /// byte数组前必须先初始化数组,再使用,初始化 /// 的数组长度必须和SizeConst一致,例:test=new char[4]; /// </summary> [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public char[] reserve; } Demo下载 TCP多线程服务器及客户端Demo 点我跳去下载 密码:3hzs git一下:我要去Git 收发实体对象 2017.3.11 补充 如果服务器和客户端公用一个实体类,那还好说,如果服务器和客户端分别使用结构相同但不是同一个项目下的实体类,该如何用正确的姿势收发呢? 首先简单看看效果如下: 具体实现: 因为前面提到不在同一项目下,如果直接序列化和反序列化,就会反序列化失败,因为不能对不是同一命名空间下的类进行此类操作,那么解决办法可以新建一个类库Studnet,然后重新生成dll,在服务器和客户端分别引用此dll,就可以对此dll进行序列化和反序列化操作了。 项目结构如下图(这里是作为演示,将客户端和服务器放在同一解决方案下,实际上这种情况解决的就是客户端和服务器是两个单独的解决方案) 客户端发送核心代码: void showClient() { address = IPAddress.Parse("127.0.0.1"); endpoint = new IPEndPoint(address, 5000); socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { socketClient.Connect(endpoint); Console.WriteLine("连接服务端成功\r\n准备发送实体Student"); Student.Studnet_entity ms = new Student.Studnet_entity() { ID = 1, Name = "张三", Phone = "13237157517", sex = 1, Now_Time = DateTime.Now }; using (MemoryStream memory = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memory, ms); Console.WriteLine("发送长度:" + memory.ToArray().Length); socketClient.Send(memory.ToArray()); Console.WriteLine("我发送了 学生实体对象\r\n"); } } catch (Exception) { throw; } } 服务端接收并解析实体对象核心代码 /// <summary> /// 服务端负责监听客户端发来的数据方法 /// </summary> void RecMsg(object socketClientPara) { byte[] arrMsgRec = new byte[1024];//手动准备空间 Socket socketClient = socketClientPara as Socket; List<byte> listbyte = new List<byte>(); while (true) { //将接受到的数据存入arrMsgRec数组,并返回真正接收到的数据长度 int length = socketClient.Receive(arrMsgRec); if (length > arrMsgRec.Length) { listbyte.AddRange(arrMsgRec); } else { for (int i = 0; i < length; i++) listbyte.Add(arrMsgRec[i]); break; } } //创建内存流 using (MemoryStream m = new MemoryStream(listbyte.ToArray())) { //创建以二进制格式对对象进行序列化和反序列化 BinaryFormatter bf = new BinaryFormatter(); Console.WriteLine("m.length" + m.ToArray().Length); //反序列化 object dataObj = bf.Deserialize(m); //得到解析后的实体对象 Student.Studnet_entity dt = dataObj as Studnet_entity; Console.WriteLine("接收客户端长度:" + listbyte.Count + " 反序列化结果是:ID:" + dt.ID + " 姓名:" + dt.Name + " 当前时间:" + dt.Now_Time); } } 收发实体对象Demo 点我前去下载Demo 密码:x2ke 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 新的一年悄然到来,生活依旧。最近一周大热的赵雷风,一首《成都》,一首《理想》再次把民谣展示在国人面前。歌词着实写的不错。 理想,你今年几岁 你总是诱惑着年轻的朋友 你总是谢了又开 给我惊喜 又让我沉入失望的生活里 。。。。 我已不是无悔的那个青年 青春被时光抛弃 已是当父亲的年纪 理想永远都年轻 你让我倔强的反抗着命运 你让我变得苍白 却依然天真的相信 花儿会再次的盛开 等等,这和我们今天的主题貌似没半毛钱关系。好了,再扯就扯远了,今天和大家分享的是 Bootstrap Table 点我查看效果 或者直接看简版效果如下(一个简单的后台管理,实现了增删查改): bottstrap相关 既然说是bootstrap table 那肯定是bootstrap实现的(这是句废话)。 关于bottstrap相关文档如下: Bootstrap中文网:http://www.bootcss.com/ Bootstrap Table Demo:http://issues.wenzhixin.net.cn/bootstrap-table/index.html Bootstrap Table API:http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/ Bootstrap Table源码:https://github.com/wenzhixin/bootstrap-table Bootstrap DataPicker:http://www.bootcss.com/p/bootstrap-datetimepicker/ Boostrap Table 扩展API:http://bootstrap-table.wenzhixin.net.cn/extensions/ Bootstrap离线API Bootstrap Table 离线API bottstrap引入 关于引入有两种方式,一种是直接下载源码,然后放到项目中,bottstrap包可以到http://v3.bootcss.com/ 中找到。 第二种方法就是使用打开Nuget,搜索这两个包 而Bootstrap Table的版本竟然是0.4,这也太坑爹了。所以博主建议Bootstrap Table的包就直接在源码里面去下载吧。Bootstrap Table最新的版本好像是1.9.0。 代码解析 @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>BootStrap Table使用</title> @*1、Jquery组件引用*@ <script src="~/Scripts/jquery-1.10.2.js"></script> @*2、bootstrap组件引用*@ <script src="~/Content/bootstrap/bootstrap.js"></script> <link href="~/Content/bootstrap/bootstrap.css" rel="stylesheet" /> @*3、bootstrap table组件以及中文包的引用*@ <script src="~/Content/bootstrap-table/bootstrap-table.js"></script> <link href="~/Content/bootstrap-table/bootstrap-table.css" rel="stylesheet" /> <script src="~/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script> @*4、页面Js文件的引用*@ <script src="~/Scripts/Home/Index.js"></script> </head> <body> <div class="panel-body" style="padding-bottom:0px;"> <div class="panel panel-default"> <div class="panel-heading">查询条件</div> <div class="panel-body"> <form id="formSearch" class="form-horizontal"> <div class="form-group" style="margin-top:15px"> <label class="control-label col-sm-1" for="txt_search_departmentname">部门名称</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_departmentname"> </div> <label class="control-label col-sm-1" for="txt_search_statu">状态</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_statu"> </div> <div class="col-sm-4" style="text-align:left;"> <button type="button" style="margin-left:50px" id="btn_query" class="btn btn-primary">查询</button> </div> </div> </form> </div> </div> <div id="toolbar" class="btn-group"> <button id="btn_add" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button id="btn_edit" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改 </button> <button id="btn_delete" type="button" class="btn btn-default"> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除 </button> </div> <table id="tb_departments"></table> </div> </body> </html> 引入需要的文件之后,我们最重要的就是定义一个空的table,如上的 <table id="tb_departments"></table> 。当然Bootstrap table还提供了一种简介的用法,直接在table标签里面定义类似“data-...”等相关属性,就不用再js里面注册了,但博主觉得这种用法虽然简单,但不太灵活,遇到父子表等这些高级用法的时候就不太好处理了,所以咱们还是统一使用在js里面初始化的方式来使用table组件。 Js初始化 $(function () { //1.初始化Table var oTable = new TableInit(); oTable.Init(); //2.初始化Button的点击事件 var oButtonInit = new ButtonInit(); oButtonInit.Init(); }); var TableInit = function () { var oTableInit = new Object(); //初始化Table oTableInit.Init = function () { $('#tb_departments').bootstrapTable({ url: '/Home/GetDepartment', //请求后台的URL(*) method: 'get', //请求方式(*) toolbar: '#toolbar', //工具按钮用哪个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) pagination: true, //是否显示分页(*) sortable: false, //是否启用排序 sortOrder: "asc", //排序方式 queryParams: oTableInit.queryParams,//传递参数(*) sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) pageNumber:1, //初始化加载第一页,默认第一页 pageSize: 10, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) search: true, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,所以,个人感觉意义不大 strictSearch: true, showColumns: true, //是否显示所有的列 showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少允许的列数 clickToSelect: true, //是否启用点击选中行 height: 500, //行高,如果没有设置height属性,表格自动根据记录条数觉得表格高度 uniqueId: "ID", //每一行的唯一标识,一般为主键列 showToggle:true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 detailView: false, //是否显示父子表 columns: [{ checkbox: true } , { field: 'MethodName', title: '方法名' }, { field: 'OperType', title: '日志类型' }, { field: 'LogFormat', title: '内容格式' }, { field: 'ParamterValues', title: '格式参数' }, { field: 'LogSql', title: '数值' }, { field: 'SQLParamter', title: '数值定义' }, { field: 'CreateDate', title: '创建时间' } ] }); }; //得到查询的参数 oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //页面大小 offset: params.offset, //页码 departmentname: $("#txt_search_departmentname").val(), statu: $("#txt_search_statu").val() }; return temp; }; return oTableInit; }; var ButtonInit = function () { var oInit = new Object(); var postdata = {}; oInit.Init = function () { //初始化页面上面的按钮事件 }; return oInit; }; 表格的初始化也很简单,定义相关的参数即可。上面一些博主觉得重要的参数都加了注释,并且初始化Table必须的几个参数博主也用(*)做了标记,如果你的表格也有太多的页面需求,直接用必须的参数就能解决。同样,在columns参数里面其实也有很多的参数需要设置,比如列的排序,对齐,宽度等等。这些博主觉得比较简单,不会涉及表格的功能,看看API就能搞定。 注:如果需要隐藏某列,只需要即可 $('#tb_departments').bootstrapTable('hideColumn', '列名'); 显示某列 $('#tableOrderRealItems').bootstrapTable('showColumn', '列名'); 在Controller里面对应的方法 public JsonResult GetDepartment(int limit, int offset, string departmentname, string statu) { var lstRes = new List<Department>(); for (var i = 0; i < 50; i++) { var oModel = new Department(); oModel.ID = Guid.NewGuid().ToString(); oModel.Name = "销售部" + i ; oModel.Level = i.ToString(); oModel.Desc = "暂无描述信息"; lstRes.Add(oModel); } var total = lstRes.Count; var rows = lstRes.Skip(offset).Take(limit).ToList(); return Json(new { total = total, rows = rows }, JsonRequestBehavior.AllowGet); } 这里有一点需要注意:如果是服务端分页,返回的结果必须包含total、rows两个参数。漏写或错写都会导致表格无法显示数据。相反,如果是客户端分页,这里要返回一个集合对象到前端。 效果及说明 小结 第一个问题上面说过,如果在js里面初始化的参数 sidePagination: "server" 设置为在服务端分页,那么我们的返回值必须告诉前端总记录的条数和当前页的记录数,然后前端才知道如何分页。并且最重要的一点,这两个参数的名字必须为total和rows。最开始博主也不知道这个,写成了total和row,结果是请求可以进到后台的GetDepartment方法,返回值total和row也都有值,可是前端就是显示如下: 找了好半天原因。原来是row写错了,应该写成rows。 第二个问题就是关于bootstrap页面样式的问题,我们使用过bootstrap的朋友应该知道,它里面所有的图标都是通过class = "glyphicon glyphicon-plus"这种方式去写的。博主按要求这样做了,可是新增、修改、删除前面的图标怎么都出不来。如下: 怎么回事呢?然后各种百度,最后发现原来是fonts文件夹的问题。我们在新建一个MVC项目的时候,会自动创建一个fonts文件夹,里面内容如下: 而我们的bootstrap.css是放在Content文件夹里面的,这样就导致找不到这些样式文件。最终通过谷歌浏览器查看控制台 原来它自动去Content里面找fonts文件夹了。这下就好办了,把我们的fonts文件夹拷贝到Content下不就行了吗。呵呵,原来真是这样,问题顺利解决 Content文件夹中存放文件如下所示: 第三点要说说表格自带的搜索功能,有上可知,在初始化表格的时候,通过设置search: true可以设置表格的搜索框出现并且可以进行模糊搜索。之前博主一直以为只有客户端分页才能使用这个搜索,多谢园友指出,其实服务端分页也可以使用这个搜索功能。只不过需要在对应的方法里加上search参数。比如 oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //页面大小 offset: params.offset, //页码 departmentname: $("#txt_search_departmentname").val(), statu: $("#txt_search_statu").val(), search:params.search }; return temp; }; 然后后端(后端可以根据这个参数值是否有值来判断做什么查询) public JsonResult GetDepartment(int limit, int offset, string departmentname, string statu, string search) { var lstRes = new List<Department>(); for (var i = 0; i < 50; i++) { var oModel = new Department(); oModel.ID = Guid.NewGuid().ToString(); oModel.Name = "销售部" + i ; oModel.Level = i.ToString(); oModel.Desc = "暂无描述信息"; lstRes.Add(oModel); } var total = lstRes.Count; var rows = lstRes.Skip(offset).Take(limit).ToList(); return Json(new { total = total, rows = rows }, JsonRequestBehavior.AllowGet); } 这样,每次用户在搜索框里面输入都会进到后台的方法,并且将用户输入的值传到search参数。但是如果你需要对多个字段进行模糊匹配,那么就意味着你的后台需要去对多个数据字段进行like操作,这样查询效率肯定低下。所以,如果需要模糊匹配的字段很多,这个search在实际项目中是用不上的。综上,博主还是觉得只有在客户端分页的时候,这个搜索的作用比较明显。 第四点:关于Bootstrap Table的排序,由于一般这种BS系统肯定会采用服务端分页,我们如果仅仅在js里面设置sortable和sortOrder等属性信息,表格是不会有效排序的。原因很简单,服务端分页的方式,排序本页数据意义不大。所以,一般的排序需要将排序方式和排序字段发送到后台,在后台排序比较合适。比如我们这里可以再参数里面增加两个: oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //页面大小 offset: params.offset, //页码 order: params.order, ordername: params.sort, departmentname: $("#txt_search_departmentname").val(), statu: $("#txt_search_statu").val() }; return temp; }; Demo下载 百度网盘:点我下载 密码:knct git地址:https://github.com/XiaoYong666/bootstrap-table 补充:搭配一种后台风格效果更佳,如下图所示: 然后提示框各种提示框推荐使用layer,效果如下: 后台布局是使用的bootstrap ,下面也放出布局Demo,有需要的小伙伴可以参考下: 链接:点我下载 密码:w7g0 关于上图中的提示框,是使用的layer弹出组件,使用方法非常简单,官方下载出来得到如下所示文件,需要将整个文件都拷贝到项目目录下, 然后只需要引用layer.js即可(需要引入jquery1.8及以上版本才支持layer) 更多关于layer的请各位小伙伴移步layer大本营:我要飞过去 本文原创地址:http://www.cnblogs.com/landeanfen/p/4976838.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 多级联动(省级联动)的效果,网上现成的都有很多,各种JS实现,Jquery实现等等,今天我们要讲的是在MVC里面,如何更方便、更轻量的实现省级联动呢? 实现效果如下: 具体实现 如图所示,在HTML页非常简单,放几个下拉框即可,可参考如下代码: <div style="margin: 50px 0"> <span>城市:</span> <select id="Province"> <option>请选择</option> </select> <span>项目:</span> <select id="City_Project"> <option>请选择</option> </select> <span>设备房:</span> <select id="Equipment_room"> <option>请选择</option> </select> </div> 有了HMTL下拉框之后呢,第一步要做的就是加载页面的时候将城市这个下拉框进行填充赋值 因为是异步加载,所以我们采用使用 AJAX 请求来获得 JSON 数据来实现加载城市 function GetProvince() { $.getJSON( "/ShowTree/GetProvincelist",//请求地址 function (data) { $.each(data, function (i, item) {//遍历输出每个元素 $("<option></option>").val(item["MenuID"]).text(item["MenuName"]).appendTo($("#Province")); }) }); } 在后台 GetProvincelist 方法中,可以这么写 /// <summary> /// 获取省份城市 /// </summary> public JsonResult GetProvincelist() { var list = Pro_City(); return Json(list, JsonRequestBehavior.AllowGet); } 这里也贴下 Pro_City 方法 /// <summary> /// 省份城市数据 /// </summary> /// <returns></returns> public IList<MenuInfo> Pro_City() { #region 查询全部城市 var menu_list = menu.Menu();//后台查询城市数据方法 DataTable _menu_show = menu_list.Tables[0]; IList<MenuInfo> li2 = new List<MenuInfo>(); //将查询出来的DataTable映射到集合中(需要查询的列名和实体一致),通过SerializeObject序列化对象集合, //然后通过DeserializeObject 反序列化 li2 = JsonConvert.DeserializeObject<IList<MenuInfo>>(JsonConvert.SerializeObject(_menu_show)); return li2; #endregion } 注:序列化和反序列化在命名空间:using Newtonsoft.Json 下 这样,我们就可以得到城市数据,我们先来看看城市数据的前台和后台是怎样的? 后台查出List集合如下 在控制台中,我们输出返回的这些集合对象,刷新下界面可以看到返回数据如下: 到这里,我们只是在页面加载的时候绑定了城市,那么像开篇演示的那样,如何选中一个城市,能加载出该城市下面的所有项目呢? change事件 首先change事件是什么? 当元素的值发生改变时,会发生 change 事件。 该事件仅适用于文本域(text field),以及 textarea 和 select 元素。 change() 函数触发 change 事件,或规定当发生 change 事件时运行的函数。 注释:当用于 select 元素时,change 事件会在选择某个选项时发生。当用于 text field 或 text area 时,该事件会在元素失去焦点时发生。 知道以上知识之后,我们可以开始着手写这个事件方法,js如下: //获取城市的 select ID添加change事件 $("#Province").change(function () { GetCityProject(); }); function GetCityProject() { $("#City_Project").empty();//清空项目 $("#Equipment_room").empty();//清空设备房 $.getJSON( "/ShowTree/GetCitylist",//请求地址 { pid: $("#Province").val() },//参数 function (data) { $.each(data, function (i, item) {//遍历输出每个元素 $("<option></option>").val(item["Value"]).text(item["Text"]).appendTo($("#City_Project"));//在此ID中追加元素 }) }); } 同样的,也是采用使用 AJAX 请求来获得 JSON 数据来实现加载城市下所有项目 后台 GetCitylist 方法可以这么写,其中pid是传入参数也是获取到的城市ID /// <summary> /// 获取城市 /// </summary> /// <param name="pid"></param> /// <returns></returns> public JsonResult GetCitylist(string pid) { //通过IList<MenuInfo> City_Project_list 获取所有数据,数据库中是根据MenuParentID来区分不同层级菜单 var citys = City_Project_list().Where(m => m.MenuParentID == pid).ToList(); List<SelectListItem> item = new List<SelectListItem>(); foreach (var City in citys) { //将获取的List<MenuInfo> 集合,循环绑定赋值到item中 item.Add(new SelectListItem { Text = City.MenuName, Value = City.MenuID.ToString() }); } //最后以Json格式返回 return Json(item, JsonRequestBehavior.AllowGet); } 注:从面上循环赋值绑定可以大家应该可以猜到,我 MenuInfo实体中有三个字段分别是:MenuID、MenuParentID 、MenuName 也将 City_Project_list 获取所有数据放置如下 /// <summary> /// 获取所有数据 /// </summary> /// <returns></returns> public IList<MenuInfo> City_Project_list() { //查询出DataTable var menu_list = menu.Menu_List(); DataTable _menu_show = menu_list.Tables[0]; IList<MenuInfo> project_list = new List<MenuInfo>(); //将查询出来的DataTable映射到集合中(需要查询的列名和实体一致),通过SerializeObject序列化对象集合, //然后通过DeserializeObject 反序列化 project_list = JsonConvert.DeserializeObject<IList<MenuInfo>>(JsonConvert.SerializeObject(_menu_show)); return project_list; } 这样,我们就实现了选择不同的城市,异步查询出该城市下面的所有项目,其中主要运用了Linq的Where筛选来实现。 最后的设备房同理可以查出,和项目的是一模一样的。 Demo下载 最后附上一个Demo可供各位看官参考 点我去Git下载Demo Or 百度云盘下载Demo 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 (四)微信公众号开发之网页授权获取用户基本信息 (五)微信公众号开发之网页中及时获取当前用户Openid及注意事项 (六)微信公众号开发之扫码支付 (七)微信公众号开发之公众号支付 (八)微信公众号开发之现金红包 前言 这篇主要是承接上篇的网页授权获取用户基本信息的后文,也是对第一种静默授权之后,用户点击公众号内链接时,如何再次取得当前用户的OpenId的大致讲解和一些注意事项。 看过上一篇的小伙伴都知道,我们在用户关注的时候就已经将该用户的基本信息存入数据库中,那么如果该用户过了很久才点击公众号内的网页链接,那么我们该如何再次获取这个唯一标识呢? 重新获取openid 具体实现 首先,我们定一个获取openid的方法 ReGetOpenId public static void ReGetOpenId() { string url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri;//获取当前url if (System.Web.HttpContext.Current.Session["openid"] == "" || System.Web.HttpContext.Current.Session["openid"] == null) { //先要判断是否是获取code后跳转过来的 if (System.Web.HttpContext.Current.Request.QueryString["code"] == "" || System.Web.HttpContext.Current.Request.QueryString["code"] == null) { //Code为空时,先获取Code string GetCodeUrls = GetCodeUrl(url); System.Web.HttpContext.Current.Response.Redirect(GetCodeUrls);//先跳转到微信的服务器,取得code后会跳回来这页面的 } else { //Code非空,已经获取了code后跳回来啦,现在重新获取openid Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt"); string openid = ""; openid = GetOauthAccessOpenId(System.Web.HttpContext.Current.Request.QueryString["Code"]);//重新取得用户的openid System.Web.HttpContext.Current.Session["openid"] = openid; } } } 注:url最好是带域名的,花生壳的域名是行不通的,再调微信平台接口的时候,会报链接不正确错误 上文中GetCodeUrl方法如下 #region 重新获取Code的跳转链接(没有用户授权的,只能获取基本信息) /// <summary>重新获取Code,以后面实现带着Code重新跳回目标页面(没有用户授权的,只能获取基本信息(openid))</summary> /// <param name="url">目标页面</param> /// <returns></returns> public static string GetCodeUrl(string url) { string CodeUrl = ""; //对url进行编码 url = System.Web.HttpUtility.UrlEncode(url); CodeUrl = string.Format("https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + Appid + "&redirect_uri=" + url + "?action=viewtest&response_type=code&scope=snsapi_base&state=1#wechat_redirect"); return CodeUrl; } #endregion 上文中 GetOauthAccessOpenId方法如下 #region 以Code换取用户的openid、access_token /// <summary>根据Code获取用户的openid、access_token</summary> public static string GetOauthAccessOpenId(string code) { Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt"); string Openid = ""; string url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + Appid + "&secret=" + Secret + "&code=" + code + "&grant_type=authorization_code"; string gethtml = MyHttpHelper.HttpGet(url); log.log("拿到的url是:" + url); log.log("获取到的gethtml是" + gethtml); OAuth_Token ac = new OAuth_Token(); ac = JsonHelper.ToObject<OAuth_Token>(gethtml); log.log("能否从html里拿到openid=" + ac.openid); Openid = ac.openid; return Openid; } #endregion 通过以上方法即可拿到用户的Openid,如上文所示,用户id保存在System.Web.HttpContext.Current.Session["openid"] 中,所以获取也是非常简单 在需要获取的地方执行 #region 获取当前用户Openid ReGetOpenId(); log.log("走完获取openid的方法之后,当前Session的值是:" + System.Web.HttpContext.Current.Session["openid"]); #endregion 注:上文中 OAuth_Token 类如下: public class OAuth_Token { /// <summary> /// 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 /// </summary> public string access_token { get; set; } /// <summary> /// access_token接口调用凭证超时时间,单位(秒) /// </summary> public string expires_in { get; set; } /// <summary> /// 用户刷新access_token /// </summary> public string refresh_token { get; set; } /// <summary> /// 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID /// </summary> public string openid { get; set; } /// <summary> /// 用户授权作用域 /// </summary> public string scope { get; set; } } 因为无法使用一般的url,所以是把程序部署在服务器上,无法进行调试,只有打印日志查看效果,最后点击链接,日志如下: 日志文件 用到的简单日志类也顺便提供放上来: /// <summary> /// 日志类 /// </summary> public class Log { private string logFile; private StreamWriter writer; private FileStream fileStream = null; public Log(string fileName) { logFile = fileName; CreateDirectory(logFile); } public void log(string info) { try { System.IO.FileInfo fileInfo = new System.IO.FileInfo(logFile); if (!fileInfo.Exists) { fileStream = fileInfo.Create(); writer = new StreamWriter(fileStream); } else { fileStream = fileInfo.Open(FileMode.Append, FileAccess.Write); writer = new StreamWriter(fileStream); } writer.WriteLine(DateTime.Now + ": " + info); } finally { if (writer != null) { writer.Close(); writer.Dispose(); fileStream.Close(); fileStream.Dispose(); } } } public void CreateDirectory(string infoPath) { DirectoryInfo directoryInfo = Directory.GetParent(infoPath); if (!directoryInfo.Exists) { directoryInfo.Create(); } } } 调用呢,很简单,调用方法如下: Log log = new Log(AppDomain.CurrentDomain.BaseDirectory + @"/log/Log.txt"); log.log("我会被输入在日志文件中") 最后呢,拿到当前用户Openid,就可以从数据库再次获取到该用户的其他基本信息。从而可以更好的辅助你完成你项目中其他的业务模块。 未完待续,持续填坑中。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 (四)微信公众号开发之网页授权获取用户基本信息 (五)微信公众号开发之网页中及时获取当前用户Openid及注意事项 (六)微信公众号开发之扫码支付 (七)微信公众号开发之公众号支付 (八)微信公众号开发之现金红包 前言 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。 注意:网页授权两种方式 更多网页授权请查阅官网文档:网页授权 静默授权 静默授权即可以在用户关注的的时候,获取用户基本信息,此过程用户无感知。 第一步,通过工厂类 转发请求 /// <returns></returns> public string HandleRequest() { string response = string.Empty; EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em != null) { switch (em.Event.ToLower()) { case ("subscribe"): response = SubscribeEventHandler(em);//通过工厂类分发过来的请求,匹配到关注事件 break; case ("unsubscribe"): response = Un_SubscribeEventHandler(em); break; case "click": response = ClickEventHandler(em); break; } } return response; } 第二步,写用户关注事件 /// <summary> /// 用户关注 /// </summary> /// <param name="em"></param> /// <returns></returns> public string SubscribeEventHandler(EventMessage em) { //回复欢迎消息 WeChat.Messages.TextMessage tm = new WeChat.Messages.TextMessage(); tm.ToUserName = em.FromUserName;//OpenId tm.FromUserName = em.ToUserName;//公众号原始Id tm.CreateTime = Common.GetNowTime(); tm.Content = "欢迎您关注****,我是服务小二,有事就问我~\n\n"; tm.GenerateContent(); //如有业务需要,可在此处先判断该用户是否已关注 //此处得到OpenId show.ShowUserInfo(em.FromUserName,em.ToUserName); return tm.GenerateContent(); } 第三步,根据得到的OpenId及accesstoken,即可获取用户基本信息(此处演示是将该用户存入数据库中) /// <summary> /// 根据OpenId将此条粉丝记录插入数据库中 /// </summary> /// <param name="FromUserName"></param> /// <param name="ToUserName"></param> public void ShowUserInfo(string FromUserName, string ToUserName) { try { Models.Users user = new Models.Users(); DAL.User userInfo = new DAL.User(); //获取accesstoken,获取用户基本信息需要Openid和accesstoken即可 accesstoken = Utility.Context.AccessToken; string url = string.Format("https://api.weixin.qq.com/cgi-bin/user/info?access_token={0}&openid={1}&lang=zh_CN", accesstoken, FromUserName); string result = HttpUtility.GetData(url); XDocument doc = XmlUtility.ParseJson(result, "root"); XElement root = doc.Root; if (root != null) { #region 取值/存值 subscribe = root.Element("subscribe").Value;//是否关注 1 是关注 nickname = root.Element("nickname").Value; //昵称 sex = root.Element("sex").Value; //性别什么 headimgurl = root.Element("headimgurl").Value; //头像url province = root.Element("province").Value;//地区 country = root.Element("country").Value; language = root.Element("language").Value; subscribe_time = root.Element("subscribe_time").Value; DateTime create_time = Common.GetTime(subscribe_time);//将时间戳转换为当前时间 city = root.Element("city").Value; user.OpenID = FromUserName;//OpenID即粉丝ID user.PublicId = ToUserName; user.UserID = FromUserName; user.NickName = nickname; user.Sex = int.Parse(sex); user.Subscribe = int.Parse(subscribe); user.Country = country; user.Province = province; user.City = city; user.CreateDate = create_time; user.HeadimgUrl = headimgurl; //将user实体存入数据库中 bool show = _user.Add(user); #endregion } } catch { throw (new ArgumentNullException()); } } 上面代码中 AccessToken的实现,新建一个Context类即可 private static DateTime GetAccessToken_Time; /// <summary> /// 过期时间为7200秒 /// </summary> private static int Expires_Period = 7200; /// <summary> /// /// </summary> private static string mAccessToken; public static string AppID = "换成相应公众号的即可"; public static string AppSecret = "换成相应公众号的即可"; /// <summary> /// 调用获取ACCESS_TOKEN,包含判断是否过期 /// </summary> public static string AccessToken { get { //如果为空,或者过期,需要重新获取 if (string.IsNullOrEmpty(mAccessToken) || HasExpired()) { //获取access_token mAccessToken = GetAccessToken(AppID, AppSecret); } return mAccessToken; } } /// <summary> /// 获取ACCESS_TOKEN方法 /// </summary> /// <param name="appId"></param> /// <param name="appSecret"></param> /// <returns></returns> private static string GetAccessToken(string appId, string appSecret) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret); string result = HttpUtility.GetData(url); XDocument doc = XmlUtility.ParseJson(result, "root"); XElement root = doc.Root; if (root != null) { XElement access_token = root.Element("access_token"); if (access_token != null) { GetAccessToken_Time = DateTime.Now; if (root.Element("expires_in") != null) { Expires_Period = int.Parse(root.Element("expires_in").Value); } return access_token.Value; } else { GetAccessToken_Time = DateTime.MinValue; } } return null; } /// <summary> /// 判断Access_token是否过期 /// </summary> /// <returns>bool</returns> private static bool HasExpired() { if (GetAccessToken_Time != null) { //过期时间,允许有一定的误差,一分钟。获取时间消耗 if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60)) { return true; } } return false; } GetData的实现 public static string GetData(string url) { return SendGetHttpRequest(url, "application/x-www-form-urlencoded"); } ParseJson的实现 public static XDocument ParseJson(string json, string rootName) { return JsonConvert.DeserializeXNode(json, rootName); } 关于第三步的 HttpUtility类中还有一些其他公用帮助方法,在这里一并放出,调用即可 /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="data">数据</param> public static string SendHttpRequest(string url, string data) { return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data); } /// <summary> /// /// </summary> /// <param name="url"></param> /// <returns></returns> public static string GetData(string url) { return SendGetHttpRequest(url, "application/x-www-form-urlencoded"); } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendPostHttpRequest(string url, string contentType, string requestData) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "POST"; byte[] postBytes = null; request.ContentType = contentType; postBytes = Encoding.UTF8.GetBytes(requestData); request.ContentLength = postBytes.Length; using (Stream outstream = request.GetRequestStream()) { outstream.Write(postBytes, 0, postBytes.Length); } string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendGetHttpRequest(string url, string contentType) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "GET"; request.ContentType = contentType; string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } View Code 顺便提下上文中用到的User类如下 public class Users { /// <summary> /// 全局凭证唯一Id /// </summary> public string OpenID { get; set; } /// <summary> /// 公众号Id /// </summary> public string PublicId { get; set; } /// <summary> /// 用户Id /// </summary> public string UserID { get; set; } /// <summary> /// 昵称 /// </summary> public string NickName { get; set; } /// <summary> /// 性别 1是男 0是女 /// </summary> public int Sex { get; set; } /// <summary> /// 是否关注 1是关注 /// </summary> public int Subscribe { get; set; } /// <summary> /// 国家 /// </summary> public string Country { get; set; } /// <summary> /// 地区 /// </summary> public string Province { get; set; } /// <summary> /// 城市 /// </summary> public string City { get; set; } /// <summary> /// 关注时间 /// </summary> public DateTime CreateDate { get; set; } /// <summary> /// 用户头像 /// </summary> public string HeadimgUrl { get; set; } /// <summary> /// 第三方平台Id,可为空 /// </summary> public string UnionID { get; set; } /// <summary> /// 用户取消关注时间 /// </summary> public DateTime Un_Subscribe_Time { get; set; } } 演示效果 数据库中此时是存在10条数据的,当点击关注此公众号的时候,就将此用户的基本信息存入数据库了,数据库刷新后变成11条数据 网页授权流程 具体介绍依然可参考官网文档:网页授权 第一步,判断该用户是否获取授权,若没有授权,则跳转至授权页面,若授权,则获取基本信息 核心代码 /// <summary> /// 获取授权用户的基本信息,包括头像,姓名,等等(推荐方法) /// </summary> /// <param name="accessToken">用户授权之后的accessToken</param> /// <param name="openid">用户授权之后的openid</param> /// <returns></returns> public static ShouQuanWeiXinUserInfo GetShouQuanMessage() { //先判断是否有获取到用户授权的Code,HttpContext.Current.Session["ShouquanCode"] if (HttpContext.Current.Session["ShouquanCode"] == null|| HttpContext.Current.Session["ShouquanCode"].ToString()=="") { HttpContext.Current.Session["ShouquanCode"] = "123"; //用户授权的Code GetShouQuanCodeUrl(HttpContext.Current.Request.Url.AbsoluteUri); } else if(HttpContext.Current.Request.QueryString["code"] == null || HttpContext.Current.Request.QueryString["code"] == "") { //用户授权的Code GetShouQuanCodeUrl(HttpContext.Current.Request.Url.AbsoluteUri); } else { var model = ShouQuanAccessToken(HttpContext.Current.Request.QueryString["code"]); var url = $"https://api.weixin.qq.com/sns/userinfo?access_token={model.access_token}&openid={model.openid}&lang=zh_CN"; string gethtml = MyHttpHelper.HttpGet(url); var ac = JsonHelpers.ToObject<ShouQuanWeiXinUserInfo>(gethtml); return ac; } return null; } 其中,用户授权的code方法如下: /// <summary> /// 重新获取用户授权的Code,可以获取用户的基本信息(头像,姓名,等等)(推荐用的方法) /// </summary> /// <param name="url">目标Url</param> /// <returns></returns> public static void GetShouQuanCodeUrl(string url) { string CodeUrl = ""; //加密过的url string value = HttpUtility.UrlEncode(url); //用户授权后的Code CodeUrl = $"https://open.weixin.qq.com/connect/oauth2/authorize?appid={Appid}&redirect_uri={value}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"; System.Web.HttpContext.Current.Response.Redirect(CodeUrl);//先跳转到微信的服务器,取得code后会跳回来这页面的 } 其中ShouQuanAccessToken方法 /// <summary> //用户授权之后,获取授权的Access_Token与基本的Access_Token是不同的(推荐方法) /// </summary> /// <param name="code">用户授权之后的Code</param> /// <returns></returns> public static OauthAccessToken ShouQuanAccessToken(string code) { var url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={Appid}&secret={Secret}&code={code}&grant_type=authorization_code"; string gethtml = MyHttpHelper.HttpGet(url); OauthAccessToken ac = new OauthAccessToken(); ac = JsonHelpers.ToObject<OauthAccessToken>(gethtml); return ac; } 用户实体 public class OauthAccessToken { public string access_token { get; set; } public string expires_in { get; set; } public string refresh_token { get; set; } public string openid { get; set; } public string scope { get; set; } } 其中用到的MyHttpHelper公众类如下 /// <summary> /// Http连接操作帮助类 /// </summary> public class HttpHelpers { #region 预定义方法或者变更 //默认的编码 private Encoding encoding = Encoding.Default; //Post数据编码 private Encoding postencoding = Encoding.Default; //HttpWebRequest对象用来发起请求 private HttpWebRequest request = null; //获取影响流的数据对象 private HttpWebResponse response = null; /// <summary> /// 根据相传入的数据,得到相应页面数据 /// </summary> /// <param name="item">参数类对象</param> /// <returns>返回HttpResult类型</returns> public HttpResult GetHtml(HttpItem item) { //返回参数 HttpResult result = new HttpResult(); try { //准备参数 SetRequest(item); } catch (Exception ex) { return new HttpResult() { Cookie = string.Empty, Header = null, Html = ex.Message, StatusDescription = "配置参数时出错:" + ex.Message }; } try { #region 得到请求的response using (response = (HttpWebResponse)request.GetResponse()) { result.StatusCode = response.StatusCode; result.StatusDescription = response.StatusDescription; result.Header = response.Headers; if (response.Cookies != null) result.CookieCollection = response.Cookies; if (response.Headers["set-cookie"] != null) result.Cookie = response.Headers["set-cookie"]; byte[] ResponseByte = null; using (MemoryStream _stream = new MemoryStream()) { //GZIIP处理 if (response.ContentEncoding != null && response.ContentEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)) { //开始读取流并设置编码方式 new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240); } else { //开始读取流并设置编码方式 response.GetResponseStream().CopyTo(_stream, 10240); } //获取Byte ResponseByte = _stream.ToArray(); } if (ResponseByte != null & ResponseByte.Length > 0) { //是否返回Byte类型数据 if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte; //从这里开始我们要无视编码了 if (encoding == null) { Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), "<meta([^<]*)charset=([^<]*)[\"']", RegexOptions.IgnoreCase); string c = (meta.Groups.Count > 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; if (c.Length > 2) { try { if (c.IndexOf(" ") > 0) c = c.Substring(0, c.IndexOf(" ")); encoding = Encoding.GetEncoding(c.Replace("\"", "").Replace("'", "").Replace(";", "").Replace("iso-8859-1", "gbk").Trim()); } catch { if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; else encoding = Encoding.GetEncoding(response.CharacterSet); } } else { if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; else encoding = Encoding.GetEncoding(response.CharacterSet); } } //得到返回的HTML result.Html = encoding.GetString(ResponseByte); } else { //得到返回的HTML //result.Html = "本次请求并未返回任何数据"; result.Html = ""; } } #endregion } catch (WebException ex) { //这里是在发生异常时返回的错误信息 response = (HttpWebResponse)ex.Response; result.Html = ex.Message; if (response != null) { result.StatusCode = response.StatusCode; result.StatusDescription = response.StatusDescription; } } catch (Exception ex) { result.Html = ex.Message; } if (item.IsToLower) result.Html = result.Html.ToLower(); return result; } /// <summary> /// 为请求准备参数 /// </summary> ///<param name="item">参数列表</param> private void SetRequest(HttpItem item) { // 验证证书 SetCer(item); //设置Header参数 if (item.Header != null && item.Header.Count > 0) foreach (string key in item.Header.AllKeys) { request.Headers.Add(key, item.Header[key]); } // 设置代理 //SetProxy(item); if (item.ProtocolVersion != null) request.ProtocolVersion = item.ProtocolVersion; request.ServicePoint.Expect100Continue = item.Expect100Continue; //请求方式Get或者Post request.Method = item.Method; request.Timeout = item.Timeout; request.KeepAlive = item.KeepAlive; request.ReadWriteTimeout = item.ReadWriteTimeout; if (!string.IsNullOrWhiteSpace(item.Host)) { request.Host = item.Host; } //Accept request.Accept = item.Accept; //ContentType返回类型 request.ContentType = item.ContentType; //UserAgent客户端的访问类型,包括浏览器版本和操作系统信息 request.UserAgent = item.UserAgent; // 编码 encoding = item.Encoding; //设置安全凭证 request.Credentials = item.ICredentials; //设置Cookie SetCookie(item); //来源地址 request.Referer = item.Referer; //是否执行跳转功能 request.AllowAutoRedirect = item.Allowautoredirect; //设置Post数据 SetPostData(item); //设置最大连接 if (item.Connectionlimit > 0) request.ServicePoint.ConnectionLimit = item.Connectionlimit; } /// <summary> /// 设置证书 /// </summary> /// <param name="item"></param> private void SetCer(HttpItem item) { if (!string.IsNullOrWhiteSpace(item.CerPath)) { //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。 ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); //初始化对像,并设置请求的URL地址 request = (HttpWebRequest)WebRequest.Create(item.URL); SetCerList(item); //将证书添加到请求里 request.ClientCertificates.Add(new X509Certificate(item.CerPath)); } else { //初始化对像,并设置请求的URL地址 request = (HttpWebRequest)WebRequest.Create(item.URL); SetCerList(item); } } /// <summary> /// 设置多个证书 /// </summary> /// <param name="item"></param> private void SetCerList(HttpItem item) { if (item.ClentCertificates != null && item.ClentCertificates.Count > 0) { foreach (X509Certificate c in item.ClentCertificates) { request.ClientCertificates.Add(c); } } } /// <summary> /// 设置Cookie /// </summary> /// <param name="item">Http参数</param> private void SetCookie(HttpItem item) { if (!string.IsNullOrEmpty(item.Cookie)) request.Headers[HttpRequestHeader.Cookie] = item.Cookie; //设置CookieCollection if (item.ResultCookieType == ResultCookieType.CookieCollection) { request.CookieContainer = new CookieContainer(); if (item.CookieCollection != null && item.CookieCollection.Count > 0) request.CookieContainer.Add(item.CookieCollection); } } /// <summary> /// 设置Post数据 /// </summary> /// <param name="item">Http参数</param> private void SetPostData(HttpItem item) { //验证在得到结果时是否有传入数据 if (request.Method.Trim().ToLower().Contains("post")) { if (item.PostEncoding != null) { postencoding = item.PostEncoding; } byte[] buffer = null; //写入Byte类型 if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0) { //验证在得到结果时是否有传入数据 buffer = item.PostdataByte; }//写入文件 else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrWhiteSpace(item.Postdata)) { StreamReader r = new StreamReader(item.Postdata, postencoding); buffer = postencoding.GetBytes(r.ReadToEnd()); r.Close(); } //写入字符串 else if (!string.IsNullOrWhiteSpace(item.Postdata)) { buffer = postencoding.GetBytes(item.Postdata); } if (buffer != null) { request.ContentLength = buffer.Length; request.GetRequestStream().Write(buffer, 0, buffer.Length); } } } /// <summary> /// 设置代理 /// </summary> /// <param name="item">参数对象</param> private void SetProxy(HttpItem item) { bool isIeProxy = item.ProxyIp.ToLower().Contains("ieproxy"); if (!string.IsNullOrWhiteSpace(item.ProxyIp) && !isIeProxy) { //设置代理服务器 if (item.ProxyIp.Contains(":")) { string[] plist = item.ProxyIp.Split(':'); WebProxy myProxy = new WebProxy(plist[0].Trim(), Convert.ToInt32(plist[1].Trim())); //建议连接 myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd); //给当前请求对象 request.Proxy = myProxy; } else { WebProxy myProxy = new WebProxy(item.ProxyIp, false); //建议连接 myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd); //给当前请求对象 request.Proxy = myProxy; } } else if (isIeProxy) { //设置为IE代理 } else { request.Proxy = item.WebProxy; } } /// <summary> /// 回调验证证书问题 /// </summary> /// <param name="sender">流对象</param> /// <param name="certificate">证书</param> /// <param name="chain">X509Chain</param> /// <param name="errors">SslPolicyErrors</param> /// <returns>bool</returns> public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } #endregion } /// <summary> /// Http请求参考类 /// </summary> public class HttpItem { /// <summary> /// 请求URL必须填写 /// </summary> public string URL { get; set; } string _Method = "GET"; /// <summary> /// 请求方式默认为GET方式,当为POST方式时必须设置Postdata的值 /// </summary> public string Method { get { return _Method; } set { _Method = value; } } int _Timeout = 100000; /// <summary> /// 默认请求超时时间 /// </summary> public int Timeout { get { return _Timeout; } set { _Timeout = value; } } int _ReadWriteTimeout = 30000; /// <summary> /// 默认写入Post数据超时间 /// </summary> public int ReadWriteTimeout { get { return _ReadWriteTimeout; } set { _ReadWriteTimeout = value; } } /// <summary> /// 设置Host的标头信息 /// </summary> public string Host { get; set; } Boolean _KeepAlive = true; /// <summary> /// 获取或设置一个值,该值指示是否与 Internet 资源建立持久性连接默认为true。 /// </summary> public Boolean KeepAlive { get { return _KeepAlive; } set { _KeepAlive = value; } } string _Accept = "text/html, application/xhtml+xml, */*"; /// <summary> /// 请求标头值 默认为text/html, application/xhtml+xml, */* /// </summary> public string Accept { get { return _Accept; } set { _Accept = value; } } string _ContentType = "text/html"; /// <summary> /// 请求返回类型默认 text/html /// </summary> public string ContentType { get { return _ContentType; } set { _ContentType = value; } } string _UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"; /// <summary> /// 客户端访问信息默认Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) /// </summary> public string UserAgent { get { return _UserAgent; } set { _UserAgent = value; } } /// <summary> /// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312 /// </summary> public Encoding Encoding { get; set; } private PostDataType _PostDataType = PostDataType.String; /// <summary> /// Post的数据类型 /// </summary> public PostDataType PostDataType { get { return _PostDataType; } set { _PostDataType = value; } } /// <summary> /// Post请求时要发送的字符串Post数据 /// </summary> public string Postdata { get; set; } /// <summary> /// Post请求时要发送的Byte类型的Post数据 /// </summary> public byte[] PostdataByte { get; set; } /// <summary> /// Cookie对象集合 /// </summary> public CookieCollection CookieCollection { get; set; } /// <summary> /// 请求时的Cookie /// </summary> public string Cookie { get; set; } /// <summary> /// 来源地址,上次访问地址 /// </summary> public string Referer { get; set; } /// <summary> /// 证书绝对路径 /// </summary> public string CerPath { get; set; } /// <summary> /// 设置代理对象,不想使用IE默认配置就设置为Null,而且不要设置ProxyIp /// </summary> public WebProxy WebProxy { get; set; } private Boolean isToLower = false; /// <summary> /// 是否设置为全文小写,默认为不转化 /// </summary> public Boolean IsToLower { get { return isToLower; } set { isToLower = value; } } private Boolean allowautoredirect = false; /// <summary> /// 支持跳转页面,查询结果将是跳转后的页面,默认是不跳转 /// </summary> public Boolean Allowautoredirect { get { return allowautoredirect; } set { allowautoredirect = value; } } private int connectionlimit = 1024; /// <summary> /// 最大连接数 /// </summary> public int Connectionlimit { get { return connectionlimit; } set { connectionlimit = value; } } /// <summary> /// 代理Proxy 服务器用户名 /// </summary> public string ProxyUserName { get; set; } /// <summary> /// 代理 服务器密码 /// </summary> public string ProxyPwd { get; set; } /// <summary> /// 代理 服务IP,如果要使用IE代理就设置为ieproxy /// </summary> public string ProxyIp { get; set; } private ResultType resulttype = ResultType.String; /// <summary> /// 设置返回类型String和Byte /// </summary> public ResultType ResultType { get { return resulttype; } set { resulttype = value; } } private WebHeaderCollection header = new WebHeaderCollection(); /// <summary> /// header对象 /// </summary> public WebHeaderCollection Header { get { return header; } set { header = value; } } /// <summary> // 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。 /// </summary> public Version ProtocolVersion { get; set; } private Boolean _expect100continue = true; /// <summary> /// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。 /// </summary> public Boolean Expect100Continue { get { return _expect100continue; } set { _expect100continue = value; } } /// <summary> /// 设置509证书集合 /// </summary> public X509CertificateCollection ClentCertificates { get; set; } /// <summary> /// 设置或获取Post参数编码,默认的为Default编码 /// </summary> public Encoding PostEncoding { get; set; } private ResultCookieType _ResultCookieType = ResultCookieType.String; /// <summary> /// Cookie返回类型,默认的是只返回字符串类型 /// </summary> public ResultCookieType ResultCookieType { get { return _ResultCookieType; } set { _ResultCookieType = value; } } private ICredentials _ICredentials = CredentialCache.DefaultCredentials; /// <summary> /// 获取或设置请求的身份验证信息。 /// </summary> public ICredentials ICredentials { get { return _ICredentials; } set { _ICredentials = value; } } } /// <summary> /// Http返回参数类 /// </summary> public class HttpResult { /// <summary> /// Http请求返回的Cookie /// </summary> public string Cookie { get; set; } /// <summary> /// Cookie对象集合 /// </summary> public CookieCollection CookieCollection { get; set; } /// <summary> /// 返回的String类型数据 只有ResultType.String时才返回数据,其它情况为空 /// </summary> public string Html { get; set; } /// <summary> /// 返回的Byte数组 只有ResultType.Byte时才返回数据,其它情况为空 /// </summary> public byte[] ResultByte { get; set; } /// <summary> /// header对象 /// </summary> public WebHeaderCollection Header { get; set; } /// <summary> /// 返回状态说明 /// </summary> public string StatusDescription { get; set; } /// <summary> /// 返回状态码,默认为OK /// </summary> public HttpStatusCode StatusCode { get; set; } } /// <summary> /// 返回类型 /// </summary> public enum ResultType { /// <summary> /// 表示只返回字符串 只有Html有数据 /// </summary> String, /// <summary> /// 表示返回字符串和字节流 ResultByte和Html都有数据返回 /// </summary> Byte } /// <summary> /// Post的数据格式默认为string /// </summary> public enum PostDataType { /// <summary> /// 字符串类型,这时编码Encoding可不设置 /// </summary> String, /// <summary> /// Byte类型,需要设置PostdataByte参数的值编码Encoding可设置为空 /// </summary> Byte, /// <summary> /// 传文件,Postdata必须设置为文件的绝对路径,必须设置Encoding的值 /// </summary> FilePath } /// <summary> /// Cookie返回类型 /// </summary> public enum ResultCookieType { /// <summary> /// 只返回字符串类型的Cookie /// </summary> String, /// <summary> /// CookieCollection格式的Cookie集合同时也返回String类型的cookie /// </summary> CookieCollection } /// <summary>HttpHelper的2次封装函数 作者: /// </summary> public class MyHttpHelper { #region 公共函数 /// <summary>返回 HTML 字符串的编码结果</summary> /// <param name="str">字符串</param> /// <returns>编码结果</returns> public static string HtmlEncode(string str) { if (string.IsNullOrEmpty(str)) { return ""; } return str.Length > 0 ? HttpUtility.HtmlEncode(str) : ""; } /// <summary>返回 HTML 字符串的解码结果</summary> /// <param name="str">字符串</param> /// <returns>解码结果</returns> public static string HtmlDecode(string str) { if (string.IsNullOrEmpty(str)) { return ""; } return str.Length > 0 ? HttpUtility.HtmlDecode(str) : ""; } /// <summary> /// 根据指定的编码对RUl进行解码 /// </summary> /// <param name="str">要解码的字符串</param> /// <param name="encoding">要进行解码的编码方式</param> /// <returns></returns> public static string UrlDecode(string str, Encoding encoding = null) { if (string.IsNullOrEmpty(str)) { return ""; } if (str.Length == 0) { return ""; } if (encoding == null) { return HttpUtility.UrlDecode(str); } else { return HttpUtility.UrlDecode(str, encoding); } } /// <summary>根据指定的编码对URL进行编码</summary> /// <param name="str">要编码的URL</param> /// <param name="encoding">要进行编码的编码方式</param> /// <returns></returns> public static string UrlEncode(string str, Encoding encoding = null) { if (string.IsNullOrEmpty(str)) { return ""; } if (str.Length == 0) { return ""; } if (encoding == null) { return HttpUtility.UrlEncode(str); } else { return HttpUtility.UrlEncode(str, encoding); } } /// <summary> /// 根据 charSet 返回 Encoding /// </summary> /// <param name="charSet">"gb2312" or "utf-8",默认: "" == "utf-8"</param> /// <returns></returns> public static Encoding GetEncoding(string charSet) { Encoding en = Encoding.Default; if (charSet == "gb2312") { en = Encoding.GetEncoding("gb2312"); } else if (charSet == "utf-8") { en = Encoding.UTF8; } return en; } #endregion #region Post /// <summary>HTTP Get方式请求数据</summary> /// <param name="url">URL</param> /// <param name="param">user=123123 & pwd=1231313"</param> /// <param name="charSet">"gb2312" or "utf-8",默认: "" == "utf-8"</param> /// <returns></returns> public static string HttpPost(string url, string param, string charSet = "utf-8") { HttpHelpers http = new HttpHelpers(); HttpItem item = new HttpItem() { URL = url, Encoding = GetEncoding(charSet),//编码格式(utf-8,gb2312,gbk) 可选项 默认类会自动识别 Method = "post",//URL 可选项 默认为Get Postdata = param }; //得到HTML代码 HttpResult result = http.GetHtml(item); //取出返回的Cookie //string cookie = result.Cookie; //返回的Html内容 string html = result.Html; if (result.StatusCode == System.Net.HttpStatusCode.OK) { return html; } //string statusCodeDescription = result.StatusDescription; return ""; } #endregion #region Get /// <summary>HTTP Get方式请求数据</summary> /// <param name="url">URL</param> /// <param name="charSet">"gb2312" or "utf-8",默认: "" == "utf-8"</param> /// <returns></returns> public static string HttpGet(string url, string charSet = "utf-8") { HttpHelpers http = new HttpHelpers(); HttpItem item = new HttpItem() { URL = url, Encoding = GetEncoding(charSet), Method = "get" }; //得到HTML代码 HttpResult result = http.GetHtml(item); //取出返回的Cookie //string cookie = result.Cookie; //返回的Html内容 string html = result.Html; if (result.StatusCode == System.Net.HttpStatusCode.OK) { return html; } //string statusCodeDescription = result.StatusDescription; return ""; } /// <summary>POST客服消息/summary> /// <param name="url">URL</param> /// <param name="postData">内容</param> /// <returns>消息状态</returns> public static string GetPage(string posturl, string postData) { Stream outstream = null; Stream instream = null; StreamReader sr = null; HttpWebResponse response = null; HttpWebRequest request = null; Encoding encoding = Encoding.UTF8; byte[] data = encoding.GetBytes(postData); // 准备请求... try { // 设置参数 request = WebRequest.Create(posturl) as HttpWebRequest; CookieContainer cookieContainer = new CookieContainer(); request.CookieContainer = cookieContainer; request.AllowAutoRedirect = true; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = data.Length; outstream = request.GetRequestStream(); outstream.Write(data, 0, data.Length); outstream.Close(); //发送请求并获取相应回应数据 response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 instream = response.GetResponseStream(); sr = new StreamReader(instream, encoding); //返回结果网页(html)代码 string content = sr.ReadToEnd(); string err = string.Empty; return content; } catch (Exception ex) { string err = ex.Message; return err; } } #endregion } View Code 封装的JsonHelpers类如下 #region 通用 /// <summary>检查字符串是否json格式</summary> /// <param name="jText"></param> /// <returns></returns> public static bool IsJson(string jText) { if (string.IsNullOrEmpty(jText) || jText.Length < 3) { return false; } if (jText.Substring(0, 2) == "{\"" || jText.Substring(0, 3) == "[{\"") { return true; } return false; } /// <summary>检查字符串是否json格式数组</summary> /// <param name="jText"></param> /// <returns></returns> public static bool IsJsonRs(string jText) { if (string.IsNullOrEmpty(jText) || jText.Length < 3) { return false; } if (jText.Substring(0, 3) == "[{\"") { return true; } return false; } /// <summary>格式化 json</summary> /// <param name="jText"></param> /// <returns></returns> public static string Fmt_Null(string jText) { return StringHelper.ReplaceString(jText, ":null,", ":\"\",", true); } /// <summary>格式化 json ,删除左右二边的[]</summary> /// <param name="jText"></param> /// <returns></returns> public static string Fmt_Rs(string jText) { jText = jText.Trim(); jText = jText.Trim('['); jText = jText.Trim(']'); return jText; } #endregion #region Json序列化 /// <summary>序列化</summary> /// <param name="obj">object </param> /// <returns></returns> public static string ToJson(object obj) { var idtc = new Newtonsoft.Json.Converters.IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd hh:mm:ss" }; return JsonConvert.SerializeObject(obj, idtc); } /// <summary>序列化--sql</summary> /// <param name="dt">DataTable</param> /// <returns></returns> public static string ToJson_FromSQL(DataTable dt) { string ss = ToJson(dt); dt.Dispose(); return ss; } #endregion #region Json反序列化 /// <summary>反序列化</summary> /// <param name="jText"></param> /// <returns></returns> public static DataTable ToDataTable(string jText) { if (string.IsNullOrEmpty(jText)) { return null; } else { try { return JsonConvert.DeserializeObject<DataTable>(jText); } catch { return null; } } } /// <summary>反序列化</summary> /// <typeparam name="T">类型</typeparam> /// <param name="jText">json字符串</param> /// <returns>类型数据</returns> public static T ToObject<T>(string jText) { return (T)JsonConvert.DeserializeObject(jText, typeof(T)); } #endregion View Code 其中,如果是VS2015以下的,可以将url字符串改成string.format("")方式 调用取值的方式 效果展示 点击公众号链接效果如下: 未完待续,持续填坑中。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 近日项目中做到一个功能,需要上传附件后能够在线预览。之前也没做过这类似的,于是乎就查找了相关资料,.net实现Office文件预览大概有这几种方式: ① 使用Microsoft的Office组件将文件直接转换为html文件(优点:代码实现最简单,工作强度最小。缺点:效果极差) ②使用Microsoft的Office组件将文件转换为PDF格式文件,然后再使用pdf2swf转换为swf文件,也就是flash文件在使用FlexPaper展示出来(优点:预览效果能接受,缺点:代码量大) ③使用Office online(优点:表现完美,缺点:不适合中小企业应用) 由于开发时间短而且还有其他功能点需要完成,所以暂时先已第一种方式实现了,这里也主要讲第一种方式,效果如下图: 具体实现 这里简单提一下效果图中的遮罩效果和上传实现,有喜欢的朋友也可以参考参考。 遮罩效果就是HTML+CSS+JS来实现的,全部代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>弹出层</title> <script src="jquery-1.6.2.min.js" type="text/javascript"></script> <style> .black_overlay{ display: none; position: absolute; top: 0%; left: 0%; width: 100%; height: 100%; background-color: black; z-index:1001; -moz-opacity: 0.8; opacity:.80; filter: alpha(opacity=80); } .white_content { display: none; position: absolute; top: 10%; left: 10%; width: 80%; height: 80%; border: 16px solid lightblue; background-color: white; z-index:1002; overflow: auto; } .white_content_small { display: none; position: absolute; top: 20%; left: 30%; width: 40%; height: 50%; border: 16px solid lightblue; background-color: white; z-index:1002; overflow: auto; } </style> <script type="text/javascript"> //弹出隐藏层 function ShowDiv(show_div,bg_div){ document.getElementById(show_div).style.display='block'; document.getElementById(bg_div).style.display='block' ; var bgdiv = document.getElementById(bg_div); bgdiv.style.width = document.body.scrollWidth; // bgdiv.style.height = $(document).height(); $("#"+bg_div).height($(document).height()); }; //关闭弹出层 function CloseDiv(show_div,bg_div) { document.getElementById(show_div).style.display='none'; document.getElementById(bg_div).style.display='none'; }; </script> </head> <body> <input id="Button1" type="button" value="点击弹出层" onclick="ShowDiv('MyDiv','fade')" /> <!--弹出层时背景层DIV--> <div id="fade" class="black_overlay"> </div> <div id="MyDiv" class="white_content"> <div style="text-align: right; cursor: default; height: 40px;"> <span style="font-size: 16px;" onclick="CloseDiv('MyDiv','fade')">关闭</span> </div> 目前来说,我还是喜欢这个自己改造的弹出层。自己在项目中也用的是这个。 </div> </body> </html> View Code 上传的话,因为文件比较小,所以采用的是保存在服务器,在数据库中存放路径的方式 前台代码 <div class="white_content" id="MyDiv" style="text-align: center; display: none;"> <div style="text-align: right; cursor: default; height: 40px;"> <span style="font-size: 16px;" onclick="CloseDiv('MyDiv','fade')">关闭</span> </div> <tr style="width: 50%" id="upload_Image"> <h1> 请上传常见问题附件</h1> <td align="right" class="Title"> 上传附件 </td> <td> <asp:FileUpload ID="FileUpload1" runat="server" /> <asp:Label ID="label1" runat="server" ForeColor="Red"></asp:Label> <asp:Button ID="UploadButton" runat="server" Text="上传附件" OnClick="UploadButton_Click" /> </td> </tr> <tr> <td colspan="2" align="center" id="show_image" style="visibility: hidden"> <asp:Image ID="Image1" runat="server" Height="118px" Width="131px" /> </td> </tr> </div> 后台方法 try { string FullName = FileUpload1.PostedFile.FileName;//获取附件物理地址 FileInfo fi = new FileInfo(FullName); string name = fi.Name;//获取附件名称 string type = fi.Extension;//获取附件类型 if (type == ".xls" || type == ".xlsx" || type == ".doc" || type == ".docx" || type == ".pdf") { string SavePath = Server.MapPath("~\\uploadFile");//附件保存到文件夹下 if (!Directory.Exists(SavePath)) { Directory.CreateDirectory(SavePath); } this.FileUpload1.PostedFile.SaveAs(SavePath + "\\" + name);//保存路径 #region 将附件内容保存到数据库中 int showsuccess = CMSModelManager.Submitted_questionsDAO.Save_File(name,type,SavePath); if (showsuccess == 1) { this.label1.Text = "上传成功"; } else { this.label1.Text = "服务器繁忙,请稍后重试"; } #endregion } else { this.label1.Text = "请选择正确的格式附件"; } } catch (Exception ex) { Response.Write(ex.Message); } 图中所示的将Word转换成HTML的实现方式: 首先新建一个帮助类 using System; using System.Collections.Generic; using System.Web; //using Microsoft.Office.Core; using Word = Microsoft.Office.Interop.Word; namespace Com.VanruPortal.Admin { public class Office2HtmlHelper { /// <summary> /// Word转成Html /// </summary> /// <param name="path">要转换的文档的路径</param> /// <param name="savePath">转换成html的保存路径</param> /// <param name="wordFileName">转换成html的文件名字</param> public static void Word2Html(string path, string savePath, string wordFileName) { Word.ApplicationClass word = new Word.ApplicationClass(); Type wordType = word.GetType(); Word.Documents docs = word.Documents; Type docsType = docs.GetType(); Word.Document doc = (Word.Document)docsType.InvokeMember("Open", System.Reflection.BindingFlags.InvokeMethod, null, docs, new Object[] { (object)path, true, true }); Type docType = doc.GetType(); string strSaveFileName = savePath + wordFileName + ".html"; object saveFileName = (object)strSaveFileName; docType.InvokeMember("SaveAs", System.Reflection.BindingFlags.InvokeMethod, null, doc, new object[] { saveFileName, Word.WdSaveFormat.wdFormatFilteredHTML }); docType.InvokeMember("Close", System.Reflection.BindingFlags.InvokeMethod, null, doc, null); wordType.InvokeMember("Quit", System.Reflection.BindingFlags.InvokeMethod, null, word, null); } /// <summary> /// Excel转成Html /// </summary> /// <param name="path">要转换的文档的路径</param> /// <param name="savePath">转换成html的保存路径</param> /// <param name="wordFileName">转换成html的文件名字</param> public static void Excel2Html(string path, string savePath, string wordFileName) { string str = string.Empty; Microsoft.Office.Interop.Excel.Application repExcel = new Microsoft.Office.Interop.Excel.Application(); Microsoft.Office.Interop.Excel.Workbook workbook = null; Microsoft.Office.Interop.Excel.Worksheet worksheet = null; workbook = repExcel.Application.Workbooks.Open(path, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); worksheet = (Microsoft.Office.Interop.Excel.Worksheet)workbook.Worksheets[1]; object htmlFile = savePath + wordFileName + ".html"; object ofmt = Microsoft.Office.Interop.Excel.XlFileFormat.xlHtml; workbook.SaveAs(htmlFile, ofmt, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); object osave = false; workbook.Close(osave, Type.Missing, Type.Missing); repExcel.Quit(); } } } View Code 后台调用方法 #region 上传成功后将文件转换 string physicalPath = Server.MapPath(Server.UrlDecode("/uploadFile"+"\\"+ name));//读取相对路径 string extension = Path.GetExtension(physicalPath);//获取后缀名 string[] show_name = name.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);//此处的name是上面上传附件中的名称分割 string show_name_View = show_name[0];//拿到实际name switch (extension) { case ".doc": case ".docx": Office2HtmlHelper.Word2Html(MapPath("/uploadFile" + "\\" + name + ""), MapPath("/Html/"), "" + show_name_View + "");//调用帮助类中生成WordHtml的方法,并保存起来 Response.Write("<script>window.open('/Html/" + show_name_View + ".html','_blank')</script>");//跳转并打开保存的相对路径中hmtl文件 break; case ".xls": case ".xlsx": Office2HtmlHelper.Excel2Html(MapPath("/uploadFile" + "\\" + name + ""), MapPath("/Html/"), "" + show_name_View + ""); Response.Write("<script>window.open('/Html/" + show_name_View + ".html','_blank')</script>"); break; default: break; } #endregion 至此,一个简易的上传附件在线浏览已经全部实现 如果小伙伴有更好得实现方式或者其他建议,欢迎留言告知~ 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 最近在网上偶然看见一个验证码,觉得很有意思,于是搜了下,是使用第三方实现的,先看效果: 总体来说效果还是可以的,官方提供的SDK也比较详细,可配置性很高。在这里在简单啰嗦几句使用方式: 使用步骤 ①进入官网下载sdk接口→ http://www.geetest.com/install/ ,因为小弟是做C#的,所以此处选择C#,具体选择看各位大佬所用语言决定~ ②第二步,获取代码,访问红框所示地址,下载demo。 ③运行Demo(src文件夹里面的GeetestSDK项目) ④移植到自己项目中 ⑴先将上述src文件拷贝到本地项目根目录下面 ⑵然后打开本地项目并添加现有项目,将GeetestSDK添加进来 ⑶在本地项目中添加引用 ⑷View中新建容器存放验证码 @using (Html.BeginForm()) { <div> 用户名: </div> <div> @Html.TextBoxFor(model => model.Name) </div> <div> 密码: </div> <div> @Html.PasswordFor(model => model.Age) </div> <div> 验证码:<div id="captcha"></div> @*新增的存放验证码的容器*@ </div> <div> <input type="submit" value="登陆" /> </div> } 界面入下图: ⑸新建一个控制器(GetcaptchaController)和分部视图(Index)用于显示请求到的页面 控制器代码 public ActionResult Index() { Response.ContentType = "application/json"; Response.Write(getCaptcha()); Response.End(); return View(); } private String getCaptcha() { GeetestLib geetest = new GeetestLib(GeetestConfig.publicKey, GeetestConfig.privateKey); String userID = "ShowTime"; Byte gtServerStatus = geetest.preProcess(userID); Session[GeetestLib.gtServerStatusSessionKey] = gtServerStatus; Session["userID"] = userID; return geetest.getResponseStr(); } 其中,GeetestConfig是新建的一个类,里面代码如下: public const String publicKey = "b46d1900d0a894591916ea94ea91bd2c"; public const String privateKey = "36fc3fe98530eea08dfc6ce76e3d24c4"; 注:需要引入此命名空间 using GeetestSDK; Index视图里面放一个空div就行,代码如下: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> </head> <body> <form id="form1"> <div> </div> </form> </body> </html> ⑹使用Ajax在登录页加载分部视图Index用于显示验证码 var handler = function (captchaObj) { //将验证码加到id为captcha的元素里 captchaObj.appendTo("#captcha"); }; //极验 $.ajax({ // 获取id,challenge,success(是否启用failback) url: "/Getcaptcha/Index", type: "get", dataType: "json", // 使用jsonp格式 success: function (data) { // 使用initGeetest接口 // 参数1:配置参数,与创建Geetest实例时接受的参数一致 // 参数2:回调,回调的第一个参数验证码对象,之后可以使用它做appendTo之类的事件 initGeetest({ gt: data.gt, challenge: data.challenge, product: "float", // 产品形式 offline: !data.success }, handler); } }); 注:需在头部引入Jquery1.9可直接引入下面两个js <script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script> <script src="http://static.geetest.com/static/tools/gt.js"></script> ⑺在登陆按钮中判断验证是否通过,登陆的Index代码如下: [HttpPost] public ActionResult Index(Models.HelloModel loginModel) { GeetestLib geetest = new GeetestLib(GeetestConfig.publicKey, GeetestConfig.privateKey); Byte gt_server_status_code = (Byte)Session[GeetestLib.gtServerStatusSessionKey]; String userID = (String)Session["userID"]; int result = 0; String challenge = Request.Form.Get(GeetestLib.fnGeetestChallenge); String validate = Request.Form.Get(GeetestLib.fnGeetestValidate); String seccode = Request.Form.Get(GeetestLib.fnGeetestSeccode); if (gt_server_status_code == 1) result = geetest.enhencedValidateRequest(challenge, validate, seccode, userID); else result = geetest.failbackValidateRequest(challenge, validate, seccode); if (result == 1) Response.Write("success");//返回1则表明验证通过,可跳转页面或者做其他处理 else Response.Write("fail"); return View(); } 运行效果 另:官方Demo下载下来是使用的嵌入式的验证效果,要更改此效果,可参考客户端SDK参数来配置 链接→ http://www.geetest.com/install/sections/idx-client-sdk.html#id19 Demo下载 链接: 点我下载 密码:63pd 常规验证码 最终效果 HTML <div style="MARGIN-TOP: 12px; margin-left: 0px; width: 130px; float: left;" id="checkInputLine" class="loginFormIpt showPlaceholder"> <input id="checkInput" class="loginFormCheckCodeInput" title='Please enter the contents of the right picture' tabindex="4" maxlength="5" require="True" title='验证码' placeholder="验证码" type="text" name="vcode" /> </div> <!-- 请输入验证码--> &nbsp;&nbsp; <img id="checkloing" src="/Home/VCode" class="loginFormCheckCodeImg" onclick="reloadcode('/Home/VCode')" title="Can not see clearly, change one." /> Ajax 请求如下: //刷新验证码 function reloadcode(srcStr) { document.getElementById("checkloing").src = srcStr + "?rand=" + Math.random(); } var checkInfo = ""; //检查数据 function Check() { checkInfo = ""; //检查不能为空的数据 $("input[Require='True']").each(function (i) { var tmpName = $(this).attr("name"); var strVal = $(this).val(); strVal = strVal.replace(/\s/g, "") if (!strVal) checkInfo += $(this).attr("placeholder") + "不能为空。<br/>"; }); if (checkInfo) return false; return true; } $(function () { $("#btnLogin").click(function () { var ii = layer.load(); if (!Check()) { layer.close(ii); layer.msg('' + checkInfo, function () { }); return; } $("#loadingDiv").show(); $.ajax({ url: "/Home/Index", type: "post", data: "userName=" + $("#tbUserName").val() + "&pwd=" + $("#tbPWD").val() + "&code=" + $("#checkInput").val(), success: function (data) { debugger; if (data.Result == "0") { layer.close(ii); layer.msg('' + data.MSG); return; } else if (data.Result == "1") { layer.close(ii); window.location.href = "/Home/Index"; } }, error: function (data) { layer.close(ii); layer.msg('' + data.MSG); } }); }); }); 其中: layer.msg('' + data.MSG); 这种弹框方式使用了layer弹出层 Home控制器里面的VCde方法就是获取到最新的验证码,代码如下: [AllowAnonymous]//跳过登陆验证 public ActionResult VCode() { VerificationCodeHelper vcode = new VerificationCodeHelper(); string codeStr = vcode.GetRandomCode(); if (!string.IsNullOrEmpty(codeStr)) { byte[] arrImg = vcode.GetVCode(codeStr); Session["code"] = codeStr; return File(arrImg, "image/gif"); } else { return RedirectToAction("/Login/VCode?rand=" + Guid.NewGuid().ToString().Substring(1, 10), "image/jpeg"); } } 其中 VerificationCodeHelper 就是封装好的 生成验证码的类,直接使用就行,代码如下: public class VerificationCodeHelper { private static Color BackColor = Color.White; private static int Width = 62; private static int Height = 21; private Random _random; // private string _code; private int _brushNameIndex; public byte[] GetVCode(string codeStr) { _random = new Random(); using (Bitmap img = new Bitmap(Width, Height)) { // _code = GetRandomCode(); // System.Web.HttpContext.Current.Session["vcode"] = _code; using (Graphics g = Graphics.FromImage(img)) { g.Clear(Color.White);//绘画背景颜色 Paint_Text(g, codeStr);// 绘画文字 // g.DrawString(strCode, new Font("微软雅黑", 15), Brushes.Blue, new PointF(5, 2));// 绘画文字 Paint_TextStain(img);// 绘画噪音点 g.DrawRectangle(Pens.DarkGray, 0, 0, Width - 1, Height - 1);//绘画边框 using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) { //将图片 保存到内存流中 img.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); //将内存流 里的 数据 转成 byte 数组 返回 return ms.ToArray(); } } } } /// <summary> /// 绘画文字 /// </summary> /// <param name="g"></param> private void Paint_Text(Graphics g, string code) { g.DrawString(code, GetFont(), GetBrush(), 3, 1); } /// <summary> /// 绘画文字噪音点 /// </summary> /// <param name="g"></param> private void Paint_TextStain(Bitmap b) { string[] BrushName = new string[] { "OliveDrab", "ForestGreen", "DarkCyan", "LightSlateGray", "RoyalBlue", "SlateBlue", "DarkViolet", "MediumVioletRed", "IndianRed", "Firebrick", "Chocolate", "Peru", " enrod" }; for (int n = 0; n < 30; n++) { int x = _random.Next(Width); int y = _random.Next(Height); b.SetPixel(x, y, Color.FromName(BrushName[_brushNameIndex])); } } /// <summary> /// 随机取一个字体 /// </summary> /// <returns></returns> private Font GetFont() { string[] FontItems = new string[]{ "Arial", "Helvetica", "Geneva", "sans-serif", "Verdana" }; int fontIndex = _random.Next(0, FontItems.Length); FontStyle fontStyle = GetFontStyle(_random.Next(0, 2)); return new Font(FontItems[fontIndex], 12, fontStyle); } /**/ /**/ /**/ /// <summary> /// 随机取一个笔刷 /// </summary> /// <returns></returns> private Brush GetBrush() { Brush[] BrushItems = new Brush[]{ Brushes.OliveDrab, Brushes.ForestGreen, Brushes.DarkCyan, Brushes.LightSlateGray, Brushes.RoyalBlue, Brushes.SlateBlue, Brushes.DarkViolet, Brushes.MediumVioletRed, Brushes.IndianRed, Brushes.Firebrick, Brushes.Chocolate, Brushes.Peru, Brushes.Goldenrod }; int brushIndex = _random.Next(0, BrushItems.Length); _brushNameIndex = brushIndex; return BrushItems[brushIndex]; } /// <summary> /// 绘画背景颜色 /// </summary> /// <param name="g"></param> private void Paint_Background(Graphics g) { g.Clear(BackColor); } /**/ /**/ /**/ /// <summary> /// 取一个字体的样式 /// </summary> /// <param name="index"></param> /// <returns></returns> private FontStyle GetFontStyle(int index) { switch (index) { case 0: return FontStyle.Bold; case 1: return FontStyle.Italic; default: return FontStyle.Regular; } } /// <summary> /// 取得一个 4 位的随机码 /// </summary> /// <returns></returns> public string GetRandomCode() { return Guid.NewGuid().ToString().Substring(0, 5); } } View Code 注意,初次加载页面的时候,将拿到的code存入Session中,点击登录的时候,将用户输入的验证码传入后台,进行比对验证是否和Session中的验证码相同,如果相同,则允许登录,否则,验证码错误 参考Action如下: [HttpPost] public ActionResult Index(string userName, string pwd) { try { string vccode = Request.Form["code"]; if (string.IsNullOrEmpty(vccode)) return Json(new { Result = "0", MSG = "请填写验证码" }); else { if (Session["code"] == null) return Json(new { Result = "0", MSG = "验证码已过期,请点击刷新验证码" }); string str = Session["code"].ToString(); str = str.ToLower(); vccode = vccode.ToLower(); if (str != vccode) return Json(new { Result = "0", MSG = "验证码填写错误!" }); else { Session["code"] = null; } if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(pwd)) { return Json(new { Result = "0", MSG = "用户或密码没有填写" }); } //查询此用户 #region 查询此用户 List<Models.MySqlUser> adminml = new List<Models.MySqlUser>(); Models.MySqlUser admin = new Models.MySqlUser(); adminml = IBCodeBll.GetModelList(userName.Trim()); if (adminml.Count > 0) { if (!adminml[0].password.ToLower().Equals(Maticsoft.Common.DEncrypt.DESEncrypt.Encrypt2(pwd.Trim()).ToLower())) { return Json(new { Result = "0", MSG = "密码错误!" }); } } else { return Json(new { Result = "0", MSG = "用户不在在!" }); } #endregion Session["UserName"] = userName.Trim().ToString(); return Json(new { Result = "1", MSG = "登录成功!" }); } } catch (Exception ex) { return Json(new { Result = "0", MSG = ex.Message }); } } End! 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
一次偶然在网上看到一个随机线条页面,看起来狂拽炫酷,今日得空先记录下来~ 随机线条 废话不多说,上图: 如上图所示,在登陆界面用到了随机线条作为背景,点击登陆后跳转到自定义的404界面。实现方式如下: 方法1: 引入外部Js: <script src="http://open.sojson.com/common/js/canvas-nest.min.js" count="200" zindex="-2" opacity="0.5" color="47,135,193" type="text/javascript"> </script> 可以直接使用,open.sojson.com是公开sdn域名,sojson工具站提供流量支持,嘻嘻~ 温馨提示:需要将引入的Js放到body里面。 方式二:自己新建js,将js拷贝过去,同样可以使用,引用方式和上面的一样 js代码 ! function() { //封装方法,压缩之后减少文件大小 function get_attribute(node, attr, default_value) { return node.getAttribute(attr) || default_value; } //封装方法,压缩之后减少文件大小 function get_by_tagname(name) { return document.getElementsByTagName(name); } //获取配置参数 function get_config_option() { var scripts = get_by_tagname("script"), script_len = scripts.length, script = scripts[script_len - 1]; //当前加载的script return { l: script_len, //长度,用于生成id用 z: get_attribute(script, "zIndex", -1), //层级 o: get_attribute(script, "opacity", 0.5), //透明度 c: get_attribute(script, "color", "0,0,0"), //线条颜色,最好使用RGB颜色 n: get_attribute(script, "count", 99) //线条数量 }; } //设置canvas的高宽 function set_canvas_size() { canvas_width = the_canvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, canvas_height = the_canvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; } //绘制过程 function draw_canvas() { context.clearRect(0, 0, canvas_width, canvas_height); //随机的线条和当前位置联合数组 var all_array = [current_point].concat(random_lines); var e, i, d, x_dist, y_dist, dist; //临时节点 //遍历处理每一个点 random_lines.forEach(function(r) { r.x += r.xa, r.y += r.ya, //移动 r.xa *= r.x > canvas_width || r.x < 0 ? -1 : 1, r.ya *= r.y > canvas_height || r.y < 0 ? -1 : 1, //碰到边界,反向反弹 context.fillRect(r.x - 0.5, r.y - 0.5, 1, 1); //绘制一个宽高为1的点 for (i = 0; i < all_array.length; i++) { e = all_array[i]; //不是当前点 if (r !== e && null !== e.x && null !== e.y) { x_dist = r.x - e.x, //x轴距离 l y_dist = r.y - e.y, //y轴距离 n dist = x_dist * x_dist + y_dist * y_dist; //总距离, m dist < e.max && (e === current_point && dist >= e.max / 2 && (r.x -= 0.03 * x_dist, r.y -= 0.03 * y_dist), //靠近的时候加速 d = (e.max - dist) / e.max, context.beginPath(), context.lineWidth = d / 2, context.strokeStyle = "rgba(" + config.c + "," + (d + 0.2) + ")", context.moveTo(r.x, r.y), context.lineTo(e.x, e.y), context.stroke()); } } all_array.splice(all_array.indexOf(r), 1); }), frame_func(draw_canvas); } //创建画布,并添加到body中 var the_canvas = document.createElement("canvas"), //画布 config = get_config_option(), //配置 canvas_id = "c_n" + config.l, //canvas id context = the_canvas.getContext("2d"), canvas_width, canvas_height, frame_func = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(func) { window.setTimeout(func, 1000 / 45); }, random = Math.random, current_point = { x: null, //当前鼠标x y: null, //当前鼠标y max: 20000 }; the_canvas.id = canvas_id; the_canvas.style.cssText = "position:fixed;top:0;left:0;z-index:" + config.z + ";opacity:" + config.o; get_by_tagname("body")[0].appendChild(the_canvas); //初始化画布大小 set_canvas_size(), window.onresize = set_canvas_size; //当时鼠标位置存储,离开的时候,释放当前位置信息 window.onmousemove = function(e) { e = e || window.event, current_point.x = e.clientX, current_point.y = e.clientY; }, window.onmouseout = function() { current_point.x = null, current_point.y = null; }; //随机生成config.n条线位置信息 for (var random_lines = [], i = 0; config.n > i; i++) { var x = random() * canvas_width, //随机位置 y = random() * canvas_height, xa = 2 * random() - 1, //随机运动方向 ya = 2 * random() - 1; random_lines.push({ x: x, y: y, xa: xa, ya: ya, max: 6000 //沾附距离 }); } //0.1秒后绘制 setTimeout(function() { draw_canvas(); }, 100); }(); 加载页面 New Add2016.12.22 我的博客------ 首页加载界面实现方式(采用HTML+CSS) HTML代码 <div class="loading"> <img src="http://images.cnblogs.com/cnblogs_com/zhangxiaoyong/770736/o_zoro.gif" /> <span>永不停歇,向上人生路!</span> </div> CSS代码: .loading { position: fixed; top: 0; z-index: 999; height: 100%; width: 100%; text-align: center; background: #ffffff; font-size: 18px; visibility: hidden; opacity: 0; -webkit-animation: loading 3s linear; /**此处是3秒消失,可根据自行需要调整时间**/ } .loading img { margin-top: 100px; } @-webkit-keyframes loading { 0%{opacity: 1;visibility: visible;} 90%{opacity: 1;} 100%{opacity: 0;visibility: visible;} } 加载效果 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 PMP是什么梗? 项目管理专业人士资格认证。它是由美国项目管理协会(Project Management Institute(PMI)发起的,严格评估项目管理人员知识技能是否具有高品质的资格认证考试。其目的是为了给项目管理人员提供统一的行业标准。目前,美国项目管理协会建立的认证考试有:PMP(项目管理师)和CAPM(项目管理助理师)已在全世界190多个国家和地区设立了认证考试机构。 可能有一部分程序员伙伴不了解PMP是什么?但应该没有撸码的不知道项目经理这个称谓吧?记得在学校时,老师给我们灌输这样一种思想,你做个两三年,当上一个公司的项目经理,就不用敲代码的,天天布置任务给别人做就可以了。听到这等好事,我当时两眼直冒绿光..... 如标题所示,小弟于今天中午已经结束了几个月的备考期,那首先来说下PMP的考试内容 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ PMP考试内容主要包括项目管理五个过程: 启动:确立一个项目或一个项目阶段。 规划:为完成项目,制定和维护一个可操作的计划。 执行:协调人力和其他资源以执行计划。 监控:通过监控和进度测量及必要时采取纠正措施以确保项目目标的实现。 收尾:正式验收项目或项目阶段并使其有条不紊地圆满结束。 PMP考试采用客观的选择题形式,共计200题,其中25题不计成绩。答题时间:9:00~13:00,共计4个小时。从2005年9月30日开始,报名PMP考试的新生的及格线为61%(200道单选题中抽查25道不计分,即答对106道题) 有小伙伴看到这里不禁发出轻蔑的奸笑,搞了半天原来都是选择题,那太Soesay了.额。。。先别急着下结论,让我们先看看考PMP的最重要的一本书(这里说的是最重要的,意思就是还有其他辅助书籍,你懂得) (PMP考试主教材) 那要小伙伴就比较好奇了,看这本书的侧面貌似有些厚度,那这本书里面都有哪些内容呢? 恩,这个问题问的好~ 项目管理知识体系指南(以下简称PMBOK)把项目管理过程分为五类: 1) 启动。成立项目组开始项目或进入项目的新阶段。启动是一种认可过程,用来正式认可一个新项目或新阶段的存在。 2) 计划。定义和评估项目目标,选择实现项目目标的最佳策略,制定项目计划。 3) 执行。调动资源,执行项目计划。 4) 控制。监控和评估项目偏差,必要时采取纠正行动,保证项目计划的执行,实现项目目标。 5) 结束。正式验收项目或阶段,使其按程序结束。 PMBOK将项目管理划分为9个知识领域: 1、项目整体管理(Project Integration Management) 项目整体管理是为了正确地协调项目所有各组成部分而进行的各个过程的集成, 是一个综合性过程。 其核心就是在多个互相冲突的目标和方案之间作出权衡, 以便满足项目利害关系者的要求。 2、项目范围管理(Project Scope Management) 项目范围管理就是确保项目不但完成全部规定要做的, 而且也仅仅是完成规定要做的工作,最终成功地达到项目的目的。基本内容是定义和控制列入或未列入项目的事项。 3、项目时间管理(Project Time Management) 其作用是保证在规定时间内完成项目。 4、项目费用管理(Project Cost Management) 项目费用管理, 是为了保证在批准的预算内完成项目所必需的诸过程的全体。 5、项目质量管理(Project Quality Management) 项目质量管理, 是为了保证项目能够满足原来设定的各种要求。 6、项目人力资源管理(Project Human Resource Management) 项目人力资源管理, 是为了保证最有效地使用参加项目者的个别能力。 7、项目沟通管理(Project Communications Management) 项目沟通管理, 是在人、思想和信息之间建立联系, 这些联系对于取得成功是必不可少的。参与项目的每一个人都必须准备用项目“语言”进行沟通, 并且要明白, 他们个人所参与的沟通将会如何影响到项目的整体。 项目沟通管理是保证项目信息及时、准确地提取、收集、传播、存贮以及最终进行处置。 8、项目风险管理(Project Risk Management) 项目风险管理, 需要的过程有识别、分析不确定的因素, 并对这些因素采取应对措施。 项目风险管理要把有利事件的积极结果尽量扩大, 而把不利事件的后果降低到最低程度。 9、项目采购管理(Project Procurement Management) 项目采购管理, 需要进行的过程都是为了从项目组织外部获取货物或服务。 10、项目干系人管理(stakeholder management) 项目干系人管理,对项目干系人需要、希望和期望的识别,并通过沟通上的管理来满足其需要、解决其问题的过程。 以上就是整本PMBOK的主体所有内容,当然,如果要面对考试,光看上面一本肯定不是够的(主要是开始难以理解,全文看上去都是讲的:输入-技术工具-输出),所以会有很多辅助书籍来帮助读者理解PMBOK,小弟统计了下几个月看的考试资料相关书籍,有图有真相 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 好了, 有小伙伴看到这里已经犯晕了,并准备开启开喷模式了。先别喷,下面才是今天我想讲的重点。 职业路线图 (图片来源于网络) 术语表 图中使用了很多术语,这里先做解释,只有我们大家都说同一种语言,沟通才会顺畅。 程序员:撰写代码,生产软件,辅助提高社会生产率的脑力劳动者。据说喝的是咖啡,抽的是烟,产的是代码。他们是计算机诞生后才逐渐兴起的一个群体,鱼龙混杂,有高飞天上的白富美,也有低到尘埃里的矮矬穷,总之虽一类而九流具足。 初级工程师:入门程序员,怀揣着IT行业多金的遐想进入了IT江湖,开始了练级打怪求升级的游戏之路。 中级工程师:经过几年的打拼,有一定的拷贝、粘贴功底,掌握了太祖长拳或罗汉掌之类的一技之长,打字速度变快了,双眼变得稍稍有些迷人了,对零壹世界有了比初级工程师更深刻的认识。 高级工程师:拷贝、粘贴之术出神入化,又习得搭积木和沙滩上盖大楼的绝技,还能像江湖郎中一样给病入膏肓的代码开药了。 架构师:坚信技术可以让人牛X,抵制了各种旁门左道的火辣诱惑,闭关多年,终于一生二、二生三、三生万物了,能够坐而论道、指手画脚了,觉得自己形神具备有仙人之资,偶尔来个仙人指路挺爽。 技术专家:看过各种江湖流派,最终在某条羊肠小道之上月黑风高之夜顿悟辟邪剑法,从而成为一代旁门高手,在自己的领域里无人能及。 CTO:史上最强的技术流?不一定。不过有一点高度是肯定的:会当凌绝顶,一览众山小。正统的CTO专门研究各种武技,寻求能在未来的江湖中克敌制胜的秘辛。国内江湖乱象纷呈,很多CTO其实在打杂做管理。 小组组长:这并不是一个正式的头衔,在《江湖异闻录》中,小组组长被掌门委以突袭带头人的角色,只因为他有带头大哥的潜力,能激发大家的战斗力。虽然没有掌握绩效考评之类的绝杀技,不过也是通往管理之路的必经过程。 项目组长:管的事儿有点儿多了,要带人,要带项目,要和产品经理搞好关系,通常也得和小分队的成员不分你我,总之什么都干,干的是项目经理的活,只是没有名分。没关系,等带好一个项目,又有项目经理的缺时就可以顶上去了。 项目经理:这是一个正式的打杂的,终于有了名分了。 高级项目经理:又叫项目群经理。他搬了把凳子以便让自己站得更高,能够看清楚纷乱的江湖里多个项目的情况,通常需要协调这个协调那个,找项目经理谈谈话,抚慰一下项目经理或项目组长委屈的心,有时也会被来自上面的压力压得喘不过气来。 部门(项目)总监:So,不知道要说什么了,开始管各种事儿了,评估项目开发过程,拟定考评、绩效、职级等各种制度,或者今天跑这个项目聊聊天,明天跑那个项目聊聊天,后者自己左右互搏和自己聊聊天,看起来闲人一个。 就这么多吧,其它的也不想说了。因为我们要讨论的是程序员的出路…… 程序员 路在何方 图中框起来的,是程序员的主旋律。 程序员,专业人士也,与瓦工、木匠、发型师、保洁员等类似,不过是社会万千分工之一种,没什么高大上,也没什么矮矬穷,只不过时代大潮中被滚滚洪流裹夹着前进的一群人而已,所谓泯然众人矣,就是说的这个。 So,该说什么啦? 你怎么混进来的 不忘初衷。 不忘初心。 你是不是忘了当初为什么要干这个? 那就想想吧。有好处。 有的人是为了程序员的高薪 有的人是想要一份体面稳定的工作而已,别出差 有的人是过来打酱油的早抱定了过两年就走的心 有的人想了解这个世界,试试看自己合不合适 有的人就喜欢计算机的世界,如鱼得水 有的人觉得这个行当既专业又神秘,感觉倍儿棒 有的人喜欢挑战,因为软件开发每一刻都有战斗的感觉,各种问题纷至沓来 有的人就喜欢不断学习新技术,为技术而生,而没有哪个行业像这个一样时刻都要学习 有的人喜欢这个行当的成就感,持续不断的小刺激总让人高潮不断 有的人渴望有自己的软件作品 有的人想用软件改变世界 有的人觉得这是一片净土,罕见尔虞我诈,不必江海寄余生 有的人觉得这行门槛低,谁都可以来搞两下 有的人是来发动战争的,攻破别人的防线感觉很爽 有的人是冲着 IT 界牛人半数都出柜这一点来的 …… 总之各种理由都有,你也一定有自己的那一个。对吧,你自己的。 将来去哪儿 你今天的选择,决定了明天的路。 有些人听说IT行业薪资高,巴巴地进来了,可是干了两三年,工资还是四五千,只好失望的走了。其实除非你有经天纬地之才,没有一开始就高薪的道理。先做事,后有钱。先想钱,钱难来。做程序员搞开发大抵是酱紫的。听说有个哥们跳到了华为,天天晚上十一二点的搞,有一天他终于不慎抱恙,晚上九点多回去了,被整个组的人鄙视;可是华为熬过三年,钱多多的,年终奖和分红抵得上你一年的工资。关键是,你熬得住吗?你媳妇熬得住吗? 我想说的是,作为程序员,心态决定你的将来。你自己怎么看待你所从事的工作,你是享受它、厌烦它、无所谓、爱恨交加……这些决定了你将在这条路上走多远。 我们生活在一张社会关系网中,因为别人能够看到你你才存在,这是《作为意识与表象的世界》,你看到的人、别人看到的你,都只是作为观察者的那个人想看到的,而非真实的人。虽然真实的人客观存在,但没有人能看到这样一个真实的、完整的人,包括你自己。 你看到你想看到的,他看到他想看到的,张三看到张三想看到的,李四看到李四想看到的……周围一圈人对你的意识构造了社会化关系中的你,但那只是你的一小部分。仅此而已。如果别人不知道你,你就不存在。为了存在,你就要在别人面前出现,这就是“存在感”。 如果想明白了这个道理,一个真正热爱技术的程序员,想在这个行当里干点儿事情的哥们姐们,就不会去在意别人说什么“软件开发是青春饭”、“程序员平均寿命低于普通人”、“程序员群体社会地位低下不如鸡”、“过了30就要另谋出路”之类的话。作为一个与其它行业没有什么差别的行业,仅仅是分工不同而已,为什么有这么多说法呢?众说纷纭,都是杂音。有用的话不这样,大音希声。其实黄小琥在《没那么简单》这首歌里也说了: “ 感觉快乐就忙东忙西 感觉累了就放空自己 别人说的话 随便听一听 自己作决定 不想拥有太多情绪 ” 当然你也可以认为我通篇都是P话,真没关系。 技术Or管理 程序员的两条主要通道 看图很明白了,程序员有两条主要的职业通道:技术和管理。 在中国有个很不好的传统:学而优则仕。 如果你在一个单位干技术干了很多年,还当不上领导,就会被人瞧不起。这也是很多人干开发干了几年后,正当年富力强生产力旺盛的时候脱离技术通道的原因。因为领导大部分都是这么一个套路:“干得好?行,带人吧。带得好?行,升经理吧……”所以,很多原本可以成为技术大牛的人,就这么被拐走了。 也有一些专注搞技术的开发人员,就要走技术通道。 既然做技术,那就免不了学习新东西,在当今IT日新月异的发展浪潮下,及时更新自身知识库成了必不可少的一项要求~ 跳出三界外 我原来有个同事,程序很厉害,是公司的高级工程师,后来不干了,开便利店去了。 有个同学在做直播创业..... 这都是跳出三界外的故事。其实也很平常,你的选择,你做主。如果你觉得这个行当不是人待的地方,再也不要受这罪了,那就走吧。如果一份工作带给你的痛苦比欢乐多很多,确实没有留恋的必要。真的,你肯定是走错了路。 软件项目管理流程 项目管理与软件开发的质量、效率、最终成果息息相关,下面主要围绕软件项目的风险评估、成本预算、客户沟通、需要分析、开发管理、成品交付等多个流程进行分析。在现今国内的项目的管理形式十分零乱,对管理欠缺重视,以致很多项目因为失去管理而最终折腰。很多的实战形人才只重视于开发环节,而对其他的流程欠缺认识,因而导致项目欠缺有条理的、阶段化的管理。 风险评估 软件项目风险是指在整个项目周期中所涉及的成本预算、开发进度、技术难度、经济可行性、安全管理等各方面的问题,以及由这些问题而对项目所产生的影响。项目的风险与其可行性成反比,其可行性越高,风险越低。软件项目的可行性分为经济可行性、业务可行性、技术可行性、法律可行性等四个方面。而软件项目风险则分为产品规模风险、需要风险、相关性风险、管理风险、安全风险等六个方面: 1. 产品规模风险 项目的风险是与产品的规模成正比的,一般产品规模越大,问题就越突出。尤其是估算产品规模的方法,复用软件的多少,需求变更的多少等因素与产品风险息息相关: (1) 估算产品规模的方法 (2) 产品规模估算的信任度 (3) 产品规模与以前产品规模平均值的偏差 (4) 产品的用户数 (5) 复用软件的多少 (6) 产品需求变更的多少 2. 需求风险 很多项目在确定需求时都面临着一些不确定性。当在项目早期容忍了这些不确定性,并且在项目进展过程当中得不到解决,这些问题就会对项目的成功造成很大威胁。如果不控制与需求相关的风险因素,那么就很有可能产生错误的产品或者拙劣地建造预期的产品。每一种情况对产品来讲都可能致命的,这些的风险因素有: (1) 对产品缺少清晰的认识 (2) 对产品需求缺少认同 (3) 在做需求分析过程中客户参与不够 (4) 没有优先需求 (5) 由于不确定的需要导致新的市场 (6) 不断变化需求 (7) 缺少有效的需求变化管理过程 (8) 对需求的变化缺少相关分析等 3. 相关性风险 许多风险都是因为项目的外部环境或因素的相关性产生的。控制外部的相关性风险, 能缓解策略应该包括可能性计划,以便从第二资源或协同工作资源中取得必要的组成部分,并觉察潜在的问题,与外部环境相关的因素有: (1) 客户供应条目或信息 (2) 交互成员或交互团体依赖性 (3) 内部或外部转包商的关系 (4) 经验丰富人员的可得性 (5) 项目的复用性 4. 技术风险 软件技术的飞速发展和经验丰富员工的缺乏,意味着项目团队可能会因为技巧的原因影响项目的成功。 在早期,识别风险从而采取合适的预防措施是解决风险领域问题的关键,比如:培训、聘请顾问以及为项目团队招聘合适的人才等。关于技术主要有下面这些风险因素: (1) 缺乏培训 (2) 对方法、工具和技术理解的不够 (3) 应用领域的经验不足 (4) 对新的技术和开发方法应用不熟悉 5. 管理风险 尽管管理问题制约了很多项目的成功,但是不要因为风险管理计划中没有包括所有管理活动而感到惊奇。在大部分项目里,项目经理经常是写项目风险管理计划的人,他们有先天性的不足——不能检查到自己的错误。因而,使项目的成功变得更加困难。如果不正视这些棘手的问题,它们就很有可能在项目进行的某个阶段影响项目本身。当我们定义了项目追踪过程并且明晰项目角色和责任,就能处理这些风险因素: (1) 计划和任务定义不够充分 (2) 对实际项目状态不了解 (3) 项目所有者和决策者分不清 (4) 不切实际的承诺 (5) 不能与员工之间的进行充分地沟通 6. 安全风险 软件产品本身是属于创造性的产品,产品本身的核心技术保密非常重要。但一直以来,我们在软件这方 面的安全意识比较淡薄,对软件产品的开发主要注重技术本身,而忽略了专利的保护。软件行业的技术人员流动是很普遍的现象,随着技术人员的流失、变更,很能会导致产品和新技术的泄密,致使我们的软件产品被它公司窃取,导致项目失败。而且在软件方面关于知识产权的认定目前还没有明确的一个行业规范,这也是我们 软件项目潜在的风险。 7. 回避风险的方式 (1) 以开发方诱导能保证需求的完整,使需求与客户的真实期望高度一致。再以书面方便形成《用户需求》这一重要的文档,避免疏漏造成的损失在软件系统的后续阶段被逐步地放大。 (2) 设立监督制度,项目开发中任何较大的决定都必须有客户参与进行的,在该项目中项目监督由项目开发中的质量监督组来实施。 (3) 需求变更需要经过统一的负责人提出,并且要用户需求的审核领导认可,需求变更应该是定期而不是随时的提出,而且开发方应该做好详细的记录,让客户了解需求变更的实际情况。 (4) 控制系统的复杂程度,过于简单的系统结构,对用户来使用比例会有明显的折扣,甚至造成软件寿命过短。反之,软件结构的过于灵活和通用,必然引起软件实现的难度增加,系统的复杂度会上升,这又会在实现和测试阶段带来风险。适当控制系统的复杂程度有利于降低开发的风险。 (5) 从软件工程的角度看,软件维护费用约占总费用的55%~70%,系统越大,该费用越高。对系统可维护性的轻视是大型软件系统的最大风险。在软件漫长的运营期内,业务规则肯定会不断发展,科学的解决此问题的做法是不断对软件系统进行版本升级,在确保可维护性的前提下逐步扩展系统。 (6) 设定应急计划,每个开发计划都至少应该设定一个应急预案去应对出现突发情况和不可遇知的风险。 成本预算 1. 成本预算方式 (1) 自上而下的预算方法 自上而下的预方法主要是依据上层、中层项目管理人员的管理经验进行判断,对构成项目整体成本的子项目成本进行估计,并把这些判断估计的结果传递给低一层的管理人员,在此基础上由这一层的管理人员对组成项目的子任务和子项目的成本进行估计,然后继续向下一层传递他们的成本估计,直到传递到最低一层。 使用此预算方式,在上层的管理人员根据他们的经验进行的费用估计分解到下层时,可能会出现下层人员认为上层的估计不足以完成相应任务的情况。这时,下层人员不一定会表达出自己的真实观点,不一定会和上层管理人员进行理智地讨论,从而得出更为合理的预算分配方案。在实际中,他们往往只能沉默地等待上层管理者自行发现问题并予以纠正,这样往往会给项目带来诸多问题。 自上而下更适用于项目启动的前期,与真实费用相差在30% ~ 70%之间。 Scrum使用自上而下的成本预算方式,它不会立即精确地确定成本,而是以最大限度容纳客户对未来产品要求所产生的变更。 (2) 自下而上的预算方法 自下而上方法要求运用WBS(Work Breakdown Structure,工作分解结构)对项目的所有工作任务的时间和预算进行仔细考察。最初,预算是针对资源(团队成员的工作时间、硬件的配置)进行的,项目经理在此之上再加上适当的间接费用(如培训费用、管理费用、不可预见费等)以及项目要达到的利润目标就形成了项目的总预算。自下而上的预算方法要求全面考虑所有涉及到的工作任务,更适用于项目的初期与中期,它能准备地评估项目的成本,与真实费用相差在5% ~ 10%之间。 注解:WBS WBS是面向提交成果对项目的分解,从提交成果的列表可以确定每个提交成果需要执行的活动。Scrum会对WBS进一步细化,把一个迭代分解为一个或多个的工作包,再把工作包分解为细小的开发任务(一般开发任务的开发周期在15个工作小时以内)。 2. 确定项目支出 总体成本预算就是结合下列多个成本预算方式综合计算的开发成本: (1) 零基数预算 在成本预算的初期应该使用零基数的计算原则,而不可以使用类似于:以上一年总体费用加上20% 这样粗略的方式计算项目成本。 (2) 软硬件成本、物品成本 物品成本是指类似于:服务器(RAM 硬盘 CPU NIC卡 RAID簇)成本、维护成本、机房租金、光纤通讯成本、软件成本等的成本。 计算成本时需要考虑组装硬盘需时的长短,技术人员需要具备的质素,产品供应商能否提供保证质量,管理时是否需要额外的管理人员这些多方因素。 (3) 软件许可证成本 (4) 外包成本 当使用类似:视频、短信、移动电信类服务、门户网站等子项目时可以考虑以外包形式完成,以降低开发成本。 (5) 人力资源成本 计算人力资源成本时应该使用以最高和最低的工作效率估算平均效率的方式,计算出人力资源的平均成本。 (6) 维修保养成本 客户沟通的过程 从客户沟通的方向出发来看,软件项目可分为:需求识别、方案定制、项目实施、项目结束等4个不同的阶段,各个阶段都具有不同的沟通重点。 1. 需求识别阶段 (1) 文本沟通 在需求识别的前期,应该通过问卷、原型展示、界面展示、逻辑处理展示、准化文档模板等方式进行全方位多角度的分析,随时将不明确之处反馈给客户,以期待客户解答。并以文本记录的方式建立需要分析书,并要求客户审核需求分析书,以达到需要分析与客户的真实期望高度一致的结果。 (2) 业务逻辑沟通 在进行业务沟通时,应该了解客户的行业语言,以促进业务分析的过程,越过应用需求和开发之间的鸿沟。沟通过程提倡以草图或者可视信息化的方式进行, 针对不同层面的企业用户提供最适合的操作界面。以多角度的方式思考问题,要抓住需求重点,尤其是客户方领导所关注的创新类和实用类需求。 (3) 需求变更的规范化管理 需求变更在软件开发类项目中是可以理解的,但必须对需求变更做好规范化的管理,以避免出现需求无止境变更的风险。需求变更必须由统一的负责人提出,并且由用户需求的审核领导者认可。需求变更的提出应该是定期而不是随时的,开发方应该做好详细的文本记录,让客户了解需求变更的实际情况和开发方为之所付出的成本代价。 2. 方案定制阶段 该阶段项目的主要任务是与客户共同制定一个以前期明确的需求、双方的资源、项目开始的阶段、实施的时间约定、项目费用限制等为基础的具有可操作性的项目计划,从本阶段开始争取客户全面参与项目的管理,并以双方的共同利益考虑项目实施的具体计划与风险规避。 3. 项目实施阶段 在该阶段,软件项目团队应该与客户共同领导项目的实施。同时,项目团队应实时评估客户满意度,并通过持续改进的方式提高客户满意度,还应要求客户参加必要的培训,以及在必要时检查项目产品。在出现客户的需求变更前,应主动与客户沟通交流,使客户充分了解项目的每个环节,以及变更带来的影响,减少需求变更。如果出现客户需求变更,应与客户一起共同解决由变更引起的成本、进度、质量变化。 4. 结束阶段 该阶段主要进行项目成果的移交,并把系统交付给维护人员,帮助客户实现商务目标,结清各种款项。完成这些工作后应该进行项目评估,审核此项目的成果并总结项目经验。 5. 售前人员注意事项 在产品型项目作为开发成果时,相关销售人员应该注意:对产品的推销不应该过分承诺。如果过分承诺,会给后续的项目实施带来困难;一旦承诺没有兑现,也会降低客户满意度,影响今后合作。如果有附加承诺,一定要以文本形式记录,让实施项目经理知晓并传达给项目组成员。 注解:在软件项目中,需要明确以下四种客户角色 A. 要明确最终使用部门和用户,要去了解他们现有的工作方式,要让他们知道项目的目标框架,知道项目要解决他们的哪些困难,但绝对不是全部困难,这样可以较好的控制项目范围。 B. 要明确需求的提出者,他或者他们要能够代表最终客户群体。提出产品需求的这类客户要具有一定的技术、业务能力和权威,能够真正代表最终客户团队的意愿和想法,最好有IT基础,能够用IT语言描述问题和需求,以利于双方的沟通、协作,避免产生歧义。 C. 要明确做需求确认的中层领导,他要把握方向。软件开发项目是解决实际生产或者管理问题,同时 也是领导系统建设的具体实现,做需求确认的客户领导,既要了解高层领导的系统建设要点和方向,又要谙熟具体业务和生产管理实际。如果是这样的客户领导来把 握和决策,对企业软件开发项目的顺利进展作用非凡。 D. 要明确谁来对成品提意见,谁来验收。项目验收环节,是项目的收尾环节,如果验收的人对项目初期的需求目标不了解,会从态度和产品实际使用效果上对验收产生负面的影响,对提供产品的企业关闭项目非常不利。根据实践总结,由需求提出人和确认人来做项 目的验收工作,能够促进项目的顺利完成,避免延期。 需求分析 1. 需求分析的过程 需求过程包括需求开发和需求管理2个部分: (1) 需求开发就是对开发前期的管理,与客房的沟通过程,可以分为4个阶段:需求获取、需求分析、编写需求和需求验证。 (2) 需求管理:就是软件项目开发过程中控制和维持需求约定的活动。包括:变更控制、版本控制、需求跟踪、需求状态跟踪。 2. 需求的层次 需求的层次包括:业务需求、用户需求、功能需求、非功能需求等4个方面。 3. 需求开发阶段的重点 (1) 提取业务对象 业务对象是指系统使用的真实对象,例如一个供应链管理 (Supply Chain Management ,简称SCM) 业务对象主要包括:生产批发商、零售商、送货商、顾客多个层次。 (2) 提取业务流程 在了解业务逻辑的过程中,应该列举出所开发软件模块的各自职能,并细化每个工作流程,深入分析业务逻辑。 (3) 性能需求 在分析的前期应该注意客户对所开发软件的技术性能指标,如存储容量限制、运行时间限制、安全保密性等。 (4) 环境需求 环境需求是指软件平台运行时所处环境的要求,如硬件方面:机型、外部设备、数据通信接口;软件方面:系统软件,包括操作系统、网络软件、数据库管理系统方面;使用方面:使用部门在制度上,操作人员上的技术水平上应具备怎样的条件。 (5) 可靠性需求 对所开发软件在投入运行后发生故障的概率,应该按实际的运行环境提出要求。对于重要的软件,或是运行失效会造成严重后果的软件,应提出较高的可靠性要求。 (6) 安全保密要求 在需求分析时应当在这方面恰当地做出规定,对所开发的软件给予特殊的设计,使其在运行中,其安全保密方面的性能得到必要的保证。 (7) 用户界面需求 为用户界面细致地规定到达的要求。 (8) 资源使用需求 开发的软件在运行时和开发时所需要的各种资源。 (9) 软件成本消耗与开发进度需求 在软件项目立项后,根据合同规定,对软件开发的进度和各步骤的费用提出要求,作为开发管理的依据。 (10) 开发目标需求 预先估计以后系统可能达到的目标,这样可以比较容易对系统进行必要的补充和修改。 4. 需求分析的任务 需求分析的主要任务是借助于当前系统的逻辑模型导出目标系统的逻辑模型,其流程如下: (1) 确定对系统的综合需求(功能、性能、运行、扩充需求) (2) 制作产品需求文档 (PRD) (3) 分析系统的数据需求(概念模型、数据字典、规范化) (4) 导出目标系统的详细的逻辑模型(数据流图、数据字典、主要功能描述) (5) 开发原形系统 (6) 从PRD提取编制软件需求规格说明书(SRS) 注解:SRS格式 1.引言 2系统概述(项目背景、系统目标、核心业务流程) 3.术语说明 4.系统结构(架构图、功能图) 5.主体功能与业务逻辑(重点) 6.接口需求(内部、外部接口、) 7.网络总体设计(拓扑网络、主机、组网) 8.运行环境(Linux、Windows、IIS、 WebLogic、Tomcat、OLAP、OLTP、JDK 8.0 、.NET Framework 4.0等) 面向对象程序设计(略) 1. 设计原则 (1) SRP单一职责链 每个类都应该只负责做一件事。 (2) OCP开封闭合原则 软件的实体(类、模块、函数等)应该是可以扩展的,但是不可修改的。 (3) LSP替换原则 子类必须能替换他们的基类型。 (4) DIP依赖倒置原则 高层模块不应该依赖于低层模块,二者都应该依赖于接口与抽象类。抽象不应该依赖于细节,细节应依赖于对象。 (5) ISP接口隔离原则 不应该强迫客户依赖于并未使用的接口,而应该把胖接口分离。 2. 实现UML建模 (1) 业务对象的提取 (2) 根据SRS、CRC等实现用况建模 (3) 实现业务顺序图 (4) 建立类图,根据用况图建立对象之间的关联 (5) 绘制活动图、实现协作图、状态图 开发管理 1. 建立项目计划 (1) 设计总体架构 针对系统的实施需要,采取适当的且成熟的框架结构。 (2) 控制可扩展度 扩展度过大,将提高系统的复杂程度,延长开发时间;扩展度过低,会直接影响系统的二次开发与维护。控制系统的可扩展性,能提高开发效率,降低系统维护的难度。 (3) 建立基础设施 合理分配部署软、硬件等基础设施所需要的时间与成本(例如:服务器的订购安装、光纤接入、软件平台订购)。 (4) 划分开发任务 利用WBS(Work Breakdown Structure,工作分解结构)对可交付结果进行分类与划分。每个项目都能划分为多个不同阶段,每个阶段又可以分为多个工作包(Work Package),工作包是WBS里最小的可交付结果,最后从工作包中分解出多个开发任务列表。 (5) 部署开发进度 一个项目应该按进度划分为多个开发阶段,每个阶段的开发周期一般在30~60个工作日以内。在此阶段内应该与客户举行协商会议,制定产品路线图,在开发过程中邀请客户积极参与并提出反馈意见。然后把该时段内的开发任务按照开发难度,依赖性,重要性等多方条件划分为多个迭代周期。 在Scrum 敏捷软件开发原则中,应该把每个迭代任务进一步细分为多个开发任务列表,再开发任务分配给组员各自负责,而开发时间应该控制在15个工作小时以内。如果开发时间超出15个工作小时,应该考虑把开发任务再度细化。开发任务建议应该由组员自主选择,而不要使用强制分配的方式。 (5) 测试项目成果 每个工作包都应该同步部署测试工作,提高项目的质量。对出错BUG的工作包应该由测试人员以文本方式记录,向开发人员展示错误所在,让开发人员及时进行修改。 2. 管理开发团队 (1) 组建团队 按照工作任务与项目时间的前提条件建立团队,按团队职责分配人员,一般团队人数应该控制在8~12人之间。当团队人数超过15人时,应该考虑把团队分解成2个独立团队,负责不同的开发任务。 (2) 分配开发任务 在每个迭代周期内(一般是15~30个工作日),应该把每个工作包进一步细分为多个开发任务,再开发任务分配给组员各自负责,开发时间应该控制在15个工作小时以内。如果开发任务的开发时间超出15个工作小时,应该考虑把任务再度细化。而开发任务应该以自由选择的方式分配给每个组员。 (3) 监督开发进度 在迭代的前期举行一次会议,让组员了解开发的进展及流程,并以自主选择的方式分配开发任务。期间可使用Microsoft Project等工具记录开发流程的进展,在每个工作包完成开发后应该进行性功能的测试,并以文本方式记录测试结果。 每天举行一次15分钟的站立会议,让组员交待昨天已完成的开发任务,当天将要做的任务,与开发过程中所遇到的问题。并在每周末举行一次例行会议,交待总体进程。 在迭代末期举行一次冲刺会议,总结项目的进展,交行已完成的任务,回顾该迭代周期内所遇到的问题,为下一个迭代做好准备。 (4) 系统测试 对每个已完成的工作包进行适时的测试,保证系统质量与性能。对测试结果进行文本的记录,并把测试结果与绩效工资收入挂钩,并以真实数据计算组员的绩效收入。 (5) 解决开发中所遇到的问题 对开发人员进行前期培训,可适当按工作能力分配任务,指导组员的开发。当遇到问题时应该在当天的站立会议时即时提出,并在15个工作小时内解决所遇到的问题以防止问题进一步扩大。 3. 监管产品质量 (1) 质量需要的是计划、设计而并非审查的。在产品建立的初级,必须与“质量保证”(QA)的部门进行协商,以正式文档的方式,决定恰当的质量策略和标准。 (2) 在开发过程中使用TDD(测试驱动开发)的模式,提高开发质量。测试人员应该以文本方式记录bug,并与开发人员共同工作的,把突出的缺陷演示给开发人员,以提高修改的效率。 (3) 在每个迭代的结束时进行一次产品效果的演示,从客户、使用者、高层领导中收集反馈信息。在团队内部举行评审会议,分析测试结果,了解产品性能,为下次迭代所需要做的改进做好计划。 4. 修改项目计划 (1) 在产品需要识别阶段,应该以文档形式记录产品功能与开发流程,在开发计划需要修改时,应该与客户共同探讨,让客户了解计划修改对项目进度所造成的影响。 (2) 项目计划的修改应该由统一的负责人提出,并且由用户需求的审核领导者认可。需求变更的提出应该是定期而不是随时的。 (3) 计划的变更应该做好详细的文本记录,让客户了解需求变更的实际情况和开发方为之所付出的成本代价。 产品交付 1. 项目的后期审核 在项目开发最终完成后,对开发人员来说可算是放下工作的重担,但对项目经理来说这往往是项目的关键时刻。前期的风险评估、成本预算、需求分析、软件设计都是为了引导项目走向这一时刻,此时所有的目光都将投向项目管理人员。你可能发现大量而琐碎的工作将要在几个小时内完成,此刻项目经理更需要保持清醒与镇定,把最后的工作视为微型项目来对待。细致地对项目进行后期的审核,分析项目成果、项目团队的效率、可交付产品的价值,以此审核结果可作为项目管理经验总结的一部分。 2. 质量评审 在项目交付前,应该把项目交给相关的“质量保证”(QA)部门进行质量评审,并邀请典型用户感受产品的质量。 3. 项目的最终交付 正常情况下在项目的前期就会订立项目交付的协议,项目交付方式分为非正式验收与正式验收两种。一般在项目完成后都会先进行非正式验收,让客户体会项目的质量并提出反馈意见,最后在客户肯定产品质量后再以书面协议的形式进行正式的产品验收。 4. 项目的最终报告 在项目的最后,应该制定项目的最终报告,此报告可以视为是对该项目一个记录,但报告不必包含项目的所有方面。一般最终报告应该包含以下方面: (1) 最初引进项目时的初期项目视图 (2) 对该项目的价值评估及支持性信息 (3) 项目的范围 (4) 项目的开发流程及WBS (5) 项目的会议记录 (6) 项目变更的报告及变更的理由 (7) 与项目相关的沟通过程文件 (8) 项目的审核报告与客户验收报告 (9) 项目成员的表现报告 (10) 项目的最终成果 好了,啰嗦了这么多,以上项目管理流程归谁来负责呢?没错,就是传说中的项目经理,项目经理需要对整个项目负最终责任。诚如大家所见,其实要做好一个项目经理,也是需要像程序猿一样,千锤百炼才能够对项目管理游刃有余。 小结 我们不管是哪种程序猿,写程序就像爬楼梯,爬的阶梯多了,站的角度不同了, 看问题和处理问题的方式和手段就不同了,我始终坚信:存在即合理。 无论是哪种项目经理,都是以高质量完成项目为目的,所以和程序猿本身并无冲突和矛盾。 综上所述:如果我们能从对方的立场去思考正在或者将要进行的工作,就可以避免很多误会和矛盾。毕竟都只是在各自的职业道路上摸索前行。 最后,小弟赋诗一首,送给广大撸码爱好者及项目经理(重要提示:有两个多音字,谁能看出我想表达的一句话并完整的留言在下面,有惊喜哟) 文章部分内容截取自:http://blog.csdn.net/foruok/article/details/40585139 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
因项目业务需要,需要做一个发送邮件功能,查了下资料,整了整,汇总如下,亲测可用~ QQ邮箱发送邮件 #region 发送邮箱 try { MailMessage mail = new MailMessage(); MailAddress from = new MailAddress("发件人邮箱", "工程管理平台", System.Text.Encoding.GetEncoding("GB2312"));//邮件的发件人 mail.From = from; MailAddress to = new MailAddress("收件人邮箱");//设置邮件的收件人 mail.To.Add(to); mail.Subject = "收款确认"; string url = "http://wwww.baidu.com"; mail.Body = "您好,有新的待确认收款" + url; mail.IsBodyHtml = true;//HTML格式,内容可以包含HMTL标签和超链接uuu mail.BodyEncoding = System.Text.Encoding.GetEncoding("GB2312");//设置邮件的格式 mail.Priority = MailPriority.Normal;//设置邮件的发送级别 mail.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess; SmtpClient client = new SmtpClient();//邮件发送服务器 //client.Port = 25; QQ发送邮件不用设置 client.Host = "smtp.qq.com"; //发件人地址所在的服务器SMTP 如网易126邮箱的为smtp.126.com client.EnableSsl = true; client.UseDefaultCredentials = false; //设置用于 SMTP 事务的端口,默认的是 25 client.Credentials = new System.Net.NetworkCredential("发件人邮箱", "授权码");//发件人邮箱登陆名和密码(生成的授权码) client.DeliveryMethod = SmtpDeliveryMethod.Network; try { client.Send(mail);//发送邮件 MessageShow("发送成功"); Response.Write("<script language='javascript'>alert('发送成功!');</script>"); } catch (System.Net.Mail.SmtpException ex) { MessageShow(ex.Message); } } catch (Exception ex) { throw ex; } #endregion 效果: 注意 重要引用: using System.Net.Mail; 其中,使用QQ发送邮件,需要使用授权码而不是QQ密码,授权码具体生成方式可以查看:http://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001256&&id=28 自定义发送邮件 附帮助类的发送邮件方式(推荐此种方式,更灵活) 邮件帮助类 1 #region 邮件帮助类 2 /// <summary> 3 /// 邮件帮助类 4 /// </summary> 5 public static class SendMailHelper 6 { 7 /// <summary> 8 /// 发送邮件 9 /// </summary> 10 /// <param name="request">邮件内容对象</param> 11 /// <returns>发送邮件所遇到的异常</returns> 12 public static string SendMail(MailRequest request) 13 { 14 try 15 { 16 MailMessage mail = new MailMessage(); 17 18 if (string.IsNullOrEmpty(request.From)) 19 { 20 request.From = ConfigurationManager.AppSettings["DefaultMailFrom"]; 21 } 22 mail.From = new MailAddress(request.From); 23 24 PaserMailAddress(request.To, mail.To); 25 PaserMailAddress(request.CC, mail.CC); 26 PaserMailAddress(request.Bcc, mail.Bcc); 27 28 mail.Subject = request.Subject; 29 mail.SubjectEncoding = System.Text.Encoding.UTF8; 30 mail.Body = request.Body; 31 mail.ReplyTo = new MailAddress(request.From); 32 mail.IsBodyHtml = true; 33 34 if (request.Attachments != null && request.Attachments.Length > 0) 35 { 36 for (int i = 0; i < request.Attachments.Length; i++) 37 { 38 Attachment mailAttach = new Attachment(ByteArrayToStream(request.Attachments[i].FileData), request.Attachments[i].FileName); 39 40 mail.Attachments.Add(mailAttach); 41 } 42 } 43 44 if (string.IsNullOrEmpty(System.Configuration.ConfigurationManager.AppSettings["SMTPSERVER_Show"])) 45 { 46 throw new ApplicationException("邮件服务无效"); 47 } 48 49 //Smtp Server 50 SmtpClient mailClient = new SmtpClient(ConfigurationManager.AppSettings["SMTPSERVER_Show"]); 51 52 if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["SMTPSERVERPORT"])) 53 { 54 //端口号 55 try 56 { 57 mailClient.Port = Int32.Parse(ConfigurationManager.AppSettings["SMTPSERVERPORT"]); 58 } 59 catch 60 { 61 return "SMTP服务器端口设置错误,端口必须设置为数值型"; 62 } 63 } 64 65 if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["MAILUSER_Show"])) 66 { 67 mailClient.Credentials = new System.Net.NetworkCredential(ConfigurationManager.AppSettings["MAILUSER_Show"], ConfigurationManager.AppSettings["MAILUSERPW_Show"]); 68 mailClient.DeliveryMethod = SmtpDeliveryMethod.Network; 69 } 70 else 71 { 72 mailClient.Credentials = CredentialCache.DefaultNetworkCredentials; 73 } 74 75 mailClient.Send(mail); 76 mail.Dispose(); 77 78 return string.Empty; 79 } 80 catch (SmtpFailedRecipientsException e) 81 { 82 return e.Message; 83 } 84 catch (SmtpFailedRecipientException e) 85 { 86 return e.Message; 87 } 88 catch (SmtpException e) 89 { 90 return e.Message; 91 } 92 catch (Exception e) 93 { 94 return e.Message; 95 } 96 } 97 98 /// <summary> 99 /// 解析分解邮件地址 100 /// </summary> 101 /// <param name="mailAddress">邮件地址</param> 102 /// <param name="mailCollection">邮件对象</param> 103 private static void PaserMailAddress(string mailAddress, MailAddressCollection mailCollection) 104 { 105 if (string.IsNullOrEmpty(mailAddress)) 106 { 107 return; 108 } 109 110 char[] separator = new char[2] { ',', ';' }; 111 string[] addressArray = mailAddress.Split(separator); 112 113 foreach (string address in addressArray) 114 { 115 if (address.Trim() == string.Empty) 116 { 117 continue; 118 } 119 120 mailCollection.Add(new MailAddress(address)); 121 } 122 } 123 124 /// <summary> 125 /// 字节数组转换为流 126 /// </summary> 127 /// <param name="byteArray">字节数组</param> 128 /// <returns>Stream</returns> 129 private static Stream ByteArrayToStream(byte[] byteArray) 130 { 131 MemoryStream mstream = new MemoryStream(byteArray); 132 133 return mstream; 134 } 135 } 136 #endregion View Code 补充上述帮助类中,还需要添加 MailRequest.cs 类(发送请求相关类) 和 MailRequestAttachments.cs 类(附件类) MailRequest.cs 类 using System; using System.Collections.Generic; using System.Text; namespace SyncAdData.DBHelper { /// <summary> /// 发送邮件请求 /// </summary> public class MailRequest { #region PrivateFields /// <summary> /// 文件名 /// </summary> private string _fromField; /// <summary> /// 返送到 /// </summary> private string _toField; /// <summary> /// 抄送 /// </summary> private string _copyField; /// <summary> /// 附件 /// </summary> private string _bccField; /// <summary> /// 标题 /// </summary> private string _subjectField; /// <summary> /// 发送人名 /// </summary> private string _bodyField; /// <summary> /// 类容 /// </summary> private MailRequestAttachments[] _attachmentsField; #endregion /// <summary> /// 发送人,多个人以分号;间隔 /// </summary> public string From { get { return this._fromField; } set { this._fromField = value; } } /// <summary> /// 收件人,多个人以分号;间隔 /// </summary> public string To { get { return this._toField; } set { this._toField = value; } } /// <summary> /// 抄送人,多个人以分号;间隔 /// </summary> public string CC { get { return this._copyField; } set { this._copyField = value; } } /// <summary> /// 秘密抄送人,多个人以分号;间隔 /// </summary> public string Bcc { get { return this._bccField; } set { this._bccField = value; } } /// <summary> /// 主题 /// </summary> public string Subject { get { return this._subjectField; } set { this._subjectField = value; } } /// <summary> /// 内容 /// </summary> public string Body { get { return this._bodyField; } set { this._bodyField = value; } } /// <summary> /// 附件列表 /// </summary> public MailRequestAttachments[] Attachments { get { return this._attachmentsField; } set { this._attachmentsField = value; } } } } View Code MailRequestAttachments.cs 类 using System; using System.Collections.Generic; using System.Text; namespace SyncAdData.DBHelper { /// <summary> /// 发送邮件请求附件 /// </summary> public class MailRequestAttachments { #region PrivateFields /// <summary> /// 文件名 /// </summary> private string _fileNameField; /// <summary> /// 文件内容 /// </summary> private byte[] _fileDataField; #endregion /// <summary> /// 文件名 /// </summary> public string FileName { get { return this._fileNameField; } set { this._fileNameField = value; } } /// <summary> /// 文件内容 /// </summary> public byte[] FileData { get { return this._fileDataField; } set { this._fileDataField = value; } } } } View Code 需要的命名空间 using System; using System.Reflection; using System.Net.Mail; using System.Web.Configuration; using System.Net; using System.IO; 其中 帮助类中的服务器地址 和 账号 密码需要在配置文件中配置 <add key="SMTPSERVER" value="邮件服务器"/> <add key="MAILUSER" value="账号"/> <add key="MAILUSERPW" value="密码"/> 前台调用 /// <summary> /// 发送邮件 /// </summary> /// <param name="StrUrl">根据业务需要,这里我需要传入几个拼接后的id值</param> /// <param name="bid">根据业务需要,这里我传的批次id</param> /// <param name="showemail">根据业务需要,这里我传入的是查询出来的收件人邮箱(如果是固定的更好,可以直接写死或者写成配置文件)</param> private void Send(string StrUrl,string bid,string showemail) { #region 读取配置发送邮件 string url = "http://localhost:9998/FinanceManage/CollectionManage/ConfirmCollection_Receipt.aspx?MenuID=14010600&id=" + StrUrl + "&batchID=" + bid + ""; string body = "您好,有新的待确认收款≥ "+url; //string bcc = string.Empty; string to = "v-zhangxy52@vanke.com";//收件人 //string cc = "";//抄送人 MailRequest mail = new MailRequest(); mail.Subject = "收款确认";//主题 mail.Body = body;//内容 // mail.Bcc = bcc;//秘密抄送人 mail.From = "v-tangqq02@vanke.com";//发送人 mail.To = to; //收件人 // mail.CC = cc; //抄送人 string sendMainResult = "-1"; if (!string.IsNullOrEmpty(mail.To.Trim()) || !string.IsNullOrEmpty(mail.CC.Trim())) { sendMainResult = SendMailHelper.SendMail(mail); if (string.IsNullOrEmpty(sendMainResult)) { BaseClass.CommFun.Alert(this.up_innerCheck, "发送成功!", Page); } } #endregion } 效果 点击确定发送之后,查看邮箱,即可看到发送内容(可根据业务需求自行调整) 刚好另一个项目中也需要用到发邮件,也是用的上述的帮助类,附效果图 至此,发送邮件功能已经全部完毕,当中不乏可以优化的地方,欢迎大家自行优化,相互交流~ 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
代码很简单,在这里就不过多阐述,先上示例图: 实现过程: html部分代码很简单 1 <div id="outer"> 2 <p>点击图片</p> 3 <img src="image/0.gif" title="点击图片放大缩小" /> 4 <img src="image/项目管理十大知识领域逻辑关系.png" title="点击图片放大缩小" /> 5 </div> js部分 1 function expandPhoto(){ 2 var overlay = document.createElement("div"); //创建div 3 overlay.setAttribute("id","overlay"); //给div添加id 4 overlay.setAttribute("class","overlay"); //给div添加class 5 document.body.appendChild(overlay); //向页面中显示此div 6 7 var img = document.createElement("img"); 8 img.setAttribute("class","overlayimg"); 9 img.src = this.getAttribute("src"); 10 document.getElementById("overlay").appendChild(img); 11 12 img.onclick = restore; 13 } 14 function restore(){ 15 document.body.removeChild(document.getElementById("overlay")); 16 document.body.removeChild(document.getElementById("img")); 17 } 18 window.onload = function(){ 19 var imgs = document.getElementsByTagName("img");//找到所有img 20 imgs[0].focus(); 21 for(var i = 0;i<imgs.length;i++){ 22 imgs[i].onclick = expandPhoto; //绑定点击事件,执行方法 23 imgs[i].onkeydown = expandPhoto; 24 } 25 26 } css部分(主要是针对新增div的样式) 1 img{padding:5px;width:100px;height:auto;cursor: pointer;} 2 #outer{ 3 width:100%; 4 height:100%; 5 } 6 .overlay{ 7 background-color:#000; 8 opacity: .7; 9 filter:alpha(opacity=70); 10 position: fixed; 11 top:0; 12 left:0; 13 width:100%; 14 height:100%; 15 z-index: 10; 16 } 17 .overlayimg{ 18 position: absolute; 19 z-index: 11; 20 left:24%; 21 top:55px; 22 width:auto; 23 cursor: pointer; 24 } 全部代码(修改图片途径即可) 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 5 <title>点击图片显示大图</title> 6 <style> 7 img{padding:5px;width:100px;height:auto;cursor: pointer;} 8 #outer{ 9 width:100%; 10 height:100%; 11 } 12 .overlay{ 13 background-color:#000; 14 opacity: .7; 15 filter:alpha(opacity=70); 16 position: fixed; 17 top:0; 18 left:0; 19 width:100%; 20 height:100%; 21 z-index: 10; 22 } 23 .overlayimg{ 24 position: absolute; 25 z-index: 11; 26 left:24%; 27 top:55px; 28 width:auto; 29 cursor: pointer; 30 } 31 </style> 32 <script> 33 function expandPhoto(){ 34 var overlay = document.createElement("div"); //创建div 35 overlay.setAttribute("id","overlay"); //给div添加id 36 overlay.setAttribute("class","overlay"); //给div添加class 37 document.body.appendChild(overlay); //向页面中显示此div 38 39 var img = document.createElement("img"); 40 img.setAttribute("class","overlayimg"); 41 img.src = this.getAttribute("src"); 42 document.getElementById("overlay").appendChild(img); 43 44 img.onclick = restore; 45 } 46 function restore(){ 47 document.body.removeChild(document.getElementById("overlay")); 48 document.body.removeChild(document.getElementById("img")); 49 } 50 window.onload = function(){ 51 var imgs = document.getElementsByTagName("img");//找到所有img 52 imgs[0].focus(); 53 for(var i = 0;i<imgs.length;i++){ 54 imgs[i].onclick = expandPhoto; //绑定点击事件,执行方法 55 imgs[i].onkeydown = expandPhoto; 56 } 57 58 } 59 </script> 60 </head> 61 <body> 62 <div id="outer"> 63 <p>点击图片</p> 64 <img src="image/0.gif" title="点击图片放大缩小" /> 65 <img src="image/项目管理十大知识领域逻辑关系.png" title="点击图片放大缩小" /> 66 </div> 67 </body> 68 </html> View Code 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
什么是PowerDesigner 引入百度百科的说法是: power designer是能进行数据库设计的强大的软件,是一款开发人员常用的数据库建模工具。使用它可以分别从概念数据模型(Conceptual Data Model)和物理数据模型(Physical Data Model)两个层次对数据库进行设计。在这里,概念数据模型描述的是独立于数据库管理系统(DBMS)的实体定义和实体关系定义;物理数据模型是在概念数据模型的基础上针对目标数据库管理系统的具体化。 在这里有必要强调下什么是数据模型呢? 数据模型是现实世界中数据特征的抽象。数据模型应该满足三个方面的要求:1)能够比较真实地模拟现实世界2)容易为人所理解3)便于计算机实现 恩?什么鬼,还是不懂~继续看 概念数据模型也称信息模型,它以实体-联系(Entity-RelationShip,简称E-R)理论为基础,并对这一理论进行了扩充。它从用户的观点出发对信息进行建模,主要用于数据库的概念级设计。通常人们先将现实世界抽象为概念世界,然后再将概念世界转为机器世界。换句话说,就是先将现实世界中的客观对象抽象为实体(Entity)和联系(Relationship),它并不依赖于具体的计算机系统或某个DBMS系统,这种模型就是我们所说的CDM;然后再将CDM转换为计算机上某个DBMS所支持的数据模型,这样的模型就是物理数据模型,即PDM 可以帮我们做哪些事 ㈠设计类图 ㈡使用PowerDesigner设计数据库关系以后,可以生成HTML,供团队成员进行讨论。 ㈢使用PowerDesigner进行面向对象分析与UML建模 动态模型 动态图包括:状态图(Statechart Diagram)、顺序图(Sequence Diagram)、协作图(Collaboration Diagram)和活动图(Activity Diagram)。 状态图:描述系统元素的状态变化。 顺序图:描述按时间顺序系统元素之间的交互。 协作图:按时空的顺序描述系统元素之间的交互和关系。 活动图:描述系统元素的活动。 功能模型 五种视图包括:用例视图、结构模型(逻辑)视图、行为模型(并发)视图、实现模型(组件)视图和部署视图。 用例视图:从用户角度表达系统功能(使用用例图+活动图)描述。 结构模型(逻辑)视图:主要使用类图和对象图描述系统静态结构,用状态图、顺序图、协作图和活动图描述对象间实现给定功能时的动态协作关系。 行为模型(并发)视图:展示系统动态行为以及其并发性,用状态图、顺序图、协作图、活动图、组件图和部署图描述。 实现模型(组件)视图:展示系统实现的结构和行为描述,用组件图描述。 部署视图:展示系统的实现环境和组件是如何在物理结构中部署的,用部署图描述。 注: 能用Powerdesigner作图就尽量用,尽量不要使用Visio; 虽然很多图之间都可以转换,但要自己判断转换后的图是否有意义; 所有的code都需要用规范的英文名称; 模型间的关系有依赖、泛化、关联、实现四种 PowerDesigner下载 链接: http://pan.baidu.com/s/1cd1pjK 密码: uea4 另:PowerDesigner是收费软件,提倡小伙伴通过正规渠道购买正版版权使用(给个表情你应该知道我在说神马 ) 注:汉化版也是博园一位神奇的博主自主捣腾的,汉化参考博园地址:http://www.cnblogs.com/yeaicc/p/PowerDesigner16CN.html 部分汉化界面下图: 使用教程 ①新建物理数据模型 ②选择新建物理数据对象模型 ③点击ok,进入物理对象模型编辑界面 ④新建两张表(Students,Class)并提供一个外键约束 具体步骤: (1)点击工具,新建两张表 ⑵双击新建好的表,进入table编辑界面,指定表名,字段等属性 (3)同理的方法去操作Class表 。然后 增加外键,Students表的classId指向class表的id字段 物理数据模型导出建表Sql ㈠选择要导出的数据 ㈡ ⑶点击应用,点击确定,根据上面选择的路径,会弹框提示导出完成 PowerDesigner连接SqlServer数据库导出表结构 ① ② ③新建成功之后,点击,配置连接,选择需要连接的数据源. ④ ⑤ ⑥ ⑦ ⑻ ⑨ ⑾ ⑿ ⒀ 未完待续。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
前言 在网上看到一个不错的简易版正则匹配和替换的工具,现在补充进来,感觉还不错,效果如下(输入验证中文汉字的正则表达式) 在线下载 密码:5tpt 注:好像也是一位园友写的,但是找不到地址了,有看到的可以留言告知下,thx 文章导读 正则表达式的本质是使用一系列特殊字符模式,来表示某一类字符串。正则表达式无疑是处理文本最有力的工具,而.NET提供的Regex类实现了验证正则表达式的方法。Regex 类表示不可变(只读)的正则表达式。它还包含各种静态方法,允许在不显式创建其他类的实例的情况下使用其他正则表达式类。 基础梳理 说明: 由于在正则表达式中“ \ ”、“ ? ”、“ * ”、“ ^ ”、“ $ ”、“ + ”、“(”、“)”、“ | ”、“ { ”、“ [ ”等字符已经具有一定特殊意义,如果需要用它们的原始意义,则应该对它进行转义,例如 希 望在字符串中至少有一个“ \ ”,那么正则表达式应该这么写: \\+ 。 RegEx类常用的方法 ①静态Match方法 使用静态Match方法,可以得到源中第一个匹配模式的连续子串。 静态的Match方法有2个重载,分别是 Regex.Match(string input, string pattern); Regex.Match(string input, string pattern, RegexOptions options); 第一种重载的参数表示:输入、模式 第二种重载的参数表示:输入、模式、RegexOptions枚举的“按位或”组合。 RegexOptions枚举的有效值是: Complied表示编译此模式 CultureInvariant表示不考虑文化背景 ECMAScript表示符合ECMAScript,这个值只能和IgnoreCase、Multiline、Complied连用 ExplicitCapture表示只保存显式命名的组 IgnoreCase表示不区分输入的大小写 IgnorePatternWhitespace表示去掉模式中的非转义空白,并启用由#标记的注释 Multiline表示多行模式,改变元字符^和$的含义,它们可以匹配行的开头和结尾 None表示无设置,此枚举项没有意义 RightToLeft表示从右向左扫描、匹配,这时,静态的Match方法返回从右向左的第一个匹配 Singleline表示单行模式,改变元字符.的意义,它可以匹配换行符 注意:Multiline在没有ECMAScript的情况下,可以和Singleline连用。Singleline和Multiline不互斥,但是和ECMAScript互斥。 ②静态的Matches方法 这个方法的重载形式同静态的Match方法,返回一个MatchCollection,表示输入中,匹配模式的匹配的集合。 ③静态的IsMatch方法 此方法返回一个bool,重载形式同静态的Matches,若输入中匹配模式,返回true,否则返回false。 可以理解为:IsMatch方法,返回Matches方法返回的集合是否为空。 RegEx类的实例 ⑴字符串替换 //例如我想把如下格式记录中的NAME值修改为WANG string line = "ADDR=1234;NAME=ZHANG;PHONE=6789"; Regex reg = new Regex("NAME=(.+);"); string modified = reg.Replace(line, "NAME=WANG;"); //修改后的字符串为 ADDR=1234;NAME=WANG;PHONE=6789 ⑵字符串匹配 string line = "ADDR=1234;NAME=ZHANG;PHONE=6789"; Regex reg = new Regex("NAME=(.+);"); //例如我想提取line中的NAME值 Match match = reg.Match(line); string value = match.Groups[1].Value; Console.WriteLine("value的值为:{0}", value); ⑶Match实例 //文本中含有"speed=30.3mph",需要提取该速度值,但是速度的单位可能是公制也可能是英制,mph,km/h,m/s都有可能;另外前后可能有空格。 string line = "lane=1;speed=30.3mph;acceleration=2.5mph/s"; Regex reg = new Regex(@"speed\s*=\s*([\d\.]+)\s*(mph|km/h|m/s)*"); Match match = reg.Match(line); //那么在返回的结果中match.Groups[1].Value将含有数值,而match.Groups[2].Value将含有单位。 var 值 = match.Groups[1].Value;//此处方便演示,在实际开发中请勿使用中文命名变量 var 单位 = match.Groups[2].Value; Console.WriteLine("speed的值为:{0} speed的单位是:{1}", 值,单位); ⑷解码gps的GPRMC字符串 //就可以获得经度、纬度值,而以前需要几十行代码。 Regex reg = new Regex(@"^\$GPRMC,[\d\.]*,[A|V],(-?[0-9]*\.?[0-9]+),([NS]*),(-?[0-9]*\.?[0-9]+),([EW]*),.*"); ⑸提取[]的值 string pattern1 = @"(?is)(?<=\[)(.*)(?=\])"; string result1 = new Regex(pattern1).Match("sadff[xxx]sdfdsf").Value; ⑹提取()的值 string pattern2 = @"(?is)(?<=\()(.*)(?=\))"; string result2 = new Regex(pattern2).Match("sad(f)dsf").Value; ⑺提取()的值 string pattern3 = @"(?is)(?<=\{)(.*)(?=\})"; string result3 = new Regex(pattern3).Match("sadff[{xxx]sdfd}sf").Value; 命名空间说明 System.Text.RegularExpressions命名空间的说明 该名称空间包括8个类,1个枚举,1个委托。他们分别是: Capture: 包含一次匹配的结果; CaptureCollection: Capture的序列; Group: 一次组记录的结果,由Capture继承而来; GroupCollection:表示捕获组的集合 Match: 一次表达式的匹配结果,由Group继承而来; MatchCollection: Match的一个序列; MatchEvaluator: 执行替换操作时使用的委托; RegexCompilationInfo:提供编译器用于将正则表达式编译为独立程序集的信息 RegexOptions 提供用于设置正则表达式的枚举值 Regex类中还包含一些静态的方法: Escape: 对字符串中的regex中的转义符进行转义; IsMatch: 如果表达式在字符串中匹配,该方法返回一个布尔值; Match: 返回Match的实例; Matches: 返回一系列的Match的方法; Replace: 用替换字符串替换匹配的表达式; Split: 返回一系列由表达式决定的字符串; Unescape:不对字符串中的转义字符转义。 正则常用表达式 ㈠校验数字的表达式 //数字 Regex reg = new Regex(@"^[0-9]*$"); //n位的数字 Regex reg = new Regex(@"^\d{n}$"); //至少n位的数字 Regex reg = new Regex(@"^\d{n,}$"); //m-n位的数字 Regex reg = new Regex(@"^\d{m,n}$"); //零和非零开头的数字 Regex reg = new Regex(@"^(0|[1-9][0-9]*)$"); //非零开头的最多带两位小数的数字 Regex reg = new Regex(@"^([1-9][0-9]*)+(.[0-9]{1,2})?$"); //带1-2位小数的正数或负数 Regex reg = new Regex(@"^(\-)?\d+(\.\d{1,2})?$"); //正数、负数、和小数 Regex reg = new Regex(@"^(\-|\+)?\d+(\.\d+)?$"); //有两位小数的正实数 Regex reg = new Regex(@"^[0-9]+(.[0-9]{2})?$"); //有1~3位小数的正实数 Regex reg = new Regex(@"^[0-9]+(.[0-9]{1,3})?$"); //非零的正整数 Regex reg = new Regex(@"^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$"); //非零的负整数 Regex reg = new Regex(@"^\-[1-9][]0-9″*$ 或 ^-[1-9]\d*$"); //非负整数 Regex reg = new Regex(@"^\d+$ 或 ^[1-9]\d*|0$"); //非正整数 Regex reg = new Regex(@"^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$"); //非负浮点数 Regex reg = new Regex(@"^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$"); //非正浮点数 Regex reg = new Regex(@"^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$"); //正浮点数 Regex reg = new Regex(@"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$"); //负浮点数 Regex reg = new Regex(@"^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"); //浮点数 Regex reg = new Regex(@"^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$"); ㈡校验字符的表达式 //汉字 Regex reg = new Regex(@"^[\u4e00-\u9fa5]{0,}$"); //英文和数字 Regex reg = new Regex(@"^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$"); //长度为3-20的所有字符 Regex reg = new Regex(@"^.{3,20}$"); //由26个英文字母组成的字符串 Regex reg = new Regex(@"^[A-Za-z]+$"); //由26个大写英文字母组成的字符串 Regex reg = new Regex(@"^[A-Z]+$"); //由26个小写英文字母组成的字符串 Regex reg = new Regex(@"^[a-z]+$"); //由数字和26个英文字母组成的字符串 Regex reg = new Regex(@"^[A-Za-z0-9]+$"); //由数字、26个英文字母或者下划线组成的字符串 Regex reg = new Regex(@"^\w+$ 或 ^\w{3,20}$"); //中文、英文、数字包括下划线 Regex reg = new Regex(@"^[\u4E00-\u9FA5A-Za-z0-9_]+$"); //中文、英文、数字但不包括下划线等符号 Regex reg = new Regex(@"^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$"); //可以输入含有^%&’,;=?$\”等字符 Regex reg = new Regex(@"[^%&’,;=?$\x22]+"); //禁止输入含有~的字符 Regex reg = new Regex(@"[^~\x22]+"); ㈢特殊需求表达式 //Email地址 Regex reg = new Regex(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); //域名 Regex reg = new Regex(@"[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?"); //InternetURL Regex reg = new Regex(@"[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"); //手机号码 Regex reg = new Regex(@"^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$"); //电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX) Regex reg = new Regex(@"^($$\d{3,4}-)|\d{3.4}-)?\d{7,8}$"); //国内电话号码(0511-4405222、021-87888822) Regex reg = new Regex(@"\d{3}-\d{8}|\d{4}-\d{7}"); //身份证号(15位、18位数字) Regex reg = new Regex(@"^\d{15}|\d{18}$"); //短身份证号码(数字、字母x结尾) Regex reg = new Regex(@"^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$"); //帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) Regex reg = new Regex(@"^[a-zA-Z][a-zA-Z0-9_]{4,15}$"); //密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线) Regex reg = new Regex(@"^[a-zA-Z]\w{5,17}$"); //强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间) Regex reg = new Regex(@"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$"); //日期格式 Regex reg = new Regex(@"^\d{4}-\d{1,2}-\d{1,2}"); //一年的12个月(01~09和1~12) Regex reg = new Regex(@"^(0?[1-9]|1[0-2])$"); //一个月的31天(01~09和1~31) Regex reg = new Regex(@"^((0?[1-9])|((1|2)[0-9])|30|31)$"); //钱的输入格式: //有四种钱的表示形式我们可以接受:”10000.00″ 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000” Regex reg = new Regex(@"^[1-9][0-9]*$"); //这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0″不通过,所以我们采用下面的形式 Regex reg = new Regex(@"^(0|[1-9][0-9]*)$"); //一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号 Regex reg = new Regex(@"^(0|-?[1-9][0-9]*)$"); //这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分 Regex reg = new Regex(@"^[0-9]+(.[0-9]+)?$"); //必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的 Regex reg = new Regex(@"^[0-9]+(.[0-9]{2})?$"); //这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样 Regex reg = new Regex(@"^[0-9]+(.[0-9]{1,2})?$"); //这样就允许用户只写一位小数。下面我们该考虑数字中的逗号了,我们可以这样 Regex reg = new Regex(@"^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$"); //1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须 Regex reg = new Regex(@"^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$"); //备注:这就是最终结果了,别忘了”+”可以用”*”替代。如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里 //xml文件 Regex reg = new Regex(@"^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$"); //中文字符的正则表达式 Regex reg = new Regex(@"[\u4e00-\u9fa5]"); //双字节字符 Regex reg = new Regex(@"[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))"); //空白行的正则表达式,可用来删除空白行 Regex reg = new Regex(@"\n\s*\r"); //HTML标记的正则表达式 Regex reg = new Regex(@"<(\S*?)[^>]*>.*?</\1>|<.*? />");// (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力) //首尾空白字符的正则表达式 Regex reg = new Regex(@"^\s*|\s*$或(^\s*)|(\s*$)");// (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式) //腾讯QQ号 Regex reg = new Regex(@"[1-9][0-9]{4,}"); //(腾讯QQ号从10000开始) //中国邮政编码 Regex reg = new Regex(@"[1-9]\d{5}(?!\d)");// (中国邮政编码为6位数字) //IP地址 Regex reg = new Regex(@"\d+\.\d+\.\d+\.\d+");// (提取IP地址时有用) //IP地址 Regex reg = new Regex(@"((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))"); 使用demo 正则的使用可以分为验证方法和匹配方法两种 因上文对正则已经做了比较详细的讲解,故在此不多做赘述,直接贴出使用demo 1 public class Validator 2 { 3 #region 匹配方法 4 /// <summary> 5 /// 验证字符串是否匹配正则表达式描述的规则 6 /// </summary> 7 /// <param name="inputStr">待验证的字符串</param> 8 /// <param name="patternStr">正则表达式字符串</param> 9 /// <returns>是否匹配</returns> 10 public static bool IsMatch(string inputStr, string patternStr) 11 { 12 return IsMatch(inputStr, patternStr, false, false); 13 } 14 15 /// <summary> 16 /// 验证字符串是否匹配正则表达式描述的规则 17 /// </summary> 18 /// <param name="inputStr">待验证的字符串</param> 19 /// <param name="patternStr">正则表达式字符串</param> 20 /// <param name="ifIgnoreCase">匹配时是否不区分大小写</param> 21 /// <returns>是否匹配</returns> 22 public static bool IsMatch(string inputStr, string patternStr, bool ifIgnoreCase) 23 { 24 return IsMatch(inputStr, patternStr, ifIgnoreCase, false); 25 } 26 27 /// <summary> 28 /// 验证字符串是否匹配正则表达式描述的规则 29 /// </summary> 30 /// <param name="inputStr">待验证的字符串</param> 31 /// <param name="patternStr">正则表达式字符串</param> 32 /// <param name="ifValidateWhiteSpace">是否验证空白字符串</param> 33 /// <returns>是否匹配</returns> 34 public static bool IsMatch(string inputStr, string patternStr, bool ifValidateWhiteSpace) 35 { 36 return IsMatch(inputStr, patternStr, false, ifValidateWhiteSpace); 37 } 38 39 /// <summary> 40 /// 验证字符串是否匹配正则表达式描述的规则 41 /// </summary> 42 /// <param name="inputStr">待验证的字符串</param> 43 /// <param name="patternStr">正则表达式字符串</param> 44 /// <param name="ifIgnoreCase">匹配时是否不区分大小写</param> 45 /// <param name="ifValidateWhiteSpace">是否验证空白字符串</param> 46 /// <returns>是否匹配</returns> 47 public static bool IsMatch(string inputStr, string patternStr, bool ifIgnoreCase, bool ifValidateWhiteSpace) 48 { 49 if (!ifValidateWhiteSpace && string.IsNullOrWhiteSpace(inputStr))//.NET 4.0 新增IsNullOrWhiteSpace 方法,便于对用户做处理 50 return false;//如果不要求验证空白字符串而此时传入的待验证字符串为空白字符串,则不匹配 51 Regex regex = null; 52 if (ifIgnoreCase) 53 regex = new Regex(patternStr, RegexOptions.IgnoreCase);//指定不区分大小写的匹配 54 else 55 regex = new Regex(patternStr); 56 return regex.IsMatch(inputStr); 57 } 58 #endregion 59 60 #region 验证方法 61 /// <summary> 62 /// 验证数字(double类型) 63 /// [可以包含负号和小数点] 64 /// </summary> 65 /// <param name="input">待验证的字符串</param> 66 /// <returns>是否匹配</returns> 67 public static bool IsNumber(string input) 68 { 69 //string pattern = @"^-?\d+$|^(-?\d+)(\.\d+)?$"; 70 //return IsMatch(input, pattern); 71 double d = 0; 72 if (double.TryParse(input, out d)) 73 return true; 74 else 75 return false; 76 } 77 78 /// <summary> 79 /// 验证整数 80 /// </summary> 81 /// <param name="input">待验证的字符串</param> 82 /// <returns>是否匹配</returns> 83 public static bool IsInteger(string input) 84 { 85 //string pattern = @"^-?\d+$"; 86 //return IsMatch(input, pattern); 87 int i = 0; 88 if (int.TryParse(input, out i)) 89 return true; 90 else 91 return false; 92 } 93 94 /// <summary> 95 /// 验证非负整数 96 /// </summary> 97 /// <param name="input">待验证的字符串</param> 98 /// <returns>是否匹配</returns> 99 public static bool IsIntegerNotNagtive(string input) 100 { 101 //string pattern = @"^\d+$"; 102 //return IsMatch(input, pattern); 103 int i = -1; 104 if (int.TryParse(input, out i) && i >= 0) 105 return true; 106 else 107 return false; 108 } 109 110 /// <summary> 111 /// 验证正整数 112 /// </summary> 113 /// <param name="input">待验证的字符串</param> 114 /// <returns>是否匹配</returns> 115 public static bool IsIntegerPositive(string input) 116 { 117 //string pattern = @"^[0-9]*[1-9][0-9]*$"; 118 //return IsMatch(input, pattern); 119 int i = 0; 120 if (int.TryParse(input, out i) && i >= 1) 121 return true; 122 else 123 return false; 124 } 125 126 /// <summary> 127 /// 验证小数 128 /// </summary> 129 /// <param name="input">待验证的字符串</param> 130 /// <returns>是否匹配</returns> 131 public static bool IsDecimal(string input) 132 { 133 string pattern = @"^([-+]?[1-9]\d*\.\d+|-?0\.\d*[1-9]\d*)$"; 134 return IsMatch(input, pattern); 135 } 136 137 /// <summary> 138 /// 验证只包含英文字母 139 /// </summary> 140 /// <param name="input">待验证的字符串</param> 141 /// <returns>是否匹配</returns> 142 public static bool IsEnglishCharacter(string input) 143 { 144 string pattern = @"^[A-Za-z]+$"; 145 return IsMatch(input, pattern); 146 } 147 148 /// <summary> 149 /// 验证只包含数字和英文字母 150 /// </summary> 151 /// <param name="input">待验证的字符串</param> 152 /// <returns>是否匹配</returns> 153 public static bool IsIntegerAndEnglishCharacter(string input) 154 { 155 string pattern = @"^[0-9A-Za-z]+$"; 156 return IsMatch(input, pattern); 157 } 158 159 /// <summary> 160 /// 验证只包含汉字 161 /// </summary> 162 /// <param name="input">待验证的字符串</param> 163 /// <returns>是否匹配</returns> 164 public static bool IsChineseCharacter(string input) 165 { 166 string pattern = @"^[\u4e00-\u9fa5]+$"; 167 return IsMatch(input, pattern); 168 } 169 170 /// <summary> 171 /// 验证数字长度范围(数字前端的0计长度) 172 /// [若要验证固定长度,可传入相同的两个长度数值] 173 /// </summary> 174 /// <param name="input">待验证的字符串</param> 175 /// <param name="lengthBegin">长度范围起始值(含)</param> 176 /// <param name="lengthEnd">长度范围结束值(含)</param> 177 /// <returns>是否匹配</returns> 178 public static bool IsIntegerLength(string input, int lengthBegin, int lengthEnd) 179 { 180 //string pattern = @"^\d{" + lengthBegin + "," + lengthEnd + "}$"; 181 //return IsMatch(input, pattern); 182 if (input.Length >= lengthBegin && input.Length <= lengthEnd) 183 { 184 int i; 185 if (int.TryParse(input, out i)) 186 return true; 187 else 188 return false; 189 } 190 else 191 return false; 192 } 193 194 /// <summary> 195 /// 验证字符串包含内容 196 /// </summary> 197 /// <param name="input">待验证的字符串</param> 198 /// <param name="withEnglishCharacter">是否包含英文字母</param> 199 /// <param name="withNumber">是否包含数字</param> 200 /// <param name="withChineseCharacter">是否包含汉字</param> 201 /// <returns>是否匹配</returns> 202 public static bool IsStringInclude(string input, bool withEnglishCharacter, bool withNumber, bool withChineseCharacter) 203 { 204 if (!withEnglishCharacter && !withNumber && !withChineseCharacter) 205 return false;//如果英文字母、数字和汉字都没有,则返回false 206 StringBuilder patternString = new StringBuilder(); 207 patternString.Append("^["); 208 if (withEnglishCharacter) 209 patternString.Append("a-zA-Z"); 210 if (withNumber) 211 patternString.Append("0-9"); 212 if (withChineseCharacter) 213 patternString.Append(@"\u4E00-\u9FA5"); 214 patternString.Append("]+$"); 215 return IsMatch(input, patternString.ToString()); 216 } 217 218 /// <summary> 219 /// 验证字符串长度范围 220 /// [若要验证固定长度,可传入相同的两个长度数值] 221 /// </summary> 222 /// <param name="input">待验证的字符串</param> 223 /// <param name="lengthBegin">长度范围起始值(含)</param> 224 /// <param name="lengthEnd">长度范围结束值(含)</param> 225 /// <returns>是否匹配</returns> 226 public static bool IsStringLength(string input, int lengthBegin, int lengthEnd) 227 { 228 //string pattern = @"^.{" + lengthBegin + "," + lengthEnd + "}$"; 229 //return IsMatch(input, pattern); 230 if (input.Length >= lengthBegin && input.Length <= lengthEnd) 231 return true; 232 else 233 return false; 234 } 235 236 /// <summary> 237 /// 验证字符串长度范围(字符串内只包含数字和/或英文字母) 238 /// [若要验证固定长度,可传入相同的两个长度数值] 239 /// </summary> 240 /// <param name="input">待验证的字符串</param> 241 /// <param name="lengthBegin">长度范围起始值(含)</param> 242 /// <param name="lengthEnd">长度范围结束值(含)</param> 243 /// <returns>是否匹配</returns> 244 public static bool IsStringLengthOnlyNumberAndEnglishCharacter(string input, int lengthBegin, int lengthEnd) 245 { 246 string pattern = @"^[0-9a-zA-z]{" + lengthBegin + "," + lengthEnd + "}$"; 247 return IsMatch(input, pattern); 248 } 249 250 /// <summary> 251 /// 验证字符串长度范围 252 /// [若要验证固定长度,可传入相同的两个长度数值] 253 /// </summary> 254 /// <param name="input">待验证的字符串</param> 255 /// <param name="withEnglishCharacter">是否包含英文字母</param> 256 /// <param name="withNumber">是否包含数字</param> 257 /// <param name="withChineseCharacter">是否包含汉字</param> 258 /// <param name="lengthBegin">长度范围起始值(含)</param> 259 /// <param name="lengthEnd">长度范围结束值(含)</param> 260 /// <returns>是否匹配</returns> 261 public static bool IsStringLengthByInclude(string input, bool withEnglishCharacter, bool withNumber, bool withChineseCharacter, int lengthBegin, int lengthEnd) 262 { 263 if (!withEnglishCharacter && !withNumber && !withChineseCharacter) 264 return false;//如果英文字母、数字和汉字都没有,则返回false 265 StringBuilder patternString = new StringBuilder(); 266 patternString.Append("^["); 267 if (withEnglishCharacter) 268 patternString.Append("a-zA-Z"); 269 if (withNumber) 270 patternString.Append("0-9"); 271 if (withChineseCharacter) 272 patternString.Append(@"\u4E00-\u9FA5"); 273 patternString.Append("]{" + lengthBegin + "," + lengthEnd + "}$"); 274 return IsMatch(input, patternString.ToString()); 275 } 276 277 /// <summary> 278 /// 验证字符串字节数长度范围 279 /// [若要验证固定长度,可传入相同的两个长度数值;每个汉字为两个字节长度] 280 /// </summary> 281 /// <param name="input">待验证的字符串</param> 282 /// <param name="lengthBegin">长度范围起始值(含)</param> 283 /// <param name="lengthEnd">长度范围结束值(含)</param> 284 /// <returns></returns> 285 public static bool IsStringByteLength(string input, int lengthBegin, int lengthEnd) 286 { 287 //int byteLength = Regex.Replace(input, @"[^\x00-\xff]", "ok").Length; 288 //if (byteLength >= lengthBegin && byteLength <= lengthEnd) 289 //{ 290 // return true; 291 //} 292 //return false; 293 int byteLength = Encoding.Default.GetByteCount(input); 294 if (byteLength >= lengthBegin && byteLength <= lengthEnd) 295 return true; 296 else 297 return false; 298 } 299 300 /// <summary> 301 /// 验证日期 302 /// </summary> 303 /// <param name="input">待验证的字符串</param> 304 /// <returns>是否匹配</returns> 305 public static bool IsDateTime(string input) 306 { 307 DateTime dt; 308 if (DateTime.TryParse(input, out dt)) 309 return true; 310 else 311 return false; 312 } 313 314 /// <summary> 315 /// 验证固定电话号码 316 /// [3位或4位区号;区号可以用小括号括起来;区号可以省略;区号与本地号间可以用减号或空格隔开;可以有3位数的分机号,分机号前要加减号] 317 /// </summary> 318 /// <param name="input">待验证的字符串</param> 319 /// <returns>是否匹配</returns> 320 public static bool IsTelePhoneNumber(string input) 321 { 322 string pattern = @"^(((0\d2|0\d{2})[- ]?)?\d{8}|((0\d3|0\d{3})[- ]?)?\d{7})(-\d{3})?$"; 323 return IsMatch(input, pattern); 324 } 325 326 /// <summary> 327 /// 验证手机号码 328 /// [可匹配"(+86)013325656352",括号可以省略,+号可以省略,(+86)可以省略,11位手机号前的0可以省略;11位手机号第二位数可以是3、4、5、8中的任意一个] 329 /// </summary> 330 /// <param name="input">待验证的字符串</param> 331 /// <returns>是否匹配</returns> 332 public static bool IsMobilePhoneNumber(string input) 333 { 334 string pattern = @"^((\+)?86|((\+)?86)?)0?1[3458]\d{9}$"; 335 return IsMatch(input, pattern); 336 } 337 338 /// <summary> 339 /// 验证电话号码(可以是固定电话号码或手机号码) 340 /// [固定电话:[3位或4位区号;区号可以用小括号括起来;区号可以省略;区号与本地号间可以用减号或空格隔开;可以有3位数的分机号,分机号前要加减号]] 341 /// [手机号码:[可匹配"(+86)013325656352",括号可以省略,+号可以省略,(+86)可以省略,手机号前的0可以省略;手机号第二位数可以是3、4、5、8中的任意一个]] 342 /// </summary> 343 /// <param name="input">待验证的字符串</param> 344 /// <returns>是否匹配</returns> 345 public static bool IsPhoneNumber(string input) 346 { 347 string pattern = @"^((\+)?86|((\+)?86)?)0?1[3458]\d{9}$|^(((0\d2|0\d{2})[- ]?)?\d{8}|((0\d3|0\d{3})[- ]?)?\d{7})(-\d{3})?$"; 348 return IsMatch(input, pattern); 349 } 350 351 /// <summary> 352 /// 验证邮政编码 353 /// </summary> 354 /// <param name="input">待验证的字符串</param> 355 /// <returns>是否匹配</returns> 356 public static bool IsZipCode(string input) 357 { 358 //string pattern = @"^\d{6}$"; 359 //return IsMatch(input, pattern); 360 if (input.Length != 6) 361 return false; 362 int i; 363 if (int.TryParse(input, out i)) 364 return true; 365 else 366 return false; 367 } 368 369 /// <summary> 370 /// 验证电子邮箱 371 /// [@字符前可以包含字母、数字、下划线和点号;@字符后可以包含字母、数字、下划线和点号;@字符后至少包含一个点号且点号不能是最后一个字符;最后一个点号后只能是字母或数字] 372 /// </summary> 373 /// <param name="input">待验证的字符串</param> 374 /// <returns>是否匹配</returns> 375 public static bool IsEmail(string input) 376 { 377 ////邮箱名以数字或字母开头;邮箱名可由字母、数字、点号、减号、下划线组成;邮箱名(@前的字符)长度为3~18个字符;邮箱名不能以点号、减号或下划线结尾;不能出现连续两个或两个以上的点号、减号。 378 //string pattern = @"^[a-zA-Z0-9]((?<!(\.\.|--))[a-zA-Z0-9\._-]){1,16}[a-zA-Z0-9]@([0-9a-zA-Z][0-9a-zA-Z-]{0,62}\.)+([0-9a-zA-Z][0-9a-zA-Z-]{0,62})\.?|((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$"; 379 string pattern = @"^([\w-\.]+)@([\w-\.]+)(\.[a-zA-Z0-9]+)$"; 380 return IsMatch(input, pattern); 381 } 382 383 /// <summary> 384 /// 验证网址(可以匹配IPv4地址但没对IPv4地址进行格式验证;IPv6暂时没做匹配) 385 /// [允许省略"://";可以添加端口号;允许层级;允许传参;域名中至少一个点号且此点号前要有内容] 386 /// </summary> 387 /// <param name="input">待验证的字符串</param> 388 /// <returns>是否匹配</returns> 389 public static bool IsURL(string input) 390 { 391 ////每级域名由字母、数字和减号构成(第一个字母不能是减号),不区分大小写,单个域长度不超过63,完整的域名全长不超过256个字符。在DNS系统中,全名是以一个点“.”来结束的,例如“www.nit.edu.cn.”。没有最后的那个点则表示一个相对地址。 392 ////没有例如"http://"的前缀,没有传参的匹配 393 //string pattern = @"^([0-9a-zA-Z][0-9a-zA-Z-]{0,62}\.)+([0-9a-zA-Z][0-9a-zA-Z-]{0,62})\.?$"; 394 395 //string pattern = @"^(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?$"; 396 string pattern = @"^([a-zA-Z]+://)?([\w-\.]+)(\.[a-zA-Z0-9]+)(:\d{0,5})?/?([\w-/]*)\.?([a-zA-Z]*)\??(([\w-]*=[\w%]*&?)*)$"; 397 return IsMatch(input, pattern); 398 } 399 400 /// <summary> 401 /// 验证IPv4地址 402 /// [第一位和最后一位数字不能是0或255;允许用0补位] 403 /// </summary> 404 /// <param name="input">待验证的字符串</param> 405 /// <returns>是否匹配</returns> 406 public static bool IsIPv4(string input) 407 { 408 //string pattern = @"^(25[0-4]|2[0-4]\d]|[01]?\d{2}|[1-9])\.(25[0-5]|2[0-4]\d]|[01]?\d?\d)\.(25[0-5]|2[0-4]\d]|[01]?\d?\d)\.(25[0-4]|2[0-4]\d]|[01]?\d{2}|[1-9])$"; 409 //return IsMatch(input, pattern); 410 string[] IPs = input.Split('.'); 411 if (IPs.Length != 4) 412 return false; 413 int n = -1; 414 for (int i = 0; i < IPs.Length; i++) 415 { 416 if (i == 0 || i == 3) 417 { 418 if (int.TryParse(IPs[i], out n) && n > 0 && n < 255) 419 continue; 420 else 421 return false; 422 } 423 else 424 { 425 if (int.TryParse(IPs[i], out n) && n >= 0 && n <= 255) 426 continue; 427 else 428 return false; 429 } 430 } 431 return true; 432 } 433 434 /// <summary> 435 /// 验证IPv6地址 436 /// [可用于匹配任何一个合法的IPv6地址] 437 /// </summary> 438 /// <param name="input">待验证的字符串</param> 439 /// <returns>是否匹配</returns> 440 public static bool IsIPv6(string input) 441 { 442 string pattern = @"^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$"; 443 return IsMatch(input, pattern); 444 } 445 446 /// <summary> 447 /// 身份证上数字对应的地址 448 /// </summary> 449 //enum IDAddress 450 //{ 451 // 北京 = 11, 天津 = 12, 河北 = 13, 山西 = 14, 内蒙古 = 15, 辽宁 = 21, 吉林 = 22, 黑龙江 = 23, 上海 = 31, 江苏 = 32, 浙江 = 33, 452 // 安徽 = 34, 福建 = 35, 江西 = 36, 山东 = 37, 河南 = 41, 湖北 = 42, 湖南 = 43, 广东 = 44, 广西 = 45, 海南 = 46, 重庆 = 50, 四川 = 51, 453 // 贵州 = 52, 云南 = 53, 西藏 = 54, 陕西 = 61, 甘肃 = 62, 青海 = 63, 宁夏 = 64, 新疆 = 65, 台湾 = 71, 香港 = 81, 澳门 = 82, 国外 = 91 454 //} 455 456 /// <summary> 457 /// 验证一代身份证号(15位数) 458 /// [长度为15位的数字;匹配对应省份地址;生日能正确匹配] 459 /// </summary> 460 /// <param name="input">待验证的字符串</param> 461 /// <returns>是否匹配</returns> 462 public static bool IsIDCard15(string input) 463 { 464 //验证是否可以转换为15位整数 465 long l = 0; 466 if (!long.TryParse(input, out l) || l.ToString().Length != 15) 467 { 468 return false; 469 } 470 //验证省份是否匹配 471 //1~6位为地区代码,其中1、2位数为各省级政府的代码,3、4位数为地、市级政府的代码,5、6位数为县、区级政府代码。 472 string address = "11,12,13,14,15,21,22,23,31,32,33,34,35,36,37,41,42,43,44,45,46,50,51,52,53,54,61,62,63,64,65,71,81,82,91,"; 473 if (!address.Contains(input.Remove(2) + ",")) 474 { 475 return false; 476 } 477 //验证生日是否匹配 478 string birthdate = input.Substring(6, 6).Insert(4, "/").Insert(2, "/"); 479 DateTime dt; 480 if (!DateTime.TryParse(birthdate, out dt)) 481 { 482 return false; 483 } 484 return true; 485 } 486 487 /// <summary> 488 /// 验证二代身份证号(18位数,GB11643-1999标准) 489 /// [长度为18位;前17位为数字,最后一位(校验码)可以为大小写x;匹配对应省份地址;生日能正确匹配;校验码能正确匹配] 490 /// </summary> 491 /// <param name="input">待验证的字符串</param> 492 /// <returns>是否匹配</returns> 493 public static bool IsIDCard18(string input) 494 { 495 //验证是否可以转换为正确的整数 496 long l = 0; 497 if (!long.TryParse(input.Remove(17), out l) || l.ToString().Length != 17 || !long.TryParse(input.Replace('x', '0').Replace('X', '0'), out l)) 498 { 499 return false; 500 } 501 //验证省份是否匹配 502 //1~6位为地区代码,其中1、2位数为各省级政府的代码,3、4位数为地、市级政府的代码,5、6位数为县、区级政府代码。 503 string address = "11,12,13,14,15,21,22,23,31,32,33,34,35,36,37,41,42,43,44,45,46,50,51,52,53,54,61,62,63,64,65,71,81,82,91,"; 504 if (!address.Contains(input.Remove(2) + ",")) 505 { 506 return false; 507 } 508 //验证生日是否匹配 509 string birthdate = input.Substring(6, 8).Insert(6, "/").Insert(4, "/"); 510 DateTime dt; 511 if (!DateTime.TryParse(birthdate, out dt)) 512 { 513 return false; 514 } 515 //校验码验证 516 //校验码: 517 //(1)十七位数字本体码加权求和公式 518 //S = Sum(Ai * Wi), i = 0, ... , 16 ,先对前17位数字的权求和 519 //Ai:表示第i位置上的身份证号码数字值 520 //Wi:表示第i位置上的加权因子 521 //Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 522 //(2)计算模 523 //Y = mod(S, 11) 524 //(3)通过模得到对应的校验码 525 //Y: 0 1 2 3 4 5 6 7 8 9 10 526 //校验码: 1 0 X 9 8 7 6 5 4 3 2 527 string[] arrVarifyCode = ("1,0,x,9,8,7,6,5,4,3,2").Split(','); 528 string[] Wi = ("7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2").Split(','); 529 char[] Ai = input.Remove(17).ToCharArray(); 530 int sum = 0; 531 for (int i = 0; i < 17; i++) 532 { 533 sum += int.Parse(Wi[i]) * int.Parse(Ai[i].ToString()); 534 } 535 int y = -1; 536 Math.DivRem(sum, 11, out y); 537 if (arrVarifyCode[y] != input.Substring(17, 1).ToLower()) 538 { 539 return false; 540 } 541 return true; 542 } 543 544 /// <summary> 545 /// 验证身份证号(不区分一二代身份证号) 546 /// </summary> 547 /// <param name="input">待验证的字符串</param> 548 /// <returns>是否匹配</returns> 549 public static bool IsIDCard(string input) 550 { 551 if (input.Length == 18) 552 return IsIDCard18(input); 553 else if (input.Length == 15) 554 return IsIDCard15(input); 555 else 556 return false; 557 } 558 559 /// <summary> 560 /// 验证经度 561 /// </summary> 562 /// <param name="input">待验证的字符串</param> 563 /// <returns>是否匹配</returns> 564 public static bool IsLongitude(string input) 565 { 566 ////范围为-180~180,小数位数必须是1到5位 567 //string pattern = @"^[-\+]?((1[0-7]\d{1}|0?\d{1,2})\.\d{1,5}|180\.0{1,5})$"; 568 //return IsMatch(input, pattern); 569 float lon; 570 if (float.TryParse(input, out lon) && lon >= -180 && lon <= 180) 571 return true; 572 else 573 return false; 574 } 575 576 /// <summary> 577 /// 验证纬度 578 /// </summary> 579 /// <param name="input">待验证的字符串</param> 580 /// <returns>是否匹配</returns> 581 public static bool IsLatitude(string input) 582 { 583 ////范围为-90~90,小数位数必须是1到5位 584 //string pattern = @"^[-\+]?([0-8]?\d{1}\.\d{1,5}|90\.0{1,5})$"; 585 //return IsMatch(input, pattern); 586 float lat; 587 if (float.TryParse(input, out lat) && lat >= -90 && lat <= 90) 588 return true; 589 else 590 return false; 591 } 592 #endregion 593 } View Code 注:感谢 Sam Xiao 对RegEx类的实例用法作出补充 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
第一种效果 (带自动匹配)这个效果再之前的的博客里面已经讲到过了,还没有看过的小伙伴可以移步→ http://www.cnblogs.com/zhangxiaoyong/p/5763432.html 第二种效果 今天主要讲第二种效果,也比较简单,先看下效果 实现 页面部分 1 <form runat="server"> 2 <asp:DropDownList ID="DropDownList" runat="server" Width="180px" AutoPostBack="false" 3 Style="position: absolute;" onchange="SearchChange();"> 4 </asp:DropDownList> 5 6 <iframe id="DivShims" src="javascript:false;" scrolling="no" frameborder="0" style="position: absolute; 7 height: 20px;" width="158px"></iframe> 8 <input type="text" id="txtCName" runat="server" style="width: 158px; position: absolute;" /> 9 </form> js部分 1 <script type="text/javascript"> 2 function SearchChange() { 3 var ddl = document.getElementById("DropDownList"); 4 var index = ddl.selectedIndex; 5 var Value = ddl.options[index].value; //获取选中下拉列表的值 6 $("#txtCName").val(Value);//将选中的值赋值给input 7 } 8 </script> 后台测试代码 1 List<string> list = new List<string>() 2 { 3 "湖北武汉", "湖北咸宁", "湖北孝感", "湖北安陆", "湖北恩施" 4 }; 5 foreach (var item in list) 6 { 7 DropDownList.Items.Add(item); 8 } Demo传送带→ https://github.com/XiaoYong666/show_TextBox 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
现在网站上越来越多的验证码,使用场景也是越来越多,登陆、注册、上传、下载。。。等等地方,都有可能大量使用到验证码,那么制作验证码到底有多简单呢?我们一起来看下最简易版的验证码实现过程~ 验证码的基本步骤 添加一个控制器 1.在MVC框架中,则需添加一个控制器,代码如下 1 /// <summary> 2 /// 验证码 3 /// </summary> 4 public ContentResult CheckCode() 5 { 6 ContentResult cr = new ContentResult(); 7 cr.ContentType = "image/JPEG";//定义图片类型 8 Random r = new Random(); 9 string code = r.Next(1000, 9999).ToString();//取随机数 10 Session["check"] = code; 11 Bitmap map = new Bitmap(60, 30);//定义大小 12 Graphics g = Graphics.FromImage(map);//画图 13 g.FillRectangle(Brushes.White, 1, 1, 58, 28);//定义矩形 14 g.DrawString(code, new Font("微软雅黑", 16), Brushes.Black, new PointF(1, 1));//向矩形中绘入文字以及定义字体和大小 15 for (int i = 0; i < 300; i++) 16 { 17 map.SetPixel(r.Next(1, 58), r.Next(1, 28), Color.Gray); 18 } 19 map.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);//保存到流中 20 return cr; 21 } 页面 前端页面代码也简单,这里我做示范直接在Home里面写 1 <p>MVC验证码示例</p> 2 <div> 3 <p><img src="Home/CheckCode" alt="看不清,切换图片" onclick="Change(this)" style="cursor:pointer;" /></p> 4 </div> JS控制 1 <script type="text/javascript"> 2 function Change(node) { 3 node.src = "/Home/CheckCode?id=" + new Date(); 4 } 5 </script> 效果 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
HTTP协议简介 既然是基于HTTP协议开发,那么就首先要了解下HTTP协议的相关内容~ 在TCP/IP体系结构中,HTTP属于应用层协议,位于TCP/IP协议的顶层。浏览Web时,浏览器通过HTTP协议与Web服务器交换信息。这些信息(文档)类型的格式由MIME定义。 HTTP协议具有以下的特点: HTTP按客户/服务器模式工作HTTP支持客户(一般情况是浏览器)与服务器的通讯,相互传输数据。HTTP定义的事务处理由以下四步组成: 客户与服务器建立连接; 客户向服务器提出请求; 如果请求被接受,则服务器送回响应,在响应中包括状态码和所需的文件; 客户与服务器断开连接 一次HTTP操作称为一次事务(transaction)。 HTTP是无状态的也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。 HTTP使用元信息作为头标HTTP对所有事务都加了头标(header)。也就是说,在主要数据前加上一块信息,称为元信息(metainformation)。它使服务器能够提供正在传送数据的有关信息。例如,传送对象是哪种类型,是用哪种语言书写的等。从功能上讲,HTTP支持四类元信息:一般信息头标、请求头标、响应头标和实体头标。 HTTP支持两种请求和响应格式HTTP由不同的两部分组成,一是从浏览器发往服务器的请求,二是服务器对客户的响应。HTTP支持两种请求和响应,即简单请求与完全请求和简单响应与完全响应。 HTTP是基于文本的简单协议 HTTP请求的格式如下所示: 1 <request-line> 2 <headers> 3 <blank line> 4 [<request-body>] 在HTTP请求中,第一行必须是一个请求行(request line),用来说明请求类型、要访问的资源以及使用的HTTP版本。紧接着是一个首部(header)小节,用来说明服务器要使用的附加信息。在首部之后是一个空行,再此之后可以添加任意的其他数据[称之为主体(body)]。在HTTP中,定义了大量的请求类型,不过Ajax开发人员关心的只有GET请求和POST请求。只要在Web浏览器上输入一个URL,浏览器就将基于该URL向服务器发送一个GET请求,以告诉服务器获取并返回什么资源。对于URL为XXX的GET请求如下所示: 1 GET / HTTP/1.1 2 Host: XXX 3 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 4 Gecko/20050225 Firefox/1.0.1 5 Connection: Keep-Alive 请求行的第一部分说明了该请求是GET请求。该行的第二部分是一个斜杠(/),用来说明请求的是该域名的根目录。该行的最后一部分说明使用的是HTTP 1.1版本(另一个可选项是1.0)。那么请求发到哪里去呢?这就是第二行的内容。第2行是请求的第一个首部,HOST。首部HOST将指出请求的目的地。结合HOST和上一行中的斜杠(/),可以通知服务器请求的是XXX(HTTP 1.1才需要使用首部HOST,而原来的1.0版本则不需要使用)。第三行中包含的是首部User-Agent,服务器端和客户端脚本都能够访问它,它是浏览器类型检测逻辑的重要基础。该信息由你使用的浏览器来定义(在本例中是Firefox 1.0.1),并且在每个请求中将自动发送。最后一行是首部Connection,通常将浏览器操作设置为Keep-Alive)。注意,在最后一个首部之后有一个空行。即使不存在请求主体,这个空行也是必需的。 如果要获取一个诸如XXX/xxx的XXX域内的页面,那么该请求可能类似于: 1 GET /xxx/ HTTP/1.1 2 Host: XXX 3 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 4 Gecko/20050225 Firefox/1.0.1 5 Connection: Keep-Alive 注意只有第一行的内容发生了变化,它只包含URL中XXX后面的部分。 要发送GET请求的参数,则必须将这些额外的信息附在URL本身的后面。其格式类似于: URL ? name1=value1&name2=value2&..&nameN=valueN 该信息称之为查询字符串(query string),它将会复制在HTTP请求的请求行中,如下所示 1 GET /xxx/?name=Professional%20Ajax HTTP/1.1 2 Host: XXX 3 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 4 Gecko/20050225 Firefox/1.0.1 5 Connection: Keep-Alive 另一方面,POST请求在请求主体中为服务器提供了一些附加的信息。通常,当填写一个在线表单并提交它时,这些填入的数据将以POST请求的方式发送给服务器。 以下就是一个典型的POST请求: 1 POST / HTTP/1.1 2 Host: XXX 3 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) 4 Gecko/20050225 Firefox/1.0.1 5 Content-Type: application/x-www-form-urlencoded 6 Content-Length: 40 7 Connection: Keep-Alive 8 name=Professional%20Ajax&publisher=Wiley 从上面可以发现, POST请求和GET请求之间有一些区别。首先,请求行开始处的GET改为了POST,以表示不同的请求类型。你会发现首部Host和User-Agent仍然存在,在后面有两个新行。其中首部Content-Type说明了请求主体的内容是如何编码的。浏览器始终以application/ x-www-form- urlencoded的格式编码来传送数据,这是针对简单URL编码的MIME类型。首部Content-Length说明了请求主体的字节数。在首部Connection后是一个空行,再后面就是请求主体。与大多数浏览器的POST请求一样,这是以简单的“名称—值”对的形式给出的,其中name是Professional Ajax,publisher是Wiley。你可以以同样的格式来组织URL的查询字符串参数。 正如前面所提到的,还有其他的HTTP请求类型,它们遵从的基本格式与GET请求和POST请求相同。下一步我们来看看服务器将对HTTP请求发送什么响应。 HTTP响应 如下所示,HTTP响应的格式与请求的格式十分类似: 1 <status-line> 2 <headers> 3 <blank line> 4 [<response-body>] 正如你所见,在响应中唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行(status line)通过提供一个状态码来说明所请求的资源情况。以下就是一个HTTP响应的例子: 1 HTTP/1.1 200 OK 2 Date: Sat, 31 Dec 2005 23:59:59 GMT 3 Content-Type: text/html;charset=ISO-8859-1 4 Content-Length: 122 5 6 <html> 7 <head> 8 <title>Wrox Homepage</title> 9 </head> 10 <body> 11 <!-- body goes here --> 12 </body> 13 </html> 在本例中,状态行给出的HTTP状态代码是200,以及消息OK。状态行始终包含的是状态码和相应的简短消息,以避免混乱。最常用的状态码有: 200 (OK): 找到了该资源,并且一切正常。 304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。 401 (UNAUTHORIZED):客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。 403 (FORBIDDEN):客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。 404 (NOT FOUND):在指定的位置不存在所申请的资源。 在状态行之后是一些首部。通常,服务器会返回一个名为Date的首部,用来说明响应生成的日期和时间(服务器通常还会返回一些关于其自身的信息,尽管并非是必需的)。接下来的两个首部大家应该熟悉,就是与POST请求中一样的Content-Type和Content-Length。在本例中,首部Content-Type指定了MIME类型HTML(text/html),其编码类型是ISO-8859-1(这是针对美国英语资源的编码标准)。响应主体所包含的就是所请求资源的HTML源文件(尽管还可能包含纯文本或其他资源类型的二进制数据)。浏览器将把这些数据显示给用户。注意,这里并没有指明针对该响应的请求类型,不过这对于服务器并不重要。客户端知道每种类型的请求将返回什么类型的数据,并决定如何使用这些数据 与HTTP相关类的简介 WebRequest类 WebRequest 是 .NET Framework 的用于访问 Internet 数据的请求/响应模型的抽象基类。使用该请求/响应模型的应用程序可以用协议不可知的方式从 Internet 请求数据。在这种方式下,应用程序处理 WebRequest 类的实例,而协议特定的子类则执行请求的具体细节。 请求从应用程序发送到某个特定的 URI,如服务器上的 Web 页。URI 从一个为应用程序注册的WebRequest 子代列表中确定要创建的适当子类。注册 WebRequest 子代通常是为了处理某个特定的协议(如 HTTP 或 FTP),但是也可以注册它以处理对特定服务器或服务器上的路径的请求。 由于 WebRequest 类是一个抽象类,所以 WebRequest 实例在运行时的实际行为由 WebRequest.Create 方法所返回的子类确定。 注意 使用 Create 方法初始化新的 WebRequest 实例。不要使用 WebRequest 构造函数。 下面的示例说明如何创建 WebRequest 实例并返回响应。 1 // Initialize the WebRequest. 2 WebRequest myRequest = WebRequest.Create("xxx"); 3 // Return the response. 4 WebResponse myResponse = myRequest.GetResponse(); 5 // Code to use the WebResponse goes here. 6 // Close the response to free resources. 7 myResponse.Close(); WebResponse 类 WebResponse 类是抽象(在 Visual Basic 中为 MustInherit)基类,协议特定的响应类从该抽象基类派生。应用程序可以使用 WebResponse 类的实例以协议不可知的方式参与请求和响应事务,而从 WebResponse 派生的协议特定的类携带请求的详细信息。 客户端应用程序不直接创建 WebResponse 对象,而是通过调用 WebRequest 实例上的GetResponse 方法来创建它。 下面的示例从 WebRequest 创建 WebResponse 实例。 1 // Initialize the WebRequest. 2 WebRequest myRequest = WebRequest.Create("xxx"); 3 // Return the response. 4 WebResponse myResponse = myRequest.GetResponse(); 5 // Code to use the WebResponse goes here. 6 // Close the response to free resources. 7 myResponse.Close(); HttpWebRequest 类 HttpWebRequest 类对 WebRequest 中定义的属性和方法提供支持,也对使用户能够直接与使用 HTTP 的服务器交互的附加属性和方法提供支持。 不要使用 HttpWebRequest 构造函数。使用 WebRequest.Create 方法初始化HttpWebRequest 的一个新实例。如果 URI 的方案是 http:// 或 https://,则 Create 将返回 HttpWebRequest 实例。 GetResponse 方法向 RequestUri 属性中指定的 Internet 资源发出同步请求并返回包含该响应的HttpWebResponse 实例。可以使用 BeginGetResponse 和 EndGetResponse 方法对 Internet 资源发出异步请求。 当要向 Internet 资源发送数据时,GetRequestStream 方法返回用于发送数据的Stream实例。BeginGetRequestStream 和 EndGetRequestStream 方法提供对发送数据流的异步访问。 下面的示例为 URI xxx 创建 HttpWebRequest 1 HttpWebRequest myReq =(HttpWebRequest)WebRequest.Create("xxx"); 通过调用 GetResponseStream 方法,以 Stream 的形式返回来自 Internet 资源的响应的内容。 下面的示例返回 HttpWebRequest 的 HttpWebResponse: 1 HttpWebRequest HttpWReq = (HttpWebRequest)WebRequest.Create("xxx"); 2 HttpWebResponse HttpWResp = (HttpWebResponse)HttpWReq.GetResponse(); 3 // Insert code that uses the response object. 4 HttpWResp.Close() 1 Uri siteUri = new Uri("xxx"); 2 WebRequest wr = WebRequest.Create(siteUri); Demo篇 创建一个test的aspx页面 创建获取请求方法 1 public string GetPost() 2 { 3 if ("POST" == Request.RequestType) 4 { 5 System.IO.Stream sm = Request.InputStream;//获取post正文 6 int len = (int)sm.Length;//post数据长度 7 byte[] inputByts = new byte[len];//字节数据,用于存储post数据 8 sm.Read(inputByts, 0, len);//将post数据写入byte数组中 9 sm.Close();//关闭IO流 10 //**********下面是把字节数组类型转换成字符串********** 11 string data = Encoding.GetEncoding("UTF-8").GetString(inputByts);//转为unicode编码 12 //data = Server.UrlDecode(data);//url参数用到. 13 return data; 14 } 15 else 16 return null; 17 } 在Load中使用Response 1 string res = GetPost(); 2 Response.Write(res); 调用篇 新建一个解决方案调用上述程序 Main方法中代码如下所示: 1 string ss = "hello world!"; 2 Encoding encoding = Encoding.GetEncoding("utf-8"); 3 byte[] bytesToPost = encoding.GetBytes(ss); 4 string res = Post("http://localhost:23094/test.aspx", bytesToPost); 5 Console.WriteLine(res); 6 Console.ReadLine(); 其中Post方法传入url和bytesToPost 1 private static string Post(string url, byte[] bytesToPost) 2 { 3 if (String.IsNullOrEmpty(url)) 4 return "url参数为空值"; 5 if (bytesToPost == null) 6 return "post数据为空值"; 7 string ResponseString = ""; 8 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 9 System.Net.ServicePointManager.DefaultConnectionLimit = 50; 10 request.KeepAlive = false; 11 request.Method = "POST"; 12 request.ContentType = "text/xml";//提交xml 13 request.ContentLength = bytesToPost.Length; 14 Stream writer = request.GetRequestStream(); 15 writer.Write(bytesToPost, 0, bytesToPost.Length); 16 HttpWebResponse HttpWebRespon = (HttpWebResponse)request.GetResponse(); 17 StreamReader myStreamReader = new StreamReader(HttpWebRespon.GetResponseStream(), Encoding.UTF8); 18 ResponseString = myStreamReader.ReadToEnd(); 19 myStreamReader.Close(); 20 21 writer.Flush(); 22 if (writer != null) 23 { 24 writer.Close(); 25 } 26 if (request != null) 27 { 28 request.Abort(); 29 } 30 return ResponseString; 31 } View Code 效果,先运行Demo 程序再运行调用程序即可看到如下效果 请求成功,返回结果 PS:具体代码执行过程大家可以下完Demo后自行debug看看,代码灰常简单,算是最简单的http请求和响应以及调用的流程了,在复杂的过程也是基于这个流程来执行的 Demo 地址 https://github.com/XiaoYong666/Http-interface-call 注:介绍部分内容转载摘抄自网络,若有文章雷同,请评论留言~定会及时注明出处 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 因工作内容需要做一个windows服务,此前并没有相关经验,所以做了一个demo来跑跑这个梗(高手跳过,需要的来踩)~ 效果如下:打开服务,可以找到我们新增的一个windows服务,这个demo是定时向一个txt文件输出一句话 生成的以日期命名的txt文件 打开文件结果如下: 全过程梳理 本文将只粗略简单的介绍一下windows服务是如何开发和安装的 一、创建windows服务如图新建一个Windows服务 进入程序如图 空白服务如下 1 public partial class Service1 : ServiceBase 2 { 3 System.Threading.Timer recordTimer; 4 5 6 public Service1() 7 { 8 InitializeComponent(); 9 } 10 11 12 protected override void OnStart(string[] args) 13 { 14 } 15 16 17 protected override void OnStop() 18 { 19 } 20 } 只要在OnStart里完成你的功能代码即可。本例中我们做一个定时向本地文件写记录的功能。 创建一个类,用户写文件 1 /// <summary> 2 /// 保存至本地文件 3 /// </summary> 4 /// <param name="ETMID"></param> 5 /// <param name="content"></param> 6 public static void SaveRecord(string content) 7 { 8 if (string.IsNullOrEmpty(content)) 9 { 10 return; 11 } 12 FileStream fileStream = null; 13 StreamWriter streamWriter = null; 14 try 15 { 16 string path = Path.Combine(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase, string.Format("{0:yyyyMMdd}", DateTime.Now)); 17 18 19 using (fileStream = new FileStream(path, FileMode.Append, FileAccess.Write)) 20 { 21 using (streamWriter = new StreamWriter(fileStream)) 22 { 23 streamWriter.Write(content); 24 25 26 if (streamWriter != null) 27 { 28 streamWriter.Close(); 29 } 30 } 31 32 if (fileStream != null) 33 { 34 fileStream.Close(); 35 } 36 } 37 } 38 catch { } 39 } 如图那么在Service1中调用 1 public partial class Service1 : ServiceBase 2 { 3 System.Threading.Timer recordTimer; 4 public Service1() 5 { 6 InitializeComponent(); 7 } 8 9 protected override void OnStart(string[] args) 10 { 11 IntialSaveRecord(); 12 } 13 /// <summary> 14 /// 定时检查,并执行方法 15 /// </summary> 16 /// <param name="source"></param> 17 /// <param name="e"></param> 18 private void IntialSaveRecord() 19 { 20 TimerCallback timerCallback = new TimerCallback(CallbackTask); 21 22 AutoResetEvent autoEvent = new AutoResetEvent(false); 23 recordTimer = new System.Threading.Timer(timerCallback, autoEvent, 0, 10000);//其中参数10000表示延时执行服务的时间间隔,毫秒为单位 24 } 25 //方法 26 private void CallbackTask(Object stateInfo) 27 { 28 Show_Java.SaveRecord(string.Format(@"当前记录时间:{0},状况:程序运行正常!", DateTime.Now)); 29 } 30 31 protected override void OnStop() 32 { 33 if (recordTimer != null) 34 { 35 recordTimer.Dispose(); 36 } 37 } 38 } 安装程序 这样服务算是写的差不多了,下面添加一个安装程序,用于安装服务。 如图,在service1页面空白处右键-添加安装程序 添加一个安装程序,如图,添加完成后 设置相应的属性,给serviceInstaller1设置属性,主要是描述信息。如图, 给serviceProcessInstaller1设置,主要是account。一般选localsystem,如图 这样服务已经写好了。那么如何添加到windows服务里面去呢。这里推荐一种简单实用的方法(也可以通过代码来安装,这里就不做过多讲解了) 安装服务 上面写好的服务,最终生成的是一个exe文件。如图, 安装程序安装时需要用到这个exe的路径,所以方便起见,将这个生成的exe文件拷贝至安装程序的运行目录下。(这里我将exe拷贝到D盘shows文件夹下面) 用管理员权限打开cmd窗口 然后分别执行 @SET FrameworkDir=%WINDIR%\Microsoft.NET\Framework @SET FrameworkVersion=v2.0.50727 @SET PATH=%FrameworkDir%\%FrameworkVersion%;%WINDIR%\System32;%PATH%; InstallUtil.exe D:\路径\程序名称.exe //安装服务 InstallUtil.exe /u D:\路径\程序名称.exe //卸载服务(程序安装好了,如果想要修改,需要先卸载该服务,再次执行安装) 运行后若无错误,效果应该如下 运行完后在服务中查看,如图: 再在安装目录下看记录的文件(因为我们设置项目的时候选的是手动,此时要记住启动该服务,程序才会定时执行) 这样,一个windows服务算是安装成功了。(方法多种多样,希望各位多提宝贵意见,不胜感激~) 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 之前的公司并未使用存储过程来做项目,所以小生对存储过程的调用、使用也是一知半解,刚好这家公司就大量用到了存储过程 这次做的功能,为了保持风格一致,也是需要使用存储过程来实现动态sql和数据分页 下面一起来看看如何实现的吧(小白一枚,不喜勿喷,请轻拍)~ 调用存储过程(其中condition 是前台传入的拼接查询条件,parameters[4] 是排序字段) 存储过程实现 是否 USE [EPMS] GO /****** Object: StoredProcedure [dbo].[sp_GetCollectionManage] Script Date: 2016/9/14 10:14:00 ZhangXiaoYong******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [dbo].[sp_GetCollectionManage] @PageSize INT,--页面大小 @pageCurrentIndex INT,--当前页数 @condition nvarchar(MAX),-- 查询条件 @pageCount INT OUT ,-- 当前页 内容数量 @orderStr nvarchar(MAX) --排序字段 AS begin /*获取记录数*/ declare @RecordCount int /*计算页面数据*/ declare @SqlMain nvarchar(4000) declare @SqlStr nvarchar(4000) /* SQL SERVER 拿到查询的数据总数*/ set @SqlMain='select @CountTemp=count(1) from ( select ef.id, e.dictdata_name, ef.city_id,ef.receive_time,ef.receive_amount,ef.batch_id,ef.city__Manager,ef.contract_id ,ROW_NUMBER() Over(order by '+@orderStr+' desc) As SerialNumber from V_city e left join Collection_Confirmation ef on e.id = ef.city_id where '+@condition+') as T left join Contract fa on T.contract_id = fa.id left join V_employeeDutyMerger show on T.dictdata_name=show.cityname and show.dutyname=''城市经理'''; exec sp_executesql @SqlMain,N'@CountTemp int output',@pageCount output; --重要执行 declare @beginIndex as varchar(20) declare @endIndex as varchar (20) --定义一个开始数,和一个结束数,用于分页 set @beginIndex=(@pageCurrentIndex-1)*@PageSize+1 set @endIndex=@pageCurrentIndex*@PageSize /* SQL SERVER 执行查询并分页输出 */ set @SqlStr= 'select T.id, T.dictdata_name, T.receive_time,T.receive_amount,T.batch_id, fa.contract_name,show.name from ( select ef.id, e.dictdata_name, ef.city_id,ef.receive_time,ef.receive_amount,ef.batch_id,ef.city__Manager,ef.contract_id ,ROW_NUMBER() Over(order by '+@orderStr+' desc) As SerialNumber from V_city e left join Collection_Confirmation ef on e.id = ef.city_id where '+@condition+') as T left join Contract fa on T.contract_id = fa.id left join V_employeeDutyMerger show on T.dictdata_name=show.cityname and show.dutyname=''城市经理'' where T.SerialNumber>='+cast(@beginIndex as varchar(20) )+ ' and T.SerialNumber <='+ cast(@endIndex as varchar(20)) --print (@pageCurrentIndex * @PageSize) --print @SqlCount1 --print @SqlStr exec (@SqlStr) end -- 执行该存储过程 declare @pageCount int; exec [dbo].[sp_GetCollectionManage] 15,1,' ef.city_id in(210)',@pageCount output,'ef.id' 存储过程实现 前台效果 第一页 第二页 注:此存储过程还有很多需要优化的地方,仅供参考,欢迎提宝贵意见~ End 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
使用示例 ①下载Bootstrap框架 网址:http://v3.bootcss.com/getting-started/#download ②解压得到三个文件 ③将文件添加进项目后,在页面中引用必要的css和js ④查看效果(a标签美化得不要不要的了~) 更多学习Bootstrap的资料可以移步:http://www.runoob.com/bootstrap/bootstrap-intro.html 未完待续。。。。 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
实现目标(输入汉字转换成拼音首字母) ①下载官方语言包 http://www.microsoft.com/downloads/zh-cn/details.aspx?FamilyID=44cac7f0-633b-477d-aed2-99aee642fc10&DisplayLang=zh-cn ②解压安装 pinyin是我本地新建的一个安装目录,安装完成后即可看到如下文件,然后项目中添加ChnCharInfo.dll文件 ③使用 End 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 因为项目中需要用到一个自动补全的功能,功能描述: 需求一:新增收件人的时候,自动下拉显示出数据库中所有的收件人信息(显示的信息包括:姓名-收件地址-联系方式) 需求二:选中一个值得时候,分别赋值到对应文本框(收件人输入框中赋值 姓名,联系方式输入框中赋值 手机号,收件地址输入框中赋值 地址) 解决需求一(因本人比较懒,所以直接选用了一个比较方便的插件:Autocomplete [参考学习地址:http://www.runoob.com/jqueryui/jqueryui-use.html]) 实现步骤 步骤① 去官网下载对应版本的包,然后在项目中添加这两个引用即可 步骤② 新建一个一般处理程序 步骤三③ 写查询及转换方法 1 在接口里面新增一个查询方法(接口只定义规则,不做具体实现) 2 继承接口并实现查询方法(因为功能需要,这里查询直接做了拼接,查询出来就是“收件人-地址-联系方式”) 3 方法里面调用这个方法进行json数据转换(通用方法可以直接使用) 1 // var name = context.Request["name"]; 2 // 查询的参数名称默认为term 3 string query = context.Request.QueryString["term"]; 4 context.Response.ContentType = "text/javascript"; 5 DataTable sendInfoManage = CMSModelManager.SendInfoManageDAO.Method(query);//调用查询方法,返回一个DataTable 6 //反序列化 7 System.Web.Script.Serialization.JavaScriptSerializer serailizer = new System.Web.Script.Serialization.JavaScriptSerializer(); 8 List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>(); 9 Dictionary<string, object> row; 10 foreach (DataRow dr in sendInfoManage.Rows) 11 { 12 row = new Dictionary<string, object>(); 13 foreach (DataColumn col in sendInfoManage.Columns) 14 { 15 row.Add(col.ColumnName, dr[col]); 16 } 17 rows.Add(row); 18 } 19 20 string s= serailizer.Serialize(rows); 21 context.Response.Write(s); View Code 步骤④ 页面接收返回数据和处理返回数据 1 $(function() { 2 var name = $("#ctl00_contentPlace_txtSender").val().trim(); 3 $("#ctl00_contentPlace_txtAddressee").autocomplete({ 4 source: function(request, response) { 5 $.ajax({ 6 url: "Handler.ashx",//请求地址 7 type: "post",//请求类型 8 data: { "name": name },//参数 9 success: function(data) { 10 //console.log(data); 11 response($.map(eval(data), function(item) { //使用该插件必须要用eval()进行处理 12 //console.log(item); 13 return { 14 value: item.show,//赋值到控件上 15 result: item.show 16 } 17 })); 18 19 }, 20 error: function(xhr) { 21 console.log("发生错误"); 22 } 23 }); 24 25 }, 26 27 }); 28 }); 29 View Code 下图对应上图的数据呈现过程 页面效果 补充:这个插件默认没有滚动条,需要手动添加样式(最大高度可以自行设定) 1 <style type="text/css"> 2 .ui-autocomplete { 3 max-height: 250px; 4 overflow-y: auto; 5 /* 防止水平滚动条 */ 6 overflow-x: hidden; 7 } 8 /* IE 6 不支持 max-height 9 * 我们使用 height 代替,但是这会强制菜单总是显示为那个高度 10 */ 11 * html .ui-autocomplete { 12 height: 250px; 13 } 14 </style> 解决需求二:特意查了下Autocomplete返回值处理情况,最终选用select做处理,最后请求的ajax改为: 1 var showName = null; 2 $(function() { 3 $("#ctl00_contentPlace_txtAddressee").autocomplete({ 4 source: function(request, response) { 5 $.ajax({ 6 url: "Handler.ashx", //请求地址 7 type: "post", //请求类型 8 data: { "name": $("#ctl00_contentPlace_txtAddressee").val().trim(), "look": $("#ctl00_contentPlace_txtSendCode").val().trim() }, //参数 9 success: function(data) { 10 //console.log(data); 11 response($.map(eval(data), function(item) { //使用该插件必须要用eval()进行处理 12 //console.log(item); 13 return { 14 value: item.show, //赋值到控件上 15 result: item.show 16 } 17 })); 18 19 }, 20 error: function(xhr) { 21 console.log("发生错误"); 22 } 23 }); 24 }, 25 select: function(event, ui) { 26 var li = (ui.item.label).split("-"); 27 $("#ctl00_contentPlace_txtAddressee").val(li[0]); 28 console.log(li[0]); 29 showName = li[0]; 30 $("#ctl00_contentPlace_txtContactInfo").val(li[2]); 31 $("#ctl00_contentPlace_txtMailingAddress").val(li[1]); 32 showNames();//可以继续调用其他方法 33 } 34 }); 35 }); 36 function showNames() { 37 console.log(showName); 38 if (showName != null) { 39 $("#ctl00_contentPlace_txtAddressee").val(""); 40 $("#ctl00_contentPlace_txtMailContent").val("测试数据"); 41 } 42 } View Code 效果展示: 选中前 选中后 天了噜,什么,竟然收件人赋值不上,找了半天原因(清空后赋值等等)。。。。。 还是没找到问题,不过因为项目急着测试,就想了个偷懒的办法,用延时再赋值的方式调了下,终于可以了 所以最终版的前台请求如下 1 var showName = null; 2 $(function() { 3 $("#ctl00_contentPlace_txtAddressee").autocomplete({ 4 source: function(request, response) { 5 $.ajax({ 6 url: "Handler.ashx", //请求地址 7 type: "post", //请求类型 8 data: { "name": $("#ctl00_contentPlace_txtAddressee").val().trim(), "look": $("#ctl00_contentPlace_txtSendCode").val().trim() }, //参数 9 success: function(data) { 10 //console.log(data); 11 response($.map(eval(data), function(item) { //使用该插件必须要用eval()进行处理 12 //console.log(item); 13 return { 14 value: item.show, //赋值到控件上 15 result: item.show 16 } 17 })); 18 }, 19 error: function(xhr) { 20 console.log("发生错误"); 21 } 22 }); 23 }, 24 select: function(event, ui) { 25 var li = (ui.item.label).split("-"); 26 $("#ctl00_contentPlace_txtAddressee").val(li[0]); 27 console.log(li[0]); 28 showName = li[0]; 29 $("#ctl00_contentPlace_txtContactInfo").val(li[2]); 30 $("#ctl00_contentPlace_txtMailingAddress").val(li[1]); 31 showNames();//可以继续调用其他方法 32 } 33 }); 34 }); 35 function showNames() { 36 if (showName != null) { 37 setTimeout(function() 38 { 39 $("#ctl00_contentPlace_txtAddressee").val(showName); 40 41 },100); 42 } 43 }; View Code 最终效果 至此,自动补全已经完成并满足需求,Autocomplete非常灵活,本文只是做了简单阐述和讲解 对Autocomplete插件更多参数和方法说明,请查阅 http://www.runoob.com/jqueryui/api-autocomplete.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 废话不多说,直接上遇到的问题 需求:在这个界面点击导出,导出页面数据,苦于没有做过webfrom项目,弄了半天还是没想到原生态的好方法,所以在网上看了下有没有导出的好例子,结果发现有人推荐使用Npoi,抱着强烈的好奇心,就去查了下这个东东,发现果然强大,哈哈,里面集成了很多东西,下面直接进入解决问题正题: 实现过程 ①发现项目里面接口方法返回DataTable是带参数的,所以新增了一个无参数的方法 ②实现这个方法,重点是将查询到的结果集放大DataTable中 ③先去官网:http://npoi.codeplex.com/ 下载需要引入dll(可以选择.net2.0或者.net4.0的dll),然后在网站中添加引用。[因为我的项目就用到了excel导出,所以只添加了这两个dll] ④执行点击事件即可[本项目是点击导出Excel时,执行button3的点击事件] ⑤添加方法(本方法可通用,其中rs是申明的一个全局DataTable ,将接口调用的查询数据库的方法直接返回给rs) 1 NPOI.HSSF.UserModel.HSSFWorkbook book = new NPOI.HSSF.UserModel.HSSFWorkbook(); 2 NPOI.SS.UserModel.ISheet sheet = book.CreateSheet("Sheet1"); 3 //设置列的信息 4 5 NPOI.SS.UserModel.IRow headerrow = sheet.CreateRow(0); 6 ICellStyle style = book.CreateCellStyle(); 7 style.Alignment = HorizontalAlignment.Center; 8 style.VerticalAlignment = VerticalAlignment.Center; 9 rs =CMSModelManager.SendInfoManageDAO.GetFirstSendInfoManageByIds(); 10 IRow rowHead = sheet.CreateRow(0); 11 //填写表头 12 for (int i = 0; i < rs.Columns.Count; i++) 13 { 14 rowHead.CreateCell(i, CellType.String).SetCellValue(rs.Columns[i].ColumnName.ToString()); 15 } 16 17 //填写内容 18 for (int i = 0; i < rs.Rows.Count; i++) 19 { 20 IRow row = sheet.CreateRow(i + 1); 21 for (int j = 0; j < rs.Columns.Count; j++) 22 { 23 row.CreateCell(j, CellType.String).SetCellValue(rs.Rows[i][j].ToString()); 24 } 25 } 26 MemoryStream ms = new MemoryStream(); 27 book.Write(ms); 28 Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}.xls", HttpUtility.UrlEncode("寄件信息表" + "_" + DateTime.Now.ToString("yyyy-MM-dd"), System.Text.Encoding.UTF8))); 29 Response.BinaryWrite(ms.ToArray()); 30 Response.End(); 31 book = null; 32 ms.Close(); 33 ms.Dispose(); View Code ⑥效果 本文完 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 在说LINQ之前必须先说说几个重要的C#语言特性 与LINQ有关的语言特性 隐式类型 (1)源起 在隐式类型出现之前, 我们在声明一个变量的时候, 总是要为一个变量指定他的类型 甚至在foreach一个集合的时候, 也要为遍历的集合的元素,指定变量的类型 隐式类型的出现, 程序员就不用再做这个工作了。 (2)使用方法 来看下面的代码: var a = 1; //int a = 1; var b = "123";//string b = "123"; var myObj = new MyObj();//MyObj myObj = new MyObj() 上面的每行代码,与每行代码后面的注释,起到的作用是完全一样的 也就是说,在声明一个变量(并且同时给它赋值)的时候,完全不用指定变量的类型,只要一个var就解决问题了 (3)你担心这样写会降低性能吗? 我可以负责任的告诉你,这样写不会影响性能! 上面的代码和注释里的代码,编译后产生的IL代码(中间语言代码)是完全一样的 (编译器根据变量的值,推导出变量的类型,才产生的IL代码) (4)这个关键字的好处: 你不用在声明一个变量并给这个变量赋值的时候,写两次变量类型 (这一点真的为开发者节省了很多时间) 在foreach一个集合的时候,可以使用var关键字来代替书写循环变量的类型 (5)注意事项 你不能用var关键字声明一个变量而不给它赋值 因为编译器无法推导出你这个变量是什么类型的。 匿名类型 (1)源起 创建一个对象,一定要先定义这个对象的类型吗? 不一定的! 来看看这段代码 (2)使用 var obj = new {Guid.Empty, myTitle = "匿名类型", myOtherParam = new int[] { 1, 2, 3, 4 } }; Console.WriteLine(obj.Empty);//另一个对象的属性名字,被原封不动的拷贝到匿名对象中来了。 Console.WriteLine(obj.myTitle); Console.ReadKey(); new关键字之后就直接为对象定义了属性,并且为这些属性赋值 而且,对象创建出来之后,在创建对象的方法中,还可以畅通无阻的访问对象的属性 当把一个对象的属性拷贝到匿名对象中时,可以不用显示的指定属性的名字,这时原始属性的名字会被“拷贝”到匿名对象中 (3)注意 如果你监视变量obj,你会发现,obj的类型是Anonymous Type类型的 不要试图在创建匿名对象的方法外面去访问对象的属性! (4)优点 这个特性在网站开发中,序列化和反序列化JSON对象时很有用 自动属性 (1)源起 为一个类型定义属性,我们一般都写如下的代码: public class MyObj2 { private Guid _id; private string _Title; public Guid id { get { return _id; } set { _id = value; } } public string Title { get { return _Title; } set { _Title = value; } } } 但很多时候,这些私有变量对我们一点用处也没有,比如对象关系映射中的实体类。 自C#3.0引入了自动实现的属性, 以上代码可以写成如下形式: (2)使用 public class MyObj { public Guid id { get; set; } public string Title { get; set; } } 这个特性也和var关键字一样,是编译器帮我们做了工作,不会影响性能的 4.初始化器 (1)源起 我们创建一个对象并给对象的属性赋值,代码一般写成下面的样子 var myObj = new MyObj(); myObj.id = Guid.NewGuid(); myObj.Title = "allen"; 自C#3.0引入了对象初始化器, 代码可以写成如下的样子 (2)使用 var myObj1 = new MyObj() { id = Guid.NewGuid(), Title = "allen" }; 如果一个对象是有参数的构造函数 那么代码看起来就像这样 var myObj1 = new MyObj ("allen") { id = Guid.NewGuid(), Title = "allen" }; 集合初始化器的样例代码如下: var arr = new List<int>() { 1, 2, 3, 4, 5, 6 }; (3)优点 我个人认为:这个特性不是那么amazing, 这跟我的编码习惯有关,集合初始化器也就罢了, 真的不习惯用对象初始化器初始化一个对象! 5.委托 (1)使用 我们先来看一个简单的委托代码 delegate Boolean moreOrlessDelgate(int item); class Program { static void Main(string[] args) { var arr = new List<int>() { 1, 2, 3, 4, 5, 6,7,8 }; var d1 = new moreOrlessDelgate(More); Print(arr, d1); Console.WriteLine("OK"); var d2 = new moreOrlessDelgate(Less); Print(arr, d2); Console.WriteLine("OK"); Console.ReadKey(); } static void Print(List<int> arr,moreOrlessDelgate dl) { foreach (var item in arr) { if (dl(item)) { Console.WriteLine(item); } } } static bool More(int item) { if (item > 3) { return true; } return false; } static bool Less(int item) { if (item < 3) { return true; } return false; } } 这段代码中 <1>首先定义了一个委托类型 delegate Boolean moreOrlessDelgate(int item); 你看到了,委托和类是一个级别的,确实是这样:委托是一种类型 和class标志的类型不一样,这种类型代表某一类方法。 这一句代码的意思是:moreOrlessDelgate这个类型代表返回值为布尔类型,输入参数为整形的方法 <2>有类型就会有类型的实例 var d1 = new moreOrlessDelgate(More); var d2 = new moreOrlessDelgate(Less); 这两句就是创建moreOrlessDelgate类型实例的代码, 它们的输入参数是两个方法 <3>有了类型的实例,就会有操作实例的代码 Print(arr, d1); Print(arr, d2); 我们把前面两个实例传递给了Print方法 这个方法的第二个参数就是moreOrlessDelgate类型的 在Print方法内用如下代码,调用委托类型实例所指向的方法 dl(item) 6.泛型 (1)为什么要有泛型 假设你是一个方法的设计者, 这个方法有一个传入参数,有一个返回值。 但你并不知道这个参数和返回值是什么类型的, 如果没有泛型,你可能把参数和返回值的类型都设定为Object了 那时,你心里肯定在想:反正一切都是对象,一切的基类都是Object 没错!你是对的! 这个方法的消费者,会把他的对象传进来(有可能会做一次装箱操作) 并且得到一个Object的返回值,他再把这个返回值强制类型转化为他需要的类型 除了装箱和类型转化时的性能损耗外,代码工作的很好! 那么这些新能损耗能避免掉吗? 有泛型之后就可以了! (2)使用 <1>使用简单的泛型 先来看下面的代码: var intList = new List<int>() { 1,2,3}; intList.Add(4); intList.Insert(0, 5); foreach (var item in intList) { Console.WriteLine(item); } Console.ReadKey(); 在上面这段代码中我们声明了一个存储int类型的List容器 并循环打印出了容器里的值 注意:如果这里使用Hashtable、Queue或者Stack等非泛型的容器 就会导致装箱操作,损耗性能。因为这些容器只能存储Object类型的数据 <2>泛型类型 List<T>、Dictionary<TKey, TValue>等泛型类型都是.net类库定义好并提供给我们使用的 但在实际开发中,我们也经常需要定义自己的泛型类型 来看下面的代码: public static class SomethingFactory<T> { public static T InitInstance(T inObj) { if (false)//你的判断条件 { //do what you want... return inObj; } return default(T); } } 这段代码的消费者如下: var a1 = SomethingFactory<int>.InitInstance(12); Console.WriteLine(a1); Console.ReadKey(); 输出的结果为0 这就是一个自定义的静态泛型类型, 此类型中的静态方法InitInstance对传入的参数做了一个判断 如果条件成立,则对传入参数进行操作之后并把它返回 如果条件不成立,则返回一个空值 注意: [1] 传入参数必须为指定的类型, 因为我们在使用这个泛型类型的时候,已经规定好它能接收什么类型的参数 但在设计这个泛型的时候,我们并不知道使用者将传递什么类型的参数进来 [2] 如果你想返回T类型的空值,那么请用default(T)这种形式 因为你不知道T是值类型还是引用类型,所以别擅自用null <3>泛型约束 很多时候我们不希望使用者太过自由 我们希望他们在使用我们设计的泛型类型时 不要很随意的传入任何类型 对于泛型类型的设计者来说,要求使用者传入指定的类型是很有必要的 因为我们只有知道他传入了什么东西,才方便对这个东西做操作 让我们来给上面设计的泛型类型加一个泛型约束 代码如下: public static class SomethingFactory<T> where T:MyObj 这样在使用SomethingFactory的时候就只能传入MyObj类型或MyObj的派生类型啦 注意: 还可以写成这样 where T:MyObj,new() 来约束传入的类型必须有一个构造函数。 (3)泛型的好处 <1>算法的重用 想想看:list类型的排序算法,对所有类型的list集合都是有用的 <2>类型安全 <3>提升性能 没有类型转化了,一方面保证类型安全,另一方面保证性能提升 <4>可读性更好 这一点就不解释了 7.泛型委托 (1)源起 委托需要定义delgate类型 使用起来颇多不便 而且委托本就代表某一类方法 开发人员经常使用的委托基本可以归为三类, 哪三类呢? 请看下面: (2)使用 <1>Predicate泛型委托 把上面例子中d1和d2赋值的两行代码改为如下: //var d1 = new moreOrlessDelgate(More); var d1 = new Predicate<int>(More); //var d2 = new moreOrlessDelgate(Less); var d2 = new Predicate<int>(Less); 把Print方法的方法签名改为如下: //static void Print(List<int> arr, moreOrlessDelgate<int> dl) static void Print(List<int> arr, Predicate<int> dl) 然后再运行方法,控制台输出的结果和原来的结果是一模一样的。 那么Predicate到底是什么呢? 来看看他的定义: // 摘要: // 表示定义一组条件并确定指定对象是否符合这些条件的方法。 // // 参数: // obj: // 要按照由此委托表示的方法中定义的条件进行比较的对象。 // // 类型参数: // T: // 要比较的对象的类型。 // // 返回结果: // 如果 obj 符合由此委托表示的方法中定义的条件,则为 true;否则为 false。 public delegate bool Predicate<in T>(T obj); 看到这个定义,我们大致明白了。 .net为我们定义了一个委托, 这个委托表示的方法需要传入一个T类型的参数,并且需要返回一个bool类型的返回值 有了它,我们就不用再定义moreOrlessDelgate委托了, 而且,我们定义的moreOrlessDelgate只能搞int类型的参数, Predicate却不一样,它可以搞任意类型的参数 但它规定的还是太死了,它必须有一个返回值,而且必须是布尔类型的,同时,它必须有一个输入参数 除了Predicate泛型委托,.net还为我们定义了Action和Func两个泛型委托 <2>Action泛型委托 Action泛型委托限制的就不那么死了, 他代表了一类方法: 可以有0个到16个输入参数, 输入参数的类型是不确定的, 但不能有返回值, 来看个例子: var d3 = new Action(noParamNoReturnAction); var d4 = new Action<int, string>(twoParamNoReturnAction); 注意:尖括号中int和string为方法的输入参数 static void noParamNoReturnAction() { //do what you want } static void twoParamNoReturnAction(int a, string b) { //do what you want } <3>Func泛型委托 为了弥补Action泛型委托,不能返回值的不足 .net提供了Func泛型委托, 相同的是它也是最多0到16个输入参数,参数类型由使用者确定 不同的是它规定要有一个返回值,返回值的类型也由使用者确定 如下示例: var d5 = new Func<int, string>(oneParamOneReturnFunc); 注意:string类型(最后一个泛型类型)是方法的返回值类型 static string oneParamOneReturnFunc(int a) { //do what you want return string.Empty; } 8.匿名方法 (1)源起 在上面的例子中 为了得到序列中较大的值 我们定义了一个More方法 var d1 = new Predicate<int>(More); 然而这个方法,没有太多逻辑(实际编程过程中,如果逻辑较多,确实应该独立一个方法出来) 那么能不能把More方法中的逻辑,直接写出来呢? C#2.0之后就可以了, 请看下面的代码: (2)使用 var arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 }; //var d1 = new moreOrlessDelgate(More); //var d1 = new Predicate<int>(More); var d1 = new Predicate<int>(delegate(int item) { //可以访问当前上下文中的变量 Console.WriteLine(arr.Count); if (item > 3) { return true; } return false; }); Print(arr, d1); Console.WriteLine("OK"); 我们传递了一个代码块给Predicate的构造函数 其实这个代码块就是More函数的逻辑 (3)好处 <1>代码可读性更好 <2>可以访问当前上下文中的变量 这个用处非常大, 如果我们仍旧用原来的More函数 想要访问arr变量,势必要把arr写成类级别的私有变量了 用匿名函数的话,就不用这么做了。 9.Lambda表达式 (1)源起 .net的设计者发现在使用匿名方法时, 仍旧有一些多余的字母或单词的编码工作 比如delegate关键字 于是进一步简化了匿名方法的写法 (2)使用 List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 }; arr.ForEach(new Action<int>(delegate(int a) { Console.WriteLine(a); })); arr.ForEach(new Action<int>(a => Console.WriteLine(a))); 匿名方法的代码如下: delegate(int a) { Console.WriteLine(a); } 使用lambda表达式的代码如下: a => Console.WriteLine(a) 这里解释一下这个lambda表达式 <1> a是输入参数,编译器可以自动推断出它是什么类型的, 如果没有输入参数,可以写成这样: () => Console.WriteLine("ddd") <2> =>是lambda操作符 <3> Console.WriteLine(a)是要执行的语句。 如果是多条语句的话,可以用{}包起来。 如果需要返回值的话,可以直接写return语句 10.扩展方法 (1)源起 如果想给一个类型增加行为,一定要通过继承的方式实现吗? 不一定的! (2)使用 来看看这段代码: public static void PrintString(this String val) { Console.WriteLine(val); } 消费这段代码的代码如下: var a = "aaa"; a.PrintString(); Console.ReadKey(); 我想你看到扩展方法的威力了。 本来string类型没有PrintString方法 但通过我们上面的代码,就给string类型"扩展"了一个PrintString方法 (1)先决条件 <1>扩展方法必须在一个非嵌套、非泛型的静态类中定义 <2>扩展方法必须是一个静态方法 <3>扩展方法至少要有一个参数 <4>第一个参数必须附加this关键字作为前缀 <5>第一个参数不能有其他修饰符(比如ref或者out) <6>第一个参数不能是指针类型 (2)注意事项 <1>跟前面提到的几个特性一样,扩展方法只会增加编译器的工作,不会影响性能(用继承的方式为一个类型增加特性反而会影响性能) <2>如果原来的类中有一个方法,跟你的扩展方法一样(至少用起来是一样),那么你的扩展方法奖不会被调用,编译器也不会提示你 <3>扩展方法太强大了,会影响架构、模式、可读性等等等等.... 11.迭代器 · (1)使用 我们每次针对集合类型编写foreach代码块,都是在使用迭代器 这些集合类型都实现了IEnumerable接口 都有一个GetEnumerator方法 但对于数组类型就不是这样 编译器把针对数组类型的foreach代码块 替换成了for代码块。 来看看List的类型签名: public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable IEnumerable接口,只定义了一个方法就是: IEnumerator<T> GetEnumerator(); (2)迭代器的优点: 假设我们需要遍历一个庞大的集合 只要集合中的某一个元素满足条件 就完成了任务 你认为需要把这个庞大的集合全部加载到内存中来吗? 当然不用(C#3.0之后就不用了)! 来看看这段代码: static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield return 2; Console.WriteLine("迭代器返回了3"); yield return 3; } 消费这个函数的代码如下: foreach (var i in GetIterator()) { if (i == 2) { break; } Console.WriteLine(i); } Console.ReadKey(); 输出结果为: 迭代器返回了1 1 迭代器返回了2 大家可以看到: 当迭代器返回2之后,foreach就退出了 并没有输出“迭代器返回了3” 也就是说下面的工作没有做。 (3)yield 关键字 MSDN中的解释如下: 在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。 也就是说,我们可以在生成迭代器的时候,来确定什么时候终结迭代逻辑 上面的代码可以改成如下形式: static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield break; Console.WriteLine("迭代器返回了3"); yield return 3; } (4)注意事项 <1>做foreach循环时多考虑线程安全性 在foreach时不要试图对被遍历的集合进行remove和add等操作 任何集合,即使被标记为线程安全的,在foreach的时候,增加项和移除项的操作都会导致异常 (我在这里犯过错) <2>IEnumerable接口是LINQ特性的核心接口 只有实现了IEnumerable接口的集合 才能执行相关的LINQ操作,比如select,where等 这些操作,我们接下来会讲到。 二:LINQ 1.查询操作符 (1)源起 .net的设计者在类库中定义了一系列的扩展方法 来方便用户操作集合对象 这些扩展方法构成了LINQ的查询操作符 (2)使用 这一系列的扩展方法,比如: Where,Max,Select,Sum,Any,Average,All,Concat等 都是针对IEnumerable的对象进行扩展的 也就是说,只要实现了IEnumerable接口,就可以使用这些扩展方法 来看看这段代码: List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 }; var result = arr.Where(a => { return a > 3; }).Sum(); Console.WriteLine(result); Console.ReadKey(); 这段代码中,用到了两个扩展方法。 <1> Where扩展方法,需要传入一个Func<int,bool>类型的泛型委托 这个泛型委托,需要一个int类型的输入参数和一个布尔类型的返回值 我们直接把a => { return a > 3; }这个lambda表达式传递给了Where方法 a就是int类型的输入参数,返回a是否大于3的结果。 <2> Sum扩展方法计算了Where扩展方法返回的集合的和。 (3)好处 上面的代码中 arr.Where(a => { return a > 3; }).Sum(); 这一句完全可以写成如下代码: (from v in arr where v > 3 select v).Sum(); 而且两句代码的执行细节是完全一样的 大家可以看到,第二句代码更符合语义,更容易读懂 第二句代码中的where,就是我们要说的查询操作符。 (4)标准查询操作符说明 <1>过滤 Where 用法:arr.Where(a => { return a > 3; }) 说明:找到集合中满足指定条件的元素 OfType 用法:arr.OfType<int>() 说明:根据指定类型,筛选集合中的元素 <2>投影 Select 用法:arr.Select<int, string>(a => a.ToString()); 说明:将集合中的每个元素投影的新集合中。上例中:新集合是一个IEnumerable<String>的集合 SelectMany 用法:arr.SelectMany<int, string>(a => { return new List<string>() { "a", a.ToString() }; }); 说明:将序列的每个元素投影到一个序列中,最终把所有的序列合并 <3>还有很多查询操作符,请翻MSDN,以后有时间我将另起一篇文章把这些操作符写全。 2.查询表达式 (1)源起 上面我们已经提到,使用查询操作符表示的扩张方法来操作集合 虽然已经很方便了,但在可读性和代码的语义来考虑,仍有不足 于是就产生了查询表达式的写法。 虽然这很像SQL语句,但他们却有着本质的不同。 (2)用法 from v in arr where v > 3 select v 这就是一个非常简单的查询表达式 (3)说明: 先看一段伪代码: from [type] id in source [join [type] id in source on expr equals expr [into subGroup]] [from [type] id in source | let id = expr | where condition] [orderby ordering,ordering,ordering...] select expr | group expr by key [into id query] <1>第一行的解释: type是可选的, id是集合中的一项, source是一个集合, 如果集合中的类型与type指定的类型不同则导致强制转化 <2>第二行的解释: 一个查询表达式中可以有0个或多个join子句, 这里的source可以不等于第一句中的source expr可以是一个表达式 [into subGroup] subGroup是一个中间变量, 它继承自IGrouping,代表一个分组,也就是说“一对多”里的“多” 可以通过这个变量得到这一组包含的对象个数,以及这一组对象的键 比如: from c in db.Customers join o in db.Orders on c.CustomerID equals o.CustomerID into orders select new { c.ContactName, OrderCount = orders.Count() }; <3>第三行的解释: 一个查询表达式中可以有1个或多个from子句 一个查询表达式中可以有0个或多个let子句,let子句可以创建一个临时变量 比如: from u in users let number = Int32.Parse(u.Username.Substring(u.Username.Length - 1)) where u.ID < 9 && number % 2 == 0 select u 一个查询表达式中可以有0个或多个where子句,where子句可以指定查询条件 <4>第四行的解释: 一个查询表达式可以有0个或多个排序方式 每个排序方式以逗号分割 <5>第五行的解释: 一个查询表达式必须以select或者group by结束 select后跟要检索的内容 group by 是对检索的内容进行分组 比如: from p in db.Products group p by p.CategoryID into g select new { g.Key, NumProducts = g.Count()}; <6>第六行的解释: 最后一个into子句起到的作用是 将前面语句的结果作为后面语句操作的数据源 比如: from p in db.Employees select new { LastName = p.LastName, TitleOfCourtesy = p.TitleOfCourtesy } into EmployeesList orderby EmployeesList.TitleOfCourtesy ascending select EmployeesList; 参考资料 《LINQ实战》 《深入理解C#》第二版 《CLR VIA C#》第三版 《C# 高级编程》第四版 还有很多网络上的文章,就不一一例举了 此文为转载,原文出处: http://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
开篇语 正则表达式,有木有人像我一样,学了好几遍却还是很懵圈,学的时候老明白了,学完了忘光了。好吧,其实还是练的不够,所谓温故而知新,可以为师矣,今天就随我来复习一下这傲娇的正则表达式吧。 为啥要有正则表达式呢?其实就是因为计算机笨(这话不是我说的),比如123456@qq.com,我们一看就是邮箱,可是计算机不认识啊,所以我们就要用一些计算机认识的语言,来制定好规则,告诉它符合这个规则的就是个邮箱,这样计算机就能帮我们找到对应的东西了。所以正则就是用来设置规则,来完成我们需求的一些操作的,比如登录验证啦,搜索指定的东西啦等等,说太多都是多余,直接看正题吧。 定义正则: 1 var re = new RegExp(“a”); //RegExp对象。参数就是我们想要制定的规则。有一种情况必须用这种方式,下面会提到。 2 var re = /a/; // 简写方法 推荐使用 性能更好 不能为空 不然以为是注释 , 正则的常用方法 1 test() :在字符串中查找符合正则的内容,若查找到返回true,反之返回false. 用法:正则.test(字符串) 例子:判断是否是数字 var str = '374829348791'; var re = /\D/; // \D代表非数字 if( re.test(str) ){ // 返回true,代表在字符串中找到了非数字。 alert('不全是数字'); }else{ alert('全是数字'); } 正则表达式中有很多符号,代表着不同的意思,用来让我们去定义不同的规则,比如上面\D,还有下面的这些: \s : 空格\S : 非空格\d : 数字\D : 非数字\w : 字符 ( 字母 ,数字,下划线_ )\W : 非字符例子:是否有不是数字的字符 (下面会根据例子,依次讲一些常用的字符,最后再作总结。) 2 search() :在字符串搜索符合正则的内容,搜索到就返回出现的位置(从0开始,如果匹配的不只是一个字母,那只会返回第一个字母的位置), 如果搜索失败就返回 -1 用法:字符串.search(正则) 在字符串中查找复合正则的内容。忽略大小写:i——ignore(正则中默认是区分大小写的 如果不区分大小写的话,在正则的最后加标识 i ) 例子:在字符串中找字母b,且不区分大小写 var str = 'abcdef'; var re = /B/i; //var re = new RegExp('B','i'); 也可以这样写 alert( str.search(re) ); // 1 3 match() 在字符串中搜索复合规则的内容,搜索成功就返回内容,格式为数组,失败就返回null。 用法: 字符串.match(正则) 量词:+ 至少出现一次 匹配不确定的次数(匹配就是搜索查找的意思) 全局匹配:g——global(正则中默认,只要搜索到复合规则的内容就会结束搜索 ) 例子:找出指定格式的所有数字,如下找到 123,54,33,879 var str = 'haj123sdk54hask33dkhalsd879'; var re = /\d+/g; // 每次匹配至少一个数字 且全局匹配 如果不是全局匹配,当找到数字123,它就会停止了。就只会弹出123.加上全局匹配,就会从开始到结束一直去搜索符合规则的。如果没有加号,匹配的结果就是1,2,3,5,4,3,3,879并不是我们想要的,有了加号,每次匹配的数字就是至少一个了。 alert( str.match(re) ); // [123,54,33,879] 4 replace() :查找符合正则的字符串,就替换成对应的字符串。返回替换后的内容。 用法: 字符串.replace(正则,新的字符串/回调函数)(在回调函数中,第一个参数指的是每次匹配成功的字符) | : 或的意思 。 例子:敏感词过滤,比如 我爱北京天安门,天安门上太阳升。------我爱*****,****上太阳升。即北京和天安门变成*号, 一开始我们可能会想到这样的方法: var str = "我爱北京天安门,天安门上太阳升。"; var re = /北京|天安门/g; // 找到北京 或者天安门 全局匹配 var str2 = str.replace(re,'*'); alert(str2) //我爱**,*上太阳升 //这种只是把找到的变成了一个*,并不能几个字就对应几个*。 要想实现几个字对应几个*,我们可以用回调函数实现: var str = "我爱北京天安门,天安门上太阳升。"; var re = /北京|天安门/g; // 找到北京 或者天安门 全局匹配 var str2 = str.replace(re,function(str){ alert(str); //用来测试:函数的第一个参数代表每次搜索到的符合正则的字符,所以第一次str指的是北京 第二次str是天安门 第三次str是天安门 var result = ''; for(var i=0;i<str.length;i++){ result += '*'; } return result; //所以搜索到了几个字就返回几个* }); alert(str2) //我爱*****,***上太阳升 //整个过程就是,找到北京,替换成了两个*,找到天安门替换成了3个*,找到天安门替换成3个*。 replace是一个很有用的方法,经常会用到。 正则中的字符 ():,小括号,叫做分组符。就相当于数学里面的括号。如下: var str = '2013-6-7'; var re1 = /\d-+/g; // 全局匹配数字,横杠,横杠数量至少为1,匹配结果为: 3- 6- var re1 = /(\d-)+/g; // 全局匹配数字,横杠,数字和横杠整体数量至少为1 3-6- var re2 = /(\d+)(-)/g; // 全局匹配至少一个数字,匹配一个横杠 匹配结果:2013- 6- 同时,正则中的每一个带小括号的项,都叫做这个正则的子项。子项在某些时候非常的有用,比如我们来看一个栗子。 例子:让2013-6-7 变成 2013.6.7 var str = '2013-6-7'; var re = /(\d+)(-)/g; str = str.replace(re,function($0,$1,$2){ //replace()中如果有子项, //第一个参数:$0(匹配成功后的整体结果 2013- 6-), // 第二个参数 : $1(匹配成功的第一个分组,这里指的是\d 2013, 6) //第三个参数 : $1(匹配成功的第二个分组,这里指的是- - - ) return $1 + '.'; //分别返回2013. 6. }); alert( str ); //2013.6.7 //整个过程就是利用子项把2013- 6- 分别替换成了2013. 6. 最终弹出2013.6.7 match方法也会返回自己的子项,如下: var str = 'abc'; var re = /(a)(b)(c)/; alert( str.match(re) ); //[abc,a,b,c]( 返回的是匹配结果 以及每个子项 当match不加g的时候才可以获取到子项的集合) [] : 表示某个集合中的任意一个,比如 [abc] 整体代表一个字符 匹配 a b c 中的任意一个,也可以是范围,[0-9] 范围必须从小到大 。 [^a] 整体代表一个字符 :^写在[]里面的话,就代表排除的意思 例子:匹配HTML标签 比如<div class="b">hahahah </div> 找出标签<div class="b"></div> var re = /<[^>]+>/g; //匹配左括号 中间至少一个非右括号的内容(因为标签里面还有属性等一些东西),然后匹配右括号 var re = /<[\w\W]+>/g; //匹配左括号 中间至少一个字符或者非字符的内容,然后匹配右括号// 其实就是找到左括号,然后中间可以有至少一个内容,一直到找到右括号就代表是一个标签。 转义字符 \s : 空格\S : 非空格\d : 数字\D : 非数字\w : 字符 ( 字母 ,数字,下划线_ )\W : 非字符.(点)——任意字符\. : 真正的点\b : 独立的部分 ( 起始,结束,空格 )\B : 非独立的部分 关于最后两个来看个栗子: var str = 'onetwo'; var str2 ="one two"; var re = /one\b/; // e后面必须是独立的 可以是起始,空格,或结束 alert( re.test(str) ); //false alert( re.test(str2) );//true 例子:写一个用class名获取节点的函数: 我们之前可能见过这样的函数: function getByClass(parent,classname){ if(parent.getElementsByClassName){ return parent.getElementsByClassName(classname); } else{ var results = new Array();//用来存储所有取到的class为box的元素 var elems = parent.getElementsByTagName("*"); for(var i =0;i<elems.length;i++){ if(elems[i].className==classname){ results.push(elems[i]); } } return results; } } 其实这是存在问题的,比如它如果一个标签里面有两个class,或者存在相同名字的class,比如<div class="box1 box1">,<div class="box1 box2>它就没办法获取到了,我们可以用正则来解决这个问题。 function getByClass(parent,classname){ if(parent.getElementsByClassName){ return parent.getElementsByClassName(classname); }else{ var arr = []; var aEle = parent.getElementsByTagName('*'); //var re = /\bclassname\b/; //不能这样写,当正则需要用到参数时候,一定要用全称的写法,简写方式会把classname当做一个字符串去匹配。 var re = new RegExp('\\b'+classname+'\\b'); // 匹配的时候,classname前面必须是起始或者空格,后面也是。 默认匹配成功就停止,所以就算有重复的也不会再匹配进去了。 //需要注意的是,全称的方式声明正则的时候,参数是字符串类型的,所以我们用的时候,需要保证这些特殊的字符在字符串内也能输出才行。\b本身是特殊字符,在字符串中无法输出,所以要加反斜杠转义才行。 for(var i=0;i<aEle.length;i++){ if( re.test(aEle[i].className) ){ arr.push( aEle[i] ); } } return arr; } } \a 表示重复的某个子项 比如: \1 重复的第一个子项 \2 重复的第二个子项 / (a) (b) (c) \1/-----匹配 abca / (a) (b) (c) \2/------匹配 abcb 例子(面试题中经常问到):找重复项最多的字符个数 split():字符串中的方法,把字符串转成数组。 sort():数组中的排序方法,按照ACALL码进行排序。 join():数组中的方法,把数组转换为字符串 var str = 'assssjdssskssalsssdkjsssdss'; var arr = str.split(''); //把字符串转换为数组 str = arr.sort().join(''); //首先进行排序,这样结果会把相同的字符放在一起,然后再转换为字符串 //alert(str); // aaddjjkklsssssssssssssssss var value = ''; var index = 0; var re = /(\w)\1+/g; //匹配字符,且重复这个字符,重复次数至少一次。 str.replace(re,function($0,$1){ //alert($0); 代表每次匹配成功的结果 : aa dd jj kk l sssssssssssssssss //alert($1); 代表每次匹配成功的第一个子项,也就是\w: a d j k l S if(index<$0.length){ //如果index保存的值小于$0的长度就进行下面的操作 index = $0.length; // 这样index一直保存的就在最大的长度 value = $1; //value保存的是出现最多的这个字符 } }); alert('最多的字符:'+value+',重复的次数:'+index); // s 17 量词:代表出现的次数 {n,m}:至少出现n次,最多m次 {n,} :至少n次 * :任意次 相当于{0,} ? :零次或一次 相当于{0,1} + :一次或任意次相当于 {1,} {n}: 正好n次 判断是不是QQ号 //^ : 放在正则的最开始位置,就代表起始的意思,注意 /[^a] / 和 /^[a]/是不一样的,前者是排除的意思,后者是代表首位。//$ : 正则的最后位置 , 就代表结束的意思 //首先想QQ号的规则 1 首位不能是0 2 必须是 5-12位的数字 var aInput = document.getElementsByTagName('input'); var re = /^[1-9]\d{4,11}$/; //123456abc为了防止出现这样的情况,所以必须限制最后 //首位是0-9,接着是4-11位的数字类型。 aInput[1].onclick = function(){ if( re.test(aInput[0].value) ){ alert('是QQ号'); }else{ alert('不是QQ号'); } }; 例子:去掉前后空格(面试题经常出现) var str = ' hello '; alert( '('+trim(str)+')' );//为了看出区别所以加的括号。 (hello) function trim(str){ var re = /^\s+|\s+$/g; // |代表或者 \s代表空格 +至少一个 前面有至少一个空格 或者后面有至少一个空格 且全局匹配 return str.replace(re,''); //把空格替换成空 } 常用的一些表单校验 匹配中文:[\u4e00-\u9fa5] //中文ACALL码的范围 行首行尾空格:^\s*|\s*$ //首行出现任意个空格或者尾行出现任意个空格(任意表示也可以没有空格) Email:^\w+@[a-z0-9]+(\.[a-z]+){1,3}$ //起始至少为一个字符(\w字母,数字或者下划线),然后匹配@,接着为任意个字母或者数字,\.代表真正的点,.后面为至少一个的字符(a-z),同时这个(比如.com)整体为一个子项作为结束,可以出现1-3次。因为有的邮箱是这样的.cn.net。(xxxx.@qq.com xxxx.@163.com xxxx.@16.cn.net ) 网址:[a-zA-z]+://[^\s]* http://...... //匹配不分大小写的任意字母,接着是//,后面是非空格的任意字符 邮政编码:[1-9]\d{5} //起始数字不能为0,然后是5个数字 身份证:[1-9]\d{14}|[1-9]\d{17}|[1-9]\d{16}x 为了方便且不冲突,我们可以用json的格式 建立自己的空间,如下: /* var re = {email : /^\w+@[a-z0-9]+(\.[a-z]+){1,3}$/,number : /\d+/}; re.email */ 正则的基础知识点大概就这么多,写的有些乱,欢迎指正 上文为转载文章:原文出处:http://www.cnblogs.com/moqing/p/5665126.html 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
发送邮件的实现 需要事先引入以下几个架包,最重要的架包是jodd-3.7这个 以上架包下载地址:http://pan.baidu.com/s/1kVs7Tyv 提取密码:h22x 新建一个Util类,其中emails.txt 是用来动态配置需要发送邮件的发送对象 package quartz; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * * @author DONG */ public class Util{ public static final String format = "HH:mm"; public static final SimpleDateFormat sdf = new SimpleDateFormat(format); public static String content = "以下电桩断网已超过1小时" +"【"+ sdf.format(new Date())+"】";//发送邮件内容 public static Date lastSend = null; public static List getEmailList(){ return getList("emails.txt"); } public static List getList(String fileName){ try{ InputStream is = Util.class.getResourceAsStream(fileName); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); List list = new ArrayList(); String line = ""; while( (line = br.readLine()) != null ){ if(!"".equals(line.trim())) list.add(line); } br.close(); isr.close(); is.close(); return list; }catch(Exception e){ e.printStackTrace(); } return null; } } 以上代码可直接copy复用 接下来就是发送邮件了 public void run() { List<String> emails = Util.getEmailList();//获取邮件发送对象的集合 if (emails.isEmpty()) { System.out.println("no email receiver"); return; } String from = "********@sina.com";//用户名,登录邮箱的账号 String psw = "**********";//密码 String[] to = emails.toArray(new String[0]); Email email = Email.create() .from(from).to(to) .subject("电桩断网超时提醒")//邮件主题 .addText(Util.content);//邮件内容 SmtpServer smtpServer = SmtpServer.create("smtp.sina.com")//调用新浪邮箱服务器 .authenticateWith(from, psw); SendMailSession session = smtpServer.createSession(); session.open(); session.sendMail(email);//执行发送 session.close(); System.out.println("--email send success. receivers: " + Arrays.deepToString(emails.toArray())); } 在需要发送邮件的地方调用run方法即可。以上就是一个超简易的发送邮件示例,亲测有效 下一篇,将补充如何自定义添加邮件内容 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
实现效果如下 实现过程 [Serializable] class User { //记住密码 private string loginID; public string LoginID { get { return loginID; } set { loginID = value; } } private string pwd; public string Pwd { get { return pwd; } set { pwd = value; } } } 首先新建一个实体类,创建两个字段并封装成对应属性 User user = new User(); // 登录时 如果没有Data.bin文件就创建、有就打开 FileStream fs = new FileStream("data.bin", FileMode.OpenOrCreate); BinaryFormatter bf = new BinaryFormatter(); // 保存在实体类属性中 user.LoginID = comboBoxEx1.Text.Trim(); //保存密码选中状态 if (checkBoxXUser.Checked) user.Pwd = passWordtext.Text.Trim(); else user.Pwd = ""; //选在集合中是否存在用户名 if (users.ContainsKey(user.LoginID)) { //如果有清掉 users.Remove(user.LoginID); } //添加用户信息到集合 users.Add(user.LoginID, user); //写入文件 bf.Serialize(fs, users); //关闭 fs.Close(); 然后对选中按钮做判断是否选中,若选中则将用户信息添加到集合中。最后记得关闭文件的读写操作~,到这里已经完成了保存用户信息到集合中了。是不是超简单,不过还没有完。因为我们上面只是保存,我们需要打开窗体或者页面的时候,是需要将用户信息再次 读取出来的。所以下面将是对用户信息的读操作 //读取文件流对象 FileStream fs = new FileStream("data.bin", FileMode.OpenOrCreate); if (fs.Length > 0) { BinaryFormatter bf = new BinaryFormatter(); //读出存在Data.bin 里的用户信息 users = bf.Deserialize(fs) as Dictionary<string, User>; //循环添加到Combox1 foreach (User user in users.Values) { comboBoxEx1.Items.Add(user.LoginID); } //combox1 用户名默认选中第一个 if (comboBoxEx1.Items.Count > 0) comboBoxEx1.SelectedIndex = comboBoxEx1.Items.Count - 1; } fs.Close(); 读的话比较简单,就不一一解释了,相信各位能看明白,另外,当中需要引用到的类库是 using System.Runtime.Serialization.Formatters.Binary; using System.Threading; 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
非在线安装 首先来这儿下载插件 http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID=2240 找个最新的下载 解压到对应的位置,当然是你电脑上svn的安装路径,最后把site-1.8.22文件夹名改为svn 最终,重启一下Myeclipse,完成了 注:Eclipse 步骤第一步不同,剩下步骤均可参考下面安装步骤 Help—>Install New Software 在线安装 第一步:打开myeclipse的 help---install from site 第二部 点击add弹出对话框 -- 在输入框中输入对应内容 为了方便你打错,那么我给你们一个路径最新的插件地址: http://subclipse.tigris.org/update_1.10.x 第三步 点击OK之后,会节目会刷新出两个选项,需要选中的 第四步 点击next,出现许可的时候选中同意,一直结束等待安装完成就可以啦,过程有些慢,需要联网,耐心等待就可以了。重新启动myeclipse 在安装过程中会出现了一个警告提示,我直接忽略了,选中ok继续安装 第五步 安装完成的时候会提示你重启,重启myeclipse就可以啦 检查是否安装成功 重新启动之后,点击Windows---preferences 搞定了,现在开始使用myeclipse的svn创建项目啦。 我这里有个svn的路径,所以我创建项目的时候选中从svn导入 输入svn路径,等着下载完成就可以啦。 完 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
直接上干货 Private Function POST(ByVal URL$, ByVal data$) Dim http On Error Resume Next http = CreateObject("WinHttp.WinHttpRequest.5.1") With http .Open("POST", URL, True) .setRequestHeader("Accept-Language", "zh-CN") .setRequestHeader("Cache-Control", "no-cache") .setRequestHeader("User-Agent", "Mozilla/4.0") .setRequestHeader("Connection", "Keep-Alive") .setRequestHeader("Content-Length", data.Length) .setRequestHeader("Content-Type", "application/x-www-form-urlencoded") .Send(data) .WaitForResponse() POST = .getallresponseheaders Dim request = http.responseText End With http = Nothing End Function 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/
废话不多说,直接上步骤 第一步 打开网址:http://natapp.cn/ 第二步 下载客户端 如下图所示: 第三步 解压出来 第四步 直接运行即可得到对应的外网地址 End! 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载! 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。 我的博客:http://www.cnblogs.com/zhangxiaoyong/