[转载]ASP.NET Web API实现微信公众平台开发(一) - msp的昌伟哥哥 - 博客园

mikel阅读(778)

来源: [转载]ASP.NET Web API实现微信公众平台开发(一) – msp的昌伟哥哥 – 博客园

最近朋友的微信公众号准备做活动,靠固定的微信公众平台模版搞定不了,于是请我代为开发微信后台。鉴于我也是第一次尝试开发微信后台,所以也踩了不少坑,此系列博客将会描述微信公众号各项功能的实现。

 

先决条件

1.一台可部署web服务的服务器或者云平台(我采用的是Microsoft Azure)

2.一个可以正常使用的微信公众账号

3.Visual Studio

 

开发准备

1.采用ASP.NET Web API网站项目作为微信公众号后台服务

 

成为微信公众号开发者

 

这一步很简单,只要在微信公众号后台的开发者中心同意协议即可,同意之后页面如下:

 

 

服务器配置

 

登录微信公众平台官网后,在公众平台后台管理页面 – 开发者中心页,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件 的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。 EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码。

 

 

详细内容参考官方指南:http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html

这里我给出C#实现的签名生成代码:

 

复制代码
//注意返回echostr字符串类型微信只接受“application/x-www-form-urlencoded”,直接返回是不被接受的

public HttpResponseMessage Get(string signature, string timestamp, string nonce, string echostr)
        {
            if (CheckSource(signature, timestamp, nonce))
            {
                var result = new StringContent(echostr, UTF8Encoding.UTF8, "application/x-www-form-urlencoded");
                var response = new HttpResponseMessage { Content = result };
                return response;
            }
            return new HttpResponseMessage();
        }

//检验是否来自微信的签名
 public bool CheckSource(string signature, string timestamp, string nonce)
        {
            var str = string.Empty;
            var token = "cwtesttoken";
            var parameter = new List<string> { token, timestamp, nonce };
            parameter.Sort();
            var parameterStr = parameter[0] + parameter[1] + parameter[2];
            var tempStr = GetSHA1(parameterStr).Replace("-", "").ToLower();
            if (tempStr == signature)
                return true;

            return false;
        }

        //SHA1加密
        public string GetSHA1(string input)
        {
            var output = string.Empty;
            var sha1 = new SHA1CryptoServiceProvider();
            var inputBytes = UTF8Encoding.UTF8.GetBytes(input);
            var outputBytes = sha1.ComputeHash(inputBytes);
            sha1.Clear();
            output = BitConverter.ToString(outputBytes);
            return output;
        }
复制代码

 

这样在我们填入我们服务器端API地址后,就能够顺利搞定服务器配置,绑定微信公众号后台了。

 

 

 配置完成

 

 

总结

 

我们完成了配置微信公众号开发的第一步,后续将会利用微信的api进行更多的操作,比如自动回复语音、文章、图文等消息。

Android手机出现"已安装了存在签名冲突的同名数据包"的原因及解决办法

mikel阅读(723)

如果你不是开发者:如果你在Android上更新一个已经安装过较早版本软件时,安装到最后一步提示你:已安装了存在签名冲突的同名数据包,然后安装失败。这是因为旧版软件的签名信息与新版不一致造成的。你可以卸载这个软件,然后安装新版软件。

如果无法卸载,可能手机(pad)在发售前将该软件内置在手机中无法卸载。如果是这个原因的话,你可以尝试“root”系统,然后卸载掉该软件的旧版本,然后安装。

 

如 果你是一个开发人员,那么出现这个问题可能是因为,较旧的版本你是使用eclipse自动发布到模拟器上的,而eclipse自动发布时使用的是一个测试 用签名,这个签名与你正式打包的签名不是一个。(这个问题一般发生在测试自动更新功能上,嘿嘿)。想继续测试自动更新,解决的办法也很简单,手工删除该软 件的旧版(eclipse自动安装的那个),然后使用adb工具安装旧版再测试新版就好。

自动更新的安装代码一般是调用Intent安装

Intent i = new Intent(Intent.ACTION_VIEW);
i.setDataAndType(Uri.parse("file://" + apkfile.toString()),"application/vnd.android.package-archive");
mContext.startActivity(i);

或许你和这略有不同,不用担心,没什么,问题不在这里。

转自:http://hi.baidu.com/cenxcen/item/824ff249eb5909f2dd0f6c64

[转载]C#模拟百度登录并到指定网站评论回帖(三) - 断言鸟 - 博客园

mikel阅读(743)

来源: [转载]C#模拟百度登录并到指定网站评论回帖(三) – 断言鸟 – 博客园

上次说到怎么获取BAIDUID,这个相信很多人都能够拿到就不多说了,今天一连说两个,获取token和raskey

  2、利用以上获得的cookie直接访问页面
在第一篇文章中写到这个,拿着我们储存到的BAIDUID直接请求这个页面,就能够获取到反馈回来的Json格式的数据
这里的话需要用到一个dll,添加引用到项目【Newtonsoft.Json】
至于这个文档怎么用,已经有其他前辈的博文中说到,我这里就不解释了,如果不懂的可以在后面看代码
如果你拿不到token,那么只有一个问题,你没有BAIDUID,如果你确认第一次是收集到了,那么请重复检验在请求token时这个Cookies是否还存在。(很多时候是Cookies的效期问题导致Cookies收集器将失效的BAIDUID自动丢弃)
  有了前面的步骤,拿到raskey也就很简单了,步骤是一样的,将前面所有的Cookies都保存下来,接下来
  很明显,你会发现反馈回来的也是一个Json数据,但可以看到后面有一串以—–BEGIN PUBLIC KEY—– 开头以—-End Public KEY—-结尾的数据,那里面就是publickey,而紧跟在后的key后的字符串就是raskey,用上面同样的办法拿到这两组数据,切记前一组要将开头和结尾的标识字符串也要截取出来,而不是单纯的乱字符串。
  现在算一下我们拿到的东西:3个页面的Cookies、token、publickey、raskey
      这几个都齐全了,那么就可以向百度提交post请求登录,但在这之前,你需要准备一串经过ras加密过后的密码,否则会一直提示密码错误,也就是err_7。
      这块需要添加另外一个引用【BouncyCastle.Crypto.dll】这是哪个大神写的就不清楚了,但非常实用,很感谢这些大神做出的贡献。之后添加一个类:
