用postman 实现api接口自动化测试

用postman 实现api接口自动化测试

1 下载

https://www.getpostman.com

可能是目前最好用的web接口调试工具
2017-08-30-15-00-06

2 添加接口请求

先来个登录接口

2017-08-31-17-53-38

再来个获取用户信息

2017-08-31-18-04-24

好多接口都是需要header头的token与tempID校验的,每个接口测试都要先登录,然后复制token等参数好麻烦。
请看下面更好的方法。

3 设置环境变量

我们可以将第一个登录接口返回的数据tempID与token设置成环境变量,便于在下一个接口使用。

登录接口tab, 点击Tests,设置环境变量
2017-08-31-18-19-11

var jsonData = pm.response.json();

pm.environment.set("tempID", jsonData.resData.tempID);
pm.environment.set("token", jsonData.resData.token);

获取用户信息tab这边的header头使用{{tempID}}、{{token}} 代替之前的值。
2017-08-31-18-21-42

先点登录接口send,再点获取用户信息接口send,好像不起作用,获取用户信息接口还报错了……

两个接口tab分别点击save 将这两个接口保存起来

2017-08-31-18-25-16
2017-08-31-18-29-41

新建一个名为openApi的Collections,重命名请求接口,保存后在软件左侧看到如下图效果
2017-08-31-18-31-20

点击postman右上角Manage Environments
2017-08-31-18-44-37

新建一个环境
2017-08-31-18-46-37

下拉选择环境
2017-08-31-18-47-20

再来一次:先点登录接口send,再点获取用户信息接口send
2017-08-31-18-50-41

终于正常了。
可以看到,没设置环境之前,设置环境变量是不起作用的。
点击postman右上角的小眼睛,可以看到设置的环境变量列表
2017-08-31-18-52-45


!!!我们有要多个环境,接口http://172.18.7.192/openApi 不应该写死,可以设置一个环境变量放到请求地址上。
2017-08-31-19-05-12

将url改造一下 用{{url}} 代替 http://172.18.7.192/openApi
2017-08-31-19-07-03

再来测试一遍,发现还是正常的。

我们可以设置不同的环境,不同的url变量,这样写好的接口测试就能在不同的环境下测试而不用修改任何东西(前提是:不同环境的测试账号是相同的)。

4 编写测试代码、console调试

postman 是用nodejs 写的,所以测试代码我们可以用js来写。

postman 执行一个http请求过程是这样子的:

请求发起之前执行pre-request-script,可以用来设置一些动态参数。

比如设置一个当前的日期
2017-09-01-16-17-11

请求完毕之后执行 tests 脚本用于验证接口返回数据
2017-09-01-16-21-04

不知道当前postman有哪些变量是可以使用的,可以使用 console.log(this)
打印出来,用postman console 工具看看。
2017-09-01-16-25-07
2017-09-01-16-25-47

5 Runner 自动测试

2017-09-01-16-30-51
看看测试结果
2017-09-01-16-32-44
两个接口的测试代码都是一样的,只做了最简单的测试,请按照自己的需要写更多数据判断。

var jsonData = pm.response.json();
tests["http 状态码是200"] = responseCode.code == 200 //验证http请求是否正常
tests["isSuccess"] = jsonData.isSuccess === 1  //验证返回的json格式数据isSuccess是否等于1

6 导出、newman 自动化测试

每次都是要人工点击才能执行一下,还不能满足某些自动化测试的需求: 每隔一段时间自动跑一次所有接口测试。

newman 是 postman 的命令行程序,是nodejs 的npm包,可实现定时自动化任务。

6.1 导出我们写的测试collections

2017-09-01-16-39-40

6.2 安装newman

在cmd或者其他命令行端执行以下命令(前提是先安装nodejs):
2017-09-01-16-42-05

安装完毕newman后,对刚才导出的postman接口做一遍测试
2017-09-01-16-45-15

导出的json文件,并没有对{{url}} 变量做处理,newman 不认识。手动批量替换一下{{url}},再来一次测试
2017-09-01-16-48-39

可以看到所有的接口测试用例都通过了。

7 各种语言的代码模板

2017-09-01-16-52-38

8 预设请求header头

openApi 每个接口请求都要写如下的header头

Content-Type:application/json
tempID:{{tempID}}
userToken:{{token}}
clientType:7

可以通过presets 将header头数据保存起来,下次新建接口测试,下拉选一下就好了

2017-09-01-16-59-47

leanote 折腾笔记

