2015年1月31日星期六

Âà±H: «¢¦Ñ¤½¦Y©ÊÃÄ-«á©~µM³o.»ò±j

¨ì9sonba¶R©ÊÃÄ`«OÃÒ·m±Ï¦^§A-ªº±B«Ã

oqpqbpdwereykyyklkqf

 

 

到9sonba買性藥`保證搶救回你-的婚姻

 

謝謝9sonba讓-我馬子變這麼.浪

 

¶R©ÊÃÄ`«~¤£¥Î¦A¸ú¸úÂÃÂðÕ-§Ö¨Ó

bzcnbsmki

 

 

哇-買性藥還送情趣用品.也太好了

 

激爽絕對獨家-天堂heaven販.售

 

ªñ¦Ê-ºØ¬KÃij£.¦b9sonba

 

 

性藥品改善我-跟老公感情.真好

 

打破市場價格-讓你享受最價`廉春藥

 

asmcqmch

§Úµo»}-9sonba¤@©wÅý`§AÄ_¨©Â¼ªº«Ü

skzsgyozcsjypse

 

 

極樂bliss讓你進入.傳說中-的極樂境界

 

讓你享受更多-種性藥品都`在9sonba

 

¦p¦óÅý¦Ñ`¤½¦^«HÂà-·N¬Ý©p¨Ó§a

ityauhlynsa

 

 

老公.吃了性藥品-變好強喔

 

激爽只賣-最頂級的`性藥品

 

¦Û±q¶R¤F¶Ê±¡-ÃĦѱCÁ``¬O¤£Åý§Ú¤U§É

rarshwvlox

 

 

9sonba讓我從此-買性.藥不用在煩腦

§Ú°¨`¤l³£³Q§Ú·Fªº«s«s¥s'³£¬O¾a¤Ñ°ó

bquckoewhduuiok

 

 

壯陽.藥春藥催-情藥增大丸應有儘有

 

只`要120秒馬上感受春藥.威力

 

«z-³o®a©ÊÃÄ«~¶R¦hÁÙ.°eÃØ«~£®