</div>
<div>
<div>Newtonsoft.Json.Net35.dll(Josn数据解析)</div>
<div>BouncyCastle.Crypto.dll</div>
<div>using System;</div>
<div>using System.IO;</div>
<div>using System.Security.Cryptography;</div>
<div>using System.Text;</div>
<div>using Org.BouncyCastle.Crypto;</div>
<div>using Org.BouncyCastle.Crypto.Parameters;</div>
<div>using Org.BouncyCastle.OpenSsl;</div>
<div>using Org.BouncyCastle.Security;</div>
<div></div>
<div>namespace 纵横小说自动评论</div>
<div>{</div>
<div>    /// &lt;summary&gt;</div>
<div>    /// rsakey密码加密帮助</div>
<div>    /// &lt;/summary&gt;</div>
<div>   public class RsaHelper</div>
<div>    {</div>
<div>        public static string PemToXml(string pem)</div>
<div>        {</div>
<div>            if (pem.StartsWith("-----BEGIN RSA PRIVATE KEY-----")</div>
<div>                || pem.StartsWith("-----BEGIN PRIVATE KEY-----"))</div>
<div>            {</div>
<div>                return GetXmlRsaKey(pem, obj =&gt;</div>
<div>                {</div>
<div>                    if ((obj as RsaPrivateCrtKeyParameters) != null)</div>
<div>                        return DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)obj);</div>
<div>                    var keyPair = (AsymmetricCipherKeyPair)obj;</div>
<div>                    return DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)keyPair.Private);</div>
<div>                }, rsa =&gt; rsa.ToXmlString(true));</div>
<div>            }</div>
<div></div>
<div>            if (pem.StartsWith("-----BEGIN PUBLIC KEY-----"))</div>
<div>            {</div>
<div>                return GetXmlRsaKey(pem, obj =&gt;</div>
<div>                {</div>
<div>                    var publicKey = (RsaKeyParameters)obj;</div>
<div>                    return DotNetUtilities.ToRSA(publicKey);</div>
<div>                }, rsa =&gt; rsa.ToXmlString(false));</div>
<div>            }</div>
<div></div>
<div>            throw new InvalidKeyException("Unsupported PEM format...");</div>
<div>        }</div>
<div>        private static string GetXmlRsaKey(string pem, Func&lt;object, RSA&gt; getRsa, Func&lt;RSA, string&gt; getKey)</div>
<div>        {</div>
<div>            using (var ms = new MemoryStream())</div>
<div>            using (var sw = new StreamWriter(ms))</div>
<div>            using (var sr = new StreamReader(ms))</div>
<div>            {</div>
<div>                sw.Write(pem);</div>
<div>                sw.Flush();</div>
<div>                ms.Position = 0;</div>
<div>                var pr = new PemReader(sr);</div>
<div>                object keyPair = pr.ReadObject();</div>
<div>                using (RSA rsa = getRsa(keyPair))</div>
<div>                {</div>
<div>                    var xml = getKey(rsa);</div>
<div>                    return xml;</div>
<div>                }</div>
<div>            }</div>
<div>        }</div>
<div></div>
<div></div>
<div>        /// &lt;summary&gt;</div>
<div>        /// RSA加密</div>
<div>        /// &lt;/summary&gt;</div>
<div>        /// &lt;param name="publickey"&gt;&lt;/param&gt;</div>
<div>        /// &lt;param name="content"&gt;&lt;/param&gt;</div>
<div>        /// &lt;returns&gt;&lt;/returns&gt;</div>
<div>        public static string RSAEncrypt(string publickey, string content)</div>
<div>        {</div>
<div>            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();</div>
<div>            byte[] cipherbytes;</div>
<div>            rsa.FromXmlString(publickey);</div>
<div>            cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false);</div>
<div></div>
<div>            return Convert.ToBase64String(cipherbytes);</div>
<div>        }</div>
<div></div>
<div>        /// &lt;summary&gt;</div>
<div>        /// RSA解密</div>
<div>        /// &lt;/summary&gt;</div>
<div>        /// &lt;param name="privatekey"&gt;&lt;/param&gt;</div>
<div>        /// &lt;param name="content"&gt;&lt;/param&gt;</div>
<div>        /// &lt;returns&gt;&lt;/returns&gt;</div>
<div>        public static string RSADecrypt(string privatekey, string content)</div>
<div>        {</div>
<div>            privatekey =</div>
<div>                @"&lt;RSAKeyValue&gt;&lt;Modulus&gt;5m9m14XH3oqLJ8bNGw9e4rGpXpcktv9MSkHSVFVMjHbfv+SJ5v0ubqQxa5YjLN4vc49z7SVju8s0X4gZ6AzZTn06jzWOgyPRV54Q4I0DCYadWW4Ze3e+BOtwgVU1Og3qHKn8vygoj40J6U85Z/PTJu3hN1m75Zr195ju7g9v4Hk=&lt;/Modulus&gt;&lt;Exponent&gt;AQAB&lt;/Exponent&gt;&lt;P&gt;/hf2dnK7rNfl3lbqghWcpFdu778hUpIEBixCDL5WiBtpkZdpSw90aERmHJYaW2RGvGRi6zSftLh00KHsPcNUMw==&lt;/P&gt;&lt;Q&gt;6Cn/jOLrPapDTEp1Fkq+uz++1Do0eeX7HYqi9rY29CqShzCeI7LEYOoSwYuAJ3xA/DuCdQENPSoJ9KFbO4Wsow==&lt;/Q&gt;&lt;DP&gt;ga1rHIJro8e/yhxjrKYo/nqc5ICQGhrpMNlPkD9n3CjZVPOISkWF7FzUHEzDANeJfkZhcZa21z24aG3rKo5Qnw==&lt;/DP&gt;&lt;DQ&gt;MNGsCB8rYlMsRZ2ek2pyQwO7h/sZT8y5ilO9wu08Dwnot/7UMiOEQfDWstY3w5XQQHnvC9WFyCfP4h4QBissyw==&lt;/DQ&gt;&lt;InverseQ&gt;EG02S7SADhH1EVT9DD0Z62Y0uY7gIYvxX/uq+IzKSCwB8M2G7Qv9xgZQaQlLpCaeKbux3Y59hHM+KpamGL19Kg==&lt;/InverseQ&gt;&lt;D&gt;vmaYHEbPAgOJvaEXQl+t8DQKFT1fudEysTy31LTyXjGu6XiltXXHUuZaa2IPyHgBz0Nd7znwsW/S44iql0Fen1kzKioEL3svANui63O3o5xdDeExVM6zOf1wUUh/oldovPweChyoAdMtUzgvCbJk1sYDJf++Nr0FeNW1RB1XG30=&lt;/D&gt;&lt;/RSAKeyValue&gt;";</div>
<div>            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();</div>
<div>            byte[] cipherbytes;</div>
<div>            rsa.FromXmlString(privatekey);</div>
<div>            cipherbytes = rsa.Decrypt(Convert.FromBase64String(content), false);</div>
<div></div>
<div>            return Encoding.UTF8.GetString(cipherbytes);</div>
<div>        }</div>
<div>    }</div>
<div>}</div>
<div>
好了,用这个类将密码加密后就Post所有数据吧,下面是具体实现代码
</div>
<div> //获取token
private Regex _regex = new Regex(@"\{.*\}", RegexOptions.IgnoreCase);
/// &lt;summary&gt;
/// 根据BAIDUID获取token
/// &lt;/summary&gt;
public string GetToken()
{
HttpCookie cookie = new HttpCookie("BDRCVFR[gltLrB7qNCt]");
cookie.Value = "mk3SLVN4HKm";
cookie.Expires = DateTime.Now.AddDays(1);
string url_getToken = string.Format("<a href="https://passport.baidu.com/v2/api/?getapi&amp;tpl=zongheng&amp;apiver=v3&amp;tt=">https://passport.baidu.com/v2/api/?getapi&amp;tpl=zongheng&amp;apiver=v3&amp;tt=</a>{0}&amp;class=login&amp;gid=0743012-DD22-4632-B84E-B054B933DDA0&amp;logintype=basicLogin&amp;callback=bd__cbs__xjugf7", Utility.GetTimeStamp());
string token_get = helper.GetPageResponse_Get(url_getToken, Utility.UrlDecode("<a href="http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F">http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F</a>"), "*/*");
if (_regex.IsMatch(token_get))
token_get = _regex.Match(token_get).Value;
var resultToken = JsonConvert.DeserializeObject&lt;ResultToken&gt;(token_get);
if (string.IsNullOrEmpty(resultToken.Data.Token))
{
ShowLog("Parameter-:获取token=" + resultToken.Data.Token + "失败\n");
return null;
}
return resultToken.Data.Token;
}
//获取raskey
public string GetRasKey(string token)
{
string url_RasKey = string.Format("<a href="https://passport.baidu.com/v2/getpublickey?token=">https://passport.baidu.com/v2/getpublickey?token=</a>{0}&amp;tpl=zongheng&amp;apiver=v3&amp;tt={1}&amp;gid=399423B-A0F5-42A0-B07F-CC5290F8F95D&amp;callback=bd__cbs__xjugf7", token, Utility.GetTimeStamp());
string publicKey = helper.GetPageResponse_Get(url_RasKey, Utility.UrlDecode("<a href="http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F">http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F</a>"), "*/*");
//获取raskey
if (_regex.IsMatch(publicKey))
publicKey = _regex.Match(publicKey).Value;
var result_publicKey = JsonConvert.DeserializeObject&lt;PublicRsaKey&gt;(publicKey);
var rsakey = result_publicKey.Key;
//将密码加密
var pemToXml = RsaHelper.PemToXml(result_publicKey.Pubkey);
pwd = RsaHelper.RSAEncrypt(pemToXml, pwd);
//日志
if (!string.IsNullOrEmpty(rsakey))
return rsakey;
ShowLog("Parameter-:获取publickey=" + result_publicKey.Pubkey + "失败!");
ShowLog("Parameter-:获取raskey=" + rsakey + "失败!");
return null;
}/// &lt;summary&gt;
/// 登录实现
/// &lt;/summary&gt;
public LoginStatus Login(string verifycode, string codestring, string token, string raskey, int userIndex)
{
string loginresult = "";
string url_Post = "<a href="https://passport.baidu.com/v2/api/?login">https://passport.baidu.com/v2/api/?login</a>";
long tt= Utility.GetTimeStamp();
string postData = string.Format("verifycode={0}&amp;username={1}&amp;u=<a href="http://zongheng.baidu.com/sendbduss.do?source=0&amp;location=http%3A%2F%2Fwww.zongheng.com%2F&amp;_t=">http://zongheng.baidu.com/sendbduss.do?source=0&amp;location=http%3A%2F%2Fwww.zongheng.com%2F&amp;_t=</a>{2}&amp;tt={3}&amp;tpl=zongheng&amp;token={4}&amp;staticpage=<a href="http://passport.zongheng.com/v3Jump.html&amp;safeflg=0&amp;rsakey=">http://passport.zongheng.com/v3Jump.html&amp;safeflg=0&amp;rsakey=</a>{5}&amp;quick_user=0&amp;ppui_logintime=20266&amp;password={6}&amp;mem_pass=on&amp;logLoginType=pc_loginBasic&amp;logintype=basicLogin&amp;loginmerge=true&amp;isPhone=false&amp;gid=0743012-DD22-4632-B84E-B054B933DDA0&amp;detect=1&amp;crypttype=12&amp;codestring={7}&amp;charset=utf-8&amp;callback=parent.bd__pcbs__ok0875&amp;apiver=v3", verifycode, HttpUtility.UrlEncode(userName), tt,tt, HttpUtility.UrlEncode(token), HttpUtility.UrlEncode(raskey), HttpUtility.UrlEncode(pwd), codestring);
loginresult =helper.GetPageResponse_Post(url_Post, postData, Utility.UrlDecode("<a href="http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F">http://passport.zongheng.com/?location=http%3A%2F%2Fwww.zongheng.com%2F</a>"));
//分析loginresult
string err = @"(?&lt;=err_no=)[^&amp;]+?(?=&amp;)";
Match regx = Regex.Match(loginresult, err);
string codestr = "(?&lt;=codeString=)[^&amp;]+?(?=&amp;)";
string codetype = "(?&lt;=vcodetype=)[^&amp;]+?(?=&amp;)";
Match regx2 = Regex.Match(loginresult, codestr);
Match regx3 = Regex.Match(loginresult, codetype);
string errno = regx.Value;
_codestring = regx2.Value;
vcodeType= regx3.Value;
//根据返回的错误号提示信息,并且请求验证码
switch (errno)
{
case "257":
return LoginStatus.codeGet;
case "7": ShowLog("账号或密码错误...");
ChangeStauts("失败", userIndex);
return LoginStatus.loginFail;;
case "0": ShowLog("登录成功...");
ChangeStauts("成功", userIndex);
break;
default: ShowLog("未知错误...");
ChangeStauts("错误", userIndex);
return LoginStatus.Error;;
}
return LoginStatus.loginSucceed;
}</div>
<div>
今天就到这里,明天说说验证码的问题,不过很大部分情况下,除非你的密码几次输入错误或者异地登录,不然一般不会让你输入验证码,如有问题欢迎评论交流

[转载]01-二维码名片的生成与读取 - 世俗孤岛 - 博客园

mikel阅读(769)

来源: [转载]01-二维码名片的生成与读取 – 世俗孤岛 – 博客园

概述


  • 通过某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的
  • 二维码通常可以包含以下内容
    • 纯文本
    • 名片
    • URL
  • 二维码具有非常广泛的应用
    • 二维码名片
    • 扫码付款
    • 网址(URL),扫描后自动打开网址

二维码的生成


  • 生成原理
    • 通过一个类CIFilter(滤镜)包含二维码中所有的信息,然后生成一张二维码图片
    • 二维码中间的icon(头像),是通过在生成的图片上添加一张图片实现的
  • 生成步骤
    • 创建滤镜
    • 初始化滤镜
    • 添加二维码信息
    • 获取生成的二维码图片
    • 将二维码图片展示出来
  • 生成的二维码的效果图

  • 具体代码实现
    • 通过storyboard创建两个imageView,分别用来展示二维码图片和头像

    • 拥有展示二维码图片的imageView
      @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    • 生成二维码
      /**点击屏幕的时候显示二维码*/
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          //1.创建滤镜
          CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
      
          //2.恢复默认
          [filter setDefaults];
      
          //3.给滤镜添加数据
          NSString *dataString = @"http://www.cnblogs.com/theDesertIslandOutOfTheWorld/";
      //    NSString *dataString = @"世俗孤岛";
          //将数据转换成NSData类型
          NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
          //通过KVC设置滤镜的二维码输入信息
          [filter setValue:data forKey:@"inputMessage"];
      
          //4.获取输出的二维码图片(CIImage类型)
          CIImage *outImage = [filter outputImage];
          //将CIImage类型的图片装换成UIImage类型的图片
          UIImage *image = [UIImage imageWithCIImage:outImage];
      
          //5.显示二维码图片
          self.imageView.image = image;
      }
    • 通过二维码扫描工具,可以扫描生成的二维码
      • 若二维码的输入信息是URL,则会打开指定的网络资源

二维码的扫描


  • 注意:
    • 二维码扫码需要使用摄像头,需要真机设备
    • Xcode7可是免费真机调试
  • 二维码扫描的过程
    • 通过摄像头扫描二维码
    • 解析二维码数据
    • 根据二维码信息执行相应的操作
  • 过程分析
    • 通过摄像头扫描二维码信息需要用到AVCaptureSession类
      • 该类是用来协调从音频/视频输入端口到输出端的数据
      • 该类需要一个输入设备AVCaptureDeviceInput,通常是摄像头
      • 该类需要一个输出端,输出指定类型的数据,有多个类可以选择(如:AVCaptureMetadataOutput)
      • 需要调用- startRunning方法开启输入源
    • 通过AVCaptureSession类捕获的数据,可以通过指定输出端的过滤类型来以特定的类型解析捕获的数据
    • 可以为输出端AVCaptureMetadataOutput设置代理,来听捕获过程
    • 通过AVCaptureVideoPreviewLayer类将获取的原始数据渲染到指定的图层
  • 二维码的生成过程
    • 通过成员属性保存输入源与输出端的协调者(AVCaptureSession对象)
      @property (nonatomic, weak) AVCaptureSession *session;
    • 通过成员属性保存展示通过输入设备输入的元数据的图层,用于在适当的时候移除图层
      @property (nonatomic, weak) AVCaptureVideoPreviewLayer *layer;
    • 创建AVCaptureSession类的对象(简称:捕获对象),用于捕获音频/视频端口的数据
      AVCaptureSession *session = [[AVCaptureSession alloc] init];
      self.session = session;
    • 给捕获对象添加输入设备(通常是摄像头)
      //设置输入设备输入数据的类型(视频)
      AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
      AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
      [session addInput:input];
    • 给捕获对象添加输出端
      AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
      //设置代理
      [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
      [session addOutput:output];
    • 设置输入源的过滤条件(只获取二维码信息)
      [output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
    • 将捕获的原始数据显示到控制器的view上
      AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:session];
      layer.frame = self.view.bounds;
      [self.view.layer addSublayer:layer];
    • 开始获取数据
      [session startRunning];
    • 实现输出端的代理方法,监听捕获过程
      - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
      {
          if (metadataObjects.count > 0)
          {
          	//获取输出端的信息
              AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
              NSLog(@"%@", object.stringValue);
      
              //停止扫描
              [self.session stopRunning];
      
              //将展示图层移除
              [self.layer removeFromSuperlayer];
          }
          else
          {
              NSLog(@"没有扫描到数据");
          }
      }

MySQL的jdbc驱动及连接字符串

mikel阅读(832)

MySQL的jdbc驱动及连接字符串_pizza_新浪博客,pizza,

 mySQL JDBC 驱动常用的有两个,一个是gjt(Giant Java Tree)组织提供的mysql驱动,其JDBC Driver名称(JAVA类名)为:org.gjt.mm.mysql.Driver

详情请参见网站http://www.gjt.org/

另一个是mysql官方提供的JDBC Driver,其JAVA类名为:com.mysql.jdbc.Driver

驱动下载网址:http://dev.mysql.com/downloads/,进入其中的MySQL Connector/J区域下载。

mysql JDBC URL格式如下:

jdbc:mysql://[host:port]/[database][?参数名1][=参数值1][&参数名2][=参数值2]…

参数名称 参数说明 缺省值 最低版本要求
user 数据库用户名(用于连接数据库) 所有版本
password 用户密码(用于连接数据库) 所有版本
useUnicode 是否使用Unicode字符集,如果参数characterEncoding设置为gb2312或gbk,本参数值必须设置为true false 1.1g
characterEncoding 当useUnicode设置为true时,指定字符编码。比如可设置为gb2312或gbk false 1.1g
autoReconnect 当数据库连接异常中断时,是否自动重新连接? false 1.1
autoReconnectForPools 是否使用针对数据库连接池的重连策略 false 3.1.3
failOverReadOnly 自动重连成功后,连接是否设置为只读? true 3.0.12
maxReconnects autoReconnect设置为true时,重试连接的次数 3 1.1
initialTimeout autoReconnect设置为true时,两次重连之间的时间间隔,单位:秒 2 1.1
connectTimeout 和数据库服务器建立socket连接时的超时,单位:毫秒。 0表示永不超时,适用于JDK 1.4及更高版本 0 3.0.1
socketTimeout socket操作(读写)超时,单位:毫秒。 0表示永不超时 0 3.0.1

对应中文环境,通常mysql连接URL可以设置为:

jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false

在使用数据库连接池的情况下,最好设置如下两个参数:

autoReconnect=true&failOverReadOnly=false

需要注意的是,在xml配置文件中,url中的&符号需要转义。比如在tomcat的server.xml中配置数据库连接池时,mysql jdbc url样例如下:

jdbc:mysql://localhost:3306/test?user=root&password=&useUnicode=true&characterEncoding=gbk

&autoReconnect=true&failOverReadOnly=false

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=569304

Mysql命令大全

mikel阅读(727)

1、连接MySQL

格式: mySQL -h主机地址 -u用户名 -p用户密码

1、连接到本机上的MYSQL。
首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u root -p,回车后提示你输密码.注意用户名前可以有空格也可以没有空格,但是密码前必须没有空格,否则让你重新输入密码。

如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是: mysql>

2、连接到远程主机上的MYSQL。假设远程主机的IP为:110.110.110.110,用户名为root,密码为abcd123。则键入以下命令:
mysql -h110.110.110.110 -u root -p 123;(注:u与root之间可以不用加空格,其它也一样)

3、退出MYSQL命令: exit (回车)

2、修改密码

格式:mysqladmin -u用户名 -p旧密码 password 新密码

1、给root加个密码ab12。
首先在DOS下进入目录mysql\bin,然后键入以下命令
mysqladmin -u root -password ab12
注:因为开始时root没有密码,所以-p旧密码一项就可以省略了。

2、再将root的密码改为djg345。
mysqladmin -u root -p ab12 password djg345

3、增加新用户

注意:和上面不同,下面的因为是MYSQL环境中的命令,所以后面都带一个分号作为命令结束符

格式:grant select on 数据库.* to 用户名@登录主机 identified by “密码”

1、增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限。首先用root用户连入MYSQL,然后键入以下命令:
grant select,insert,update,delete on *.* to [email=test1@”%]test1@”%[/email]” Identified by “abc”;

但增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了,解决办法见2。

2、增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作(localhost指本地主机,即MYSQL数据库所在的那台主机),这样用户即使用知道test2的密码,他也无法从internet上直接访问数据库,只能通过MYSQL主机上的web页来访问了。
grant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by “abc”;

如果你不想test2有密码,可以再打一个命令将密码消掉。
grant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by “”;

4.1 创建数据库

注意:创建数据库之前要先连接Mysql服务器

命令:create database <数据库名>

例1:建立一个名为xhkdb的数据库
mysql> create database xhkdb;

例2:创建数据库并分配用户

①CREATE DATABASE 数据库名;

②GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON 数据库名.* TO 数据库名@localhost IDENTIFIED BY ‘密码’;

③SET PASSWORD FOR ‘数据库名’@’localhost’ = OLD_PASSWORD(‘密码’);

依次执行3个命令完成数据库创建。注意:中文 “密码”和“数据库”是户自己需要设置的。

4.2 显示数据库

命令:show databases (注意:最后有个s)
mysql> show databases;

注意:为了不再显示的时候乱码,要修改数据库默认编码。以下以GBK编码页面为例进行说明:

1、修改MYSQL的配置文件:my.ini里面修改default-character-set=gbk
2、代码运行时修改:
①Java代码:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk
②PHP代码:header(“Content-Type:text/html;charset=gb2312”);
③C语言代码:int mysql_set_character_set( MYSQL * mysql, char * csname);
该 函数用于为当前连接设置默认的字符集。字符串csname指定了1个有效的字符集名称。连接校对成为字符集的默认校对。该函数的工作方式与SET NAMES语句类似,但它还能设置mysql- > charset的值,从而影响了由mysql_real_escape_string() 设置的字符集。

4.3 删除数据库

命令:drop database <数据库名>
例如:删除名为 xhkdb的数据库
mysql> drop database xhkdb;

例子1:删除一个已经确定存在的数据库
mysql> drop database drop_database;
Query OK, 0 rows affected (0.00 sec)

例子2:删除一个不确定存在的数据库
mysql> drop database drop_database;
ERROR 1008 (HY000): Can’t drop database ‘drop_database’; database doesn’t exist
//发生错误,不能删除’drop_database’数据库,该数据库不存在。
mysql> drop database if exists drop_database;
Query OK, 0 rows affected, 1 warning (0.00 sec)//产生一个警告说明此数据库不存在
mysql> create database drop_database;
Query OK, 1 row affected (0.00 sec)
mysql> drop database if exists drop_database;//if exists 判断数据库是否存在,不存在也不产生错误
Query OK, 0 rows affected (0.00 sec)

4.4 连接数据库

命令: use <数据库名>

例如:如果xhkdb数据库存在,尝试存取它:
mysql> use xhkdb;
屏幕提示:Database changed

use 语句可以通告MySQL把db_name数据库作为默认(当前)数据库使用,用于后续语句。该数据库保持为默认数据库,直到语段的结尾,或者直到发布一个不同的USE语句:
mysql> USE db1;
mysql> SELECT COUNT(*) FROM mytable;   # selects from db1.mytable
mysql> USE db2;
mysql> SELECT COUNT(*) FROM mytable;   # selects from db2.mytable

使用USE语句为一个特定的当前的数据库做标记,不会阻碍您访问其它数据库中的表。下面的例子可以从db1数据库访问作者表,并从db2数据库访问编辑表:
mysql> USE db1;
mysql> SELECT author_name,editor_name FROM author,db2.editor
->        WHERE author.editor_id = db2.editor.editor_id;

USE语句被设立出来,用于与Sybase相兼容。

有些网友问到,连接以后怎么退出。其实,不用退出来,use 数据库后,使用show databases就能查询所有数据库,如果想跳到其他数据库,用
use 其他数据库名字
就可以了。

4.5 当前选择的数据库

命令:mysql> select database();

MySQL中SELECT命令类似于其他编程语言里的print或者write,你可以用它来显示一个字符串、数字、数学表达式的结果等等。如何使用MySQL中SELECT命令的特殊功能?

1.显示MYSQL的版本
mysql> select version();
+———————–+
| version()             |
+———————–+
| 6.0.4-alpha-community |
+———————–+
1 row in set (0.02 sec)

2. 显示当前时间
mysql> select now();
+———————+
| now()               |
+———————+
| 2009-09-15 22:35:32 |
+———————+
1 row in set (0.04 sec)

3. 显示年月日
SELECT DAYOFMONTH(CURRENT_DATE);
+————————–+
| DAYOFMONTH(CURRENT_DATE) |
+————————–+
|                       15 |
+————————–+
1 row in set (0.01 sec)

SELECT MONTH(CURRENT_DATE);
+———————+
| MONTH(CURRENT_DATE) |
+———————+
|                   9 |
+———————+
1 row in set (0.00 sec)

SELECT YEAR(CURRENT_DATE);
+——————–+
| YEAR(CURRENT_DATE) |
+——————–+
|               2009 |
+——————–+
1 row in set (0.00 sec)

4. 显示字符串
mysql> SELECT “welecome to my blog!”;
+———————-+
| welecome to my blog! |
+———————-+
| welecome to my blog! |
+———————-+
1 row in set (0.00 sec)

5. 当计算器用
select ((4 * 4) / 10 ) + 25;
+———————-+
| ((4 * 4) / 10 ) + 25 |
+———————-+
|                26.60 |
+———————-+
1 row in set (0.00 sec)

6. 串接字符串
select CONCAT(f_name, ” “, l_name)
AS Name
from employee_data
where title = ‘Marketing Executive’;
+—————+
| Name          |
+—————+
| Monica Sehgal |
| Hal Simlai    |
| Joseph Irvine |
+—————+
3 rows in set (0.00 sec)
注意:这里用到CONCAT()函数,用来把字符串串接起来。另外,我们还用到以前学到的AS给结果列’CONCAT(f_name, ” “, l_name)’起了个假名。

5.1 创建数据表

命令:create table <表名> ( <字段名1> <类型1> [,..<字段名n> <类型n>]);

例如,建立一个名为MyClass的表,

字段名 数字类型 数据宽度 是否为空 是否主键 自动增加 默认值
id int 4 primary key auto_increment
name char 20
sex int 4 0
degree double 16

mysql> create table MyClass(
> id int(4) not null primary key auto_increment,
> name char(20) not null,
> sex int(4) not null default ‘0’,
> degree double(16,2));

5.3 删除数据表

命令:drop table <表名>

例如:删除表名为 MyClass 的表
mysql> drop table MyClass;

DROP TABLE用于取消一个或多个表。您必须有每个表的DROP权限。所有的表数据和表定义会被取消,所以使用本语句要小心!

注意:对于一个带分区的表,DROP TABLE会永久性地取消表定义,取消各分区,并取消储存在这些分区中的所有数据。DROP TABLE还会取消与被取消的表有关联的分区定义(.par)文件。

对与不存在的表,使用IF EXISTS用于防止错误发生。当使用IF EXISTS时,对于每个不存在的表,会生成一个NOTE。

RESTRICT和CASCADE可以使分区更容易。目前,RESTRICT和CASCADE不起作用。

5.4 表插入数据

命令:insert into <表名> [( <字段名1>[,..<字段名n > ])] values ( 值1 )[, ( 值n )]

例如:往表 MyClass中插入二条记录, 这二条记录表示:编号为1的名为Tom的成绩为96.45, 编号为2 的名为Joan 的成绩为82.99, 编号为3 的名为Wang 的成绩为96.5。
mysql> insert into MyClass values(1,’Tom’,96.45),(2,’Joan’,82.99), (2,’Wang’, 96.59);

注意:insert into每次只能向表中插入一条记录。

5.5 查询表中的数据

1)、查询所有行
命令: select <字段1,字段2,…> from < 表名 > where < 表达式 >
例如:查看表 MyClass 中所有数据
mysql> select * from MyClass;

2)、查询前几行数据
例如:查看表 MyClass 中前2行数据
mysql> select * from MyClass order by id limit 0,2;

select一般配合where使用,以查询更精确更复杂的数据。

5.6 删除表中数据

命令:delete from 表名 where 表达式

例如:删除表 MyClass中编号为1 的记录
mysql> delete from MyClass where id=1;

下面是一个删除数据前后表的对比。

FirstName LastName Age
Peter Griffin 35
Glenn Quagmire 33

下面以PHP代码为例删除 “Persons” 表中所有 LastName=’Griffin’ 的记录:

<?php 
   $con = mysql_connect("localhost","peter","abc123"); 
   if (!$con) 
   {
      die('Could not connect: ' . mysql_error()); 
   } 
   mysql_select_db("my_db", $con); 
   mysql_query("DELETE FROM Persons WHERE LastName='Griffin'"); mysql_close($con); 
?>

在这次删除之后,表是这样的:

FirstName LastName Age
Glenn Quagmire 33

5.7 修改表中数据

语法:update 表名 set 字段=新值,… where 条件
mysql> update MyClass set name=’Mary’ where id=1;

例子1:单表的MySQL UPDATE语句:
UPDATE [LOW_PRIORITY] [IGNORE] tbl_name SET col_name1=expr1 [, col_name2=expr2 …] [WHERE where_definition] [ORDER BY …] [LIMIT row_count]

例子2:多表的UPDATE语句:
UPDATE [LOW_PRIORITY] [IGNORE] table_references SET col_name1=expr1 [, col_name2=expr2 …] [WHERE where_definition]

UPDATE 语法可以用新值更新原有表行中的各列。SET子句指示要修改哪些列和要给予哪些值。WHERE子句指定应更新哪些行。如果没有WHERE子句,则更新所有 的行。如果指定了ORDER BY子句,则按照被指定的顺序对行进行更新。LIMIT子句用于给定一个限值,限制可以被更新的行的数目。

5.8 增加字段

命令:alter table 表名 add字段 类型 其他;
例如:在表MyClass中添加了一个字段passtest,类型为int(4),默认值为0
mysql> alter table MyClass add passtest int(4) default ‘0’

加索引
mysql> alter table 表名 add index 索引名 (字段名1[,字段名2 …]);
例子: mysql> alter table employee add index emp_name (name);

加主关键字的索引
mysql> alter table 表名 add primary key (字段名);
例子: mysql> alter table employee add primary key(id);

加唯一限制条件的索引
mysql> alter table 表名 add unique 索引名 (字段名);
例子: mysql> alter table employee add unique emp_name2(cardnumber);

删除某个索引
mysql> alter table 表名 drop index 索引名;
例子: mysql>alter table employee drop index emp_name;

增加字段:
mysql> ALTER TABLE table_name ADD field_name field_type;

修改原字段名称及类型:
mysql> ALTER TABLE table_name CHANGE old_field_name new_field_name field_type;

删除字段:
MySQL ALTER TABLE table_name DROP field_name;

5.9 修改表名

命令:rename table 原表名 to 新表名;

例如:在表MyClass名字更改为YouClass
mysql> rename table MyClass to YouClass;

当你执行 RENAME 时,你不能有任何锁定的表或活动的事务。你同样也必须有对原初表的 ALTER 和 DROP 权限,以及对新表的 CREATE 和 INSERT 权限。

如果在多表更名中,MySQL 遭遇到任何错误,它将对所有被更名的表进行倒退更名,将每件事物退回到最初状态。

RENAME TABLE 在 MySQL 3.23.23 中被加入。

6、备份数据库

命令在DOS的[url=file://\\mysql\\bin]\\mysql\\bin[/url]目录下执行

1.导出整个数据库
导出文件默认是存在mysql\bin目录下
mysqldump -u 用户名 -p 数据库名 > 导出的文件名
mysqldump -u user_name -p123456 database_name > outfile_name.sql

2.导出一个表
mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
mysqldump -u user_name -p database_name table_name > outfile_name.sql

3.导出一个数据库结构
mysqldump -u user_name -p -d –add-drop-table database_name > outfile_name.sql
-d 没有数据 –add-drop-table 在每个create语句之前增加一个drop table

4.带语言参数导出
mysqldump -uroot -p –default-character-set=latin1 –set-charset=gbk –skip-opt database_name > outfile_name.sql

例如,将aaa库备份到文件back_aaa中:
[root@test1 root]# cd /home/data/mysql
[root@test1 mysql]# mysqldump -u root -p –opt aaa > back_aaa

7.1 一个建库和建表的实例1

drop database if exists school; //如果存在SCHOOL则删除
create database school; //建立库SCHOOL
use school; //打开库SCHOOL
create table teacher //建立表TEACHER
(
id int(3) auto_increment not null primary key,
name char(10) not null,
address varchar(50) default ‘深圳’,
year date
); //建表结束

//以下为插入字段
insert into teacher values(”,’allen’,’大连一中’,’1976-10-10′);
insert into teacher values(”,’jack’,’大连二中’,’1975-12-23′);

如果你在mysql提示符键入上面的命令也可以,但不方便调试。
1、你可以将以上命令原样写入一个文本文件中,假设为school.sql,然后复制到c:\\下,并在DOS状态进入目录[url=file://\\mysql\\bin]\\mysql\\bin[/url],然后键入以下命令:
mysql -uroot -p密码 < c:\\school.sql
如果成功,空出一行无任何显示;如有错误,会有提示。(以上命令已经调试,你只要将//的注释去掉即可使用)。

2、或者进入命令行后使用 mysql> source c:\\school.sql; 也可以将school.sql文件导入数据库中。

7.2 一个建库和建表的实例2

drop database if exists school; //如果存在SCHOOL则删除
create database school; //建立库SCHOOL
use school; //打开库SCHOOL
create table teacher //建立表TEACHER
(
id int(3) auto_increment not null primary key,
name char(10) not null,
address varchar(50) default ”深圳”,
year date
); //建表结束

//以下为插入字段
insert into teacher values(””,”glchengang”,”深圳一中”,”1976-10-10”);
insert into teacher values(””,”jack”,”深圳一中”,”1975-12-23”);

注:在建表中
1、将ID设为长度为3的数字字段:int(3);并让它每个记录自动加一:auto_increment;并不能为空:not null;而且让他成为主字段primary key。

2、将NAME设为长度为10的字符字段

3、将ADDRESS设为长度50的字符字段,而且缺省值为深圳。

4、将YEAR设为日期字段。

[转载]MVC5+EF6 入门完整教程11--细说MVC中仓储模式的应用 - MiroYuan - 博客园

mikel阅读(756)

来源: [转载]MVC5+EF6 入门完整教程11–细说MVC中仓储模式的应用 – MiroYuan – 博客园

摘要:

第一阶段1~10篇已经覆盖了MVC开发必要的基本知识。

第二阶段11~20篇将会侧重于专题的讲解,一篇文章解决一个实际问题。

根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解。

文章提纲

  • 概述要点
  • 理论基础
  • 详细步骤
  • 总结

概述要点

设计模式的产生,就是在对开发过程进行不断的抽象。

我们先看一下之前访问数据的典型过程。

在Controller中定义一个Context, 例如:

private AccountContext db = new AccountContext();

在Action中访问,例如获取用户列表:

var users=db.SysUsers;

 

类似于这种,耦合性太高。业务逻辑直接访问数据存储层会导致一些问题,如

重复代码;不容易集中使用数据相关策略,例如缓存;后续维护,修改增加新功能不方便 等等。

 

我们使用repository来将业务层和数据实体层分开来,业务逻辑层应该对组成数据源层的数据类型不可知,比如数据源可能是数据库或者Web service

在数据源层和业务层之间增加一个repository层进行协调,有如下作用:

1.从数据源中查询数据

2.映射数据到业务实体

3.将业务实体数据的修改保存到数据源 (持久化数据)

这样repository就将业务逻辑和基础数据源的交互进行了分隔。

数据和业务层的分离有如下三个优点:

1.集中管理不同的底层数据源逻辑。

2.给单元测试提供分离点。

3.提供弹性架构,整体设计可以适应程序的不断进化。

我们将会对原有做法进行两轮抽象,实现我们想要的效果。

 

理论基础

仓储和工作单元模式是用来在数据访问层和业务逻辑层之间创建一个抽象层。

应用这些模式,可以帮助用来隔离你的程序在数据存储变化。

 

 

下图比较了不使用库模式和使用库模式时controller和context 交互方式的差异。

说明:库模式的实现有多种做法,下图是其中一种。

 

 

准备工作

首先我们先搭建好空的框架,准备基本的结构和一些测试数据。

我们不再在第一阶段的MVCDemo上进行更改了, 重新建立一个新项目XEngine作为我们第二阶段的演示项目 。

1.新建项目

 

2.新建Model

我们新建 SysUser, SysRole , SysUserRole

 

3. 安装EF, 准备测试数据

a.安装EF

b.新建文件夹DAL

à 新建类 XEngineContext.cs

à 新建类 XEngineInitializer.cs

 

c.修改HomeController.cs,运行Index视图,来生成数据库结构和测试数据。

说明:准备工作有不清楚的请参考第三篇文章:

http://www.cnblogs.com/miro/p/4053473.html

 

至此,准备工作已经OK,下面就看看如何运用仓储模式来改造我们的项目。

 

详细步骤

整个过程分成两轮抽象。

第一轮抽象 : 解耦Controller和数据层

对每一个实体类型建立一个对应的仓储类。

以SysUser来说,新建一个仓储接口和仓储类。

在controller中通过类似于下面这种方式使用:

ISysUserRepository sysUserRepository = new SysUserRepository();

 

下面来看创建 SysUser 仓储类具体做法:

1.新建个文件夹 Repositories, 后面新建的仓储类都放在这个文件夹中

 

2.创建接口 ISysUserRepository

接口中声明了一组典型的CRUD方法。

其中查找方法有两个:返回全部和根据ID返回单个。

 

 

 

3.创建对应的仓储类 SysUserRepository

创建类 SysUserRepository, 实现接口 ISysUserRepository

a. 把接口中的方法全部实现

 

b.关闭连接

 

说明:

GC.SuppressFinalize(this);

因为对象会被Dispose释放,所以需要调用GC.SuppressFinalize来让对象脱离终止队列,防止对象终止被执行两次。

 

 

 

 

 

 

 

4.Controller中使用SysUser仓储类

我们新建个Controller : UserController

用 List 模板生成视图。

 

修改Controller如下:

 

运行Index就可以看到用户列表了。

 

 

 

更新和删除就不再举例了,都比较简单,大家可以自己去试验,方法类似。

至此,第一次抽象就完成了。

可以看到,我们增加了一个抽象层,将数据连接的部分移到Repository中去,这样实现了Controller和数据层的解耦。

 

观察一下可以发现,还存在两个问题:

1.如果一个controller中用到多个repositories,每个都会产生一个单独的context

2.每个entity type 都要实现一个对应的repository class ,这样会产生代码冗余。

下面我们就再进行一次抽象,解决这两个问题。

说明:

为方便讲述,实体类型 和 仓储类 以下直接用 entity type 和 repository class表示。

 

 

 

第二轮抽象:通过泛型消除冗余的repository class

为每个 entity type 创建一个repository class 会

a. 产生很多冗余代码

b. 会导致不一致地更新

举例:

你要在一个 transaction中更新两个不同的 entity type

如果使用不同的context instance, 一个可能成功,另外一个可能失败。

 

我们使用 generic repository去除冗余代码

使用unit of work保证所有repositories使用同一个 context

说明:

后面将会新建一个unit of work class 用来协调多个repositories工作, 通过创建单一的context让大家共享。

 

 

一、首先解决代码冗余的问题。

我们对ISysUserRepository和SysUserRepository 再进行一次抽象,去除repository class的冗余。

 

仿照ISysUserRepository和SysUserRepository,新建IGenericRepository和GenericRepository

步骤和前面类似:

1. 创建泛型接口 IGenericRepository

下图中右边为IGenericRepository, 大家观察下两者的区别

2.创建对应的泛型仓储类 GenericRepository

下面折叠起来的部分没有任何变化。

 

3.修改UserController

把原来的注释掉,给泛型类指定SysUser,主要更改部分如红线表示。前端不用做任何更改。

 

运行Index就可以看到和之前一样的结果了。

 

大家可以看到,通过泛型类已经消除了冗余。

如果有其他实体只需要改变传入的TEntity就可以了,不需要再重新创建repository class

 

二、接下来解决第二个问题:context的一致性。

我们在DAL文件夹中新建一个类UnitOfWork用来负责context的一致性:

当使用多个repositories时,共享同一个context

我们把使用多个repositories的一系列操作称为一个 unit of work

当一个unit of work完成时,我们调用context的SaveChanges方法来完成实际的更改。由于是同一个context, 所有相关的操作将会被协调好。

这个类只需要一个Save方法和一组repository属性。

每个repository属性返回一个repository实例,所有这些实例都会共享同样的context.

把 GenericRepository.cs 中的Save 和 Dispose 删除, 移到UnitOfWork中。

将IGenericRepository 中的IDisposable接口继承也去掉.

Save & Dispose 的工作统一在UnitOfWork中完成。

 

 

在UserController中使用UnitOfWork, 修改如下:

前端同样不用做任何更改,运行Index就可以看到和之前一样的结果了。

 

三、And one more thing

前面已经将代码冗余和context不一致的问题全都解决了。

不过还有个问题。

大家看我们的查询方法:

IEnumerable<TEntity> Get();

TEntity GetByID(object id);

上面的方法一个是返回所有结果,一个是根据id返回单笔记录。

在实际应用中,有个问题肯定会遇到:

需要根据各种条件进行查询。

比如 查询用户, 要实现类似 GetUsersByName 或者 GetUsersByDescription 等等。

解决这个问题常用的一种做法是:

创建一个继承类,针对特定的entity type 添加 特定的Get方法,比如前面说的 GetUsersByName.

这样做有一个缺点,会产生冗余代码。特别是在一个复杂程序中,可能会产生大量的继承类和特定的方法,维护起来又需要花费很多工作。我们不用这种做法。

 

我们使用表达式参数的方法。

改造一下Get方法。

先分析一下需求,常用的有三点:

1. 过滤条件

2. 排序

3. 需要Eager loading 的相关数据

针对这三点,我们给Get加入三个可选参数

再来看下GenericRepository 中的实现

 

最后修改下Index方法做测试:

a. 不加入任何条件

b. 加入过滤条件,选出张三

 

c. 按姓名排序

好了,到目前为止,应该接近完美了,数据层的问题应该可以解决一半了。

 

总结

本篇是MVC系列文章,第二阶段专题篇的第一篇。

首先将Controller和Context之间抽象出一层来专门负责数据访问。

然后进行第二次抽象,将共有的方法进行泛化,提取出一个GenericRepository出来,将每个具体的类型放到UnitOfWork中进行统一处理。

最后改造了查询方法,通过传入表达式可以根据条件灵活返回查询结果。

需要说明的是,EF本身的设计其实就是repository+unit of work , 如果是简单应用直接用原来的做法就可以了。

另外MVC中仓储模式的实现也有多种做法,本文介绍的微软官方文档提供的做法,个人觉得是比原生的做法更方便灵活,也更容易扩展。

 

感谢大家支持,祝学习进步。

 

PS.

另外公司研发部招聘工程师一名,主要研发数据可视化相关新产品,有兴趣的可以博客园短消息联系我。

base 在苏州高新区

ssh mysql命令大全

mikel阅读(960)

启动:net start mySQL;
进入:mySQL -u root -p/mySQL -h localhost -u root -p databaseName;
列出数据库:show databases;
选择数据库:use databaseName;
列出表格:show tables;
显示表格列的属性:show columns from tableName;
建立数据库:source fileName.txt;
匹配字符:可以用通配符_代表任何一个字符,%代表任何字符串;
增加一个字段:alter table tabelName add column fieldName dateType;
增加多个字段:alter table tabelName add column fieldName1 dateType,add columns fieldName2 dateType;
多行命令输入:注意不能将单词断开;当插入或更改数据时,不能将字段的字符串展开到多行里,否则硬回车将被储存到数据中;
增加一个管理员帐户:grant all on *.* to user@localhost identified by “password”;
每条语句输入完毕后要在末尾填加分号’;’,或者填加’\g’也可以;
查询时间:select now();
查询当前用户:select user();
查询数据库版本:select version();
查询当前使用的数据库:select database();

1、删除student_course数据库中的students数据表:
rm -f student_course/students.*

2、备份数据库:(将数据库test备份)
导出整个数据库
mysqldump -u 用户名 -p 数据库名 > 导出的文件名
mysqldump -u root -p dataname >dataname.sql
这个时候会提示要你输入root用户名的密码,输入密码后dataname数据库就成功备份在mysql/bin/目录中.

导出一个表
mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
mysqldump -u root -p dataname users> dataname_users.sql

导出一个数据库结构
mysqldump -u wcnc -p -d –add-drop-table smgp_apps_wcnc >d:\wcnc_db.sql
-d 没有数据 –add-drop-table 在每个create语句之前增加一个drop table

导入数据库
常用source 命令
进入mysql数据库控制台,
如mysql -u root -p

mysql>use 数据库

然后使用source命令,后面参数为脚本文件(如这里用到的.sql)
mysql>source d:\wcnc_db.sql

3、创建临时表:(建立临时表zengchao)
create temporary table zengchao(name varchar(10));

4、创建表是先判断表是否存在
create table if not exists students(……);

5、从已经有的表中复制表的结构
create table table2 select * from table1 where 1<>1;

6、复制表
create table table2 select * from table1;

7、对表重新命名
alter table table1 rename as table2;

8、修改列的类型
alter table table1 modify id int unsigned;//修改列id的类型为int unsigned
alter table table1 change id sid int unsigned;//修改列id的名字为sid,而且把属性修改为int unsigned

9、创建索引
alter table table1 add index ind_id (id);
create index ind_id on table1 (id);
create unique index ind_id on table1 (id);//建立唯一性索引

10、删除索引
drop index idx_id on table1;
alter table table1 drop index ind_id;

11、联合字符或者多个列(将列id与”:”和列name和”=”连接)
select concat(id,’:’,name,’=’) from students;

12、limit(选出10到20条)<第一个记录集的编号是0>
select * from students order by id limit 9,10;

14、MySQL会使用索引的操作符号
<,<=,>=,>,=,between,in,不带%或者_开头的like

15、使用索引的缺点
1)减慢增删改数据的速度;
2)占用磁盘空间;
3)增加查询优化器的负担;
当查询优化器生成执行计划时,会考虑索引,太多的索引会给查询优化器增加工作量,导致无法选择最优的查询方案;

16、分析索引效率
方法:在一般的SQL语句前加上explain;
分析结果的含义:
1)table:表名;
2)type:连接的类型,(ALL/Range/Ref)。其中ref是最理想的;
3)possible_keys:查询可以利用的索引名;
4)key:实际使用的索引;
5)key_len:索引中被使用部分的长度(字节);
6)ref:显示列名字或者”const”(不明白什么意思);
7)rows:显示MySQL认为在找到正确结果之前必须扫描的行数;
8)extra:MySQL的建议;