写博客、做笔记,使用markdown 是挺爽的,然而图片插入却不是很方便。目前发现能使用ctrl+v 完成截图插入的笔记软件有为知笔记、leanote,很可惜,这两个软件都开始收费了,leanote更是霸道,不续费,以前的笔记都没法查看。好在leanote是开源的,改改代码,使leanote生成的私有图片协议链接,变成本地链接;去掉vip续费提醒也不失为一款出色的markdown编辑器。

默认图片是保存到本地的,导出markdown文件,将会上传图片到七牛云,替换markdown中的图片链接,这样就方便发布到博客、分享给别人看了。使用效果如下:

修改过程记录一下,给用的上的人参考。

1.修改图片保存目录

public/js/app/service.js
public/js/app/service_login.js

var basePath = app.getPath('appData') + '/leanote';

改为

const path = require('path');
var basePath = path.dirname(path.dirname(app.getAppPath())) + '/data';
basePath = basePath.replace(/\\/g,'/');

修改后,笔记数据就默认保存到leanote软件目录下的data目录了。

2. 修改md 插入图片路径

2.1 截图粘贴图片

node_modules/file.js

pasteImage2 方法

me._addImage(Common.objectId(), filePath, function(newImg) {
                callback && callback(Evt.getImageLocalUrl(newImg.FileId));
            });

改为

me._addImage(Common.objectId(), filePath, function(newImg) {
                callback && callback(Evt.getImageLocalUrl(newImg.Path));
            });

node_modules/evt.js

    getImageLocalUrl: function(fileId) {
        return 'leanote://file/getImage?fileId=' + fileId;
    }

改为

    getImageLocalUrl: function(fileId) {
        return fileId;
    }

2.2 插入图片

public/js/common.js

insertLocalImage 方法

var url = EvtService.getImageLocalUrl(newImage.FileId);

改为

var url = EvtService.getImageLocalUrl(newImage.Path);

3.去掉过期需要升级vip

public/js/app/page.js

