<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    qileilove

    blog已經轉移至github,大家請訪問 http://qaseven.github.io/

    Burpsuite截斷上傳拿webshell

     很多大大都在用burpsiute,大大們說這個工具的功能有很多很多,小菜我也不知到底是些什么。然后找了篇大大寫的Burpsiute截斷上傳拿webshell的科普帖看了看,然后去實踐了一下。
      在后臺上傳圖片的地方添加xxx.jpg 圖片
      打開burpsiute
      進入proxy后進入options修改代理端口等
      然后設置瀏覽器
     在瀏覽器里提交上傳截取到數據
      修改/pimage => /pimage/xx.asp
      最后把空格修改
      得到webshell地址

    posted @ 2014-12-03 13:44 順其自然EVO 閱讀(479) | 評論 (0)編輯 收藏

    LoadRunner腳本開發-字符串編碼轉換

     相關函數
      lr_convert_string_encoding函數
      功能:字符串編碼轉換
      原型:
      int lr_convert_string_encoding(const char *sourceString, const char *fromEncoding, const char *toEncoding, const char *paramName);
      返回值:0(執行成功)、-1(執行失敗)
      參數說明:
      sourceString:要轉換的字符串
      fromEncoding:源字符的編碼
      toEncoding:保存在參數parmaName中的字符串編碼,即要轉換的目標編碼
      paramName:保存轉換編碼后的字符串
      說明:
      1.lr_convert_string_encoding支持system locale,Unicode,UTF-8字符串編碼的相互轉換,參數paramName中保存結果字符串,該結果字符串包含字符串結束符NULL
      2.結果字符串中的可打印字符在VuGen和日志中按實際字符顯示,不可打印字符則以十六進制顯示,例如:
      rc = lr_convert_string_encoding("A", NULL, LR_ENC_UTF8, "stringInUnicode");
      結果字符串(即stringInUnicode參數值)顯示為:A\x00, 而不是\x41\x00,因為A為可打印字符串.
      3.fromEncoding and toEncoding可選值:
      loadrunner <wbr>腳本開發-字符串編碼轉換
      例子:
    Action()
    {
    int rc = 0;
    char *converted_buffer_unicode = NULL;
    rc = lr_convert_string_encoding("hello", NULL, LR_ENC_UNICODE, "stringInUnicode");
    if(rc < 0)
    {
    lr_output_message("convert_string_encoding failed ");
    // error
    }
    return 0;
    }
      輸出結果:

    posted @ 2014-12-03 13:44 順其自然EVO 閱讀(1005) | 評論 (0)編輯 收藏

    網頁主動探測工具使用

     單位的項目是IBatis做的,每個查詢的SQL里面都有很多判斷
      上次優化SQL之后,其中的一個分支報錯,但是作為dba,不可能排查每一個分支.
      所以,干脆用爬蟲爬過所有的網頁,主動探測程序的異常.
      這樣有兩個好處
      1.可以主動查看網頁是否異常 (500錯誤,404錯誤)
      2.可以篩查速度較慢的網頁,從這個方向也可以定位慢SQL吧.(也有服務器資源不足,造成網絡超時的情況)
      前提,
      必須是互聯網公司,大多數網頁不用登錄也可以瀏覽
      首先,建表
      CREATE SEQUENCE seq_probe_id INCREMENT BY 1 START WITH 1 NOMAXvalue NOCYCLE CACHE 2000;
      create table probe(
      id int primary key,
      host varchar(40) not null,
      path varchar(500) not null,
      state int not null,
      taskTime int not null,
      type varchar(10) not null,
      createtime date default sysdate not null
      ) ;
      其中host是域名,path是網頁的相對路徑,state是HTTP狀態碼,taskTime是網頁獲取時間,單位是毫秒,type是類型(html,htm,jpg等)
      程序結構
      程序分三個主要步驟,再分別用三個隊列實現生產者消費者模式.
      1.連接.根據連接隊列的目標,使用Socket獲取網頁,然后放入解析隊列
      2.解析.根據解析隊列的內容,使用正則表達式獲取該網頁的合法連接,將其再放入連接隊列.然后將解析的網頁放入持久化隊列
      3.持久化.將持久化隊列的內容存入數據庫,以便查詢。
      程序使用三個步驟并行,每個步驟可以并發的方式.
    但是通常來說,解析和持久化可以分別用單線程的方式執行.
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Set;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.ConcurrentSkipListSet;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    public class Probe {
    private static final BlockingQueue<Task> CONNECTLIST = new LinkedBlockingQueue<Task>();
    private static final BlockingQueue<Task> PARSELIST = new LinkedBlockingQueue<Task>();
    private static final BlockingQueue<Task> PERSISTENCELIST = new LinkedBlockingQueue<Task>();
    private static ExecutorService CONNECTTHREADPOOL;
    private static ExecutorService PARSETHREADPOOL;
    private static ExecutorService PERSISTENCETHREADPOOL;
    private static final List<String> DOMAINLIST = new CopyOnWriteArrayList<>();
    static {
    CONNECTTHREADPOOL = Executors.newFixedThreadPool(200);
    PARSETHREADPOOL = Executors.newSingleThreadExecutor();
    PERSISTENCETHREADPOOL = Executors.newFixedThreadPool(1);
    DOMAINLIST.add("域名");
    }
    public static void main(String args[]) throws Exception {
    long start = System.currentTimeMillis();
    CONNECTLIST.put(new Task("域名", 80, "/static/index.html"));
    for (int i = 0; i < 600; i++) {
    CONNECTTHREADPOOL.submit(new ConnectHandler(CONNECTLIST, PARSELIST));
    }
    PARSETHREADPOOL.submit(new ParseHandler(CONNECTLIST, PARSELIST, PERSISTENCELIST, DOMAINLIST));
    PERSISTENCETHREADPOOL.submit(new PersistenceHandler(PERSISTENCELIST));
    while (true) {
    Thread.sleep(1000);
    long end = System.currentTimeMillis();
    float interval = ((end - start) / 1000);
    int connectTotal = ConnectHandler.GETCOUNT();
    int parseTotal = ParseHandler.GETCOUNT();
    int persistenceTotal = PersistenceHandler.GETCOUNT();
    int connectps = Math.round(connectTotal / interval);
    int parseps = Math.round(parseTotal / interval);
    int persistenceps = Math.round(persistenceTotal / interval);
    System.out.print("\r連接總數:" + connectTotal + " \t每秒連接:" + connectps + "\t連接隊列剩余:" + CONNECTLIST.size()
    + " \t解析總數:" + parseTotal + " \t每秒解析:" + parseps + "\t解析隊列剩余:" + PARSELIST.size() + " \t持久化總數:"
    + persistenceTotal + " \t每秒持久化:" + persistenceps + "\t持久化隊列剩余:" + PERSISTENCELIST.size());
    }
    }
    }
    class Task {
    public Task() {
    }
    public void init(String host, int port, String path) {
    this.setCurrentPath(path);
    this.host = host;
    this.port = port;
    }
    public Task(String host, int port, String path) {
    init(host, port, path);
    }
    private String host;
    private int port;
    private String currentPath;
    private long taskTime;
    private String type;
    private String content;
    private int state;
    public int getState() {
    return state;
    }
    public void setState(int state) {
    this.state = state;
    }
    public String getCurrentPath() {
    return currentPath;
    }
    public void setCurrentPath(String currentPath) {
    this.currentPath = currentPath;
    this.type = currentPath.substring(currentPath.indexOf(".") + 1,
    currentPath.indexOf("?") != -1 ? currentPath.indexOf("?") : currentPath.length());
    }
    public long getTaskTime() {
    return taskTime;
    }
    public void setTaskTime(long taskTime) {
    this.taskTime = taskTime;
    }
    public String getType() {
    return type;
    }
    public void setType(String type) {
    this.type = type;
    }
    public String getHost() {
    return host;
    }
    public int getPort() {
    return port;
    }
    public String getContent() {
    return content;
    }
    public void setContent(String content) {
    this.content = content;
    }
    }
    class ParseHandler implements Runnable {
    private static Set<String> SET = new ConcurrentSkipListSet<String>();
    public static int GETCOUNT() {
    return COUNT.get();
    }
    private static final AtomicInteger COUNT = new AtomicInteger();
    private BlockingQueue<Task> connectlist;
    private BlockingQueue<Task> parselist;
    private BlockingQueue<Task> persistencelist;
    List<String> domainlist;
    private interface Filter {
    void doFilter(Task fatherTask, Task newTask, String path, Filter chain);
    }
    private class FilterChain implements Filter {
    private List<Filter> list = new ArrayList<Filter>();
    {
    addFilter(new TwoLevel());
    addFilter(new OneLevel());
    addFilter(new FullPath());
    addFilter(new Root());
    addFilter(new Default());
    }
    private void addFilter(Filter filter) {
    list.add(filter);
    }
    private Iterator<Filter> it = list.iterator();
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    if (it.hasNext()) {
    it.next().doFilter(fatherTask, newTask, path, chain);
    }
    }
    }
    private class TwoLevel implements Filter {
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    if (path.startsWith("../../")) {
    String prefix = getPrefix(fatherTask.getCurrentPath(), 3);
    newTask.init(fatherTask.getHost(), fatherTask.getPort(), path.replace("../../", prefix));
    } else {
    chain.doFilter(fatherTask, newTask, path, chain);
    }
    }
    }
    private class OneLevel implements Filter {
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    if (path.startsWith("../")) {
    String prefix = getPrefix(fatherTask.getCurrentPath(), 2);
    newTask.init(fatherTask.getHost(), fatherTask.getPort(), path.replace("../", prefix));
    } else {
    chain.doFilter(fatherTask, newTask, path, chain);
    }
    }
    }
    private class FullPath implements Filter {
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    if (path.startsWith("http://")) {
    Iterator<String> it = domainlist.iterator();
    boolean flag = false;
    while (it.hasNext()) {
    String domain = it.next();
    if (path.startsWith("http://" + domain + "/")) {
    newTask.init(domain, fatherTask.getPort(), path.replace("http://" + domain + "/", "/"));
    flag = true;
    break;
    }
    }
    if (!flag) {
    newTask = null;
    }
    } else {
    chain.doFilter(fatherTask, newTask, path, chain);
    }
    }
    }
    private class Root implements Filter {
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    if (path.startsWith("/")) {
    newTask.init(fatherTask.getHost(), fatherTask.getPort(), path);
    } else {
    chain.doFilter(fatherTask, newTask, path, chain);
    }
    }
    }
    private class Default implements Filter {
    @Override
    public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
    String prefix = getPrefix(fatherTask.getCurrentPath(), 1);
    newTask.init(fatherTask.getHost(), fatherTask.getPort(), prefix + "/" + path);
    }
    }
    public ParseHandler(BlockingQueue<Task> connectlist, BlockingQueue<Task> parselist,
    BlockingQueue<Task> persistencelist, List<String> domainlist) {
    this.connectlist = connectlist;
    this.parselist = parselist;
    this.persistencelist = persistencelist;
    this.domainlist = domainlist;
    }
    private Pattern pattern = Pattern.compile("\"[^\"]+\\.htm[^\"]*\"");
    private void handler() {
    try {
    Task task = parselist.take();
    parseTaskState(task);
    if (200 == task.getState()) {
    Matcher matcher = pattern.matcher(task.getContent());
    while (matcher.find()) {
    String path = matcher.group();
    if (!path.contains(" ") && !path.contains("\t") && !path.contains("(") && !path.contains(")")
    && !path.contains(":")) {
    path = path.substring(1, path.length() - 1);
    if (!SET.contains(path)) {
    SET.add(path);
    createNewTask(task, path);
    }
    }
    }
    }
    task.setContent(null);
    persistencelist.put(task);
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    private void parseTaskState(Task task) {
    if (task.getContent().startsWith("HTTP/1.1")) {
    task.setState(Integer.parseInt(task.getContent().substring(9, 12)));
    } else {
    task.setState(Integer.parseInt(task.getContent().substring(19, 22)));
    }
    }
    /**
    * @param fatherTask
    * @param path
    * @throws Exception
    */
    private void createNewTask(Task fatherTask, String path) throws Exception {
    Task newTask = new Task();
    FilterChain filterchain = new FilterChain();
    filterchain.doFilter(fatherTask, newTask, path, filterchain);
    if (newTask != null) {
    connectlist.put(newTask);
    }
    }
    private String getPrefix(String s, int count) {
    String prefix = s;
    while (count > 0) {
    prefix = prefix.substring(0, prefix.lastIndexOf("/"));
    count--;
    }
    return "".equals(prefix) ? "/" : prefix;
    }
    @Override
    public void run() {
    while (true) {
    this.handler();
    COUNT.addAndGet(1);
    }
    }
    }
    class ConnectHandler implements Runnable {
    public static int GETCOUNT() {
    return COUNT.get();
    }
    private static final AtomicInteger COUNT = new AtomicInteger();
    private BlockingQueue<Task> connectlist;
    private BlockingQueue<Task> parselist;
    public ConnectHandler(BlockingQueue<Task> connectlist, BlockingQueue<Task> parselist) {
    this.connectlist = connectlist;
    this.parselist = parselist;
    }
    private void handler() {
    try {
    Task task = connectlist.take();
    long start = System.currentTimeMillis();
    getHtml(task);
    long end = System.currentTimeMillis();
    task.setTaskTime(end - start);
    parselist.put(task);
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    private void getHtml(Task task) throws Exception {
    StringBuilder sb = new StringBuilder(2048);
    InetAddress addr = InetAddress.getByName(task.getHost());
    // 建立一個Socket
    Socket socket = new Socket(addr, task.getPort());
    // 發送命令,無非就是在Socket發送流的基礎上加多一些握手信息,詳情請了解HTTP協議
    BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
    wr.write("GET " + task.getCurrentPath() + " HTTP/1.0\r\n");
    wr.write("HOST:" + task.getHost() + "\r\n");
    wr.write("Accept:*/*\r\n");
    wr.write("\r\n");
    wr.flush();
    // 接收Socket返回的結果,并打印出來
    BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String line;
    while ((line = rd.readLine()) != null) {
    sb.append(line);
    }
    wr.close();
    rd.close();
    task.setContent(sb.toString());
    socket.close();
    }
    @Override
    public void run() {
    while (true) {
    this.handler();
    COUNT.addAndGet(1);
    }
    }
    }
    class PersistenceHandler implements Runnable {
    static {
    try {
    Class.forName("oracle.jdbc.OracleDriver");
    } catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    public static int GETCOUNT() {
    return COUNT.get();
    }
    private static final AtomicInteger COUNT = new AtomicInteger();
    private BlockingQueue<Task> persistencelist;
    public PersistenceHandler(BlockingQueue<Task> persistencelist) {
    this.persistencelist = persistencelist;
    try {
    conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
    ps = conn
    .prepareStatement("insert into probe(id,host,path,state,tasktime,type) values(seq_probe_id.nextval,?,?,?,?,?)");
    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    private Connection conn;
    private PreparedStatement ps;
    @Override
    public void run() {
    while (true) {
    this.handler();
    COUNT.addAndGet(1);
    }
    }
    private void handler() {
    try {
    Task task = persistencelist.take();
    ps.setString(1, task.getHost());
    ps.setString(2, task.getCurrentPath());
    ps.setInt(3, task.getState());
    ps.setLong(4, task.getTaskTime());
    ps.setString(5, task.getType());
    ps.executeUpdate();
    conn.commit();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }
    }
      ParseHandler 使用了一個職責鏈模式,
      TwoLevel 處理../../開頭的連接(../../sucai/sucai.htm)
      OneLevel 處理../開頭的連接(../sucai/sucai.htm)
      FullPath 處理絕對路徑的連接(http://域名/sucai/sucai.htm)
      Root 處理/開頭的連接(/sucai/sucai.htm)
      Default 處理常規的連接(sucai.htm)
      ParseHandler FullPath 過濾需要一個白名單.
      這樣可以使程序在固定的域名爬行
      ParseHandler parseTaskState 解析狀態碼 可能需要根據實際情況進行調整
      比如網頁404,服務器可能會返回一個錯誤頁,而不是通常的HTTP狀態碼。
      第一版僅僅實現了功能,錯誤處理不完整,
      所以僅僅在定制的域名下生效,其實并不通用,后續會逐步完善.

    posted @ 2014-12-03 13:43 順其自然EVO 閱讀(203) | 評論 (0)編輯 收藏

    JUnit測試框架的使用經驗分享

    1、學習Junit框架的使用
      可通過以下兩個示例進行學習
      A、Junit使用方法示例1
      1)把Junit引入當前項目庫中
      新建一個 Java 工程—coolJUnit,打開項目coolJUnit 的屬性頁 -> 選擇“Java Build Path”子選項 -> 點選“Add Library…”按鈕 -> 在彈出的“Add Library”對話框中選擇 JUnit,并在下一頁中選擇版本 Junit 4 后點擊“Finish”按鈕。這樣便把 JUnit 引入到當前項目庫中了。
      2)新建單元測試代碼目錄
      單元測試代碼是不會出現在最終軟件產品中的,所以最好為單元測試代碼與被測試代碼創建單獨的目錄,并保證測試代碼和被測試代碼使用相同的包名。這樣既保證了代碼的分離,同時還保證了查找的方便。遵照這條原則,在項目 coolJUnit 根目錄下添加一個新目錄 testsrc,并把它加入到項目源代碼目錄中。
      3)在工程中添加類
      添加類SampleCaculator,類中有兩個方法,分別計算加減法。編譯代碼。
    public class SampleCalculator {
    //計算兩整數之和
    public int add(int augend, int addend){
    return augend + addend;
    }
    //計算兩整數之差
    public int subtration(int minuend, int subtrahend){
    return minuend - subtrahend;
    }
    }
      4)寫單元測試代碼
      為類SampleCalculator添加測試用例。在資源管理器SampleCalculator.java文件處右擊選new>選Junit Test Case(見圖4),Source foler選擇testsrc目錄,點擊next,選擇要測試的方法,這里把add和subtration方法都選上,最后點finish完成。
      Junit自動生成測試類SampleCalculatorTest,修改其中的代碼(如下)。
      其中assertEquals斷言,用來測試預期目標和實際結果是否相等。
      assertEquals( [Sting message], expected, actual )
      expected是期望值(通常都是硬編碼的),actual是被測試代碼實際產生的值,message是一個可選的消息,如果提供的話,將會在發生錯誤時報告這個消息。
      如想用斷言來比較浮點數(在Java中是類型為float或者double的數),則需指定一個額外的誤差參數。
      assertEquals([Sting message], expected, actual, tolerance)
      其它斷言參見課本和參考書介紹。測試方法需要按照一定的規范書寫:
      1. 測試方法必須使用注解 org.junit.Test 修飾。
      2. 測試方法必須使用 public void 修飾,而且不能帶有任何參數。
      5)查看運行結果
      在測試類上點擊右鍵,在彈出菜單中選擇 Run As JUnit Test。運行結果如下圖,綠色的進度條提示我們,測試運行通過了。
     B、Junit使用方法示例2
      1)在工程中添加類
      類WordDealUtil中的方法wordFormat4DB( )實現的功能見文件注釋。
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    public class WordDealUtil {
    /**
    * 將Java對象名稱(每個單詞的頭字母大寫)按照
    * 數據庫命名的習慣進行格式化
    * 格式化后的數據為小寫字母,并且使用下劃線分割命名單詞
    * 例如:employeeInfo 經過格式化之后變為 employee_info
    * @param name Java對象名稱
    */
    public static String wordFormat4DB(String name){
    Pattern p = Pattern.compile("[A-Z]");
    Matcher m = p.matcher(name);
    StringBuffer strBuffer = new StringBuffer();
    while(m.find()){
    //將當前匹配子串替換為指定字符串,
    //并且將替換后的子串以及其之前到上次匹配子串之后的字符串段添加到一個StringBuffer對象里
    m.appendReplacement(strBuffer, "_"+m.group());
    }
    //將最后一次匹配工作后剩余的字符串添加到一個StringBuffer對象里
    return m.appendTail(strBuffer).toString().toLowerCase();
    }
    }
      2)寫單元測試代碼
    import static org.junit.Assert.*;
    import org.junit.Test;
    public class WordDealUtilTest {
    @Test
    public void testWordFormat4DB() {
    String target = "employeeInfo";
    String result = WordDealUtil.wordFormat4DB(target);
    assertEquals("employee_info", result);
    }
    }
      3)進一步完善測試用例
      單元測試的范圍要全面,如對邊界值、正常值、錯誤值的測試。運用所學的測試用例的設計方法,如:等價類劃分法、邊界值分析法,對測試用例進行進一步完善。繼續補充一些對特殊情況的測試:
    //測試 null 時的處理情況
    @Test public void wordFormat4DBNull(){
    String target = null;
    String result = WordDealUtil.wordFormat4DB(target);
    assertNull(result);
    }
    //測試空字符串的處理情況
    @Test public void wordFormat4DBEmpty(){
    String target = "";
    String result = WordDealUtil.wordFormat4DB(target);
    assertEquals("", result);
    }
    //測試當首字母大寫時的情況
    @Test public void wordFormat4DBegin(){
    String target = "EmployeeInfo";
    String result = WordDealUtil.wordFormat4DB(target);
    assertEquals("employee_info", result);
    }
    //測試當尾字母為大寫時的情況
    @Test public void wordFormat4DBEnd(){
    String target = "employeeInfoA";
    String result = WordDealUtil.wordFormat4DB(target);
    assertEquals("employee_info_a", result);
    }
    //測試多個相連字母大寫時的情況
    @Test public void wordFormat4DBTogether(){
    String target = "employeeAInfo";
    String result = WordDealUtil.wordFormat4DB(target);
    assertEquals("employee_a_info", result);
    }
      4)查看分析運行結果,修改錯誤代碼
      再次運行測試。JUnit 運行界面提示我們有兩個測試情況未通過測試(見圖6),當首字母大寫時得到的處理結果與預期的有偏差,造成測試失敗(failure);而當測試對 null 的處理結果時,則直接拋出了異常——測試錯誤(error)。顯然,被測試代碼中并沒有對首字母大寫和 null 這兩種特殊情況進行處理,修改如下:
    //修改后的方法wordFormat4DB
    public static String wordFormat4DB(String name){
    if(name == null){
    return null;
    }
    Pattern p = Pattern.compile("[A-Z]");
    Matcher m = p.matcher(name);
    StringBuffer sb = new StringBuffer();
    while(m.find()){
    if(m.start() != 0)
    m.appendReplacement(sb, ("_"+m.group()).toLowerCase());
    }
    return m.appendTail(sb).toString().toLowerCase();
    }
      2、使用Junit框架對類Date和類DateUtil(參見附錄)進行單元測試。
      只對包含業務邏輯的方法進行測試,包括:
      類Date中的
      isDayValid(int year, int month, int day)
      isMonthValid(int month)
      isYearValid(int year)
      類DateUtil中的
      isLeapYear(int year)
      getDayofYear(Date date)

    posted @ 2014-12-03 13:43 順其自然EVO 閱讀(411) | 評論 (0)編輯 收藏

    質量管理失敗的10大原因

     質量管理是企業管理的重要組成部分,其重要作用眾所周知。然而,在實際生產經營中,質量管理這張答卷卻并非每個企業都能出色回答。依筆者所見,其中主要原因有10個。
      一:缺少遠見
      遠見是指洞察未來從而決定企業將要成為什么樣企業的遠大眼光,它能識別潛在的機會并提出目標,現實地反映了將來所能獲得的利益。遠見提供了企業向何處發展、企業如何制定行動計劃以及企業實施計劃所需要的組織結構和系統的順序。缺少遠見就導致把質量排斥在戰略之外,這樣企業的目標及優先順序就不明確,質量在企業中的角色就不易被了解。要想從努力中獲得成功,企業需要轉變其思維方式,創造不斷改進質量的環境。
      二:沒有以顧客為中心
      誤解顧客意愿、缺少超前為顧客服務的意識,雖改進了一些工作但沒有給顧客增加價值,也會導致質量管理的失敗。例如,傳遞公司著迷于準時傳遞,努力把準時從42%提高到92%,然而令管理者驚訝的是公司失去了市場,原因是公司強調了時間準時卻沒有時間回答顧客的電話和解釋產品。顧客滿意是一個動態的持續變化的目標,要想質量管理成功就必須集中精力了解顧客的期望,開發的項目要滿足或超出顧客的需要。國外一家公司聲稱對不滿意顧客提供全部賠償,公司為此付出了代價,但收入卻直線上升,員工的流動率也從117%降至50%。
      三:管理者貢獻不夠
      調查表明,大多數質量管理活動的失敗不是技術而是管理方面的原因。所有的質量管理權威都有一個共識:質量管理最大的一個障礙是質量改進中缺少上層主管的貢獻。管理者的貢獻意味著通過行動自上而下地溝通公司的想法,使所有員工和所有活動都集中于不斷改進,這是一種實用的方法。只動嘴或公開演說不適合質量管理,管理者必須參與和質量管理有關的每一個方面工作并持續保持下去。在一項調查中70%的生產主管承認,他們的公司現在花費更多的時間在改進顧客滿意的因素上。然而他們把這些責任授權給中層管理者,因而說不清楚這些努力成功與否。試想,這樣的質量管理能夠成功嗎?
      四:沒有目的的培訓
      企業許多錢花費在質量管理的培訓上,然而許多企業并沒有因此得到根本的改進。因為太多的質量管理培訓是無關緊要的。例如,員工們學習了控制圖,但不知道在那里用,不久他們就忘記所學的了。可以說,沒有目標、沒有重點的培訓實際上是一種浪費,這也是質量管理失敗的一個因素。
      五:缺少成本和利益分析
      許多企業既不計算質量成本,也不計算改進項目的利益,即使計算質量成本的企業也經常只計算明顯看得見的成本(如擔保)和容易計算的成本(如培訓費),而完全忽視了有關的主要成本,如銷售損失和顧客離去的無形成本。有的企業沒有計算質量改進所帶來的潛在的利益。例如,不了解由于顧客離去而帶來的潛在銷售損失等。國外研究表明:不滿意的顧客會把不滿意告訴22個人,而滿意的顧客只將滿意告訴8個人。減少顧客離去率5%可以增加利潤25%~95%,增加5%顧客保留可以增加利潤35%~85%。
      六:組織結構不適宜
      組織結構、測量和報酬在質量管理培訓、宣傳中沒有引起注意。如果企業還存在煩瑣的官僚層次和封閉職能部門,無論多少質量管理的培訓都是沒有用的。在一些企業中,管理者的角色很不清楚,質量管理的責任常常被授給中層管理者,這導致了質量小組之間的權力爭斗,質量小組缺少質量總體把握,結果是爭論和混亂。扁平結構、放權、跨部門工作努力對質量管理的成功是必須的。成功的企業保持開放的溝通形式,發展了全過程的溝通,消除了部門間的障礙。研究表明:放權的跨部門的小組所取得的質量改進成果可以達到部門內的小組所取得成果的200%到600%。
      七:質量管理形成了自己的官僚機構
      在質量管理活動過程中,通常把質量管理授權于某質量特權人物。質量成為一個平行的過程,產生帶有自己的規則,標準和報告人員的新的官僚層次和結構,無關的質量報告成為正常。這個質量特權人物逐漸張大滲透,成為花費巨大而沒有結果的龐然大物。質量官僚們把自己同日常的生活隔離開來,不了解真實的情況,反而成為質量改進的障礙。
      八:缺少度量和錯誤的度量
      缺少度量和錯誤的度量是導致質量管理失敗的另一個原因。不恰當地度量鼓勵了短期行為而損失了長期的績效,一個部門的改進以損失另一個部門為代價。例如,選擇合適的價格改進了采購部門的績效,但給生產部門帶來了極大的質量問題。企業沒有參考對比就如同獵手在黑夜里打獵物,其結果只是亂打一氣,偶然有結果,更可能是巨大的損失。公司需要與質量改進有關的績效度量手段,包括過程度量和結果度量。成功的公司都是以顧客為基礎度量和監測質量改進的過程。
      九:報酬和承認不夠
      戰略目標、績效度量和報酬或承認是支持企業質量改進的三大支柱。改變觀念和模式轉變需要具有重要意義的行為改變,行為在很大程度上受承認和報酬制度的影響。企業如何承認和回報員工是傳遞公司戰略意圖的主要部分。為使質量管理的努力富有成效,企業應當承認和回報 有良好績效者,從而使質量改進成為現實。
      十:會計制度不完善
      現行的會計制度對質量管理的失敗負有很大的責任。它歪曲了質量成本,沒有搞清楚其潛在的影響。例如,與不良產品有關的成本如擔保,甚至沒有被看成是質量成本;廢棄,返工被看成是企業的一般管理費。

    posted @ 2014-12-03 13:42 順其自然EVO 閱讀(178) | 評論 (0)編輯 收藏

    能存活19年的Bug不是Bug

     近日,各大網站,包括新浪、騰訊、網易、搜狐都報道了一則關于微軟宣布修復了一個存在了19年的安全漏洞的新聞,以騰訊科技為例,它的標題是《微軟修復已存在19年的漏洞》。對于一個軟件制造界外的人來說,這是一個大快人心的消息,就跟一個隱藏了19年的納粹分子終于被抓住的新聞一樣轟動。但以程序員為職業的我,聽到這樣一個消息,卻有一種非常不解、甚至是荒謬、搞笑的感覺。從軟件生產的角度講,如果一個bug能存活19年,那它還是個bug嗎?
      一、很多項目生命期不超過19年
      我在很多國企開發過項目,這些項目幾乎每過幾年都會重新開發一回,老項目或者廢棄、或者推倒重來,遇到領導換班子或上級政策方向的改變,更容易發生這種事情。事實上,有大量的軟件存活不到19年,都很短命。這一方面是技術的原因,更重要的一方面是國情的因素。如果在這樣的一個項目里有一個bug,當這個軟件幾年后被遺棄時,從來沒有被人發現——更符合軟件科學的說,沒有給用戶帶來任何煩惱。這樣的bug對于用戶來說是不可見、不可知、根本不存在的。我們沒有必要、也不應該將這樣的bug稱作bug,更不應該為這樣的bug大驚小怪。
      二、修改bug有風險
      我記得有一個非常有趣的關于bug段子,說的是:
      代碼中有99個小bug,
      99個小bug,
      修復了一個,
      現在,代碼中的有117個小bug。
      雖然是個笑話,但作為程序員,我一點都笑不出來,因為這種事情在我們項目的開發過程中經常的會遇到。由于糾正接口中一個bug而導致其它程序調用這個接口時出現了另外的問題。你可能會嘲笑說這是測試程序寫的不夠周全,但很多時候,復雜的軟件內部關聯是很難讓加班加點的程序員考慮周全的。
      所以,在一個復雜的軟件里,特別是對于老項目,最早開發這個項目的人已經流失,而項目文檔又寫的不夠清晰,如果一個bug不是特別嚴重、不影響核心業務,如果能說服客戶不修改,那就優先考慮不修改,如果非要修改,那必須要深思熟慮、準備充足的測試用例,并想好回退方案,以防萬一。
      三、是bug?還是設計的功能特征?
      之前就有一篇很好的文章指出,Bash里一個所謂的bug實際上是25年專門設計的功能,只是時過境遷,現在的使用環境發生了很大的變化,人們并沒有及時的調整過去的老代碼,或者現在的新環境并沒有照顧過去的老接口。
      所以,我們今天看到的一個愚蠢的 bug,也許在歷史上的某一天,是一個有意而為之的神奇特性。我們應該思考的不僅僅是這一刻的 bug 或者安全隱患本身,而是在軟件開發這個極具創新的活動中,如何有效的保證某個特意設計的功能不會變成 bug。
      總之,一個19年的bug,一直默默無聞,沒有被人發現、沒有給用戶帶來麻煩、造成損失。我想,時間證明了這個bug是個善良的bug,是個好bug,我寧愿將它升級成一個功能。即使不能如此,使用用戶在這些年的使用中也早就適應了這個bug,能夠很好的與它和睦相處,已經不把它當成危險的敵人了。事實上,在用戶的心里,它已經升級進化,蛻掉了bug的外殼。這樣的bug,還是應該順其自然,不改為好。程序員朋友們,你說呢?

    posted @ 2014-12-03 13:41 順其自然EVO 閱讀(178) | 評論 (0)編輯 收藏

    使用復雜度度量去改進軟件質量

    復雜度度量可以用來評估開發和測試活動,決定應該對哪里進行重構以提升質量和預防問題。在QA&Test 2014 conference 大會上,來自于英特爾的Shashi Katiyar就有效利用針對軟件質量改進的復雜度度量提出了自己的見解。
      復雜度是一種不同的軟件元素間交互的度量。按照Shashi的說法,軟件復雜度直接反映了軟件的質量和成本:如果代碼復雜度比較高,那么這段代碼的質量就會比較低,而且它的維護成本也會比較高。
      Shashi提出,如果軟件產品中有復雜的代碼,那么組織會面臨以下的問題:
      較高的缺陷風險
      難以增加新的功能
      難以理解或維護這段代碼
      難以驗證
      你可以使用McCabe圈復雜度來度量復雜度。這種度量規定了代碼中線性獨立的路徑條數,它反映了測試難度和軟件的可靠性。它可以用來評估開發和維護工作量。
      基于復雜度數據,你掌握要覆蓋所有路徑最少需要多少測試用例。復雜度數據可以幫助你去:
      集中力量搞好復雜的模塊
      找到最有效的測試技術
      了解停止測試的時機
      增加軟件的可測試性
      Shashi解釋說,你在軟件系統的管理中做到更具可預測性:
      在任何軟件產品開始工作之前,如果有人知道它是一個復雜的模塊,那么就有可能在評估期為它賦予一些額外的時間。了解了復雜度能夠預先幫助項目團隊去進行評估,這種做法要勝過在開發和測試期去關注它,從而確保不會讓產品的質量做出妥協。
      英特爾收集了復雜度度量和模塊變更數量的數據。這些復雜度數據結合了客戶記錄的缺陷。如果一個模塊是復雜的,并且由于缺陷進行了大量的變更,那么就決定去重構它。在重構之前他們確保有覆蓋這些代碼的測試用例。這種工作方式增加了重構的投資收益率。
      Shashi探討了他所看到的軟件開發復雜度與質量相關的挑戰:
      在競爭激烈瞬息萬變的環境中,公司通過為它的用戶提供更多的特性來努力使它的服務有所不同。這就導致了大量的代碼行和復雜度,這是個大挑戰。如果未采用適當的預防措施去管理產品的復雜度,那么很快這些產品就將成為難以維護的產品。隨著時間的推移,很多公司都不在使用老代碼和老技術了,他們知道自己的系統太復雜了,把它們進行新技術的移植是一項極其復雜的任務。
      “在高復雜度的環境中,創新和開發高質量軟件是極其重要的”Shashi說。“組織可以設定去減少所有高復雜度程序的復雜度,更加頻繁地變更以改進他們軟件的質量”。

    posted @ 2014-12-03 13:41 順其自然EVO 閱讀(204) | 評論 (0)編輯 收藏

    一個需求的奮斗史

    一、需求中的相關術語簡介
      1.馬斯洛的需求層次理論
      生理需求、安全需求、社交需求、尊重需求、自我實現需求
      需求的本質是問題,問題的本質則是理想與現實的矛盾產生的差距
      2.產品經理素質之一:了解用戶需求、理解用戶心理
      3.用戶vs客戶:用戶,使用產品的人;客戶,夠買產品的人、為產品付錢的人
      相比您的需求,我們可能更重視您的用戶的需求。
      以用戶為中心PK 以老板為中心的產品
      4.優先滿足哪些用戶需要和產品的商業目標要結合起來考慮。對于騰訊來說,用戶數不可能再有爆發式增長,只能考慮從已有的用戶身上挖掘用戶價值,
      故需要優先滿足的是核心用戶的需求。對于剛創業的,沒什么用戶,需要添加些大眾的功能,滿足一般用戶,讓用戶數增長起來。
      5.需求分析過程
      1>獲取需求的方法:a.電話訪談;b.人物角色創建
      2>試著描述用戶:通過功能反思其適用的用戶人群
      3>用戶研究:
      a.橫向,用戶的說和做。說表現了目標和觀點,做反映了行為,用戶的說和做往往是不一致的
      b.縱向,定性與定量。定性研究可找出原因,偏向于了解;而定量研究可發現現象,偏向于證實
      a和b結合。從定性到定量,再定性再定量,并且螺旋上升,而了解和證實也是不斷迭代進化的。
      聽用戶定性的說(電話訪談)-->聽用戶定量地說(調查問卷)-->看用戶定性地做(用戶可用性功能測試)-->看用戶定量地做(數據分析)
      二、需求管理
      導入:用戶研究
      需求管理:需求采集-->需求分析-->需求篩選
      導出:需求開發
      需求采集包括:明確目標、選擇采集方法、指定采集計劃、執行采集、資料整理,
      三、需求采集
      1.用戶訪談中常見問題與對策
      第一,用戶說和做經常不一致的問題。解決方法,a.讓用戶在說的同時也做;b.區分用戶說的事與觀點,如我做了什么,步驟如何,碰倒什么問題,如何解決的。
      第二,樣本少,以篇概全的問題。解決方法,a.隨機抽樣訪談;b.以增量方式進行訪談(先訪談n個,根據基本結論,再訪談n個,觀察結論,并加大樣本)。
      第三,用戶過于強勢,帶我們上道。解決方法,時刻牢記訪談的目的。
      第四,我們過于強勢,帶用戶上道。解決方法,牢記訪談目的,管好自己的嘴。
      訪談中的注意事項:
      A.避免一組固定的問題,準備好問題清單,并逐漸引導用戶
      B.首先關注目標,任務其次。比用戶行為更重要的是行為背后的原因,多問問用戶為什么這么做。
      C.避免讓用戶成為設計師:聽用戶說,但不要照著做,用戶的解決方案通常短淺、片面。
      D.避免討論技術:對于略懂技術的用戶,不要糾結于產品的實現方式。
      E.鼓勵講故事:故事是最好的幫助設計師理解用戶的方法
      F.避免誘導性的問題:典型的誘導問題是如果有**功能,你會用嗎?一般來說,用戶會給出毫無意義的肯定答復。
     2.用戶大會
      用戶大會即邀請產品的用戶到某一集中地點開會,可短時間內從多人處收集大量信息,為一種特別的用戶訪談。
      1>明確目的:
      產品二期賣點確認、輔助運營決策;三期需求收集;現有產品用戶體驗改進等
      2>資源確定:
      A.時間:日期、幾點、時長
      B.地點:場地、宣稱用品、IT設備、禮物、食品飲料、桌椅
      C.人物:
      工作人員:分組分工,產品、運營、開發人員相互搭配,有冗余
      用戶:確定目標用戶、數據提取、預約,考慮人數彈性
      嘉賓:相關老板、合作部門的同事,不管是否來,提前發出邀請
      D.材料
      用戶數據、產品介紹資料(測試環境的準備以及靜態Demo備用)
      E.緊急備案
      用戶大會前兩天開一次確認會
      3>現場執行
      A.輔助工作:場地設置(輕松);引導/拍照/服務/機動;進場簽到(送禮品);全程主持(進度控制);送客/收拾殘局
      B.主流程
      產品介紹:買點介紹,與用戶互動,觀察用戶更關注哪些功能,輔助上線前的運營決策,到底主推哪些賣點;
      抽取部分用戶做焦點小組(主持人一對多的訪談),收集后續的需求,產品三期開始啟動;
      抽取部分用戶可用性測試,幫助產品二期做最后的微調
      合影留念
      4>結束以后
      資料整理:賣點總結報告、需求收集報告、可用性測試報告
      運營:本次活動在淘寶論壇發帖;內部郵件
      整個活動資料的整理歸檔,包括照片、各項資料/報告信息、用戶數據等
      3.調查問卷
      用戶訪談:提綱為開放式的,適用于心里比較疑惑的時候尋找產品方向,適合與較少的訪談對象進行深入的交流
      調查問卷:封閉式問題較多,適合大用戶量的信息收集,但不夠深入,只能獲得某些明確問題的答案,不適合安排問答題。
      調查問卷常見問題:
      第一,樣本的偏差,及樣本與想了解的目標用戶群體出現偏差。解決方法:A。盡可能覆蓋目標群體中各種類型的用戶,比如性別、年齡段、行業、收入等,保證各種類型用戶的
      樣本比例接近全體的比例; B.將目標群體的特征也定義成一系列問題,放入調查問卷中。
      第二,樣本過少的問題,此時采用百分比沒有意義。解決方法:至少獲得大約100份的答案。
      第三,問卷內容的細節問題。解決方法:A.問題表述應無引導性,如使用你是否喜歡某個產品代替你喜歡某個產品嗎?B.答案順序打亂,對于陳述者用戶趨向于選第一個或最后一個
      答案,對于一組數字,如價格和打分,則趨向于取中間位置。應該準備幾種形式的問卷。或小范圍試答后,根據反饋修改,再大面積投放。
      4.定性地做--可用性測試
      可用性測試目的:讓實際用戶使用產品或原型方法來發現界面設計中的可用性問題。
      方法:招募測試用戶、準備測試任務、旁觀用戶操作的全過程、詢問用戶對于產品整體的主觀看法或感覺以及當時如此做的原因,分析產生可用性問題列表,劃分等級。
      可用性測試的常見問題與對策
      第一,如果可用性測試做得太晚(往往在產品要上線的時候),這是發現問題于事無補。解決方法,產品無任何型時,拿競爭對手的產品給用戶做;產品有紙面原型時,
      拿著手繪的產品,加上紙筆給用戶做;在產品只有頁面demo時,拿demo給用戶做;在產品可運行后,拿真實的產品給用戶做。
      第二,總覺得可用性測試很專業,所以干脆不做。
      工作中需求采集還有以下方法
      單項需求卡片
      現場調查--AB測試
      日記研究--用戶寫的產產品應用體驗
      卡片分類法--與用戶一起設計既定產品需求下的模塊分類

    posted @ 2014-12-03 13:40 順其自然EVO 閱讀(202) | 評論 (0)編輯 收藏

    基于SaltStack完成LVS的配置管理

     之前由于工作需求,編寫了SaltStack的 LVS遠程執行模塊 , LVS service狀態管理模塊 及 LVS server狀態管理模塊 ,并 提交給了SaltStack官方 Loadblance(DR)及RealServer的配置管理.
      前置閱讀
      LVS-DR模式配置詳解 ,需要注意的是,LVS-DR方式工作在數據鏈路層,文中描述需要開啟ip_forward,其實沒有必要, 詳情見 LVS DR模式原理剖析
      環境說明
      三臺服務器用于LVS集群,其中主機名為lvs的擔當的角色為loadblance,對應的IP地址為192.168.36.10;主機名為web-01和web-02的主機擔當的角色為RealServer, 對應的IP地址分別為192.168.36.11及192.168.36.12
      LVS VIP: 192.168.36.33, Port: 80, VIP綁定在lvs的eth1口
      最最重要的是loadblance主機為Linux,并已安裝ipvsadm, Windows/Unix等主機的同學請繞過吧,這不是我的錯......
      開工
      Note
      以下所有操作均在Master上進行
      配置SaltStack LVS模塊
      如果使用的Salt版本已經包含了lvs模塊,請忽略本節內容,測試方法:
      salt 'lvs' cmd.run "python -c 'import salt.modules.lvs'"
      如果輸出有 ImportError 字樣,則表示模塊沒有安裝,需要進行如下操作:
      test -d /srv/salt/_modules || mkdir /srv/salt/_modules
      test -d /srv/salt/_states || mkdir /srv/salt/_states
      wget https://raw.github.com/saltstack/salt/develop/salt/modules/lvs.py -O /srv/salt/_modules/lvs.py
      wget https://raw.github.com/saltstack/salt/develop/salt/states/lvs_service.py -O /srv/salt/_states/lvs_service.py
      wget https://raw.github.com/saltstack/salt/develop/salt/states/lvs_server.py -O /srv/salt/_states/lvs_server.py
      配置pillar
    /srv/pillar/lvs/loadblance.sls
    lvs-loadblance:
    - name: lvstest
    vip: 192.168.36.33
    vip-nic: eth1
    port: 80
    protocol: tcp
    scheduler: wlc
    realservers:
    - name: web-01
    ip: 192.168.36.11
    port: 80
    packet_forward_method: dr
    weight: 10
    - name: web-02
    ip: 192.168.36.12
    port: 80
    packet_forward_method: dr
    weight: 30
    /srv/pillar/lvs/realserver.sls
    lvs-realserver:
    - name: lvstest
    vip: 192.168.36.33
    /srv/pillar/top.sls
    base:
    'lvs':
    - lvs.loadblance
    'web-0*':
    - lvs.realserver

    posted @ 2014-12-03 13:40 順其自然EVO 閱讀(180) | 評論 (0)編輯 收藏

    測試團隊管理-第四篇:磨合與陣痛

     新測試部門在形式上成立了,面上的整合也都做了,深度的整合才剛剛開始。對已有部門人員和組織架構盤點一遍,發現情況極不樂觀。第一,兩名原代理測試主管,一名開始休為期四個月的產假,一名剛休四個月產假回來。在代理測試主管下面,有幾名測試骨干,但是沒人具備一點兒測試團隊管理經驗,過往基本上是每位 測試主管直接管理下面的二十位左右測試工程師,缺乏管理層次。第二,五分之一的部門員工正在孕期、產期或者哺乳期。第三,測試能力嚴重匱乏,以手工黑盒測 試為主,性能測試可以開展一點點,測試自動化和其他能力基本為零。第四,測試部門嚴重缺乏過程資產和技術積累,沒有Bug總結分析,沒有測試項目總結分析,沒有測試分析設計,尚停留在測試執行層面,只有一些基本的測試計劃、測試用例、測試報告和Bug庫中的Bug。
      公司老板對原測試部門的不滿積壓太久,沒有耐心再一點點等了,直接給出一劑猛藥,立即執行,不容商量,那就是換血和淘汰。原測試主管不合格,先轉為部門助理,后繼安排再定,著手社招新測試主管;外部招聘加內部培養幾名一線測試經理,搭建起部門的核心架構;對基層測試工程師,逐一評估,分批淘汰。這些命令對于剛熟悉新公司和部門基本情況的我,是發自內心不愿看到的,也是絕大多數人不愿看到的。
      在高層的重壓下,在開發部門部門經理的疑慮中,在兩名老測試主管的迷茫與彷徨中,在基層測試工程師的不解甚至背后議論中,招聘進新測試主管,提拔幾名一線測試經理,走了一些測試工程師。外人看到的,是新來的部門經理冷血、部門人員動蕩。我有時感覺自己好像站在一艘船上,這艘船四處漏水、在大海中顛簸飄搖; 而我就是就是這艘船的船長兼舵手,在東修西補的同時,還要盡力保持正確的航向高速行駛,不能翻船。
    相關文章
    測試團隊管理-第三篇:部門整合(2)

    posted @ 2014-12-03 13:39 順其自然EVO 閱讀(162) | 評論 (0)編輯 收藏

    僅列出標題
    共394頁: First 上一頁 6 7 8 9 10 11 12 13 14 下一頁 Last 
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 免费人成在线观看播放国产| 久久国产精品成人免费| 亚洲av成人一区二区三区| 亚洲日本中文字幕区| 亚洲2022国产成人精品无码区 | 久久久久亚洲国产| 亚洲无限乱码一二三四区| 日韩亚洲Av人人夜夜澡人人爽| 亚洲大尺度无码无码专区| 伊人久久亚洲综合| 国产亚洲精品a在线观看| 久久久青草青青国产亚洲免观 | 2022久久国产精品免费热麻豆| 一级毛片免费播放| 美丽姑娘免费观看在线观看中文版| 久草免费福利资源站| 精品视频在线免费观看| 免费网站观看WWW在线观看| a级毛片在线免费看| 久久中文字幕免费视频| 亚洲免费观看在线视频| 又黄又爽又成人免费视频| 国产精品视频免费一区二区| 夫妻免费无码V看片| 免费A级毛片无码A∨男男| 一本色道久久综合亚洲精品高清| 亚洲精品无码不卡在线播HE| 亚洲av日韩综合一区在线观看| 亚洲最大的成网4438| 亚洲另类自拍丝袜第1页| 亚洲精品乱码久久久久久V| 日韩色视频一区二区三区亚洲| 日本视频免费观看| a毛片免费播放全部完整| 亚洲一区免费观看| 成年女人毛片免费播放人| 亚洲高清成人一区二区三区| 亚洲人色婷婷成人网站在线观看| 亚洲欧洲一区二区| 亚洲中文字幕久久久一区| 九九视频高清视频免费观看|