17、使用较短的定长列
1)尽可能使用较短的数据类型;
2)尽可能使用定长数据类型;
a)用char代替varchar,固定长度的数据处理比变长的快些;
b)对于频繁修改的表,磁盘容易形成碎片,从而影响数据库的整体性能;
c)万一出现数据表崩溃,使用固定长度数据行的表更容易重新构造。使用固定长度的数据行,每个记录的开始位置都是固定记录长度的倍数,可以很容易被检测到,但是使用可变长度的数据行就不一定了;
d)对于MyISAM类型的数据表,虽然转换成固定长度的数据列可以提高性能,但是占据的空间也大;

18、使用not null和enum
尽量将列定义为not null,这样可使数据的出来更快,所需的空间更少,而且在查询时,MySQL不需要检查是否存在特例,即null值,从而优化查询;
如果一列只含有有限数目的特定值,如性别,是否有效或者入学年份等,在这种情况下应该考虑将其转换为enum列的值,MySQL处理的更快,因为所有的enum值在系统内都是以标识数值来表示的;

19、使用optimize table
对于经常修改的表,容易产生碎片,使在查询数据库时必须读取更多的磁盘块,降低查询性能。具有可变长的表都存在磁盘碎片问题,这个问题对blob数据类型更为突出,因为其尺寸变化非常大。可以通过使用optimize table来整理碎片,保证数据库性能不下降,优化那些受碎片影响的数据表。 optimize table可以用于MyISAM和BDB类型的数据表。实际上任何碎片整理方法都是用mysqldump来转存数据表,然后使用转存后的文件并重新建数据表;

