跟着驴肉火烧学习Java的装饰模式

群里面有一个小伙伴提出问题,装饰模式和继承和派生到底区别在哪里。事实上,除了代码量的缩减以外,装饰模式更大的意义是设计方法的一种改进:

用数学模型可以这样抽象地说——在面向对象的编程中,继承和派生是一种全集和子集的关系;而后引入了接口Interface的概念,将类于类之间交集的关系抽象成了接口。装饰模式是将这两种思想相结合,利用装饰者,对原来的对象进行修饰并赋予新的特性。赋予特性的顺序,数量,均不受类的约束,而是在运行时增删改。

装饰的本质,是在具有相同特性的对象上增加修饰,他不是对类的操作,而是对对象的修饰。装饰者在接收到被装饰者时,不需要管被装饰者是谁,只需要给被装饰者加一层自己对他的修饰就行。

驴肉火烧案例模型

我特别爱吃驴肉火烧,尤其是保定口的驴肉火烧,肥瘦焖子青椒肥肠,无肉不欢的我总是喜欢吃这么几种驴肉火烧的组合方式:

  • 肥瘦火烧
  • 肥瘦火烧+焖子
  • 肥瘦火烧+青椒
  • 肥瘦火烧+焖子+青椒
  • 肥瘦火烧+青椒+焖子(别问为啥,先后顺序口感不同,强迫症!节目效果必须这样做)
  • 肥瘦火烧+焖子+青椒+肥肠(土豪配置)
  • ……

如果咱用继承与派生的方式来抽象模型的话,会形成这样的结构:

  • 肥瘦火烧(父类)
  • 焖子火烧 extends 肥瘦火烧(肥瘦火烧+焖子的子类)
  • 青椒火烧 extends 肥瘦火烧(肥瘦火烧+青椒的子类)
  • 焖子青椒火烧 extends 焖子火烧(焖子火烧+青椒的子类)
  • 青椒焖子火烧 extends 青椒火烧(青椒火烧+焖子的子类)
  • 焖子青椒肥肠火烧 extends 焖子青椒火烧(焖子青椒火烧+肥肠的子类)
  • ……

也就是说,这些原材料有多少种的排列组合方式,我们就要派生出多少种类。OMG,这个代码量虽然比过程化结构设计省了好多,那还是有大量的劳动要做啊!

如果你是焖子店老板,必须提前准备6+种火烧给我这挑剔的顾客选,累屎啦!

换一种思路,如果先前就备货肥瘦火烧,然后将焖子,青椒,肥肠现切进肥瘦火烧中,那么老板的工作量是不是就少了好多呢?

这就是装饰模式的优势:

我们的备菜是4个类——肥瘦火烧、焖子、青椒、肥肠

  • 肥瘦火烧
  • 焖子修饰一下肥瘦火烧
  • 青椒修饰一下肥瘦火烧
  • 焖子修饰一下然后青椒修饰一下肥瘦火烧
  • ……

可以派生出24种不同的火烧排列组合方式,然而代码量只有4个类的代码量!LOL,老板乐开了花。

装饰模式

回到概念上来,装饰模式,即对对象进行装饰。

装饰者和被装饰者,事实上同属一个父类~ 装饰者的目的是对被装饰者属性,行为进行扩充。

生动的Java代码

实现装饰模式,需要3要素:

  1. 抽象出共同的属性方法 interface LvHuoCommonMethods
  2. 被装饰者class LvHuo implements LvHuoCommonMethods
  3. 装饰者class XiuShiZheCaiJiao implements LvHuoCommonMethods等

上代码:

抽象出的共同属性:

/**
 * LvHuoCommonMethods.java
 * 驴肉火烧的基础方法
 */
public interface LvHuoCommonMethods {
    public void getPeiFang();
    public double getJiaGe();
}

被装饰者:

/**
 * LvHuo.java
 * 驴肉火烧,被修饰的类
 */
public class LvHuo implements LvHuoCommonMethods {
    @Override
    public double getJiaGe() {
        System.out.println("基本驴肉火烧:5元");
        return 5.0;
    }

    @Override
    public void getPeiFang() {
        System.out.println("火烧一个。");
        System.out.println("肥瘦1两。");
    }
}

装饰者菜椒:

/**
 * XiuShiZheCaiJiao.java
 * 修饰者菜椒
 */
public class XiuShiZheCaiJiao implements LvHuoCommonMethods {
    private LvHuoCommonMethods huoshao;

    public XiuShiZheCaiJiao(LvHuoCommonMethods huoshao_){
        super();
        this.huoshao = huoshao_;
    }

    @Override
    public void getPeiFang(){
        huoshao.getPeiFang();
        System.out.println("剁两段菜椒放到火烧里。");
    }

    @Override
    public double getJiaGe(){
        double price = huoshao.getJiaGe() + 0.5;
        System.out.println("菜椒另付5毛。");
        return price;
    }
}

装饰者焖子:

/**
 * XiuShiZheMenZi.java
 * 修饰者焖子
 */
public class XiuShiZheMenZi implements LvHuoCommonMethods {
    private LvHuoCommonMethods huoshao;

    public XiuShiZheMenZi(LvHuoCommonMethods huoshao_){
        super();
        this.huoshao = huoshao_;
    }

    @Override
    public void getPeiFang(){
        huoshao.getPeiFang();
        System.out.println("切一片焖子剁碎放到火烧里。");
    }

    @Override
    public double getJiaGe(){
        double price = huoshao.getJiaGe() + 2;
        System.out.println("焖子另付2元。");
        return price;
    }
}

主方法:

