讨论
先问是不是,再问为什么。 难道美国的程序员就不加班吗?他们就一天八小时工作,还想来就来,想走就走?非工作时间完全找不到人?还有什么食物饮料都免费提供? 让我来告诉你真实的情况吧,还...真是这样的。 加班文化应该是中美两地最明显的不同了,形成这种差异的原因不是一两句可以讲清楚的。我在两个国家都工作过,也在不同岗位工作过,所以就尽量从不同角度来简要说一下。 国内程序员加班严重,首先问题不在程序员这个群体。 产品层面拍脑袋决策多 产品层面,拍脑袋决策多,美其名曰:互联网思维、小步快跑、试错思维,其实是没想清楚,反正我就需求一提,技术团队来做,上线后成功了我是产品天才,失败了也没啥坏处,找个借口说什么“互联网市场变化太快”也能搪塞过去,或者找个新项目,继续画大饼。 国内的产品经理实在是太多了,很多还是刚毕业的小鲜肉,没有一点经验积累,凭借天马行空的想象力,提一个需求,就能让一帮技术专家为之操劳好几个月去实现,这简直是不可思议的。即使产品经理很厉害,但人数一多,每个人都要业绩,都拼命想点子、提需求,技术团队怎么能招架得住那么多需求呢。于是就会这些现象:     - 节假日来了,我们上一个活动,或者节日小彩蛋功能。     - 竞品上了一个 xx 功能,看起来不错,我们也抓紧上一个。     - 10个产品经理同时跟你说:这个需求我跟老板对过了,xx 之前必须上线。 产品经理没有想清楚某个功能的价值,或者带来的收益不痛不痒,而技术团队则需要为此付出巨大的心血,也就是加班。当然这也不一定是产品经理的错,国内公司,老板的指令太多了,不说了,你懂的。 在美国这边,产品经理大多是行业经验非常丰富的人,要么是从工程师转过来,要么是从多年的商业领域经历然后读个 MBA转过来。上线一个功能,经过严谨的理论推演、多轮前期调研、科学的 AB test,从提出 idea 到最终用户看到更新,经常耗时半年或一年。很多不成功的 idea 还没面市,就因为数据不好而“胎死腹中”。这样一方面提高了产品需求的质量,另一方面也给技术团队足够多的时间来设计和实现一个可持续发展的架构。 技术团队话语权低 管理层在做决策时,往往更重视产品更新迭代,而忽视技术的建设。 比如说技术团队有100个小时,老板可能会把90小时都投入到实现产品功能,给技术优化、修补技术债务。于是就会出现技术实现非常粗糙的情况,举个栗子,给你一周时间,实现一个 IM 功能,你还会考虑什么模块化、可维护性、性能优化之类的吗?当然是怎么快怎么来,百度一下有没有别人实现过的代码,ctrl+v / ctrl+c,一点运行,诶,跑通了,上线。 所以国内很多程序员看别人的代码,感觉都是一团 shit,完全没法维护,干脆自己重写一个。 技术老大能跑到老板面前说“我们先把产品需求停一停,让我们的技术团队好好地优化一下基础设施吧。”吗?只要他不是傻子,都不会这样说。 话语权低还体现在,对产品几乎没有决策权,甚至不能质疑产品决策,提反对意见,总会被产品经理各种理由挡回来,产品经理有一句话终结争论的尚方宝剑:这是老板要做的。 久而久之,技术债务越堆越高,bug 越来越多,程序员不加班,谁加班? 而美国公司的现象是,技术话语权非常高,一个没想清楚的产品需求,能被技术团队直接打回去。同时,技术团队可以自主地花很多时间在技术优化上面。产品不能按时上线?经理说:没关系,延后,按照最优的技术方案来推进。有时候你想赶紧上线,提交一段粗制滥造的代码,会被同事鄙视死,被老板骂死。 每个季度最后一个月,专门抽出精力,来弥补技术债务,产品需求放一边先。 在这样的环境下,短期看起来效率低,但长期是最高效的。国内是“欲速侧不达”,美国是“慢工出细活”。 技术基础设施差 知乎上有一个比较热门的讨论:腾讯的技术建设是否落后? 这不但是腾讯的问题,实际上绝大部分公司的技术基础设施都非常差。 有多少个公司能养一个专门的 Infra 团队?有多少个技术团队能专门成立一个小团队,进行 Infra 优化?即使有,有多少个公司能保证 Infra 团队的成果能在全公司推行? 我相信能做到这样的公司是凤毛麟角的。 中小公司不必说,在这样高度竞争环境下,哪有财力养一个不能直接带来 KPI 提升的团队,即使有钱,也没时间搞优化。 而大公司呢,即使腾讯这样的体量,有一个 TEG 事业群做基础设施建设。但是现实情况是,依然没有全公司通用的内部工具,各个团队依然在重复造轮子。阿里算是做的很好的,前几年开始推行的大中台战略取得了显著的成果,解放了各个产品部门的生产力。 计算机程序这种东西,一个人写和一群人写,几千个用户使用和几千万用户使用,是完全不同的概念。没有强大的基础设施建设,技术团队越壮大,效率越低,用户数越多,bug 越多。出来混总是要还的,今天没有人关注基础设施,未来就会又无数个程序员为此加班。 而说回美国这边,专门的 Infra 团队几乎是每个公司的标配了。这也是工程师文化的结果,创始人、高管团队一定有很强的技术背景,走过了很多弯路,所以非常重视基础设施建设这种“重要而不紧急”的事情。 畸形的职场文化 最后再来说说文化的差异。 美国人非常注重个人和家庭,牺牲自己的时间来给公司加班,这是不可能的。一到下班时间准点走,非上班时间完全不查邮件。很多公司入职培训就专门强调了怎么平衡工作和生活,其实就是说:下班时间就是你的时间,你有权利不处理公司事务。 反观国内,不知道从什么时候起,加班就是约定俗成的。还出现了看似很有道理的人生经验,比如“年轻人就应该多拼一下”,“加班能学到更多东西,对未来也有好处”。 加班,竟然成了光荣的、可以拿来攀比的现象,比如说加班到凌晨一两点,拍个照,发个朋友圈,收到老板的点赞很开心。而管理者呢,还就吃这一套,谁加班多、会来事儿,考核的时候就更有好处。 在这种加班氛围下,甚至还出现了“伪加班”。早上十点到公司,上个厕所、看会儿新闻、刷刷朋友圈,就到十一点半了,什么工作也没做,然后准备吃午饭;饭后逛一逛、睡个觉,又到2点上班时间了,写会儿代码、刷刷知乎,一下午又过去了,很多公司有免费晚餐,吃完饭回来7点左右,再写写代码,磨到10点。哦耶,又是充实的一天呢,发个朋友圈给老板知道。 其实一天有效工作时间还不够8小时,但就是“加班”了。 综上,大概就是国内加班严重的原因。 我觉得这也是某个时代特有的现象,谁都没有错,谁也没有办法改变这种情况。在国内这样一个高度竞争的环境下,公司为了生存必须跑快点,个人为了发展必须多做事,毕竟你不做就会有千千万万的人来抢。等到中国真的发展到美国这样的发达程度,生存的减小了,才有资格谈更好的生活品质。
6天前 喜欢(0) 浏览(43) 评论(0)
博客
Yii2.0 RESTful API 认证教程 隔了怎么长时间,终于到了 Yii2.0 RESTful API 认证介绍了. 废话不多说,直接正文开始 认证介绍 和Web应用不同,RESTful APIs 通常是无状态的, 也就意味着不应使用 sessions 或 cookies, 因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过 sessions 或 cookies 维护, 常用的做法是每个请求都发送一个秘密的 access token 来认证用户, 由于 access token 可以唯一识别和认证用户,API 请求应通过 HTTPS 来防止man-in-the-middle (MitM) 中间人攻击. 认证方式 HTTP 基本认证 :access token 当作用户名发送,应用在access token可安全存在API使用端的场景, 例如,API使用端是运行在一台服务器上的程序。 请求参数: access token 当作API URL请求参数发送,例如 https://example.com/users?access-token=xxxxxxxx, 由于大多数服务器都会保存请求参数到日志, 这种方式应主要用于JSONP 请求,因为它不能使用HTTP头来发送 access token OAuth 2 : 使用者从认证服务器上获取基于 OAuth2 协议的 access token, 然后通过 HTTP Bearer Tokens 发送到 API 服务器。 上方进行简单介绍,内容来自 Yii Framework 2.0 权威指南 实现步骤 我们都知道 Yii2.0 默认的认证类都是 User,前后台都是共用一个认证类,因此我们要把API 认证类 单独分离出来,达到前、后、API都分离, 继上一章:(这里暂时使用默认User数据表,正式环境请分离不同的数据表来进行认证) 准备条件 继上篇的 User 数据表,我们还需要增加一 个access_token 的字段, 1. 直接在你的数据库中新增 access_token 字段。 2. 使用数据迁移的方式 * 进入项目根目录打开控制台输入以下命令: php yii migrate/create add_access_token_to_user * 打开 你的项目目录/console/migrations/m180704_054630_add_access_token_to_user.php 修改如下内容: ``` public function safeUp() { $this->addColumn('user', 'access_token', $this->string()); } public function safeDown() { $this->dropColumn('user', 'access_token'); } ``` * 执行迁移命令 ``` php yii migrate ``` 浏览器打开前台目录 frontend 页面,点击注册账号,先注册一个账号 配置 打开 api\config\main.php 配置 user 应用组件: * 设置 `identityClass` 属性为哪个认证类 * 设置 `enableSession` 属性为 `false` * 设置 `enableAutoLogin` 属性为 `true` 将 session 组件注释掉,或删掉 'user' => [ 'identityClass' => 'api\models\User', 'enableAutoLogin' => true, 'enableSession'=>false, //'identityCookie' => ['name' => '_identity-backend', 'httpOnly' => true], ], //'session' => [ // this is the name of the session cookie used for login on the backend // 'name' => 'advanced-backend', // ], 编写 api\models\User.php 实现认证类,继承 IdentityInterface 将 common\models\User 类拷贝到 api\models\目录下,修改命名空间为api\models <?php namespace api\models; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; use yii\web\IdentityInterface; ... class User extends ActiveRecord implements IdentityInterface { ... ... } 将 common\models\LoginForm.php 类拷贝到api\models\目录下,修改命名空间,并重写login方法: <?php namespace api\models; use Yii; use yii\base\Model; ... ... public function login() { if ($this->validate()) { $access_token=$this->_user->generateAccessToken(); $this->_user->save(); return $access_token; } else { return false; } } 上方代码给User模型添加了一个generateAccessToken()方法,因此我们到api\models\User.php中添加此方法 namespace api\models; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; use yii\web\IdentityInterface; ... ... class User extends ActiveRecord implements IdentityInterface { ... ... /** * 生成accessToken字符串 * @return string * @throws \yii\base\Exception */ public function generateAccessToken() { $this->access_token=Yii::$app->security->generateRandomString(); return $this->access_token; } } 接下来打开 之前的User 控制器编写登录方法 use api\models\LoginForm; ... ... //省略一些代码 /** * 登陆 * @return array * @throws \yii\base\Exception * @throws \yii\base\InvalidConfigException */ public function actionLogin() { $model = new LoginForm(); if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->login()) { return [ 'access_token' => $model->login(), ]; } else { return $model->getFirstErrors(); } } ... 最后新增一条URL规则 打开 api\config\main.php 修改 components属性,添加下列代码: 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user', 'extraPatterns'=>[ 'POST login'=>'login', ], ], ], ] 使用一个调试工具来进行测试 http://youdomain/users/login 记住是POST 请求发送,假如用POSTMAN有问题的话指定一下 Content-Type:application/x-www-form-urlencoded。 ok,不出意外的话,相信你已经可以收到一个access_token了,接下来就是如何使用这个token,如何维持认证状态,达到不携带这个token将无法访问,返回401 维持认证状态 实现认证只需两步: 在你的 REST 控制器类中配置 authenticator 行为来指定使用哪种认证方式 在你的 user identity class 类中实现 yii\web\IdentityInterface::findIdentityByAccessToken() 方法. 接下来我们围绕这两步来实现: 添加一个REST控制器 因我这里暂未设计其他数据表 所以我们暂且还使用User 数据表吧 在api\controllers\新加一个控制器 命名为 ArticleController 并继承 yii\rest\ActiveController,配置认证方式代码:代码如下: <?php namespace api\controllers; use yii\rest\ActiveController; use Yii; use yii\filters\auth\CompositeAuth; use yii\filters\auth\HttpBasicAuth; use yii\filters\auth\HttpBearerAuth; use yii\filters\auth\QueryParamAuth; class ArticleController extends ActiveController { public $modelClass = 'api\models\User'; public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => CompositeAuth::className(), 'authMethods' => [ HttpBasicAuth::className(), HttpBearerAuth::className(), QueryParamAuth::className(), ], ]; return $behaviors; } } 注意:这个控制器并非真正的Article,实则还是User 实现 findIdentityByAccessToken() 方法: 打开 api\models\User.php 重写 findIdentityByAccessToken() 方法 ... ... class User extends ActiveRecord implements IdentityInterface { ... ... public static function findIdentityByAccessToken($token, $type = null) { return static::findOne(['access_token' => $token]); } ... } 为刚才新加的控制器添加路由规则(ps:好像多了一步......) 修改 api\config\main.php 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user', 'extraPatterns'=>[ 'GET send-email'=>'send-email' 'POST login'=>'login', ], ], ['class' => 'yii\rest\UrlRule', 'controller' => 'article', 'extraPatterns'=>[ ], ], ], ] 接下来访问一下你的域名 http://youdomain/articles,不携带任何参数是不是返回 401了? ok,这里介绍两种访问方式,一种是URL访问,另一种是通过header 来进行携带 http://youdomain/articles?access-token=y3XWtwWaxqCEBDoE-qzZk0bCp3UKO920 传递 header头信息 Authorization:Bearer y3XWtwWaxqCEBDoE-qzZk0bCp3UKO920 注意 Bearer 和你的token中间是有 一个空格的,很多同学在这个上面碰了很多次 好啦,基于YII2.0 RESTful 认证就此结束了, 更过完整的功能 请移步官方文档 授权验证 另外还有速率验证,就自行发觉吧 另外,如果看不懂,或者写的不好,请移步 魏曦 老师的视频教程,本人所有内容都是跟随 魏曦老师 学的 魏曦教你学 写完认证发现我们的接口返回的数据不是很直观,现实生活中通常也不是这样子的,我们可能会返回一些特定的格式 自定义响应内容 打开 api\config\main.php 在 components数组里面添加如下内容分 'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; $response->data = [ 'success' => $response->isSuccessful, 'code' => $response->getStatusCode(), 'message' => $response->statusText, 'data' => $response->data, ]; $response->statusCode = 200; }, ], 这里的状态码统一设为 200 ,具体的可另行配置,假如登陆操作 密码错误或者其他,我们可以在控制器中这样使用: $response = Yii::$app->response; $response->setStatusCode(422); return [ 'errmsg' => '用户名或密码错误!' ]; 水平有限,难免有纰漏,请不吝赐教,在下会感激不尽
18天前 喜欢(0) 浏览(95) 评论(0)
博客
Yii2 RESTful API 快速搭建教程 这篇说下yii2.0开发 API 吧,使用 RESTful API模式 安装Yii2.0 通过 Composer 安装 这是安装Yii2.0的首选方法。如果你还没有安装 Composer,你可以按照这里的说明进行安装。 安装完 Composer,运行下面的命令来安装 Composer Asset 插件: php composer.phar global require "fxp/composer-asset-plugin:^1.2.0" 安装高级的应用程序模板,运行下面的命令: php composer.phar create-project yiisoft/yii2-app-advanced advanced 2.0.13 初始化高级模板 cd advanced init 修改数据库连接属性 打开 common\config\main-local.php,配置数据库连接信息 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=127.0.0.1;dbname=yiiapi', 'username' => 'root', 'password' => 'root', 'charset' => 'utf8', ], 执行 migrate 数据库迁移 yii migrate 拷贝backend目录,命名为api 打开api\config\main.php 修改id,controllerNamespace: return [ 'id' => 'app-api', 'basePath' => dirname(__DIR__), 'controllerNamespace' => 'api\controllers', ] 打开common\config\main.php开启url路由美化规则 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ ], ], 打开common\config\bootstrap.php添加以下别名 Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api'); 为什么要单独创建API应用 单独创建API应用,目的是便于维护,可以避免以下问题 配置的冲突 控制器的命名不便 url美化规则冲突 分工明确frontend为前台目录;backend为后台目录;api为api目录 接下来打开 api\controllers 新建一个User控制器,继承 yii\rest\ActiveController,命名为 UserController,代码如下: <?php namespace api\controllers; use yii\rest\ActiveController; class UserController extends ActiveController { public $modelClass = 'common\models\User'; } 这里创建 user控制器继承 yii\rest\ActiveController 并指定要操作的模型 启用JSON 输入 配置 request 应用程序组件的 parsers 属性使用 yii\web\JsonParser 用于 JSON 输入 打开配置文件 api\config\main-local.php 修改为如下代码: <?php $config = [ 'components' => [ 'request' => [ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 'cookieValidationKey' => 'P0r2XoT9LCUnyVlSgxBbJOqQxdCJ3i29', 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ], ], ], ]; if (!YII_ENV_TEST) { // configuration adjustments for 'dev' environment $config['bootstrap'][] = 'debug'; $config['modules']['debug'] = [ 'class' => 'yii\debug\Module', ]; $config['bootstrap'][] = 'gii'; $config['modules']['gii'] = [ 'class' => 'yii\gii\Module', ]; } return $config; 配置URL规则 为刚才的 user控制器添加url美化规则 打开 api\config\main.php 修改 components属性,添加下列代码: ... 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user' ], ], ] ... ok,到此就成了一个 符合 RESTful 风格的API 看起来在控制器了什么也没有写,只是指定了一个模型,但是她的背后完成了很多的功能哦,列表如下: GET /users: 逐页列出所有用户 HEAD /users: 显示用户列表的概要信息 POST /users: 创建一个新用户 GET /users/123: 返回用户 123 的详细信息 HEAD /users/123: 显示用户 123 的概述信息 PATCH /users/123: and PUT /users/123: 更新用户123 DELETE /users/123: 删除用户123 OPTIONS /users: 显示关于末端 /users 支持的动词 OPTIONS /users/123: 显示有关末端 /users/123 支持的动词 如何访问呢 你可以使用 curl命令进行访问,命令如下: curl -i -H "Accept:application/json" "http://localhost/users" 命令行下还是比较麻烦的,也不方便测试,推荐使用 API测试工具 这类的工具有很多,我就不一一列举了,这里推荐 Postman,很好很强大,Chorme也有插件,可以安装,这里我推荐直接下载软件安装调试,比较方便 你可能发现了 访问任何路由地址都是加的s,users , 为什么呢? 资源,你要理解 资源二字,既然是资源肯定是个集合,肯定有一大堆,所以要加上复数,我是这么理解的。 你说我就是不想加上s,我就想采用http://localhost/user 这种方式来进行访问,好吧,可以,满足你,只是不推荐 继续打开配置文件api\config\main.php修改刚才添加的 urlManager 如下: 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user', 'pluralize' => false, //设置为false 就可以去掉复数形式了 ], ], ] 加入 'pluralize' => false, 就表示去掉复数形式了,再次强调不推荐 ok,在控制器中我们没有写任何一句代码,他就给我们生成许多方法,但是有时候我们可能需要修改一些代码,来达到我们想要的效果,比如连表查询,然后再返回数据 接下来我们就实现这样的功能: 打开刚才新建的user控制器, 重写 action方法: <?php namespace api\controllers; use yii\rest\ActiveController; class UserController extend extends ActiveController { public $modelClass = 'common\models\User'; public function actions() { $action= parent::actions(); // TODO: Change the autogenerated stub unset($action['index']); unset($action['create']); unset($action['update']); unset($action['delete']); } public function actionIndex() { //你的代码 } } 这样我们就可以重写他的代码了。哈哈 我们再新建一个自己的 action <?php namespace api\controllers; use yii\rest\ActiveController; class UserController extend extends ActiveController { public $modelClass = 'common\models\User'; public function actions() { $action= parent::actions(); // TODO: Change the autogenerated stub unset($action['index']); unset($action['create']); unset($action['update']); unset($action['delete']); } public function actionIndex() { //你的代码 } public function actionSendEmail() //假如是get请求 { //业务逻辑 } } 然后试着访问一下 http://localhost/users/send-email,报错?找不到? 报错就对了,那是因为我们没有设置其他路由访问 修改 api\config\main.php 'urlManager' => [ 'enablePrettyUrl' => true, 'enableStrictParsing' => true, 'showScriptName' => false, 'rules' => [ ['class' => 'yii\rest\UrlRule', 'controller' => 'user', //'pluralize' => false, //设置为false 就可以去掉复数形式了 'extraPatterns'=>[ 'GET send-email'=>'send-email' ], ], ], ] 接下来重新访问就没有问题了,ps:你自己编写的任何 action 都要在 extraPatterns 进行配置 差点忘了 状态码 这个东西,我们现在所有的东西返回来的都是一个 JSON,加入没有数据局返回的是空的数组,所以这肯定不行啊,我们得加上 一些特定的状态码 来标识这些数据啊,怎么加? 继续修改 api\config\main.php 在 components 添加如下代码: 'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; $response->data = [ 'success' => $response->isSuccessful, 'code' => $response->getStatusCode(), 'message' => $response->statusText, 'data' => $response->data, ]; $response->statusCode = 200; }, ], 这里统一使用 200来表示,当然并不是所有的都是 200,你应该具体情况具体对待,切记不要乱使用 任意加各种标识,请 遵循这些 规范 状态码 是不是觉得还少了点什么?认证 对就是 认证,就差 认证 就完美了,篇幅有限,内容多了反而影响阅读兴趣,下篇进行 认证介绍 感谢以下,特别是 魏曦老师的视频教程 魏曦教你学 Yii Framework 2.0 权威指南 不足之处,欢迎指正
18天前 喜欢(0) 浏览(83) 评论(0)
问题
在使用postman调接口的时候发现使用post方式的时候不管使用form-data和x-www-form-urlencoded的情况,我通过 print_r(\Yii::$app->getRequest()->post()); 打印的值都是一样的,详细见图片 然后我使用put/get方式试试看,结果如图 网上都说form-data的数据格式就是如下格式 [----------------------------961448793903793318528971 Content-Disposition:_form-data;_name] => "token" 8gumyDwEdEo2fhr6xnL2pGQku_4zTK7W ----------------------------961448793903793318528971-- 为什么post就不是这种格式呢??
1月前 喜欢(0) 浏览(114) 评论(0)
Wiki
使用当前路由和GET参数创建URL 生成链接: echo Url::to(['post/view','id'=>100,'src'=>'google']) 打印结果:/index.php?r=post/view&id=100&src=google 去掉 src 参数 echo Url::current(['src' => null]); 打印结果:/index.php?r=post/view&id=100 新加 test 参数 echo Url::current(['test' => 'new']); 打印结果:/index.php?r=post/view&id=100&test=new
1月前 喜欢(0) 浏览(179) 评论(0)
问题
vagrant已经安装使用了很久了,已经在虚拟机安装了LNMP环境,也是可以使用的,可是今天出现一个奇怪现象:我vagrant up正常启动后,去浏览器访问突然不能访问了,ssh进去发现环境也都是正常的,在虚拟机curl 192.168.1.105可以正常访问的,在win的cmd去ping 192.168.1.105也是通的。 vagrantFile的ip设置是:config.vm.network "public_network",ip: "192.168.1.105" win下的cmd ipconfig结果如下: 虚拟机的ifconfig结果如下: vagrant启动页面如图: 我也不知道为什么会这样,之前一直好好的,防火墙都关了,请各位帮帮忙,谢谢 而且发现一个现象,去重启电脑之后,vagrant up后偶尔出现正常访问,一旦vagrant关闭或vagrant重启就不能访问
1月前 喜欢(0) 浏览(216) 评论(0)
博客
Yii2 获取模块名、控制器名、方法名在视图中: 模块名 $this->context->module->id 控制器名 $this->context->id 方法名 $this->context->action->id 在控制器中 模块名 Yii::$app->controller->module->id; 控制器名 Yii::$app->controller->id; 方法名 Yii::$app->controller->action->id; 或 模块名 $this->module->id; 控制器名 $this->id; 方法名 $this->action->id; 在控制器的 beforeAction 方法中(方法接收$action参数) 模块名 $action->controller->module->id; 控制器名 $action->controller->id; 方法名 $action->id;
2月前 喜欢(0) 浏览(332) 评论(0)
Wiki
1.建议不要关闭,有数据安全风险 2.确实需要关闭请在对应控制器中加入以下代码 class BaseController extends Controller { //关闭 csrf 验证 public $enableCsrfValidation = false; }
2月前 喜欢(0) 浏览(276) 评论(0)
Wiki
示例如下 Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ ['test01', 30], ['test02', 20], ['test03', 25], ])->execute();
2月前 喜欢(0) 浏览(363) 评论(0)
博客
在处理一个文章内容时,需要在指定标签后面插入 指定内容 1.查找到指定位置(例如 我要在第三个标签<\p>后插入广告代码): /** * 选择到标签指定位置后 字节长度 * $find 标签名字 * $number 第几个标签位置 * $body 被查找的内容 * @param unknown $number * @param unknown $find * @param unknown $body * @return number * @author kangjy */ public function position($number,$find,$body) { //获取标签长度 $findLen = strlen($find); //初始化位置 $position =0; for ($number=1; $number<=3; $number++) { $position = stripos($body, $find, $position); //var_dump('第'.$number.'个 位置'.$position); $position += $findLen; // var_dump('第'.$number.'个 位置加寻找词的长度'); } return $position ; } 2.插入指定字符串代码 /** * 指定位置插入字符串 * @param $str 原字符串 * @param $i 插入位置 * @param $substr 插入字符串 * @return string 处理后的字符串 * @author kangjy */ public function insertToStr($str, $i,$substr){ //指定插入位置前的字符串 $startstr=""; for($j=0; $j<$i; $j++){ $startstr .= $str[$j]; } //指定插入位置后的字符串 $laststr=""; for ($j=$i; $j<strlen($str); $j++){ $laststr .= $str[$j]; } //将插入位置前,要插入的,插入位置后三个字符串拼接起来 $str = $startstr . $substr . $laststr; //返回结果 return $str; } 这样就可以了
2月前 喜欢(0) 浏览(295) 评论(0)
博客
yii2是一个快速开发的框架,其中gii扩展不得不说是一个很大的助力,通过gii自动生成代码,把一些通用的代码交给程序去生成,很大程度上减少开发者的时间成本。但gii也有一些弊端,那就是生成的代码是yii2自带的模板,每次生成的代码并不是我们想要的,因而每次都要去对应的做调整。 那么如何才能让程序生成我们想要的代码呢?没错,yii2的gii扩展是支持自定义模板的,通过自定义模板我们就可以让gii生成我们想要的代码,又一次节约了去修改生成模板的时间成本。 配置 gii生成器所用到的模板文件位于目录 vendor\yiisoft\yii2-gii\generators\crud\default,我们既然要自定义模板,最好是在原有的模板基础下做调整。 1.复制一份模板,拷贝default目录,放在任意位置,此处我们放置在根目录 /backend/giitpl/crud 中。 2.修改模板(本文为教程不做细说,此处自行修改即可) 3.打开配置文件 /backend/config/main-local.php 修改$config['modules']['gii']的配置(如下): $config['modules']['gii'] = [ 'class' => 'yii\gii\Module', 'allowedIPs' => ['127.0.0.1', '::1'], 'generators' => [ 'crud' => [ //生成器名称 'class' => 'yii\gii\generators\crud\Generator', 'templates' => [ //设置我们自己的模板 //模板名 => 模板路径 'myCrud' => '@backend/giitpl/crud/default', ] ] ], ]; 4.通过gii生成代码,打开gii界面,使用crud generator生成代码(注:此处配置要修改code template模板) 选择我们自定义的模板,然后点击生成,那么gii自定义的模板代码就生成好了。 总结 通过自定义gii模板,我们能够更加灵活快速的完成需要的功能,把许多相似度高,应用场景多,功能类似(比如:列表页,详情页等)的代码做成gii的模板,通过程序去生成。还是那句话yii2就是一个快速开发的框架。
2月前 喜欢(0) 浏览(283) 评论(0)
博客
Gii 这个扩展无疑是 yii2 快速开发的一大助力,通过使用gii生成代码很大程序上节约了开发的时间成本,那么如何使用gii这个组件呢?本文为你们简单介绍一下yii2中gii的一些常用功能。 生成模型(Model) 在数据库新建一张test的测试表 CREATE TABLE `test` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', `name` varchar(255) DEFAULT NULL COMMENT '名称', `desc` varchar(255) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 1.打开gii的界面 /index.php?r=gii或者 /gii(url美化之后) 2.创建Model 首先,点击Model generator下面的start按钮,进入model生成界面,输入数据表名test,输入model class(一般会自动生成一个,也可以自定义model class名称) 然后,点击下方的preview,会列出要生成的模型 最后,点击generate生成model文件,如下提示测生成成功 生成CRUD 同样,点击CRUD Generator,去创建CRUD也就是增删改查的操作界面及功能,输入对应的参数(注:view path 不填,即生成在默认的位置) 然后,点击preview,查看要生成的文件列表(注:如果已经存在对应的控制会显示下方diff,可以选择覆盖或者保留) 最后点击Generate生成相应的CRUD文件,然后就可以查看对应的页面了,如此简单就完成了一个数据表的增删改成
2月前 喜欢(0) 浏览(319) 评论(0)
Wiki
1.视图页面直接加载 $css = <<<CSS p {text-indent:5em;} div{width: 500px;} CSS; $this->registerCss($css,['type'=>'text/css'],'test'); $js = <<<JS // console.log('sss'); JS; $this->registerJs($js); 视图页面直接加载css,js文件 $this->registerJsFile('@web/assets/js/site.js'); $this->registerCssFile('@web/assets/css/site.css');
2月前 喜欢(0) 浏览(382) 评论(0)
Wiki
yii2 Carousel是一个基于JavaScript的图片轮播组件,使用Carousel可以快速的在网站任意位置放置一个图片轮播的效果 先看下效果: 代码如下: <?php echo Carousel::widget([ 'items' => [ // 只有图片的格式 '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>', // 与上面的效果一致 ['content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>'], // 包含图片和字幕的格式 [ 'content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg"/>', 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', //'options' => [...], //配置对应的样式 ], ] ]); ?> 在轮播幻灯片列表。每个数组元素的可配属性 [ // 必要的,轮播的内容(HTML),比如一个图像标签 'content' => '<img src="http://www.yii-china.com/statics/images/b_1.jpg'/> // 可选的,该轮播标题(HTML) 'caption' => '<h4>This is title</h4><p>This is the caption text</p>', // 可选的,轮播样式 'options' => [], ]
2月前 喜欢(0) 浏览(341) 评论(0)
Wiki
以高级版为例:打开配置文件 /frontend/config/main.php 添加以下配置: return [ ··· 'defaultRoute' => 'topic/index', ··· ]
2月前 喜欢(0) 浏览(345) 评论(0)
Wiki
事务在程序应用中使用非常广泛,现在提供一个 Yii2 数据库事务使用的简单示例,让大家了解在 Yii2 中如何使用数据库事务 $transaction = Yii::$app->db->beginTransaction(); try { $connection->createCommand($sql1)->execute(); //如果执行失败则抛出错误 if(status == false) throw new \Exception('这里是错误原因'); $connection->createCommand($sql2)->execute(); //只有执行了commit(),对于上面数据库的操作才会真正执行 $transaction->commit(); }catch (Exception $e) { //获取抛出的错误 $error = $e->getMessage(); //操作回滚 $transaction->rollBack(); }
2月前 喜欢(0) 浏览(496) 评论(0)
博客
package main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client lease clientv3.Lease leaseResp *clientv3.LeaseGrantResponse leaseId clientv3.LeaseID leaseRespChan <-chan *clientv3.LeaseKeepAliveResponse err error ) //客户端配置 config = clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, DialTimeout: 5 * time.Second, } //建立连接 if client, err = clientv3.New(config); err != nil { fmt.Println(err) return } //上锁(创建租约,自动续租) lease = clientv3.NewLease(client) //设置一个ctx取消自动续租 ctx,cancleFunc := context.WithCancel(context.TODO()) //设置10秒租约(过期时间) if leaseResp,err = lease.Grant(context.TODO(),10);err != nil { fmt.Println(err) return } //拿到租约id leaseId = leaseResp.ID //自动续租(不停地往管道中扔租约信息) if leaseRespChan,err =lease.KeepAlive(ctx,leaseId);err != nil { fmt.Println(err) } //启动一个协程去监听 go listenLeaseChan(leaseRespChan) //业务处理 kv := clientv3.NewKV(client) //创建事务 txn := kv.Txn(context.TODO()) txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job9"),"=",0)). Then(clientv3.OpPut("/cron/lock/job9","xxx",clientv3.WithLease(leaseId))). Else(clientv3.OpGet("/cron/lock/job9"))//否则抢锁失败 //提交事务 if txtResp,err :=txn.Commit();err != nil { fmt.Println(err) return } else { //判断是否抢锁 if !txtResp.Succeeded { fmt.Println("锁被占用:",string(txtResp.Responses[0].GetResponseRange().Kvs[0].Value)) return } } fmt.Println("处理任务") //释放锁(停止续租,终止租约) defer cancleFunc()//函数退出取消自动续租 defer lease.Revoke(context.TODO(),leaseId) //终止租约(去掉过期时间) time.Sleep(10 * time.Second) } func listenLeaseChan(leaseRespChan <-chan *clientv3.LeaseKeepAliveResponse) { var ( leaseKeepResp *clientv3.LeaseKeepAliveResponse ) for { select { case leaseKeepResp = <-leaseRespChan: if leaseKeepResp == nil { fmt.Println("租约失效了") goto END } else { fmt.Println(leaseKeepResp.ID) } } } END: }
2月前 喜欢(0) 浏览(466) 评论(0)
博客
少比比直接代码(你可以理解为给key设置过期时间,但是比redis要强大的是它可以自动续租) package main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" "go.etcd.io/etcd/mvcc/mvccpb" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client err error kv clientv3.KV keepResp *clientv3.LeaseKeepAliveResponse keepRespChan <-chan *clientv3.LeaseKeepAliveResponse ) //创建租约 lease := clientv3.NewLease(client) //设置10秒租约(过期时间为10秒) if leaseRes,err := lease.Grant(context.TODO(),10);err != nil { fmt.Println(err) return } else { //得到租约id leaseId := leaseRes.ID //定义一个上下文使得租约5秒过期 ctx,_:= context.WithTimeout(context.TODO(),5*time.Second) //自动续租(底层会每次讲租约信息扔到 <-chan *clientv3.LeaseKeepAliveResponse 这个管道中) if keepRespChan,err = lease.KeepAlive(ctx,leaseId);err != nil { fmt.Println(err) return } //启动一个新的协程来select这个管道 go func() { for { select { case keepResp = <- keepRespChan: if keepResp == nil { fmt.Println("租约失效了") goto END//失效跳出循环 } else { //每秒收到一次应答 fmt.Println("收到租约应答",keepResp.ID) } } } END: }() //得到操作键值对的kv kv = clientv3.NewKV(client) //进行写操作 if putResp,err = kv.Put(context.TODO(),"/cron/lock/job1","",clientv3.WithLease(leaseId)/*高速etcd这个key对应的租约*/);err != nil { fmt.Println(err) return } else { fmt.Println("写入成功",putResp.Header.Revision/*这东西你可以理解为每次操作的id*/) } } //监听这个key的租约是否过期 for { if getResp,err = kv.Get(context.TODO(),"/cron/lock/job1");err != nil { fmt.Println(err) return } if getResp.Count == 0 { fmt.Println("kv过期了") break } fmt.Println("kv没过期",getResp.Kvs) time.Sleep(2 * time.Second) }
2月前 喜欢(0) 浏览(459) 评论(0)
博客
etcd做注册服务使用 类似java生态的zookeeper,最近在学学习 1、下载etcd包 //直接下载即可(因为包比较大有翻墙,可以直接去 https://golangtc.com/download/package 下载) go get go.etcd.io/etcd/clientv3 2、使用记录 packge main import ( "context" "fmt" "go.etcd.io/etcd/clientv3" //"log/syslog" "time" ) func main() { var ( config clientv3.Config client *clientv3.Client err error kv clientv3.KV putResp *clientv3.PutResponse getResp *clientv3.GetResponse delResp *clientv3.DeleteResponse keepResp *clientv3.LeaseKeepAliveResponse keepRespChan <-chan *clientv3.LeaseKeepAliveResponse ) } //客户端配置 config = clientv3.Config{ Endpoints:[]string{"127.0.0.1:2379"}, DialTimeout:5 * time.Second, } //建立连接 if client,err = clientv3.New(config);err != nil { fmt.Println(err) return } //得到操作etcd键值对的kv kv = clientv3.NewKV(client) //写入etcd if putResp,err = kv.Put(context.TODO(),"/cron/jobs/job2","....",clientv3.WithPrevKV()/*可选参数,得到上次操作的值*/);err != nil { fmt.Println(err) } else { fmt.Println(putResp.Header.Revision) if putResp.PrevKv != nil { fmt.Println(string(putResp.PrevKv.Value)) } } //读取某个key的value值 getResp,err = kv.Get(context.TODO(),"/cron/jobs/job1"/*,clientv3.WithCountOnly()可选参数,得到数量*/) if err != nil { fmt.Println(err) return } else { fmt.Println(getResp.Kvs[0].Value/*得到的是一个切片*/) } //读取前缀为XXX的所有的key的value(需要加上参数clientv3.WithPrefix()) if getResp,err = kv.Get(context.TODO(),"/cron/jobs/",clientv3.WithPrefix());err != nil { fmt.Println(err) return } else { fmt.Println(getResp.Kvs) } //删除操作 if delResp,err = kv.Delete(context.TODO(),"/cron/jobs/job2",clientv3.WithPrevKV()/*得到删除之前的值*/);err != nil { fmt.Println(err) return } else { if len(delResp.PrevKvs) != 0 { fmt.Println(delResp.PrevKvs) } }
2月前 喜欢(0) 浏览(412) 评论(0)
Wiki
在views/layouts/main.php 中添加下面的代码: //views/layouts/main.php $this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, yii2, Yii中文网, yii教程, yii实战教程']); !isset($this->metaTags['description']) && $this->registerMetaTag(["name" => "description", "content" => 'Yii中文网,零基础学习yii2框架,提供丰富的yii2视频教程和应用实例,以及各种实用扩展推荐,我们旨在学术的互相学习探讨,共同进步!']); 解释一下为什么在 description 加 !isset($this->metaTags['description']),因为不同的页面可能需要设置不同的 description 信息。此处如此设置是在没有 description 的设置页面,默认使用上述的 description 的内容。 举个例子: 我想在文章详情页的描述是文章内容简介,我们就可以在具体的文章详情页添加如下代码: //views/topic/detail.php $this->registerMetaTag(["name" => "description", "content" => '某某文章的简介内容']);
2月前 喜欢(0) 浏览(365) 评论(0)
社区公告
[公告] Yii中文网为优化用户体验进行大版本升级,老版网站会维持一段时间,可以点击顶部"旧版"链接访问旧版网站。
沟通交流

:492175201(技术1群)

:183620600(技术2群)

:291010569(技术3群)

订阅号 | 更多精彩内容推送
本周推荐