20、使用procedure analyse()
可以使用procedure analyse()显示最佳类型的建议,使用很简单,在select语句后面加上procedure analyse()就可以了;例如:
select * from students procedure analyse();
select * from students procedure analyse(16,256);
第二条语句要求procedure analyse()不要建议含有多于16个值,或者含有多于256字节的enum类型,如果没有限制,输出可能会很长;

21、使用查询缓存
1)查询缓存的工作方式:
第一次执行某条select语句时,服务器记住该查询的文本内容和查询结果,存储在缓存中,下次碰到这个语句时,直接从缓存中返回结果;当更新数据表后,该数据表的任何缓存查询都变成无效的,并且会被丢弃。
2)配置缓存参数:
变量:query_cache _type,查询缓存的操作模式。有3中模式,0:不缓存;1:缓存查询,除非与 select sql_no_cache开头;2:根据需要只缓存那些以select sql_cache开头的查询; query_cache_size:设置查询缓存的最大结果集的大小,比这个值大的不会被缓存。

google proto buffer安装和简单示例

mikel阅读(854)

1、安装

下载google proto buff。

解压下载的包,并且阅读README.txt,根据里面的指引进行安装。

$ ./configure

$ make

$ make check

$ make install

没有意外的话,前面三步应该都能顺利完成,第四步的时候,需要root权限。我采用的默认的路径,所以,仅仅用root权限,还是安装不了,要自己先在/usr/local下新建一个lib的目录,然后执行make install,这样,应该就能顺利安装google proto buffer了。