/**
 * MainContainer.java
 * 这个方法,将做火烧的顺序调换,生成了两个不同的火烧
 *
 * 可以尝试下
 *
 * 只加焖子
 * 只加菜椒
 *
 * 多加一个修饰者咋搞,加一个修饰者肥肠试试?
 */
public class MainContainer {
    public static void main(String args[]){
        /* ↓↓↓*********** Important!***********↓↓↓ */
        LvHuoCommonMethods lvrouhuoshao1 = new LvHuo();                           //做个火烧
        LvHuoCommonMethods menzihuoshao1 = new XiuShiZheMenZi(lvrouhuoshao1);     //加焖子
        LvHuoCommonMethods menzicaijiaohuoshao1 = new XiuShiZheCaiJiao(menzihuoshao1);    //加菜椒
        /* ↑↑↑*********** Important!***********↑↑↑ */
        menzicaijiaohuoshao1.getPeiFang();
        System.out.println("你需要付款:" + menzicaijiaohuoshao1.getJiaGe() + "元");
        System.out.println();
        System.out.println();
        LvHuoCommonMethods lvrouhuoshao2 = new LvHuo();                           //做个火烧
        LvHuoCommonMethods caijiaohuoshao2 = new XiuShiZheCaiJiao(lvrouhuoshao2);     //加菜椒
        LvHuoCommonMethods caijiaomenzihuoshao2 = new XiuShiZheMenZi(caijiaohuoshao2);    //加焖子
        caijiaomenzihuoshao2.getPeiFang();
        System.out.println("你需要付款:" + caijiaomenzihuoshao2.getJiaGe() + "元");
    }
}

运行的结果如下:

火烧一个。
肥瘦1两。
切一片焖子剁碎放到火烧里。
剁两段菜椒放到火烧里。
基本驴肉火烧:5元
焖子另付2元。
菜椒另付5毛。
你需要付款:7.5元


火烧一个。
肥瘦1两。
剁两段菜椒放到火烧里。
切一片焖子剁碎放到火烧里。
基本驴肉火烧:5元
焖子另付2元。
菜椒另付5毛。
你需要付款:7.5元

读者如果有耐性听我BB到这里,可以再有耐心一点,加上一个修饰者肥肠~

然后在主方法里做一个 肥肠肥肠菜椒焖子肥肠火烧~

如果做出来,那么对修饰者的理解也就差不多了。本例子只对IO流进行修饰,如果被修饰的主体变成了列表、堆栈,那么可以做的事情就很多了。大型的项目里,这样的方法能极大地减轻工作量!

使用Bouncy Castle创建PGP密钥对

最后一个寒假了,由于正在走向Java猴子的路上,所以要适应一大批Java的轮子,那就从最基本的加密开始做笔记吧。

PGP是常用的非对称加密手段,也是我所喜欢加在项目里的轮子,Bouncy Castle中的PGP包可以满足所有PGP需求。

Bouncy Castle介绍

Bouncy Castle(以下简称为BC)是一个开源的Java包,其中几乎包含了程序猿们所听说过的所有加密方式,十分简洁易用。Bouncy Castle的优势不仅仅体现在其轻便性,更重要的是他横跨了所有的Java平台,即使是J2ME都在他的魔爪之下。

当然,Bouncy Castle还有对应的C#类库,并且中文资料挺多~

Bouncy Castle PGP包

PGP包的包名为org.bouncycastle.bcpg,使用Maven的话需要添加以下依赖:

<!-- Provider依赖 -->
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.56</version>
        </dependency>

<!-- OpenPGP包依赖 -->
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpg-jdk15on -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpg-jdk15on</artifactId>
            <version>1.56</version>
        </dependency>

BC中PGP密钥对的构成

这里不讨论PGP的原理(毕竟几年没学数论了23333)。我们知道在信息的非对称加密中,需要有公钥和私钥两把钥匙。

公钥(PublicKey)负责加密内容,私钥(PrivateKey)负责解密内容。在PGP的规则里,私钥是由密钥对的所有者保管的,他是用对称加密方法(CAST-128)被加密在密钥中(也就是SecretKey),这样确保只有知道密钥口令的人才能掌握私钥。

BC将PublicKey和加密后的PrivateKey存放在的SecretKey对象中。

BC所处理的PGP的加解密流程也不在本文的讨论范围中,后续会跟上。

Java使用BC生成密钥对

先上代码:

     
    /**
     * 私有方法,用于生成指定位宽的PGP RSA密钥对
     *
     * @param rsaWidth_ RSA密钥位宽
     * @return 未经私钥加密的PGP密钥对
     * @throws Exception IO错误,数值错误等
     */
    private static PGPKeyPair generateKeyPair(int rsaWidth_) throws Exception {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");//获取密钥对生成器实例
        kpg.initialize(rsaWidth_);//设定RSA位宽
        KeyPair kp = kpg.generateKeyPair();//生成RSA密钥对
        return new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, kp, new Date());//返回根据日期,密钥对生成的PGP密钥对
    }

    /**
     * 获取PGP密钥<br>
     * 密钥是将密钥对的私钥部分用对称的加密方法CAST-128算法加密,再加上公钥部分
     *
     * @param identity_   密钥ID也就是key值,可以用来标记密钥属于谁
     * @param passPhrase_ 密钥的密码,用来解出私钥
     * @param rsaWidth_   RSA位宽
     * @return PGP密钥
     * @throws Exception IO错误和数值错误等
     */
    public static PGPSecretKey getSecretKey(String identity_, String passPhrase_, int rsaWidth_) throws Exception {
        char[] passPhrase = passPhrase_.toCharArray(); //将passPharse转换成字符数组
        PGPKeyPair keyPair = KeyPairGeneratorRSA.generateKeyPair(rsaWidth_); //生成RSA密钥对
        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1); //使用SHA1作为证书的散列算法
        return new PGPSecretKey(
                PGPSignature.DEFAULT_CERTIFICATION,
                keyPair,
                identity_,
                sha1Calc,
                null,
                null,
                new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
                //密钥的加密方式
                new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build(passPhrase)
        );
    }