UserService.init 方法

 fullSync(function(err, info) {
                    err = false; //加上这行
                    if (err) {

4.导出md, 图片上传到七牛云

4.1 修改配置文件

public/config.js

var Config = {
    "plugins": [
        "theme",
        "import_leanote",
        "import_evernote",
        "import_html",
        "export_md", //新增
        "export_pdf",
        "export_html",
        "export_leanote",
        "export_evernote",
        "langs",
        "accounts"
    ],
    "langs": [
        {
            "filename": "en-us",
            "name": "English"
        },
        {
            "filename": "de-de",
            "name": "Deutsch"
        },
        {
            "filename": "zh-cn",
            "name": "简体中文"
        },
        {
            "filename": "zh-hk",
            "name": "繁体中文"
        },
        {
            "filename": "ja-jp",
            "name": "日本語"
        }
    ],
    "lang": "",
    "theme": "",
    "view": "snippet",
    //新增七牛配置
    "qiniu": {
        "ak": "xx",
        "sk": "xxx",
        "bucket": "mynote",
        "domain": "http://note.img.xuphp.com", //图片域名
        "uploadUrl": "http://up-z2.qiniu.com" //上传域名
    }
};

4.2 添加第三方七牛云sdk

npm install qn

4.3 新增导出markdown插件

public/plugins/export_md/plugin.js

/**
 * 导出markdown插件
 * 上传图片到七牛云
 *
 */
define(function () {
    var async; //  = require('async');
    var resanitize; // = require('resanitize');
    //===========
    // start

    var exportmarkdown = {
        langs: {
            'en-us': {
                'export': 'Export markdown',
                'Exporting': 'Exporting',
                'Exporting: ': 'Exporting: ',
                'exportSuccess': 'markdown saved successful!',
                'exportFailure': 'markdown saved failure!',
                'notExists': 'Please sync your note to ther server firslty.'
            },
            'de-de': {
                'export': 'Als markdown exportieren',
                'Exporting': 'Exportiere',
                'Exporting: ': 'Exportiere: ',
                'exportSuccess': 'markdown erfolgreich gespeichert!',
                'exportFailure': 'markdown speichern fehlgeschlagen!',
                'notExists': 'Bitte Notizen zuerst mit dem Server synchronisieren.'
            },
            'zh-cn': {
                'export': '导出markdown',
                'Exporting': '正在导出',
                'Exporting: ': '正在导出: ',
                'exportSuccess': 'markdown导出成功!',
                'exportFailure': 'markdown导出失败!'
            },
            'zh-hk': {
                'export': '導出markdown',
                'Exporting': '正在導出',
                'Exporting: ': '正在導出: ',
                'exportSuccess': 'markdown導出成功!',
                'exportFailure': 'markdown導出失敗!'
            }
        },

        _inited: false,
        init: function () {
            var me = this;
            if (me._inited) {
                return;
            }

            async = require('async');
            resanitize = require('resanitize');

            me._inited = true;
        },

        replaceAll: function (src, pattern, to) {
            if (!src) {
                return src;
            }
            while (true) {
                var oldSrc = src;
                src = src.replace(pattern, to);
                if (oldSrc === src) {
                    return src;
                }
            }
        },

        fixFilename: function (filename) {
            var reg = new RegExp("/|#|\\$|!|\\^|\\*|'| |\"|%|&|\\(|\\)|\\+|\\,|/|:|;|<|>|=|\\?|@|\\||\\\\", 'g');
            filename = filename.replace(reg, "-");
            // 防止出现两个连续的-
            while (filename.indexOf('--') != -1) {
                filename = this.replaceAll(filename, '--', '-');
            }
            if (filename.length > 1) {
                // 最后一个-
                filename = filename.replace(/\-$/, '');
            }
            return filename;
        },


        render: function (note, callback) {
            var me = this;
            me.fixFiles(note, function (content, files) {
                // 非markdown才需要这样, 补全html标签
                if (!note.IsMarkdown) {
                    content = $('<div>' + content + '</div>').html();
                }

                callback(content);
            });
        },

        findAllImages: function (note) {
            var content = note.Content;
            var allMatchs = [];

            // markdown下
            // [](http://localhost://fileId=32);
            if (note.IsMarkdown) {
                var reg = new RegExp('!\\[([^\\]]*?)\\]\\((.+)\\)', 'g');
                var matches = reg.exec(content);
                while (matches) {
                    var all = matches[0];
                    var title = matches[1]; // img与src之间
                    var fileId = matches[2];
                    allMatchs.push({
                        fileId: fileId,
                        title: title,
                        all: all
                    });
                    // 下一个
                    matches = reg.exec(content);
                }
            }
            return allMatchs;
        },
        /**
         * 七牛云
         * @param file 本地路径
         * @param fileName
         */
        upload: function (file,fileName) {
            var path = require("path");

            var qn = require('qn');
            var client = qn.create({
                accessKey: Config.qiniu.ak,
                secretKey: Config.qiniu.sk,
                bucket: Config.qiniu.bucket,
                //origin: 'http://{bucket}.u.qiniudn.com',
                // timeout: 3600000, // default rpc timeout: one hour, optional
                uploadURL: Config.qiniu.uploadUrl
            });

            // upload a file with custom key
            client.uploadFile(file, {key: fileName}, function (err, result) {
                console.log(result);
                Notify.show({title: 'Info', body: getMsg(result.url+"上传成功!")});
                // {
                //   hash: 'FhGbwBlFASLrZp2d16Am2bP5A9Ut',
                //   key: 'qn/lib/client.js',
                //   url: 'http://qtestbucket.qiniudn.com/qn/lib/client.js'
                //   "x:ctime": "1378150371",
                //   "x:filename": "client.js",
                //   "x:mtime": "1378150359",
                //   "x:size": "21944",
                // }
            });
        },

        fixFiles: function (note, callback) {
            var me = this;

            var content = note.Content;

            var allImages = me.findAllImages(note) || [];

            var allMatchs = allImages;

            if (allMatchs.length == 0) {
                callback(content, []);
                return;
            }


            var files = {}; // fileId => {}

            function replaceContent() {

                for (var i = 0; i < allMatchs.length; ++i) {
                    var eachMatch = allMatchs[i];
                    var link;
                    var href;
                    var fileName = eachMatch.fileId.split("/").slice(-3).join("/");

                    href = Config.qiniu.domain + '/' + fileName;
                    me.upload(eachMatch.fileId,fileName);
                    //上传图片到七牛云
                    link = '![' + eachMatch.title + '](' + href + ')';
                    content = content.replace(eachMatch.all, link);
                }
            }

            // 得到图片资源
            var fileIdFixed = {};
            async.eachSeries(allImages, function (eachMatch, cb) {
                var fileId = eachMatch.fileId;
                if (fileIdFixed[fileId]) {
                    cb();
                    return;
                }

                Api.fileService.getImageInfo(fileId, function (err, doc) {
                    fileIdFixed[fileId] = true;
                    if (doc) {
                        var base64AndMd5 = Api.fileService.getFileBase64AndMd5(doc.Path);
                        if (base64AndMd5) {
                            files[doc.FileId] = {
                                base64: base64AndMd5.base64,
                                md5: base64AndMd5.md5,
                                type: doc.Type,
                                title: doc.Title
                            }
                        }
                        cb();
                    }
                    else {
                        cb();
                    }
                });

            }, function () {
                replaceContent();
                callback(content, files);
            });
        },

        //--------------

        // 得到可用的文件名, 避免冲突
        getExportedFilePath: function (pathInfo, n, cb) {
            var me = this;
            if (n > 1) {
                pathInfo.nameNotExt = pathInfo.nameNotExtRaw + '-' + n;
            }
            var absPath = pathInfo.getFullPath();

            // Api.nodeFs.existsSync(absPath) 总是返回false, 不知道什么原因
            // 在控制台上是可以的
            Api.nodeFs.exists(absPath, function (exists) {
                if (!exists) {
                    cb(absPath);
                }
                else {
                    me.getExportedFilePath(pathInfo, n + 1, cb);
                }
            });
        },

        getTargetPath: function (callback) {
            // showSaveDialog 不支持property选择文件夹
            Api.gui.dialog.showOpenDialog(Api.gui.getCurrentWindow(),
                {
                    defaultPath: Api.gui.app.getPath('userDesktop') + '/',
                    properties: ['openDirectory']
                },
                function (targetPath) {
                    callback(targetPath);
                }
            );
        },

        loadingIsClosed: false,

        exportmarkdownForNotebook: function (notebookId) {
            var me = this;
            if (!notebookId) {
                return;
            }
            me.getTargetPath(function (targetPath) {
                if (!targetPath) {
                    return;
                }

                me.loadingIsClosed = false;
                Api.loading.show(Api.getMsg('导出markdown'),
                    {
                        hasProgress: true,
                        isLarge: true,
                        onClose: function () {
                            me.loadingIsClosed = true;
                            setTimeout(function () {
                                me.hideLoading();
                            });
                        }
                    });
                Api.loading.setProgress(1);

                Api.noteService.getNotes(notebookId, function (notes) {
                    if (!notes) {
                        me.hideLoading();
                        return;
                    }

                    var total = notes.length;
                    var i = 0;
                    async.eachSeries(notes, function (note, cb) {
                        if (me.loadingIsClosed) {
                            cb();
                            me.hideLoading();
                            return;
                        }
                        i++;
                        Api.loading.setProgress(100 * i / total);
                        me._exportmarkdown(note, targetPath, function () {
                            cb();
                        }, i, total);
                    }, function () {
                        me.hideLoading();
                        Notify.show({title: 'Info', body: getMsg('导出 markdown成功')});
                    });
                });
            });
        },

        hideLoading: function () {
            setTimeout(function () {
                Api.loading.hide();
            }, 1000);
        },

        exportmarkdown: function (noteIds) {
            var me = this;
            if (!noteIds || noteIds.length == 0) {
                return;
            }
            me.getTargetPath(function (targetPath) {
                if (!targetPath) {
                    return;
                }

                me.loadingIsClosed = false;
                Api.loading.show(Api.getMsg('导出markdown'),
                    {
                        hasProgress: true,
                        isLarge: true,
                        onClose: function () {
                            me.loadingIsClosed = true;
                            setTimeout(function () {
                                me.hideLoading();
                            });
                        }
                    });
                Api.loading.setProgress(1);

                var i = 0;
                var total = noteIds.length;

                async.eachSeries(noteIds, function (noteId, cb) {
                    if (me.loadingIsClosed) {
                        cb();
                        return;
                    }

                    i++;
                    Api.loading.setProgress(100 * i / total);
                    Api.noteService.getNote(noteId, function (note) {
                        me._exportmarkdown(note, targetPath, function () {
                            cb();
                        }, i, total);
                    });

                }, function () {
                    me.hideLoading();
                    Notify.show({title: 'Info', body: getMsg('导出 markdownSuccess')});
                });
            });
        },

        _exportmarkdown: function (note, path, callback, i, total) {
            var me = this;
            if (!note) {
                return;
            }

            if (me.loadingIsClosed) {
                callback();
                return;
            }

            setTimeout(function () {
                Api.loading.setMsg(Api.getMsg('导出markdown: ') + (note.Title || getMsg('Untitled')));
                Api.loading.setProgressRate(i + '/' + total);
            }, 100);

            var name = note.Title ? note.Title + '.md' : getMsg('Untitled') + '.md';
            name = me.fixFilename(name);

            var targetPath = path + Api.commonService.getPathSep() + name;

            // 将路径和名字区分开
            var pathInfo = Api.commonService.splitFile(targetPath);
            pathInfo.nameNotExt = me.fixFilename(pathInfo.nameNotExt); // 重新修正一次
            var nameNotExt = pathInfo.nameNotExt;
            pathInfo.nameNotExtRaw = pathInfo.nameNotExt;

            // 得到可用文件的绝对路径
            me.getExportedFilePath(pathInfo, 1, function (absmarkdownFilePath) {
                me.render(note, function (content) {
                    Api.commonService.writeFile(absmarkdownFilePath, content);
                    callback();
                });
            });
        },

        // 打开前要执行的
        onOpen: function () {
            var me = this;
            var gui = Api.gui;

            var menu = {
                label: Api.getMsg('导出 markdown'),
                enabled: function (noteIds) {
                    return true;
                },
                click: (function () {
                    return function (noteIds) {
                        me.init();
                        me.exportmarkdown(noteIds);
                    }
                })()
            };
            Api.addExportMenu(menu);

            Api.addExportMenuForNotebook({
                label: Api.getMsg('导出 markdown'),
                enabled: function (notebookId) {
                    return true;
                },
                click: (function () {
                    return function (notebookId) {
                        me.init();
                        me.exportmarkdownForNotebook(notebookId);
                    }
                })()
            });
        },
        // 打开后
        onOpenAfter: function () {
        },
        // 关闭时需要运行的
        onClose: function () {
        }
    };

    return exportmarkdown;
});

大功告成,以后有空再增加一键发布到wordpress的功能。

centos6.8 yum 安装 lamp

安装 EPEL源

https://fedoraproject.org/wiki/EPEL

找到相应的centos版本

If you are running an EL6 version, please visit here to get the newest ‘epel-release’ package for EL6: The newest version of ‘epel-release’ for EL6

  1. rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm

安装 remi 源

http://rpms.famillecollet.com/

找到相应的centos版本

Enterprise Linux 6 – repository auto-configuration package : remi-release-6.rpm

  1. rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

安装mysql

  1. yum --enablerepo=remi install mysql mysql-server

可能因为网络问题,连接不上remi源,先用vpn:https://www.flyvpn.com/cn/

配置mysql

  1. #配置 mysqld 开机自启动
  2. chkconfig mysqld on
  3. #启动 mysql
  4. service mysqld start
  5. #更改 root 密码
  6. mysql -uroot
  7. #进入 mysql,然后
  8. update mysql.user SET password=password('123456') WHERE user='root';
  9. # 让外网可以访问
  10. update mysql.user set host = '%' where user ='root';
  11. flush privileges;
  12. quit;

安装php

  1. yum --enablerepo=remi-php56 install php php-mysql php-common php-gd php-mbstring php-mcrypt php-devel php-xml php-soap php-fpm php-cli php-pdo

php可用源版本选择

http://rpms.famillecollet.com/enterprise/6/

设置php-fpm 开机启动

  1. chkconfig --add php-fpm
  2. chkconfig php-fpm on

安装nginx

http://nginx.org/en/linux_packages.html

创建nginx的yum 安装源
/etc/yum.repos.d/nginx.repo

  1. [nginx]
  2. name=nginx repo
  3. baseurl=http://nginx.org/packages/centos/6/$basearch/
  4. gpgcheck=0
  5. enabled=1
  1. #安装
  2. yum install --enablerepo=nginx nginx
  3. #启动
  4. service nginx start
  5. #设置为开机启动:
  6. chkconfig --add nginx
  7. chkconfig nginx on

nginx配置:/etc/nginx/conf.d/default.conf

  1. server {
  2. listen 80;
  3. server_name foo.com;
  4. root /www;
  5. index index.html index.htm index.php;
  6. location / {
  7. try_files $uri $uri/ /index.php;
  8. }
  9. location ~ \.php$ {
  10. try_files $uri =404;
  11. fastcgi_index index.php;
  12. fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  13. include fastcgi_params;
  14. fastcgi_pass 127.0.0.1:9000;
  15. }
  16. }

phalcon 笔记(不定期更新)

1. model 初始化配置

public function initialize()
	{
		$this->useDynamicUpdate(true); // 动态更新字段,非全部更新
	}

2.数据库配置,记录查询sql

$di->setShared('db', function () use ($config,$di) {
    $dbConfig = $config->database->toArray();
    $adapter = $dbConfig['adapter'];
    unset($dbConfig['adapter']);

    $class = 'Phalcon\Db\Adapter\Pdo\\' . $adapter;
    /** @var Phalcon\Db\Adapter\Pdo\Mysql $connection */
    $connection =  new $class($dbConfig);
    /** @var Phalcon\Events\Manager $eventsManager */
    $eventsManager = $di->get("eventsManager");
    $logger = new \Phalcon\Logger\Adapter\File(__DIR__."/../logs/debugs.log");
    $eventsManager->attach('db', function($event, $connection) use ($logger) {
        if ($event->getType() == 'beforeQuery') {
            $logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
        }
        if ($event->getType() == 'beforeSave') {
            $logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
        }
    });
    $connection->setEventsManager($eventsManager);

    return $connection;
});

2.禁用或启用特性
https://docs.phalconphp.com/en/latest/reference/models.html#disabling-enabling-features
phalcon的orm有坑,如果数据库某个字段为null,那么update,save的时候会报错,因为phalcon默认检测字段是否Not null。
应该在model将此特性关闭。将notNullValidations设置为false

 

   public function initialize()
    {
        $this->setup(['notNullValidations'=>FALSE]);
    }

phalcon数据库配置添加表前缀

Phalcon默认居然不能设置数据表前缀,常用的框架都支持。
修改方法一:
新建一个基础模型,然后所有的模型在该类上继承即可

<?php
class BaseModel extends \Phalcon\Mvc\Model {
    public function getSource()
    {
        return 'gw_'.strtolower(get_class($this));
    }
}

但是此种方法,用phalcon devtools命令行模式生成model文件,文件名称是有表前缀的

修改方法二:
2.1 先在app/config/config.php 配置文件加上数据库前缀配置tablePrefix

    'database' => array(
        'adapter'     => 'Mysql',
        'host'        => 'localhost',
        'username'    => 'root',
        'password'    => '',
        'dbname'      => 'test',
        'charset'     => 'utf8',
        'port' => '3306',
        'tablePrefix' => 'gw_'
    ),

2.2 修改phalcon devtools
代码phalcon\devtools\scripts\Phalcon\Builder\Model.php
在220行$table = $this->options->get(‘name’);之后加上代码

$table = $this->options->get('name');
if(isset($config->database->tablePrefix)){
    $table = $config->database->tablePrefix.$table;
}

在480行
$methodRawCode[] = $this->snippet->getModelSource($this->options->get(‘name’));
修改代码为:
$methodRawCode[] = $this->snippet->getModelSource($table);

此时使用工具命令

phalcon model user

生成model :
app/models/User.php
内容如下,不会提示table不存在了:

<?php

use Phalcon\Mvc\Model\Validator\Email as Email;

class User extends \Phalcon\Mvc\Model
{

    /**
     *
     * @var integer
     */
    public $id;

    /**
     *
     * @var string
     */
    public $username;

    /**
     *
     * @var string
     */
    public $password;

    /**
     *
     * @var integer
     */
    public $status;

    /**
     *
     * @var string
     */
    public $real_name;

    /**
     *
     * @var string
     */
    public $mobile;

    /**
     *
     * @var string
     */
    public $email;

    /**
     *
     * @var integer
     */
    public $sex;

    /**
     *
     * @var integer
     */
    public $logins;

    /**
     *
     * @var integer
     */
    public $create_time;

    /**
     * Validations and business logic
     *
     * @return boolean
     */
    public function validation()
    {
        $this->validate(
            new Email(
                array(
                    'field'    => 'email',
                    'required' => true,
                )
            )
        );

        if ($this->validationHasFailed() == true) {
            return false;
        }

        return true;
    }

    /**
     * Initialize method for model.
     */
    public function initialize()
    {
        $this->setSource("gw_user");
    }

    /**
     * Returns table name mapped in the model.
     *
     * @return string
     */
    public function getSource()
    {
        return 'gw_user';
    }

    /**
     * Allows to query a set of records that match the specified conditions
     *
     * @param mixed $parameters
     * @return User[]
     */
    public static function find($parameters = null)
    {
        return parent::find($parameters);
    }

    /**
     * Allows to query the first record that match the specified conditions
     *
     * @param mixed $parameters
     * @return User
     */
    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }

}