安装完后,先写一个测试程序来测试下安装,先来看看proto文件:

package hello;

message Hello

{

required int32 id = 1; //user id

required string name = 2; //user name

optional string email = 3; //user email

}

接着,要用protoc生成一个对应的类,我把它生成在./out目录里:

protoc hello.proto –cpp_out=./out

接下来,在out目录下,会生成两个文件:

$> ls

hello.pb.cc hello.pb.h

接下来,编写测试用的c++代码:

hello.cc

#include <stdio.h>

#include <string.h>

#include “out/hello.pb.h”

using namespace std;

using namespace hello;

int main()

{

Hello a;

a.set_id(101);

a.set_name(“xg”);

string tmp;

bool ret = a.SerializeToString(&tmp);

if (ret)

{

printf(“encode success!\n”);

}

else

{

printf(“encode faild!\n”);

}

Hello b;

ret = b.ParseFromString(tmp);

if (ret)

{

printf(“decode success!\n id= %d \n name = %s\n”, b.id(), b.name().c_str());

}

else

{

printf(“decode faild!\n”);

}

return 0;

}

接着,编译一下这个代码,由于使用了protobuf的库,所以编译的时候,要把这些库也链接进来:

g++ hello.cc ./out/hello.pb.cc -o hello -I./out -I/usr/local/protobuf/include -L/usr/local/lib -lprotobuf