以上就是BC根据用户标识和私钥密码生成PGPSecretKey的过程,其实最关键的是PGPSecretKey的构造方法,以下是这个构造方法JavaDoc的翻译:

	/**
     * 用证书等级生成的认证,将公私钥对和PGP ID密码绑定构造PGP密钥(SecretKey)
     *
     * @param certificationLevel PGP密钥的证书等级
     * @param keyPair 需要绑定的公私钥对
     * @param id 需要绑定的ID
     * @param checksumCalculator 散列值计算器,用于计算私钥密码散列
     * @param hashedPcks the hashed packets to be added to the certification.(先不管)
     * @param unhashedPcks the unhashed packets to be added to the certification.(也先不管)
     * @param certificationSignerBuilder PGP证书的生成器
     * @param keyEncryptor 如果需要加密私钥,需要在这里传入私钥加密器
     * @throws PGPException 一些PGP错误
     */
    public PGPSecretKey(
        int                         certificationLevel,
        PGPKeyPair                  keyPair,
        String                      id,
        PGPDigestCalculator         checksumCalculator,
        PGPSignatureSubpacketVector hashedPcks,
        PGPSignatureSubpacketVector unhashedPcks,
        PGPContentSignerBuilder     certificationSignerBuilder,
        PBESecretKeyEncryptor       keyEncryptor)

生成完整的PGP密钥证书只需要PGPSecretKey这个的构造方法。后续获得publicKey只需要使用getPublicKey()方法即可。

怎么从密钥对象中得到私钥呢?需要用extractPrivateKey()方法,这个方法有一个参数,根据PGP的逻辑,这个参数一定是用来通过密文也就是代码中的passParse来解密PrivateKey内容的。方法如下:

//我们已经有了secretKey对象
PGPPrivateKey privateKey = secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(passParse));

写完了,睡觉去~

作大死系列之——为已有的Windows系统创建EFI分区

这篇文章的诞生是这样的,去年冬天我入手了Y50,并且上了昂贵的850PRO。然而随着工作需求的增加,128G的850显然不够用了(装了一个用友就20多个G没了233333),所以又换了块更大容量更加昂贵的850PRO。

造成作大死的需求

此时产生了一个需求——我要将原来850PRO的Windows系统分区(也就是传说中的C:\,Windows默认安装在了sda3.old下),移到新硬盘的sda2.new下,并且我不要原来硬盘的系统保留区(废柴的sda2.old)。然而ESP分区(EFI System Partition)仍然保持100MB放在sda1中(因为后续要用grub引导bootmgfw.efi,gurb和bootmgfw并存于sda1)。
简洁地说,我只有Windows系统区而没有引导区,怎么样建立ESP分区,并引导Windows启动?
此需求的要求是

  • UEFI + GPT分区表(BIOS+MBR请绕道其他文章,BIOS+GPT这种变态请跳至wzyboy的神文)

此需求还适用于如下场景:

  • EFI分区丢失,误删,不小心写错引导
  • 先装XNIX,再装Windows,不损坏GRUB
  • BALABALABALA

方法

分析一下现在的分区结构

  1. sda1 —— ESP分区 100MB (这是一个空白的分区,木有东西)
  2. sda2 —— Windows系统目录 150GB
  3. sda3 —— ArchLinux挂载点 / 50GB

提取了Windows安装盘中的PE之后,打开PE,进入CMD。按照正常的操作,只需要DISKPART分配驱动器号——EFI分区分配(X:);Windows分区分配(C:),然后输入:

bcdboot C:\Windows /l zh-cn /s x: /f UEFI

然后出现了尝试复制启动文件失败的问题23333333。

经查验,发现事实上ESP分区并没有被成功建立,原来bcdboot只认识FAT格式的分区,于是将EFI分区format一下子,问题就解决了QAQ(实际上这个步骤我已经忘掉了,但是我就提一下有这个步骤~)

GRUB

由于EFI分区已重构,GRUB写在EFI分区中的引导信息也没了,那么此时就需要在Linux环境下重建GRUB引导。以ArchLinux为例:

# grub-install --target=x86_64-efi --efi-directory=$esp --bootloader-id=grub

其中,$esp是挂载后的UEFI分区。

OK,备忘录写完了!P.S. 这篇文章写了差不多有2个月了。因为第一个草稿是:Dick_Wu,2月前(2016年11月10日 @ 23:09:22)

Database-存储过程学习[2]

上一篇的坑,本篇介绍存储过程中常用的Transact-SQL语法。由于存储过程可以理解为Transact-SQL语句的集合,所以首先需要掌握Transact-SQL的基本流程。

BEGIN..END

在C/C++, Java, C#一类的语言中,程序员用{}大括号来标记代码区块,较为古老的Pascal使用begin..end;或者begin..end.来标记。而在Transact-SQL中,代码区块是用BEGIN..END来标记的。

在控制流中,区块所包含的部分作为一组语句,区块外的控制器可以将其看作一条语句。举个例子:

BEGIN  --Exec insert
    SET @sql_mix_table_Str = &#039;INSERT INTO &#039; + @destTableName + &#039; SELECT * FROM &#039; + @sourceTableName -- Insert parameter translation, translate into sql.
    -- Insert statements for procedure here
    EXEC (@sql_mix_table_Str);
END

在这段代码中,将规格化SQL语句和SQL语句作为一个区块。就类似于C/C++中大括号的作用。