o[7-20

§§¶§.ÃĬKÃĶÊ-±¡ÃļW¤j¤YÀ³¦³¾¨¦³

hlogvuynvwrbmru

 

 

春藥催情性藥品.壯陽-藥品金蒼蠅

 

好險有9sonba讓我-買催情'藥好方便喔

 

ªü®Q«Â©ÊÃÄ©~.µM¶R¶V¦h-°e¶V¦h

 

 

買了性藥品-後跟.老公感情變好

 

9sonba性藥讓我-人生變.彩色

 

wrofpffdfvclrhljeq

¿E²n°í«ù¥u.¤W¦³-¥Îªº¬KÃÄ

 

 

找了好-久終於.找到GAY專用性藥

 

哈.靠性藥品讓我變成萬人-斬

 

lyyqhdkf

¿E²n·PÁ¤ä«ù-©ÊÃÄ«~¥þ.­±ºÆ¨gÃØ°e

tpzzjzwhqvilkmvuhey

 

 

激爽壯陽專家讓我-馬子哀哀'叫

 

©ÊÃÄ«~¥þ²y³Ì.¦³®Ä-³Ìª¾¦W¬KÃĤ§¤ý

 

 

原來乖乖-女吃這`種藥才變蕩婦

 

好-險有9sonba讓我買壯陽藥好方.便喔

 

imofomtylicrvyr

Re: §O³Q¯º¤F©~µM.¨Sª±-¹L¶Ê±¡Ãd¨àRUSH

voqwxfyuiqkladt

 

 

找了好-久終於.找到GAY專用性藥

 

老婆吃-這藥挑逗到`我一直想要

 

2015年1月30日星期五

¶Ê±¡ÃĤj¤ý'±Ð§A«ç»ò¨É¨ü¤H.¥Í

rbpfmygzu

 

 

9sonba性藥品-讓妳成`為犀利人妻

 

獨.家-超火性藥品RUSH讓你high翻天

 

桥接模式 - 306573704

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
桥接模式 - 306573704  阅读原文»

1.概述

  将抽象部分与实现部分脱耦,使它们都可以独立的变化。 ——《设计模式》GOF

  所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。

  桥接模式所解决的问题是将两个维度的变化分离开,使它们可以独立的变化,是采用组合的方式使两个维度联系起来。

2.优缺点

  优点
  1)将实现抽离出来,再实现抽象,使得对象的具体实现依赖于抽象,满足了依赖倒转原则。

  2)将可以共享的变化部分,抽离出来,减少了代码的重复信息。

  3)对象的具体实现可以更加灵活,可以满足多个因素变化的要求。

  缺点
  1)客户必须知道选择哪一种类型的实现。

3.与继承的比较

  待补充。

4.策略模式、建造者模式、桥接模式的比较

  如果不考虑使用场景,单从实现过程来看,桥接模式是策略模式的横向扩展,即维度扩展,如软件维度增加一个手机维度;建造者模式是策略模式的纵向扩展,即增加了一个层次,如软件维度分解成不同的流程,由一个类统一调度。

  PS:求喷~

5.策略模式与建造者模式比较代码

  待补充。

6.桥接模式与策略模式的比较代码

  《大话设计模式C++版》中以手机和软件为例描述了桥接模式。我们先用策略模式来实现一种手机上安装不同软件。

1 #include <iostream>
2 #include <string>
3 #include <vector>
4 using namespace std;
5
6 //手机软件
7 class HandsetSoft
8 {
9 public:
10 virtual void Run()=0;
11 };
12
13 //游戏软件
14 class HandsetGame : public HandsetSoft
15 {
16 public:
17 virtual void Run()
18 {
19 cout<<"运行手机游戏"<<endl;
20 }
21 };
22
23 //通讯录软件
24 class HandSetAddressList : public HandsetSoft
25 {
26 public:
27 virtual void Run()
28 {
29 cout<<"手机通讯录"<<endl;
30 }
31 };
32
33 //手机
34 class Handset
35 {
36 protected:
37 HandsetSoft* m_soft;
38 public:
39 void SetHandsetSoft(HandsetSoft* temp)
40 {
41 m_soft = temp;
42 }
43 virtual void Run()
44 {
45 m_soft->Run();
46 }
47 };
48
49
50 //客户端
51 int main()
52 {
53 Handset *brand = new HandSet();
54 brand->SetHandsetSoft(new HandsetGame());
55 brand->run();
56
57 return 0;
58 }

  再将手机加上品牌这一维度:

1 #include <iostream>
2 #include <string>
3 #include <vector>
4 using namespace std;
5
6 //手机软件
7 class HandsetSoft
8 {
9 public:
10 virtual void Run()=
bb_black_学习笔记――(4)闪烁LED之shell命令 - lizhezhe  阅读原文»

上一篇笔记在终端输入shell命令实现了LED灯的点亮与熄灭,作为初学者,已经兴奋了一阵,因为终于有了零的突破。手动点亮LED总会比较麻烦,还是得通过程序让它自己去点亮与熄灭,这样才更好玩。这篇笔记里,笔者通过学习shell脚本,实现LED的闪烁,这样才好玩。本篇笔记还是先从现象看起,然后在分析其实现原理。

第一步:实现LED灯闪烁

1、先来个简单的shell脚本,了解一下shell脚本的使用流程。下图为创建一个名为hello_sh的shell脚本编辑执行的过程。

clipboard[5]

几点说明:

(1)shell脚本可以没有文件后缀名,但是为了方便,也可以添加.sh为其后缀。上图中用_sh作为表示,也可以没有;

(2)shell脚本创建可以利用touch filename的形式来创建;

(3)编辑shell脚本可以直接使用vi编辑器,关于vi编辑器的简单实用请查看上一篇笔记;

(4)shell脚本是不需要编译就可以运行,有shell解释器完成命令的解读与执行;

(5)新建的shell脚本需要为其添加可执行权限,否则执行时会出错;

(6)运行shell脚本直接使用 ./* 就可以运行;

(7)上图中hello_sh脚本文件里只有一行内容,目的是输出hello字符,脚本内容如下图。

image

2、下面就进入今天的主题,首先还是要手动创建shell脚本文件,笔者的文件名为led_sh。创建和添加权限的过程就不在此贴了,请参考上文。下面直接贴出led_sh的内容。

image

几点说明:

(1)有关shell的详细使用,请参考这个链接http://c.biancheng.net/cpp/shell/。这个网站有精简的shell教程,对于初学者来说入门还是挺快的;

(2)简答解释一下脚本内容:

1)第1行:在终端显示hello led一串字符;

2)第2~4行:这部分是一个if语句,目的是查看是否有gpio44文件,如果没有,通过第3行脚本添加一个;

3)第4行:为gpio44添加输出属性;

4)第7~13行:这部分是一个while语句,在while语句里面先点亮LED,然后睡眠0.25秒再熄灭LED,再睡眠0.25秒。如此不停的循环。

5)第14行:关闭LED灯。

3、通过vi编辑器输入上面的脚本,然后运行,就可以看到LED灯闪烁的状态了。现象和上一篇笔记是一样的,不过这次直接运行脚本就可以实现不断的闪烁了。

clipboard[7]clipboard[9]

第二步:学习简单的shell脚本

1、echo用法上一篇笔记已经说明,此处不再说明。

2、if用法。

if 语句通过关系运算符判断表达式的真假来决定执行哪个分支。if语句有三种用法,这里只说明最简单的一种语法,if ... else 语句:

1 if [ expression ] then
2 Statement(s) to be executed if expression is true
3 fi

几点说明:

(1)如果 expression 返回 true,then 后边的语句将会被执行;如果返回 false,不会执行任何语句。

(2)最后必须以 fi 来结尾闭合 if,fi 就是 if 倒过来拼写。

(3)expression 和方括号([ ])之间必须有空格,否则会有语法错误。

3、while用法。

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:

while command
do Statement(s) to be executed if command is true
done

几点说明:

(1)命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假,否则不退出;

(2)循环体的内容包含在do……done之间。


本文链接:bb_black_学习笔记――(4)闪烁LED之shell命令,转载请注明。

阅读更多内容

Oracle Study之--基于ASM的TSPITR(基于表空间的完全恢复)

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Oracle Study之--基于ASM的TSPITR(基于表空间的完全恢复)  阅读原文»

Oracle Study之--基于ASM的TSPITR(基于表空间的完全恢复)

系统环境:

操作系统:AIX5.3-08

数据库: Oracle 10gR2

Understanding RMAN TSPITR

In order to use TSPITR effectively, you need to understand what problems it can solve for you, what the major elements used in TSPITR are, what RMAN does during TSPITR, and limitations on when and how it can be applied.

RMAN TSPITR Concepts

Figure 8-1, "Tablespace Point-in-Time Recovery (TSPITR) Architecture"illustrates the context within which TSPITR takes place, and a general outline of the process.

Figure 8-1 Tablespace Point-in-Time Recovery (TSPITR) Architecture

wKiom1TIXrnRCEMWAAGW05m6DzE496.jpg

Description of "Figure 8-1 Tablespace Point-in-Time Recovery (TSPITR) Architecture"

The figure contains the following entities:

  • Thetarget instance, containing the tablespace to be recovered

  • TheRecovery Managerclient

  • Thecontrol fileand (optional)recovery catalog, used for the RMAN repository records of backup activity

  • Archived redo logsandbackup setsfrom the target database, which are the source of the reconstructed tablespace.

  • Theauxiliary instance, an Oracle database instance used in the recovery process to perform the actual work of recovery.

There are four other important terms related to TSPITR, which will be used in the rest of this discussion:

  • Thetarget time, the point in time or SCN that the tablespace will be left at after TSPITR

  • Therecovery set, which consists of the datafiles containing the tablespaces to be recovered;

  • Theauxiliary set, which includes datafiles required for TSPITR of the recovery set which are not themselves part of the recovery set. The auxiliary set typically includes:

    The auxiliary instance has other files associated with it, such as a control file, parameter file, and online logs, but they are not part of the auxiliary set.

    • A copy of the SYSTEM tablespace

    • Datafiles containing rollback or undo segments from the target instance

    • In some cases, a temporary tablespace, used during the export of database objects from the auxiliary instance

  • Theauxiliary destination, an optional location on disk which can be used to store any of the auxiliary set datafiles, control files and online logs of the auxiliary instance during TSPITR. Files stored here can be deleted after TSPITR is complete.

Performing Basic RMAN TSPITR

Having selected your tablespaces to recover and your target time, you are now ready to perform RMAN TSPITR. You have a few different options available to you:

  • Fully automated TSPITR--in which you specify an auxiliary destination and let RMAN manage all aspects of the TSPITR. This is the simplest way to perform TSPITR, and is recommended unless you specifically need more control over the location of recovery set files after TSPITR or auxiliary set files during TSPITR, or control over the channel configurations or some other aspect of your auxiliary instance.

  • Customized TSPITR with an automatic auxiliary instance--in which you base your TSPITR on the behavior of fully automated TSPITR, possibly still using an auxiliary destination, but customize one or more aspects of the behavior, such as the location of auxiliary set or recovery set files, or specifying initialization parameters or channel configurations for the auxiliary instance created and managed by RMAN.

  • TSPITR with your own auxiliary instance--in which you take responsibility for setting up, starting, stopping and cleaning up the auxiliary instance used in TSPITR, and possibly also manage the TSPITR process using some of the methods available in customized TSPITR with an automatic auxiliary instance.

案例分析:

用户在过去的时间点对emp1表做了误操作,需要通过物理备份恢复到过去的数据,本案例采用基于表空间的不完全恢复来实现对表的恢复!

1、案例测试环境

  SQL> show parameter name  NAME                                 TYPE        VALUE  ------------------------------------ ----------- ------------------------------  db_file_name_convert                 string  db_name                              string      zsdb  db_unique_name                       string      zsdb  global_names                         boolean     FALSE  instance_name                        string      zsdb  lock_name_space                      string  log_file_name_convert                string  service_names                        string      zsdb  SQL> select name from v$datafile;  NAME  --------------------------------------------------------------------------------  +DG1/zsdb/datafile/system.257.870003801  +DG1/zsdb/datafile/undotbs1.260.870004111  +DG1/zsdb/datafile/sysaux.258.870003999  +DG1/zsdb/datafile/users.262.870004141  +DG1/zsdb/datafile/zstb.259.870004085  +DG1/zsdb/datafile/testtb.261.870004125  6 rows selected.  SQL> col member for a50  SQL> select group#,member from v$logfile;      GROUP# MEMBER  ---------- --------------------------------------------------           4 +DG1/zsdb/onlinelog/group_4.266.870004663           4 +RCY1/zsdb/onlinelog/group_4.256.870004669           5 +DG1/zsdb/onlinelog/group_5.267.870004689           5 +RCY1/zsdb/onlinelog/group_5.257.870004693  SQL> set linesize 120  SQL> r    1* select file_id,file_name,tablespace_name from dba_data_files     FILE_ID FILE_NAME                                          TABLESPACE_NAME  ---------- -------------------------------------------------- ------------------------------           4 +DG1/zsdb/datafile/users.262.870004141             USERS           3 +DG1/zsdb/datafile/sysaux.258.870003999            SYSAUX           2 +DG1/zsdb/datafile/undotbs1.260.870004111          UNDOTBS1           1 +DG1/zsdb/datafile/system.257.870003801            SYSTEM           5 +DG1/zsdb/datafile/zstb.259.870004085              ZSTB           6 +DG1/zsdb/datafile/testtb.261.870004125            TESTTB  6 rows selected.  

2、建立测试

  SQL> select * from tab;  TNAME                          TABTYPE  CLUSTERID  ------------------------------ ------- ----------  DEPT 
zabbix企业应用之监控域名过期时间与ssl证书过期时间  阅读原文»

zabbix企业应用之监控域名过期时间与ssl证书过期时间

如果各位维护过n多个域名,可能会对备案与续费有所了解,备案是十分麻烦,各种流程,而续费的话,虽然比较简单,但如果你没有提前续费,可能导致域名不可用,甚至被他人给恶意注册,为了解决这样的问他,我今天给各位分享一下,如何使用zabbix监控域名过期时间与ssl证书过期时间,默认的触发器是在域名或ssl证书要过期前60天通知。

下面是监控域名过期时间的效果图

wKiom1TIOHeTYbIlAARFBuMm5aY078.jpg

下面是监控ssl证书过期时间效果图

wKioL1TIOabg9BotAAKsOUQWSlM422.jpg

如何实现:

一、客户端

1、修改zabbix_agentd.conf文件

在zabbix_agentd.conf最后添加以下内容

UserParameter=check_domain_beian[*],python/usr/local/zabbix_server/bin/check_domain_beian.py$1
UserParameter=check_ssl_cert_expire[*],/bin/bashusr/local/zabbix_server/bin/check_ssl_cert_expire.sh$1$2

其中check_domain_beian是监控域名过期时间,check_ssl_cert_expire是监控ssl证书过期时间

2、把下面脚本放入客户端的bin里(我的是放在/usr/local/zabbix_server/bin)

监控域名过期时间的脚本/usr/local/zabbix_server/bin/check_domain_beian.py内容为

#/usr/bin/envpython
#author:denglei
#email:dl528888@gmail.com
importdatetime
check_api='http://whomsy.com/api/%s?output=json'%sys.argv[1]
header={"User-Agent":"Mozilla-Firefox5.0"}
result=urllib2.urlopen(check_api)
check_result=result.read()
printcheck_result
c=re.findall("GODADDY.COM*",check_result)
cc=re.findall("GoDaddy*",check_result)
iflen(c)==0andlen(cc)==0:
c=re.findall("RegistrarRegistrationExpirationDate.*",check_result)
c=re.findall("ExpirationDate.*",check_result)
m=re.search("\d+-\d+-\d+..\d+:\d+:\d+",c[0])
e=re.sub('T','',d)
eliflen(c)==0andlen(cc)!=0:
c=re.findall("Recordexpireson.*",check_result)
m=re.search("\d+-\d+-\d+",c[0])
e=time.strftime("%Y-%m-%d%T",time.strptime(d,'%Y-%m-%d'))
c=re.findall("ExpirationDate.*",check_result)
m=re.search("\d+-\w+-\d+",c[0])
m=re.search("\w+\w+\d+\d+:\d+:\d+\w+\d+",c[0])
e=time.strftime("%Y-%m-%d%T",time.strptime(d,'%a%b%d%H:%M:%S%Z%Y'))
e=time.strftime("%Y-%m-%d%T",time.strptime(d,'%d-%b-%Y'))
iflen(c)==0:
defconver_time(start_time):
start_time=start_time
now_time=time.strftime('%Y-%m-%d%H:%M:%S')
warn_start=datetime.datetime.strptime(start_time,"%Y-%m-%d%H:%M:%S")
warn_finish=datetime.datetime.strptime(now_time,"%Y-%m-%d%H:%M:%S")
#td=warn_finish-warn_start
td=warn_start-warn_finish
warn_continue=(td.microseconds+(td.seconds+td.days*24*3600)*10**6)/10**6
ifwarn_continue>=86400:
day=warn_continue//86400
iflen(e)!=0:
printconver_time(e)

阅读更多内容

­ì¨Ó§¯Ã~¤§.«e©ÊÃÄ«~³£-¶R¶Q¤F

ebcumdqkzddxaoq

 

 

極樂bliss就是他-開始改變.你的世界

 

極樂bliss.現在開始玩樂你-的感官

 

©ÊÃÄ«~Åý.§Ú-¸ò§Ú¨kªB¤Í¤£¥Î¤À¤â°Õ

m[7-20

¶ã²×©ó.§ä¨ì¥«-»ù³Ì«K©yªº¬KÃÄ

zyvafyjvfqwhfjgqekd

 

 

春藥.大帝9sonba知名性-藥都在這

Re: 3¬í¤º¥ß-¨è¿E`µo¨k¤Hªº¦å¯ß¼Q±i

caixqeidgkesvjp

 

 

這麼怕-小三就買`春藥改變自已

 

馬子在床上都在裝`怎麼讓她變蕩婦呢?

 

GAY³Ì·R-ªº¬KÃij£¦b.³o¸Ì°Õ

agntxtdykpfo

 

 

9sonba不.是獨家性`藥不上架

 

°¨¤l¦b§É¤W'©ñ¤£¶}-Åý§Ú­ÌÀ°§U§A

zaiiuoivph

 

 

春`藥快來-小三退散

9sonba¶Ê±¡-ÃÄÅý§Ú¨C¤Ñ²n±o«s«s.¥s

qhacyubbowlj

 

 

還不快買春-藥小三都來.搶老公了

 

超強力催情解決性-冷淡.想要瘋狂做愛

 

¬K.ÃĤj¤ý-Åý§Ú­Ì.¤Ò©dhigh¨ì©³

tyxjapymr

 

 

買了性藥品-後跟.老公感情變好

 

GAY最愛的性.藥都在-這裡啦

 

¤Ò©d§n§Ö`Â÷-±B¤F?§Ö¥¦­«¬B³Ìªì¼öÅÊ

rqyjjslrvicpox

 

 

嘿嘿自從買了-催情藥老公`總是笑呵呵

 

這-有同志葛葛.最愛的性藥ㄝ

 

Re: RUSH 15¬í-Åý§Ahigh¨ì`Åq®p

qxkcpluuzvvjvbgsrra

 

 

原來老婆要吃了-催情藥才會這麼浪

 

同志最`愛的春藥.都在這裡啦

 

§ä¤F¦n¤[.²×©ó§ä¨ì¦P-§Ó±M¥Î©ÊÃÄ

 

 

感謝9sonba讓我-老婆變這麼.騷

 

天阿.老公吃了壯陽-藥變得天天要

 

obbyjjcgktf

2015年1月29日星期四

¥þ²y³Ì¦³®Ä³Ì-ª¾¦W§§¶§ÃÄ.¤¤²Ä¤@«~µP

feawtizocgtwgcln

 

 

RUSH催情.藥ㄝ你居然沒-試過

 

老公吃這-藥後居然性.慾大開

 

©Ê`ÃÄ«~´N¬O­n¤£¤@¼Ë¤~¯S.§O

klstyycdedaovij

 

 

9sonba催情王-國讓你.性福

¥þ¥x³Ì±j-©ÊÃÄ«~`ºô³£¦b9sonba

rggqndu

 

 

到9sonba買性藥`保證搶救回你-的婚姻

 

我馬`子都被我幹的哀哀叫'都是靠天堂

 

Fw: «z-³o®a©ÊÃÄ«~¶R¦hÁÙ.°eÃØ«~£®

 

 

性藥品改-變我一生命運.真爽

 

3秒內立-刻激`發男人的血脈噴張

 

jimaqfupcxemmhiimt

Fw: §§¶§.ÃĬKÃĶÊ-±¡ÃļW¤j¤YÀ³¦³¾¨¦³

 

 

wow這家春-藥網怎麼`這麼便宜

 

nqmuitvgkmdoz

Fw: ¸ó¦~¤F.¤d¸U¤£­n-§Ñ¤F©ÊÃÄ«~¸Ñ±Ï§A

 

 

馬子在床上都在裝`怎麼讓她變蕩婦呢?

 

壯陽藥-品使用讓您性生`活充滿情趣

 

wnnxepdicnttwttizo

Âà±H: ¬KÃÄÅý§Ú`¸ò§Ú-¦Ñ±C¤£¥ÎÂ÷±B°Õ

 

 

上網.買壯陽藥不在-讓我臉紅紅

 

壯陽.藥春藥催-情藥增大丸應有儘有

 

hbkytzfsgru

­ì¨Ó±d±d.°¨¤l³o»ò-¦º¤ßÁϦa¬O³o­ì¦]

 

 

女朋友放不開就買-春藥.讓她發浪

 

自從吃了極樂馬子都-說不要再幹我'啦

 

rmtqypd

Re: ¤Ñ°óheaven-Åý§A­«¦^.¨k©Êªº´LÄYÄ_®y

ffhcoxcbatj

 

 

春藥壯陽藥品-應有.盡有上百種

 

什麼?激爽最-新產品?極-樂bliss

 

¬K.ÃÄÅý©p¦b§É¤W¤£¦A-²Û²ÛÁy

aifgksshawcrayhpwhg

 

 

全台最強-性藥品`網都在9sonba

 

Fw: §A­n.ªº¬KÃÄ-³£¦b9sonba

ngrwlnkfnzgedjblctj

 

 

每年全台.百萬人-吃過性藥你還懷疑?

 

我馬`子都被我幹的哀哀叫'都是靠天堂

 

2015年1月28日星期三

Canvas之蛋疼的正方体绘制体验 - 子迟

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Canvas之蛋疼的正方体绘制体验 - 子迟  阅读原文»

事情的起因

  之前写了篇谈谈文字图片粒子化 I,并且写了个简单的demo -> 粒子化。正当我在为写谈谈文字图片粒子化II准备demo时,突然想到能不能用正方体代替demo中的球体粒子。我不禁被自己的想法吓了一跳,球体的实现仅仅是简单的画圆,因为球体在任意角度任意距离的视图都是圆(如果有视图的话);而正方体有6个面8个点12条线,在canvas上的渲染多了n个数量级。先不说性能的问题,单单要实现六个面的旋转和绘制就不是一件特别容易的事情。

  说干就干,经过曲折的过程,终于得到了一个半成品 -> 粒子化之正方体

  

事情的经过

  事情的经过绝不像得到的结果那样简单。虽然半成品demo视觉上还有些许违和感,但已经能基本上达到我对粒子化特效的要求。

  那么接下来说说我这次的蛋疼体验吧。

  之前我们已经实现了一个点在三维系的坐标转换(如不懂,可参考 rotate 3d I),并且得到了这样的一个demo -> 3d球体。 那么我想,既然能得到点在三维系的空间转换坐标,根据点-线-面的原理,理论上应该很容易实现正方体在三维系的体现,不就是初始化相对位置一定的8个点么?而且之前也简单地实现了一个面的demo -> 3d爱心,当时认为并不难。

  于是我根据一定的相对位置,在三维系中初始化了8个点,每帧渲染的同时实现8个点的位置转移,并且根据8个点的位置每帧重绘12条线,得到demo -> 3d正方体

  似乎很顺利,接着给6个面上色,效果图如下:

  这时我意识到应该是面的绘制顺序出错了,在每帧的绘制前应该先给面排个序,比如图示的正方体的体心是三维系的原点,那么正方体的后面肯定是不可见的,所以应该先绘制。而在制作三维球体旋转时,是根据球体中心在三维系的坐标z值排序的,这一点也很好理解,越远的越容易被挡就越先画嘛;同时我在WAxes的这篇用Canvas玩3D:点-线-面中看到他绘制正方体的方法是根据6个面中心点的z值进行排序,乍一想似乎理所当然,于是我去实现了,体心在原点体验良好,demo -> 3d正方体,但是体心一改变位置,就坑爹了...

  

  图示的正方体体心在原点的右侧(沿x轴正方向),但是画出来的正方体却有违和感,为何?接着我还原了绘制的过程:

  

  绘制过程先绘制了正方体的左面,再绘制了上面,而根据生活经验这两个面的绘制顺序应该是先上面,再左面!不断的寻找错误,我发现这两个面中点的z值是一样的,甚至除了前后两个面,其他的四个面的z值都是一样的,也就是说这个例子中后面最先绘,前面最后绘,其他四个面的绘制顺序是任意的。我继续朝着这个方向前进,根据我的生活经验,如果像上图一样体心在原点右边(其实应该是视点,当时认为是原点),那么如果面的z值相同,应该根据面与原点的x方向的距离进行排序,毕竟距离小的先看到,如果x方向距离又相同,那么根据y方向的距离进行排序,代码如下:

  

var that = this;
this.f.sort(function (a, b) {
if(b.zIndex !== a.zIndex)
return b.zIndex - a.zIndex;
else if(b.xIndex !== a.xIndex) {
// 观察基准点(0,0,0)
if(that.x >= 0)
return b.xIndex - a.xIndex;
else
return a.xIndex - b.xIndex;
} else {
if(that.y >= 0)
return b.yIndex - a.yIndex;
else
return a.yIndex - b.yIndex;
}

  因为排序中this指向了window,还需赋值给一个另外的变量。事情似乎在此能画上一个圆满的句号,but...

  调整后下继续出现违和感(截图如下),虽然违和感的体验就在那么一瞬,但是我还是觉得是不是这个排序思路出错了?于是进一步验证,通过调试,将面的排序结果和正确的绘制顺序作对比,最终发现排序算法是错误的,最后知道真相的我眼泪掉下来。

  于是在知乎上问了下:怎样在二维上确定一个三维空间正方体六个面的绘制顺序? 有计算机图形学基础的请无视。

  原来这是一个古老的问题,在各位图形学大大的眼里是很基础的问题了。原来这个问题称为隐藏表面消除问题。

  然后我跟着这个方法进行了绘制,一开始把视点和原点搞混掉了。也就是判断每个面的法向量(不取指向体心的那条)和面(近似取面中心)到视点的那条向量之间的角度,如果小于90度则是可见。想了一下,似乎还真是那么一回事。然后需要设定视点的坐标,随意设置,只要合乎常理就行,这里我设置了(0,0,-500),在z方向肯定是个负值。

  一个正方体差不多搞定了,多个正方体呢?问题又出现:

  很显然,正方体之间也有绘制的先后顺序,这里粗略地采用根据体心排序的方法,按照Milo Yip的说法,这可以解决大部分情况,但也会漏掉一些最坏情况。最好的做法是zbuffer算法。

  于是乎,一个多正方体demo新鲜出炉了-> 多正方体demo

  如果要打造 粒子化之正方体 的效果,参考-> 谈谈文字图片粒子化 I

  梳理一下多正方体具体渲染过程:

  • 先将正方体进行排序,确定正方体的绘制顺序
  • 接着渲染每个正方体,先渲染正方体的各个点,改变各个点最新的坐标
for(var i = 0; i < 8; i++)
this.p.render();
  • 点渲染完后,根据最新的点的坐标调整正方体体心坐标,为下一帧的正方体排序准备
this.changeCoordinate();
  • 获取每个面法向量和面中点和视点夹角cos值,如果大于0(夹角小于90)则绘制:
for(var i = 0; i < 6; i++)
this.f.angle = this.f.getAngle();

this.f.sort(function (a, b) {
return a.angle > b.angle;
});

for(var i = 0; i < 6; i++) {
// 夹角 < 90,绘制
if(this.f.angle > 0)
this.f.draw();
}
  • 反复渲染

  完整代码如下:

1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title> rotate 3d</title>
6 <script>
7 window.onload = function() {
8 var canvas = document.getElementById('canvas');
9 ES6笔记之参数默认值(译) - Bosn Ma  阅读原文»

  • 原文链接:http://dmitrysoshnikov.com/
  • 原文作者:Dmitry Soshnikov
  • 译者做了少量补充。这样的的文字是译者加的,可以选择忽略。

在这个简短的笔记中我们聊一聊ES6的又一特性:带默认值的函数参数。正如我们即将看到的,有些较为微妙的CASE。

ES5及以下手动处理默认值

在ES6默认值特性出现前,手动处理默认值有几种方式:

function log(message, level) {
level = level || 'warning';
console.log(level, ': ', message);
}

log('low memory'); // warning: low memory
log('out of memory', 'error'); // error: out of memory

为了处理参数未传递的情况,我们常看到typeof检测:

if (typeof level == 'undefined') {
level
= 'warning';
}

有时也可以检查arguments.length

if (arguments.length == 1) {
level
= 'warning';
}

这些方法都可以很好的工作,但都过于手动且缺少抽象。ES6规范了直接在函数头定义参数默认值的句法结构。

ES6默认值:基本例子

默认参数特性在很多语言中普遍存在,其基本形式可能大多数开发者都比较熟悉:

function log(message, level = 'warning') {
console.log(level,
': ', message);
}

log(
'low memory'); // warning: low memory
log('out of memory', 'error'); // error: out of memory

参数默认值使用方便且毫无违和感。接下来让我们深入细节实现,扫除默认参数所带来的一些困惑。

实现细节

以下为一些函数默认参数的ES6实现细节。

执行时求值

相对其它一些语言(如Python)在定义时一次性对默认值求值,ECMAScript在每次函数调用的执行期才会计算默认值。这种设计是为了避免在复杂对象作为默认值使用时引发一些困惑。接下来请看下面Python的例子:

def foo(x = []):
x.append(
1)
return x

# 我们可以看到默认值在函数定义时只创建了一次
#
并且存于函数对象的属性中
print(foo.__defaults__) # ([],)

foo()
# [1]
foo() # [1, 1]
foo() # [1, 1, 1]

print(foo.__defaults__) # ([1, 1, 1],)

为了避免这种现象,Python开发者通常把默认值定义为None,然后为这个值做显式检查:

def foo(x = None):
if x is None:
x
= []
x.append(
1)
print(x)

print(foo.__defaults__) # (None,)

foo()
# [1]
foo() # [1]
foo() # [1]

print(foo.__defaults__) # ([None],)

就目前,很好很直观。接下来你会发现,若不了解默认值的工作方式,ES5语义上会产生一些困惑。

外层作用域的遮蔽

来看下面的例子:

var x = 1;

function foo(x, y = x) {
console.log(y);
}

foo(
2); // 2, 不是 1!
来上例的y输出结果看起来像是1,但实际上是2,不是1。原因是参数中的x与全局的x不同。由于默认值在函数调用时求值,所以当赋值=x时,x已经在内部作用域决定了,引用的是参数x本身。也就是说,参数x被全局的同名变量遮蔽,所以每次默认值中访问x时,实际访问到的是参数中的x

参数的TDZ(Temporal Dead Zone,暂存死区)

ES6提到所谓的TDZ(暂存死区),意指这样的程序区域:初始化前的变量或参数不能被访问。

考虑到对于参数,不能将自己作为默认值:

var x = 1;

function foo(x = x) { // throws!
...
}
赋值=x正如我们上面提到的那样,x会被解释为参数级作用域中的x,而全局的x会被遮蔽。但是,x位于TDZ,在初始化前不能被访问。因此,它不能自己初始化自己。

注意,上面之前的例子中的<

阅读更多内容

2015年1月26日星期一

MariaDB数据库主从复制、双主复制、半同步复制、基于SSL的安全复制实现及其功能特性介绍

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
MariaDB数据库主从复制、双主复制、半同步复制、基于SSL的安全复制实现及其功能特性介绍  阅读原文»

MariaDB数据库主从复制、双主复制、半同步复制、基于SSL的安全复制实现及其功能特性介绍

一.复制概述

MariaDB/MySQL内建的复制功能是构建大型,高性能应用程序的基础。将MySQL的数据分布到多个系统上去,这种分布的机制,是通过将MySQL的某一台主机的数据复制到其它主机(slaves)上,并重新执行一遍来实现的。复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。主服务器将更新写入二进制日志文件,并维护文件的一个索引以跟踪日志循环。这些日志可以记录发送到从服务器的更新。当一个从服务器连接主服务器时,它通知主服务器从服务器在日志中读取的最后一次成功更新的位置。从服务器接收从那时起发生的任何更新,然后封锁并等待主服务器通知新的更新。

请注意当你进行复制时,所有对复制中的表的更新必须在主服务器上进行。否则,你必须要小心,以避免用户对主服务器上的表进行的更新与对从服务器上的表所进行的更新之间的冲突。

1.mysql支持的复制类型:

(1):基于语句的复制:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。MySQL默认采用基于语句的复制,效率比较高。

一旦发现没法精确复制时,会自动选着基于行的复制。

(2):基于行的复制:把改变的内容复制过去,而不是把命令在从服务器上执行一遍. 从mysql5.0开始支持

(3):混合类型的复制: 默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。

2.复制解决的问题

MySQL复制技术有以下一些特点:

(1).数据分布 (Data distribution )

(2).负载平衡(load balancing)

(3).备份(Backups)

(4).高可用性和容错行 High availability and failover

3.复制如何工作

整体上来说,复制有3个步骤:

(1).master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events);

(2).slave将master的binary log events拷贝到它的中继日志(relay log);

(3).slave重做中继日志中的事件,将改变反映它自己的数据。

下图描述了复制的过程,经典的图哦:摘自《高性能MySQL_第3版》这本经典书中的复制那章;

wKioL1TEevjT8n3DAADUwaoLIc4842.jpg

该过程的第一部分就是master记录二进制日志。在每个事务更新数据完成之前,master在二日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,master通知存储引擎提交事务。

下一步就是slave将master的binary log拷贝到它自己的中继日志。首先,slave开始一个工作线程��I/O线程。I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志。

SQL slave thread(SQL从线程)处理该过程的最后一步。SQL线程从中继日志读取事件,并重放其中的事件而更新slave的数据,使其与master中的数据一致。只要该线程与I/O线程保持一致,中继日志通常会位于OS的缓存中,所以中继日志的开销很小。

此外,在master中也有一个工作线程:和其它MySQL的连接一样,slave在master中打开一个连接也会使得master开始一个线程。复制过程有一个很重要的限制��复制在slave上是串行化的,也就是说master上的并行更新操作不能在slave上并行操作。

二.复制配置过程简介

有两台MySQL数据库服务器node3和node4,node3为主服务器,node4为从服务器,初始状态时,node3和node4中的数据信息相同,当node3中的数据发生变化时,node4也跟着发生相应的变化,使得node3和node4的数据信息同步,达到备份的目的。

要点:

负责在主、从服务器传输各种修改动作的媒介是主服务器的二进制变更日志,这个日志记载着需要传输给从服务器的各种修改动作。因此,主服务器必须激活二进制日志功能。从服务器必须具备足以让它连接主服务器并请求主服务器把二进制变更日志传输给它的权限。

配置主从复制的过程:

1).主节点操作步骤

(1)、启用二进制日志

(2)、设置一个在当前集群中唯一的server-id;

(3)、创建一个有复制权限(replication slave,replication client)帐号;

2).slave节点的操作步骤

(1)、启用中继日志;

(2)、设置一个在当前集群中唯一的server-id;

(3)、使用有复制权限的用户帐号连接至主服务器,并启动复制线程;

注意:

(1)、服务器版本:主从服务器版本一致;

如果版本不一致,必须保证从服务器的版本高于主服务器的版本;

(2)、如果mysql数据库的隔离级别为可读,其二进制日志格式尽量使用基于行的;

实验环境:

服务器版本为:

CentOS 6.6 64bit

数据库软件版本为:

mariadb-10.0.12.tar.gz

node3节点的IP地址:172.16.31.20/16

openstack icehouse 安装消息队列模块rabbitmq  阅读原文»

openstack icehouse 安装消息队列模块rabbitmq

rabbitmq是一个在AMQP基础上,可复用的企业消息系统。MQ是Message Queue消息队列,是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信。

系统环境:Centos6.5_64位。安装rabbitmq-server消息队列模块的步骤:

1.rabbit-mq可以通过YUM直接安装的。yum -y install rabbitmq-server就可以完成安装。

2.开启下rabbitmq-server的插件:/usr/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_management

3.重启rabbitmq-server服务:/etc/init.d/rabbitmq-server restart

4.netstat -plantup|grep 15672netstat -plantup|grep 5672查看消息队列的端口,其中5672为默认的rabbitmq监听端口,15672端口为WEB界面访问需要打开的端口。

5.访问http://IP:15672/进行访问,输入guest/guest可以看到rabbitmq的信息,rabbitmq的队列存储方式分成2种:内存和硬盘。

wKioL1TE-rWQezVKAAFDcAd1q1k388.jpg

wKiom1TE-l7RJ9CeAAQHCuZAJGQ846.jpg

wKioL1TE-52RVq13AAOL6WoOxGU737.jpg

6.使用rabbitmqctl命令添加用户:rabbitmqctladd_useropenstackopenstack

7.使用rabbitmqctl命令设置openstack账户为管理员角色:rabbitmqctlset_user_tagsopenstackadministrator

8.使用rabbitmqctl命令查询用户:rabbitmqctllist_users

本文出自 "林夕" 博客,请务必保留此出处http://lutaoxu.blog.51cto.com/4224602/1608156

分享至 一键收藏,随时查看,分享好友!
昵称:
登录快速注册
内容:

阅读更多内容

博客园客户端UAP开发随笔 – 让自己的App连接世界(2):WinRT中的内置分享 - MS-UAP

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
博客园客户端UAP开发随笔 � 让自己的App连接世界(2):WinRT中的内置分享 - MS-UAP  阅读原文»

看到一篇眼前一亮的博文,是不是有一种希望其他小伙伴都能看到的感觉呢?有没有一种“不转不是程序员”的冲动呢?在 PC 浏览器上看到还好办,直接网址复制,另一边 IM 上就发过去了,但是如果是 App 中的内容,就没这么方便了,总不能那边 IM 上喊话:“隔壁老王,博客园上有篇叫‘博客园客户端(Universal App)开发随笔 – 为应用插上分享的翅膀’的博文超好看,要不你也瞅瞅?”。隔壁老王再去搜索就太麻烦了。可能你会说了,嗨,直接分享不就完了么。嗯,没错,就是分享功能。那么如何把分享功能引入我们的 App 中呢?往下看。

Universal App 中 Windows / Windows Phone 之间分享的异同

既然是 Universal App,那自然是共享一套分享的代码啦。但是在这两个之间还是有点不一样的。区别在于,Windows 版由于有 Charm Bar 这个系统级的菜单,而这菜单里是自带分享入口的,所以 Windows 版只需要提前准备好需要分享的内容,而不需要再提供另外的分享入口。而 Windows Phone 没有这个入口,所以除了需要准备好内容,还需要通过菜单或按钮的形式,显式提供一个分享的入口。

image

不过随着 Windows 10 的发布,Charm Bar 的寿命也走到了尽头,在之前的 Technical Preview 中还是偶尔可以(误触)调出 Charm Bar 的,但是在刚刚发布的 Costumer Preview,我们应该就见不到 Charm Bar 的身影了,所有它提供的入口,都被移到了下图所示的位置:

image

这一点倒是不难理解,因为所有的 App 都可以窗口化了,右滑调出 Charm Bar 的方法已经不好用,所以单独提供一个入口。不过不得不说,这个入口的尺寸不是很适合点击,而且呼出的菜单更是容易误触,尤其是在 Surface Pro 的高分屏上。这个改动,也使得原来上/下滑呼出 App Commands 的操作需要两步才能完成,极为繁琐。我们也因此不得不在新版的博客园中添加了 App Commands 的快捷入口。

另外,Windows 默认提供了两种分享的方式,一是使用开发者提供的信息,二是直接分享当前屏幕截图。而截至到目前的 Windows Phone Technical Preview 中,也是只有一种分享方式的,没有截屏的选项。在 Windows Phone 中,我们还需要手动添加一个入口,如下图所示。

创建分享(以 Text 举例)

好了,我们以博客园的博文分享举例,来介绍一下如何创建一个分享。

在动手之前,我们要先在脑海里想一下需要分享的内容。例如,博客园我们需要分享如下格式的信息(分割线以内):

--------------

标题:博文标题

作者:博文作者

摘要:博文摘要

博文链接

--------------

思路是这样的,我们先创建一个字符串,包含以上信息,然后加入注册到分享内容,最后显式或系统调用出分享入口,进行分享。

具体实现如下:

首先添加引用:

using Windows.ApplicationModel.DataTransfer;

然后创建一个叫RegisterForShare的方法,将需要分享的内容注册到分享区:

private void RegisterForShare()
{
DataTransferManager dataTransferManager
= DataTransferManager.GetForCurrentView();
dataTransferManager.DataRequested
+= new TypedEventHandler<DataTransferManager, DataRequestedEventArgs>(this.ShareTextHandler);
}

在这个方法里,我们创建了一个 DataTransferManager,并让他在 DataRequested 事件时出发 ShareTextHandler 方法:

private void ShareTextHandler(DataTransferManager sender, DataRequestedEventArgs e)
{
DataRequest request
= e.Request;
request.Data.Properties.Title
= loader.GetString("ShareTitlePrefixText") + this.post.Title;
request.Data.Properties.Description
= this.post.Summary;
string summaryText = (this.post.Author != null ? loader.GetString("ShareContentAuthor") + this.post.Author.Name : "") + "\n"
+ loader.GetString("ShareContentSummary")
+ (this.post.Summary.Length > 50 ? this.post.Summary.Substring(0, 50) : this.post.Summary) + "\n"
+ this.post.Link.Href;
request.Data.SetText(summaryText);
}

接下来实现这个方法,创建一个 request,并完善其中的内容,其中 request.Data.Properties.Title 是会显示在分享的标题部分的内容,例如如果是邮件分享,那么邮件标题就是 request.Data.Properties.Title,如果 Onenote 分享,那么 note 的标题也会是 request.Data.Properties.Title。request.Data.Properties.Description 不是必选项,仅仅是个备注参考,不会在分享时显示出来。最后的 request.Data.SetText(summaryText); 将其余需要分享的内容设置好。这样,我们的需要分享的内容就像之前想的一样都已经填写进去了。

接下来要做的,是先调用 RegisterForShare 方法,这个过程可以在构造函数中完成。

分享入口

如果是 Windows,那么在构造函数调用了 RegisterForShare 方法以后,就已经可以直接分享了,入口就是之前提到过的 Charm Bar 或 菜单:

image

右边的下拉菜单,可以选择分享内容或截屏:

image

我们选择邮件分享内容

image

就可以发送了。

如果是 Windows Phone,那么我们还需要多做一步。

创建一个菜单的入口:

<AppBarButton x:Name="btn_Share" x:Uid="ShareBlogButton" Label="分享博文" Click="btn_Share_Click" Icon="ReShare"/>

然后在点击的方法中,显式调用分享入口:

private void btn_Share_Click(object sender, RoutedEventArgs e)
{
Windows.ApplicationModel.DataTransfer.DataTransferManager.ShowShareU
ASP.NET MVC路由解析 - Sinte-Beuve  阅读原文»

继续往下看《ASP.NET MVC5框架揭秘》。

ASP.NET系统通过注册路由和现有的物理文件路径发生映射。而对于ASP.NET MVC来说,请求的是某个Controller中的具体的Action方法。

路由注册和忽略的方法与ASP.NET不同,位于RouteCollectionExtensions中。

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
  name:
"Default",
  url:
"{controller}/{action}/{id}",
  defaults:
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  );
UrlParameter.Optional表示只有请求的URL真正包含具体变量值时RouteData里才会有相应的变量存储。
Area路由映射
场景:假设在Controller下有一个文件夹api,里面包含了用来调用的api。而且api目录下的Controller可能包含和Controller目录下相同命名的Controller。
这个时候可以通过AreaRegisteration来实现。AreaRegisteration是一个抽象类。
public abstract class AreaRegistration
{
protected AreaRegistration();
public abstract string AreaName { get; }
public static void RegisterAllAreas();
public static void RegisterAllAreas(object state);
public abstract void RegisterArea(AreaRegistrationContext context);
}

建一个ApiAreaRegisteration,实现抽象方法和属性。

public class ApiAreaRegisteration :AreaRegistration
{
     //返回一个Area名
public override string AreaName
{
get { return "api"; }
}
  
public override void RegisterArea(AreaRegistrationContext context)
{
object defaults = new
{
Controller
= "utility",
Action
= "Add",
num
= 1
};
context.MapRoute(
"apidefault","api/{controller}/{action}/{num}",defaults,new string[]{"MvcTest.Controllers.api"});
}
}

在global.asax里注册

protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
}
AreaRegistrationContext 是对应的上下文包含AreaName、Routes等信息和一系列注册路由的方法。
调用
AreaRegistration.RegisterAllAreas()静态方法后,会反射创建ApiAreaRegisteration 对象,调用RegisterArea()方法实现路由注册。

注:本博文大多数内容来自《ASP.NET MVC5框架揭秘》中,只是做内容的梳理和总结。


本文链接:ASP.NET MVC路由解析,转载请注明。

阅读更多内容