这样,就生成了测试程序。

运行一下:

$> ./hello

encode success!

decode success!

id= 101

name = xg

原文

http://hi.baidu.com/hins_pan/item/3be48ad0c6fdb7342a35c766

[转载]python scrapy+Mongodb爬取蜻蜓FM,酷我及懒人听书 - yinsolence - 博客园

mikel阅读(959)

来源: [转载]python scrapy+Mongodb爬取蜻蜓FM,酷我及懒人听书 – yinsolence – 博客园

  • 1、初衷:想在网上批量下载点听书、脱口秀之类,资源匮乏,大家可以一试
  • 2、技术:wireshark scrapy jsonMonogoDB
  • 3、思路:wireshark分析移动APP返回的各种连接分类、列表、下载地址等(json格式)
  • 4、思路:scrapy解析json,并生成下载连接
  • 5、思路:存储到MongoDB
  • 6、难点:wireshark分析各类地址,都是简单的scrapy的基础使用,官网的说明文档都有
  • 7、按照:tree /F生成的文件目录进行说明吧

    1 items.py 字段设置,根据需要改变

”’
from scrapy import Item,Field
class QtscrapyItem(Item):
id = Field()
parent_info = Field()
title = Field()
update_time = Field()
file_path = Field()
source = Field()
”’