IF..ELSE

说完了区块之后,分支语句IF和ELSE 的语法就更好解释了。IF..ELSE的语法要求是:

IF
    {Code Block}
ELSE
    {Code Block}

IF和ELSE之后紧跟着代码区块,对这两个关键字来说,至寻找紧跟的一句控制流语句。所以,如果是多句语句,需要用代码区块BEGIN..END。具体的流程如图所示:

Created with Raphaël 2.1.2BEGINYES or NO ?Code Block IFENDCode Block ELSEyesno

举个例子:

DECLARE @compareprice money, @cost money   
EXECUTE Production.uspGetList &#039;%Bikes%&#039;, 700,   
    @compareprice OUT,   
    @cost OUTPUT  
IF @cost &lt;= @compareprice   
BEGIN  
    PRINT &#039;These products can be purchased for less than   
    $&#039;+RTRIM(CAST(@compareprice AS varchar(20)))+&#039;.&#039;  
END  
ELSE  
    PRINT &#039;The prices for all products in this category exceed   
    $&#039;+ RTRIM(CAST(@compareprice AS varchar(20)))+&#039;.&#039;  
-- from MSDN

WHILE

循环语句是控制流程的重要部分,在Transact-SQL中,只保留了While当型控制结构,这对于控制流程的开发人员来说已经足够了。

WHILE语句的控制流程如下:

WHILE 布尔表达式
    { SQL语句 | 代码区块 | CONTINUE | BREAK }

WHILE与IF..ELSE相同,在表达式之后只能跟一句SQL语句。但是,还有CONTINUE和BREAK两个控制关键字。其中CONTINUE是重新进入当前循环的下一次循环,而BREAK是跳出当前循环。这与C/C++是相同的。

除了基本控制流程中的WHILE语句之外,之后还会引出游标这一个概念,在游标中使用WHILE。下一篇文章会介绍游标中使用WHILE的具体实现。

后面的任务

原来打算先看存储过程的异常处理及参数传递的,但是WHILE中引出了游标的概念,所以下一步打算先介绍游标。敬请期待~

OpenSSL重大漏洞和Nginx的小变化

OpenSSL重大漏洞

What the huack! 好久没折腾VPS配置了,刚测试一下SSL就给我来了个大News:

SSL Report F

看到这个的时候,我心里是很慌的,毕竟最近阿里云给我发了好多短信提示各种漏洞都给我忽略了,这回这个可是高危漏洞啊!

研究了一下这个OpenSSL的漏洞(CVE-2016-2017),翻译全篇难度太大了,大概摘要一下:

由于1.0.1t、1.0.2和1.0.2h之前的OpenSSL没有考虑到AES-NI填充检测逻辑时的内存分配问题,在握手未完成时,服务器的报错信息以明文方式传输返回,所以中间人攻击者可以在握手的过程中探测到返回的明文数据。

需要多次探测,并且能够修改被攻击者所发送的加密前数据(至少是头部信息)。

感觉,能够执行这个漏洞的人都不是中间人了吧,至少必须对网站的访问者成功攻击之后才能够利用这个漏洞进行中间人攻击。BTW,这对于访问量越大的网站,风险越大,对于我等小博客主无所谓啊~

但是!丢人啊!评分评一个F,还要被挂在SSLLab的网站上鞭尸。这能忍?于是果断升级nginx + libressl的版本。那么问题又来了……

Nginx 1.9.5支持HTTP V2啦

对于使用Nginx的网站来说,这是一个好消息。对于我来说,臣妾根本不知道这个信息好吗?下载最新Nginx下来之后,一般的configure会这么写嘛:

./configure \
--user=xxx\
--group=xxx\ 
--prefix=/usr/local/nginx\
...
#此处省略1000个配置选项
...
--with-http_ssl_module\
--with-http_sub_module\
#下面那行是关键
--with-http_spdy_module\
#上面那行也是关键
--with-openssl=/OpenSSL源代码目录\
--with-ld-opt=-lrt

然后Configure居然过不了?给我报错error: invalid option "--with-http_spdy_module"噗。。。这是什么鬼?Nginx不支持spdy了?于是Google一下Nginx的官方Module库:

This module was superseded by the ngx_http_v2_module module in 1.9.5.

这个模块已经在1.9.5版本中被增强为ngx_http_v2_module了。

原来是这样啊。所以大家编译的时候就不用--with-http_spdy_module啦,替换成--with-http_v2_module即可。

接下来就应该make了你们说是不是,那么问题又来了……

Nginx 1.9.5不会编译LibreSSL啦

报错了。。。