2 pipelines.py 字段设置及相关处理,根据需要改变

”’
import pymongo as pymongo
from scrapy import signals
import json
import codecs
from scrapy.conf import settings
class QtscrapyPipeline(object):
def init(self):
self.file = codecs.open(‘qingting_209.json’, ‘wb’, encoding=’utf-8′)
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False) + “\n”
# print(line)
self.file.write(line)
return item
class QtscrapyMongoPipeline(object):
def init(self):
host = settings[‘MONGODB_HOST’]
port = settings[‘MONGODB_PORT’]
dbName = settings[‘MONGODB_DBNAME’]
client = pymongo.MongoClient(host=host, port=port)
tdb = client[dbName]
self.post = tdb[settings[‘MONGODB_DOCNAME’]]
def process_item(self, item, spider):
qtfm = dict(item)
self.post.insert(qtfm)
return item
”’

3 settings.py 基础配置 配置数据库存储相关 QtscrapyPipeline 来自pipelines.py中定义的类

”’
ITEM_PIPELINES = {
# ‘qtscrapy.pipelines.QtscrapyPipeline’: 300,
‘qtscrapy.pipelines.QtscrapyMongoPipeline’: 300,
}
MONGODB_HOST = ‘127.0.0.1’
MONGODB_PORT = 12345
MONGODB_DBNAME = ‘qingtingDB’
MONGODB_DOCNAME = ‘qingting’
”’