make[2]: *** No rule to make target `install_sw&#039;.  Stop.
make[1]: *** [/root/ssl/libressl-2.3.0/.openssl/include/openssl/ssl.h] Error 2
make: *** [build] Error 2

WUT?编译出错了?然后我们的Nginx开发者表示,我们没有义务对LibreSSL的编译正确与否负责,我们只需要对OpenSSL负责就好了。所以你可以自己编译好了LibreSSL再以模块的方式添加进Nginx中。

所以呢,解决方案有两个:

  • 先编译LibreSSL,再编译Nginx
  • 升级LibreSSL到2.3.6以上的版本(这样还可以解决高危漏洞CVE-2016-2017)

好啦,强迫症治愈。

Database-存储过程学习[1]

最近在做项目的过程中遇到了存储过程的编写,在之前的数据库开发中,还从来没有接触过Stored Procedure,所以打算开一个专题来记录一下我学习存储过程的过程(好拗口)。

存储过程

存储过程是由一个或多个 Transact-SQL 语句或对 Microsoft .NET Framework 公共语言运行时 (CLR) 方法的引用构成的一个组。按照我目前的理解,Stored Procedure是一群SQL的集合,放在一个方法中。某些业务逻辑可以交给数据库开发人员,将某一个存储/取值的过程建立一个方法,利用方法完成一系列的SQL操作,减轻业务开发人员的负担。我认为这样的优势在于:

  • 多个SQL语句的集合完成某一存储过程,将部分需要调用大量SQL的业务逻辑交给数据库完成。
  • 由于存储过程是一次编译,在之后的执行中,比使用ODBC调用多条SQL会有更高的性能。
  • 面向对象的方法处理SQL存储过程

下面举个例子:

USE AdventureWorks2012;
GO
CREATE PROCEDURE HumanResources.uspGetEmployeesTest2    --建立存储过程HumanResources.uspGetEmployeesTest2
@LastName nvarchar(50),     --参数1,LastName
@FirstName nvarchar(50)     --参数2,FirstName
AS

SET NOCOUNT ON;             --存储过程不返回记数
SELECT FirstName, LastName, Department
FROM HumanResources.vEmployeeDepartmentHistory
WHERE FirstName = @FirstName AND LastName = @LastName
AND EndDate IS NULL;        --存储过程中需要执行的SQL语句
GO

这是MSDN上最简单的Stored Procedure,包含一个SQL语句。执行这一个步骤与SQL语句:

SELECT FirstName, LastName,Department FROM HumanResources.vEmployeeDepartmentHistory
WHERE FirstName = ? AND LastName = ?;

效果几乎等同,但使用存储过程的好处是,我们不需要业务开发人员额外开发DAO来获取FirstName和LastName信息。DBA就像编写脚本一样编写存储过程,业务开发人员只需要执行存储过程存取数据即可。

调用存储过程

在上一个例子中,在数据库HumanResources中建立了useGetEmployeesTest2这个存储过程,将存储过程编译之后,我们可以在SQL Server Management Studio中数据库=>可编程性=>存储过程中找到编译后的存储过程useGetEmployeesTest2.

执行存储过程的SQL是:

USE [DBName];

EXEC [ProcedureName] @param1 = [paramValue1], @param2 [paramValue2] ... ;
GO;

那么,执行useGetEmployeesTest2这个存储过程的SQL可以这么写:

USE HumanResources;
GO;

EXEC useGetEmployeesTest2 @LastName = 'Wu', @FirstName = 'Richard';

这样就找到了姓名’Richard Wu’的FirstName, LastName, Department

修改存储过程

此时,存储过程已经编译完毕,假如说某一天业务需求发生变化,或是数据库发生变化,需要修改存储过程逻辑怎么办?这时候我们可以选择再新建一个符合业务需求的存储过程。当然,修改原来的存储过程会更好。

变更useGetEmployeesTest2的需求,此时,我们不仅仅要找出Department,还需要找出Age。

USE AdventureWorks2012;
ALTER PROCEDURE HumanResources.uspGetEmployeesTest2     --修改存储过程HumanResources.uspGetEmployeesTest2
@LastName nvarchar(50),     --参数1,LastName
@FirstName nvarchar(50)     --参数2,FirstName
AS

SET NOCOUNT ON;             --存储过程不返回记数
SELECT FirstName, LastName, Department, Age
FROM HumanResources.vEmployeeDepartmentHistory
WHERE FirstName = @FirstName AND LastName = @LastName
AND EndDate IS NULL;        --存储过程中需要执行的SQL语句
GO

可以看到,存储过程也是一个实体,用ALTER语句修改其内容。

后面的任务

在熟悉了存储过程的编写,编译,执行后,接下来的步骤就是熟悉存储过程的基本语法啦。在这里~~挖坑~~……等下一篇

Evernote架构摘要 – A Digest of Evernote’s Architecture (*)

前言:

这篇文章我最近研究小规模分布式系统时,在Evernote博客中发现的惊喜,里面大体介绍了Evernote后台的架构。虽然是一篇科普文没啥干货只有思路,但还是感触,老美的企业还是很有分享精神的(虽然据说Evernote正在走下坡路吧)。为了分(练)享(习)给(计)更(算)多(机)的(英)同(语)学,我选择翻译这一篇文章A Digest of Evernote’s Architecture (*)。翻译用的语调十分诙(dou)谐(bi),希望搏大家一笑的同时,能给大家带来知识。

evernote-highlevel-architecture1

让阿拉(译者:沪语的咱们)从这张简陋的Evernote服务物理构造概览开始。俺不想深入地说这张图里每一个组成部分的众多细节;俺们会计划在之后的文章中讨论这些有趣的点。

坐在图片的左上角的你是活动的起点,所有的统计数据截至2011年5月17日的…(译者:呵呵)

网络系统:差不多所有从Evernote进出的数据流量是通过HTTPS的443端口来到www.evernote.com的。这包括所有的“网络”活动,也包含所有通过俺们基于ThriftAPI的客户端同步事件。总得加起来,每天这些活动要产生1.5亿次HTTPS访问,峰值速率差不多是250Mbps。(俺们的凌晨运维团队脸很黑,太平洋时间每天凌晨6:30是流量高峰的来临时间。)(译者:中国是UTC+8时区,太平洋时间是UTC-8,这么算起来差不多北京时间22点达到流量高峰,啊哈哈哈原来Evernote码农睡不了懒觉是我大中华区的锅。)

俺们通过边界网关协议来引导由俺们的主运营商 (NTT) 和次运营商 (Level 3) 提供的专线网络流量。这些流量透过Vyatta路由器走向A10负载均衡器,这些全新的负载均衡器是一月份部署的,因为俺们旧负载均衡器的SSL表现出现了瓶颈。当前,俺们用一台AX 5000加上一个故障转移盒子就可以很爽地处理现在的流量,不过未雨绸缪的俺们正准备测试分布式N+1配置,以准备未来的流量增长。

Shards:Evernote的核心服务是由一大堆的服务器提供的,俺们管它们叫”Shards”。(碎片渣渣)每个Shard(碎片)处理一大伙差不多是100,000个Evernote注册用户的所有数据和流量。(web的和API的)从俺们有了超过九百万用户开始,这些数据流量被分配给了差不多90台Shard。

在物理上,Shards是部署在一组SuperMicro牌的机器上的,这些机器的配置是Intel四核处理器,大得很的内存和塞满了硬盘架的希捷RAID镜像硬盘阵列。在这些机器上,俺们运行了一个基础的Debian系统服务来管理两个Xen虚拟机。主虚拟机运行着俺们的核心程序栈:Debian + Java 6 + Tomcat + Hibernate + Ehcache + Stripes + GWT + MySQL(存储数据元) + 分层文件系统(存储文件数据)。(译者:这都是些啥。。。)

所有在一台机器上主虚拟机里的用户数据,会通过DRBD(Linux平台的分散式存储系统)同步复制到另一台机器上副虚拟机里头。这意味着,每一个byte的用户数据都至少分布在2台物理服务器的4块不同的企业级硬盘里,加上每晚上的定期备份。一旦俺们发现某个服务器出现了问题,俺们可以通过Heartbeat这个黑科技进行故障切换,将主虚拟机切换到那台副虚拟机上,宕机时间短得一笔。

因为每个用户的数据是完全位于一台虚拟Shard机上的,所以阿拉可以将每一个Shard作为一个孤岛,事实上没有干扰或是附属关系。这意味着,一台Shard上的问题,不会滚雪球一样地影响到其他的Shard。

为了将用户和他对应的Shard连接起来,俺们在开发负载均衡器上花了很多的心思。负载均衡器通过一大串的规则,在URL或者cookies里寻找这个用户的Shard。

用户存储:虽然大量的主要数据是存储于笔记仓库Shards里的,但是所有用户都共享唯一的一个主「用户仓库」账号数据库(也是基于MySQL的),其中包括每个账户小规模的信息,例如:用户名,密码的MD5,还有这个用户所对应的Shard编号。这个数据库足够小,以至于能够跑在内存里,不过俺们会结合RAID镜像、通过DRBD复制到备用服务器、并且每晚备份的方法,来保持高的冗余度。

AIR数据处理机:为了让你们看到图文并茂的笔记,俺们维护了一个拥有28台服务器的服务器池,这些服务器夜以继日地用它们8颗核心处理新图像。在繁忙的日子里,这些服务器将转换130万到140万个分散的图像。目前,这些服务器是Linux和Windows混着跑,不过既然俺们已经移除了一些麻烦的遗留依赖(估计是说Windows),俺们打算在这个月底之前将所有的服务器都运行Debian操作系统。

这些服务器正在流水化运行由俺们的R&B研发猴子团队开发的”Advanced Imaging and Recognition”(AIR)「高级图像和识别」(不是空气,也不是Adobe AIR)。这个软件清理【优化?】每一张图片,识别看上去像文字的区域,然后通过「识别引擎」根据列表匹配每一个字的含义。这套系统里的引擎,是由俺们在这方面的精英团队开发的,吹个牛逼举个例子,俺们的手写识别技术,可以和业界最佳的同行们的商业许可技术软件相媲美。

其他服务:每台服务都被俺们处心积虑想方设法地塞进一对专用的盒子里,然后放到俺们位于加州圣克拉拉市的数据中心里。除了为俺们提供核心服务的硬件以外,俺们还有差不多由一到两台刀片(刀片服务器)组成的服务器组或是Xen虚拟机用来运行一些轻量级服务。比如,俺们的「收件服务」SMTP入口是由一对运行着Postfix的Debian服务器负责的,然后俺们的自定义Java邮件处理进程是基于Dwarf搭建的。俺们的@myen Twitter入口是跑在内部运行的twitter4j进程上。

俺们所有的网站都是Apache的,俺们的博客学习Dick Wu是用的WordPress(逃,俺们几乎所有的备份接口切换是有惠普提供的(译者:这段翻译不是很会,贴上原文most of our fully redundant internal switching topology is from HP),俺们用Puppet来管理配置,俺们的监控服务是由ZabbixOpsviewAlertSite提供的。俺们每晚会进行异地数据备份,这些备份数据将会通过1Gbps的宽带传输到另外一个数据中心。

等等,可是为啥?俺意识到,这篇文章明显留下了许多问题:关于为啥俺们在许多地方选择X而不是Y?为啥俺们用自己的服务器而不是用阿里云?为什么用这么闷骚老旧的软件(Java, SQL, 本地存储等等)而不是新的黑科技?(译者:原文是魔术子弹)…

俺们会努力在接下来的几个月里透露这些问题的细节。

(*)2011年6月29日更新:在Conde Nast童鞋的要求下,文章的标题改成现在的了。原来的标题是《架构摘要》。

我是猪脚[2]——在VirtualBox中安装Ubuntu

根据杨老师程序设计基础课的要求,大一的学弟学妹们都需要安装或者虚拟一套Linux操作系统。所以就有了我和@Jimmy Zhou开了这一个我是助教系列,这是这个系列的第二篇文章

一、配置VirtualBox运行环境

首先我们要打开VirtualBox,点击新建。

ubuntusetup1

接下来就是给这个虚拟机取名,我给它取名Ubuntu,大家爱怎么取就怎么取~然后选择类型为Linux,版本是Ubuntu(64Bit)。当然,下载32位或者没有开启IntelVM的同学只能选择Ubuntu(32 Bit)。

ubuntusetup2

接下来是给虚拟机君分配内存。因为我有8GB物理内存,所以就给了虚拟机2GB,实际上1GB足矣,512MB也够用。

ubuntusetup3

 

分配完内存之后,就是创建虚拟硬盘。

ubuntusetup4

 

选择创建以后,选择VDI。其实VMDK VHD啥的都行。如果用VHD的话,还可以做到开机引导。由于是启蒙教程,所以我们就用最简单的VDI吧。

ubuntusetup5

分配大小的话,当然是动态分配啦,这样的话虚拟硬盘文件就会按照硬盘大小自动适应,而不是一下子占用10GB存储空间。

ubuntusetup6

接下来就是选择VDI文件存储的位置,点右边的小文件夹图标,选择存储位置(最好不要是C盘,尤其是SSD用户。。。土豪请忽略这句话)。

ubuntusetup7

 

就像这样。

ubuntusetup8

我给它分配了10GB的硬盘空间,如果你觉得10GB不够用的话,尽管分大好啦,反正是动态分配的容量。

ubuntusetup9

创建完成后,我们会发现VirtualBox的左侧新增了一个虚拟机,我们右键它,选择设置。

ubuntusetup10

首先进入系统配置,主板选项卡。主要是把软驱前面的勾去掉,然后把光驱挪到第一启动位。

ubuntusetup11

处理器标签可以设置多处理器。我这里选择了4个处理器,其实一个处理器也够用了。

ubuntusetup12

显示设置,显存当然要给到最大,不然的话图形界面会卡死。

ubuntusetup13

然后是存储设置,我们需要分配Ubuntu的安装光盘镜像。

ubuntusetup14

点击分配光驱右侧的光驱标志,然后选择一个虚拟光盘。

ubuntusetup15

找到教程[1]中下载的Ubuntu Kylin安装镜像。选中之后,存储树显示成这样就说明安装光盘已经塞进虚拟机了。

ubuntusetup16

网络设置用缺省的NAT就行。当然在后续的教程里,我们是需要再配置这一个模块的。

ubuntusetup17

确定完成所有设置之后,进入下一步,安装Ubuntu。

二、安装Ubuntu

双击VirtualBox主页的虚拟机,虚拟机君就会开心地运行了。缓慢的加载之后,终于出现了Ubuntu的欢迎界面。

ubuntusetup18

在这一步需要勾选安装第三方软件,否则的话一些闭源的软件是没法用Ubuntu的软件市场安装的。

ubuntusetup19

然后会问你用什么方法安装,虚拟就是任性,清除整个磁盘并安装啦!如果勾上Use LVM的话,以后还可以任意扩充分区容量啦,所以果断勾选LVM。

ubuntusetup20

然后安装程序会傻傻地问您在什么地方,别看咱们离上海这么远,定位上海才是正确的(或许是Ubuntu认为中国首都是上海吧),然后继续。

ubuntusetup21

接下来是选择键盘布局,俺们就是汉语键盘啦。别告诉我有用法语键盘的。。。

ubuntusetup22

您的姓名按照爱好填,用户名按照爱好填,密码需要设置。这和Windows的安装是差不多的。一定要设置密码哟!

ubuntusetup23

然后虚拟机就开始勤劳地安装了。它会很慢很卡,现在你可以去上一节课或者睡一觉,醒来说不定分就安装好了。

ubuntusetup24

安装完成提示重启。注意如果卡在一个黑屏,并且让你remove any disk to restart的话,只要按下回车就可以重启了。

ubuntusetup25

 

重启以后看到了登陆界面,当然是要用刚刚设置的密码啦。

ubuntusetup26

然后你就会吐槽。这个屏幕好小啊!!!好坑爹啊!所以我们需要在虚拟机窗口点击设备,然后安装增强功能。

ubuntusetup27

然后Ubuntu会弹出提示要不要运行增强功能的安装包。

ubuntusetup28

当然是运行啦!运行会问你密码,继续输入即可。

ubuntusetup29

直到看到Press Return to close this Window,按回车就安装完了。

ubuntusetup30

 

这时候重启Ubuntu,下次点亮,Ubuntu的分辨率就可以根据VirtualBox的窗口大小自适应了。还有一个无缝模式,可以在Windows中打开Ubuntu的窗口,有兴趣的同学可以试一下。

ubuntusetup31

三、Linux终端命令初探

用apt-get做个例子吧,抛砖引玉教大家Ubuntu Shell的一些入门命令。

首先是换源,国外的源太慢啦!大家会受不了的。

点击Ubuntu桌面右上角的设置齿轮,然后进入系统设置,选择软件和更新。

ubuntusetup33

选择下载至右边的网址下拉菜单,然后选择中国下载服务器里头的mirriors.aliyun.com,没错土豪阿里巴巴的源!

ubuntusetup34

选择服务器之后,会提示软件列表过时,重新载入下就行。如果过了几分钟卡住了,关闭那个重新载入的窗口即可。

ubuntusetup35

 

接下来就可以打开终端了,终端的位置在Ubuntu桌面左侧的导航条上。

Treminal

打开终端后就可以看到Ubuntu的终端界面啦。事实上世界上大多数正在运行的Linux是没有图形界面的,程序猿们都是在黑框里面敲各种各样的命令实现各种功能。学习Shell命令,是每一个程序猿的必经之路。

那我们就从更新系统开始介绍Ubuntu Terminal的一些命令吧,输入:

sudo apt-get update

敲完之后回车。会提示输入密码,那就继续敲密码然后回车咯。记住Linux的终端是不会出***符号的,所以你输入密码后,终端界面不会有任何的反应。

Treminal2

等更新完软件列表之后,再输入一个更新命令:

sudo apt-get upgrade

Treminal3

Treminal4

 

更新半天之后,会问您希望继续执行吗?输入y或者Y。。。回车就会开始更新系统了。等待很长时间之后,终于安装完了~

本文最后再介绍几条开关机命令:

sudo shutdown -h now

这条命令是关机命令,同样需要输入密码执行。

sudo shutdown -r now

当然还可以定时关机

sudo shutdown -h 100   //100分钟以后自动关机

接下来会介绍如何配置程序设计基础课的基本环境GCC。请大家移步Jimmy Zhou同学的我是猪脚[3]——How to Install gcc And How to Program On Ubuntu。这货还写了英文版………欢迎膜拜。

我是猪脚[1]——安装VirtualBox

根据杨老师程序设计基础课的要求,大一的学弟学妹们都需要安装或者虚拟一套Linux操作系统。所以就有了我和@Jimmy Zhou开了这一个我是助教系列。

这是本系列的第一篇文章,主要是介绍 开源虚拟机VirtualBox的安装方法,并且简单地介绍一下最适合Linux入门的Ubuntu操作系统及其获取方式。

一、准备

首先需要下载VirtualBox和Ubuntu,分别在https://www.virtualbox.org/wiki/Downloads 和http://www.ubuntu-china.cn/download 可以下载。为了防止小白手抽,给出下载链接:

VirtualBox

http://download.virtualbox.org/virtualbox/4.3.26/VirtualBox-4.3.26-98988-Win.exe

Ubuntu 14.04 Kylin x64(64位的骚年点这里)

http://cdimage.ubuntu.com/ubuntukylin/releases/14.04/release/ubuntukylin-14.04-desktop-amd64.iso

Ubuntu 14.04 Kylin x86(8632位的骚年点这里)

http://cdimage.ubuntu.com/ubuntukylin/releases/14.04/release/ubuntukylin-14.04-desktop-i386.iso

二、配置BIOS

VirtualBox需要配合intel或者AMD的硬件虚拟化技术才能发挥最佳的性能。

以Lenovo的机器为例,联想是关机状态按Novo键进入BIOS Setup,其他品牌可能是F9 F11 或者 F12之类的吧,具体可以百度。

20150402_215609

BIOS中的Configration标签,打开Intel Virtual Technology(设置为Enabled)。

20150402_215626

对于AMD的CPU,可能是AMD Virtualization或者AMD-V。这个问百度应该都可以找到图片。

三、安装VirtualBox

打开VirtualBox的安装包,之后的步骤看后面的图就行。有时候会弹出UAC或者驱动提示,允许即可。不对,不是即可,是一定要允许!!!

VBoxSetup1 VBoxSetup2 VBoxSetup3 VBoxSetup4 VBoxSetup5

安装的时候会弹出安装驱动的提示,一定要始终信任然后安装。

VBoxSafetyDrivers VBoxSetup6

 

四、配置权限

由于VirtualBox有高权限的系统调用,所以需要系统管理员权限运行。

我们需要右键VirtualBox的快捷方式,选择属性:

选择兼容性选项卡,点击更改所有用户的设置。

VBoxSetting

勾选以管理员身份运行此程序。高DPI屏幕的,要勾选高DPI设置时禁用显示缩放,避免显示错误。

VBoxSetting2

然后就可以打开VirtualBox玩了。

好吧马上就去更新接下来请移步多图文:我是猪脚[2]——在VirtualBox中安装Ubuntu Kylin 14.04

博客全站强制SSL和Nginx的一个问题

强制SSL

PositiveSSL_tl_trans

昨天半夜,Jimmy Zhou同学上了DO并且随着大流加了SSL。因为我的站早就启用了StartCom的免费SSL证书,所以他就来给我的SSL评了下分。结果——C。

强迫症瞬间就犯了,在Jimmy同学的鼓动下,入了$9一年的COMODO SSL。接下来就是升级网站的SSL配置,具体过程如下:

  • 1、升级Openssl(2015.10更新:改成了LibreSSL),使用更安全,ARM平台表现更好的加密算法: CHACHA20_POLY1305。。
  • 2、重新编译安装Nginx至最新版本,开启SPDY。(顺便吐槽下:lnmp一键安装包的一键升级Nginx脚本存在问题,还是手动配置大法好)
  • 3、禁用SSLv3(这是我搬回国内以后忘记做得事情)。
  • 4、强制http://dickwu.com 和 http://www.dickwu.com 301 至https://dickwu.com。

然后SSL评级就A了。离A+还差一步,至少强迫症解决了。

Update: 配置CHACHA20_Poly1305的详细教程,移步这里

Nginx Rewrite规则加斜杠的问题

新的强迫症出现:

这是老问题了,既然昨天动到了Nginx就想办法解决一遍。具体问题是这样的:

如果我访问https://dickwu.com/class04这个网址,最后没有加斜杠,Nginx会认为站点是在dickwu.com/下,CSS什么的自然是到dickwu.com/css里去找,这就造成了网页里所有css,js,图片挂掉的问题。

网上的办法都是在Nginx的配置文件中加一句server_name_in_redirect off; 但事实上Nginx早就把这个配置作为默认选项了。那问题肯定就出在了Wordpress的Rewrite规则上。

搞了一晚上,没想到办法。最后用了被Jimmy嘲笑的一招:

if (-d $request_filename){

rewrite ^/(.*)([^/])$ /$1$2/ permanent;

}

但是这绝对不是解决的办法。

求大神相救!!!!!!!!!!!!!!!!!