└─spiders

4 qingting.py 爬虫,各显神通

”’
from scrapy.spiders import BaseSpider
from scrapy.http import Request
import sys, json
from qtscrapy.items import QtscrapyItem
from scrapy_redis.spiders import RedisSpider
reload(sys)
sys.setdefaultencoding(“utf-8”)

1 酷我听书地址分析

http://ts.kuwo.cn/service/gethome.php?act=new_home
http://ts.kuwo.cn/service/getlist.v31.php?act=catlist&id=97
http://ts.kuwo.cn/service/getlist.v31.php?act=cat&id=21&type=hot
http://ts.kuwo.cn/service/getlist.v31.php?act=detail&id=100102396

2 配合Redis使用class qtscrapy(RedisSpider):

class qtscrapy(BaseSpider):
name = “qingting”
# redis_key = ‘qingting:start_urls’
base_url = “http://api2.qingting.fm/v6/media/recommends/guides/section/
start_urls = [“http://api2.qingting.fm/v6/media/recommends/guides/section/0“,
http://ts.kuwo.cn/service/gethome.php?act=new_home“,
http://api.mting.info/yyting/bookclient/ClientTypeResource.action?type=0&pageNum=0&pageSize=500&token=_4WfzpCah8ujgJZZzboaUGkJQvWGfEEL-zdukwv7lbY*&q=0&imei=ODY1MTY2MDIxNzMzNjI0″]
allowed_domains = [“api2.qingting.fm”, “ts.kuwo.cn”, “api.mting.info”]
def parse(self, response):

3 根据返回的url判断,在思考是scrapy执行多爬虫还是这种混杂

    if "qingting" in response.url:
        qt_json = json.loads(response.body, encoding="utf-8")
        if qt_json["data"] is not None:
            for data in qt_json["data"]:
                if data is not None:
                    for de in data["recommends"]:
                        if de["parent_info"] is None:
                            pass
                        else:
                            jm_url = "http://api2.qingting.fm/v6/media/channelondemands/%(parent_id)s/programs/curpage/1/pagesize/1000" % \
                                     de["parent_info"]
                            yield Request(jm_url, callback=self.get_qt_jmlist, meta={"de": de})
        for i in range(0, 250):
            url = self.base_url + str(i)
            yield Request(url, callback=self.parse)
    if "kuwo" in response.url:
        kw_json = json.loads(response.body, encoding="utf-8")
        if kw_json["cats"] is not None:
            for data in kw_json["cats"]:
                pp_id = data["Id"]
                kw_url = "http://ts.kuwo.cn/service/getlist.v31.php?act=catlist&id=%s" % pp_id
                yield Request(kw_url, callback=self.get_kw_catlist)
    if "mting" in response.url:
        # print(response)
        lr_json = json.loads(response.body, encoding="utf-8")
        if len(lr_json["list"]) > 0:
            for l in lr_json["list"]:
                try:
                    lr_url = "http://api.mting.info/yyting/bookclient/ClientTypeResource.action?type=%(id)s&pageNum=0&pageSize=1000&sort=2&token=_4WfzpCah8ujgJZZzboaUGkJQvWGfEEL-zdukwv7lbY*&imei=ODY1MTY2MDIxNzMzNjI0" % l
                    yield Request(lr_url, callback=self.get_lr_booklist)
                except:
                    pass
        for r in range(-10, 1000):
            lr_url = "http://api.mting.info/yyting/bookclient/ClientTypeResource.action?type=%s&pageNum=0&pageSize=1000&token=_4WfzpCah8ujgJZZzboaUGkJQvWGfEEL-zdukwv7lbY*&q=0&imei=ODY1MTY2MDIxNzMzNjI0" % t
            yield Request(lr_url, callback=self.parse)

4 需递归几次是由App结构决定的

def get_qt_jmlist(self, response):
    jm_json = json.loads(response.body, encoding="utf-8")
    de = response.meta["de"]
    for jm_data in jm_json["data"]:
        if jm_data is None:
            pass
        else:
            try:
                file_path = "http://upod.qingting.fm/%(file_path)s?deviceid=ffffffff-ebbe-fdec-ffff-ffffb1c8b222" % \
                            jm_data["mediainfo"]["bitrates_url"][0]
                item = QtscrapyItem()
                # print(item)
                # print(jm_data["id"])
                item["id"] = str(jm_data["id"])
                parent_info = "%(parent_id)s_%(parent_name)s" % de["parent_info"]
                item["parent_info"] = parent_info
                item["title"] = jm_data["title"]
                item["update_time"] = str(jm_data["update_time"])[:str(jm_data["update_time"]).index(' ')]
                item["file_path"] = file_path
                item["source"] = "qingting"
                yield item
            except:
                pass
    pass
def get_kw_catlist(self, response):
    try:
        kw_json = json.loads(response.body, encoding="utf-8")
        if kw_json["sign"] is not None:
            if kw_json["list"] is not None:
                for data in kw_json["list"]:
                    p_id = data["Id"]
                    kw_p_url = "http://ts.kuwo.cn/service/getlist.v31.php?act=cat&id=%s&type=hot" % p_id
                    yield Request(kw_p_url, callback=self.get_kw_cat)
    except:
        print("*" * 300)
        print(self.name, kw_json)
        pass
def get_kw_cat(self, response):
    try:
        kw_json = json.loads(response.body, encoding="utf-8")
        p_info = {}
        if kw_json["sign"] is not None:
            if kw_json["list"] is not None:
                for data in kw_json["list"]:
                    id = data["Id"]
                    p_info["p_id"] = data["Id"]
                    p_info["p_name"] = data["Name"]
                    kw_pp_url = "http://ts.kuwo.cn/service/getlist.v31.php?act=detail&id=%s" % id
                    yield Request(kw_pp_url, callback=self.get_kw_jmlist, meta={"p_info": p_info})
    except:
        print("*" * 300)
        print(self.name, kw_json)
        pass
def get_kw_jmlist(self, response):
    jm_json = json.loads(response.body, encoding="utf-8")
    p_info = response.meta["p_info"]
    for jm_data in jm_json["Chapters"]:
        if jm_data is None:
            pass
        else:
            try:
                file_path = "http://cxcnd.kuwo.cn/tingshu/res/WkdEWF5XS1BB/%s" % jm_data["Path"]
                item = QtscrapyItem()
                item["id"] = str(jm_data["Id"])
                parent_info = "%(p_id)s_%(p_name)s" % p_info
                item["parent_info"] = parent_info
                item["title"] = jm_data["Name"]
                item["update_time"] = ""
                item["file_path"] = file_path
                item["source"] = "kuwo"
                yield item
            except:
                pass
    pass
def get_lr_booklist(self, response):
    s_lr_json = json.loads(response.body, encoding="utf-8")
    if len(s_lr_json["list"]) > 0:
        for s_lr in s_lr_json["list"]:
            s_lr_url = "http://api.mting.info/yyting/bookclient/ClientGetBookResource.action?bookId=%(id)s&pageNum=1&pageSize=2000&sortType=0&token=_4WfzpCah8ujgJZZzboaUGkJQvWGfEEL-zdukwv7lbY*&imei=ODY1MTY2MDIxNzMzNjI0" % s_lr
            meta = {}
            meta["id"] = s_lr["id"]
            meta["name"] = s_lr["name"]
            yield Request(s_lr_url, callback=self.get_lr_kmlist, meta={"meta": meta})
def get_lr_kmlist(self, response):
    ss_lr_json = json.loads(response.body, encoding="utf-8")
    parent = response.meta["meta"]
    if len(ss_lr_json["list"]) > 0:
        for ss_lr in ss_lr_json["list"]:
            try:
                item = QtscrapyItem()
                item["id"] = str(ss_lr["id"])
                parent_info = "%(id)s_%(name)s" % parent
                item["parent_info"] = parent_info
                item["title"] = ss_lr["name"]
                item["update_time"] = ""
                item["file_path"] = ss_lr["path"]
                item["source"] = "lr"
                yield item
            except:
                pass

”’
###5 结果展示,爬取了大概40万记录