yii2 advanced模板,添加第3个应用api (可以参考 https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-zh-CN/topic-adding-more-apps.md 注意,替换命名空间比较累人也容易出错,好在我用PhpStorm可以按路径搜索替换)
由于我是使用单一域名放这3个应用的,所以,涉及必要的服务器配置问题
nginx(Ubuntu 18.04)参考配置为:
zime@zime-virtual-machine:~$ cat /etc/nginx/sites-available/gxlq_pig
server {
charset utf-8;
client_max_body_size 128M;
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
access_log /home/zime/PhpstormProjects/gxlq_pig/log/access.log;
error_log /home/zime/PhpstormProjects/gxlq_pig/log/error.log info;
# rewrite_log on; # use rewrite log
set $front_root /home/zime/PhpstormProjects/gxlq_pig/frontend/web;
set $back_root /home/zime/PhpstormProjects/gxlq_pig/backend/web;
set $api_root /home/zime/PhpstormProjects/gxlq_pig/api/web;
set $prj_root /home/zime/PhpstormProjects/gxlq_pig;
root $prj_root;
location / {
#root $front_root;
try_files $uri index.php?$args /frontend/web/index.php?$args;
location ~ ^\.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
access_log off;
expires 360d;
rewrite ^/(.+)$ /web/$1 break;
rewrite ^/(.+)/(.+)$ /web/$1/$2 break;
try_files $uri =404;
}
}
location /api {
#alias $api_root;
rewrite ^(/api)/$ $1 permanent;
# try_files $uri index.php?$args /api/web/index.php?$args;
try_files $uri /api/web/index.php?$args;
}
location ~ ^/api/(.+\.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar))$ {
access_log off;
expires 360d;
rewrite ^/api/(.+)$ /api/web/$1 break;
rewrite ^/api/(.+)/(.+)$ /api/web/$1/$2 break;
try_files $uri =404;
}
location /admin {
#alias $back_root;
rewrite ^(/admin)/$ $1 permanent;
try_files $uri /backend/web/index.php?$args;
}
location ~ ^/admin/(.+\.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar|woff2|svg|eot|woff|ttf))$ {
access_log off;
expires 360d;
rewrite ^/admin/(.+)$ /backend/web/$1 break;
rewrite ^/admin/(.+)/(.+)$ /backend/web/$1/$2 break;
try_files $uri =404;
}
# deny accessing php files for the /assets directory
location ~ ^/assets/.*\.php$ {
deny all;
}
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
# include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
try_files $uri =404;
}
location = /requirements.php {
deny all;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.(ht|svn|git) {
deny all;
}
}
apache(Windows 2008)参考配置为:
httpd-ssl.conf 如下
<VirtualHost ****.com:8066>
DocumentRoot "C:/wamp/apps/gxlq_pig"
ServerName ****.com:8066
ErrorLog "C:/wamp/logs/gxlq_pig-error.log"
CustomLog "C:/wamp/logs/gxlq_pig-access.log" common
# debug rewrite
# LogLevel alert rewrite:trace8
SSLEngine on
SSLCertificateFile "C:/wamp/bin/apache/apache2.4.9/conf/cert.pem"
SSLCertificateKeyFile "C:/wamp/bin/apache/apache2.4.9/conf/key.pem"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
BrowserMatch "MSIE [2-5]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
<Directory "C:/wamp/apps/gxlq_pig">
# Options Indexes FollowSymLinks
AllowOverride all
Require all granted
</Directory>
</VirtualHost>
项目根目录 gxlq_pig 下的 .htaccess 如下
# prevent directory listigs
Options -Indexes
# follow symbolic links
Options FollowSymlinks
RewriteEngine on
RewriteCond %{REQUEST_URI} ^/api/$
RewriteRule ^(api)/$ /$1 [R=301,L]
RewriteCond %{REQUEST_URI} ^/api
RewriteRule ^api(/.+)?$ /api/web/$1 [L,PT]
RewriteCond %{REQUEST_URI} ^/admin/$
RewriteRule ^(admin)/$ /$1 [R=301,L]
RewriteCond %{REQUEST_URI} ^/admin
RewriteRule ^admin(/.+)?$ /backend/web/$1 [L,PT]
RewriteCond %{REQUEST_URI} ^.*$
RewriteRule ^(.*)$ /frontend/web/$1
gxlq_pig/api/web目录下的.htaccess如下(其他两个应用对应web目录下的.htaccess是一样的)
# use mod_rewrite for pretty URL support
RewriteEngine on
#if a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward the request to index.php
RewriteRule . index.php
修改api/config/main.php配置文件(主要是共享单一域名需要修改的一些地方,注意,下面的配置文件中没有禁用csrf,作为API实用角度来说,应该在这里全局禁用csrf)
<?php
$params = array_merge(
require __DIR__ . '/../../common/config/params.php',
require __DIR__ . '/../../common/config/params-local.php',
require __DIR__ . '/params.php',
require __DIR__ . '/params-local.php'
);
return [
'id' => 'app-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
'controllerNamespace' => 'api\controllers',
'homeUrl' => '/api',
'components' => [
'request' => [
'csrfParam' => '_csrf-api',
'csrfCookie' => [
'httpOnly' => true,
'path' => '/api', // share domain
],
'enableCookieValidation' => false,
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
'baseUrl' => '/api',
],
'response' => [
'format' => \yii\web\Response::FORMAT_JSON,
'charset' => 'UTF-8',
],
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true, 'path' => '/api'], // share domain
],
'session' => [
// this is the name of the session cookie used for login on the api
'name' => 'advanced-api',
'cookieParams' => [
'path' => '/api', // share domain
]
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'errorHandler' => [
'errorAction' => 'site/error',
],
//*
'urlManager' => [
'enablePrettyUrl' => true,
//'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => 'user'],
['class' => 'yii\rest\UrlRule', 'controller' => 'group'],
],
],
//*/
],
'params' => $params,
];
直接在api/controllers/SiteController.php进行添加修改。先修改actionIndex,访问https://****/api/site/wechat-post测试一下“通不通”
public function actionIndex()
{
//return $this->render('index');
return Json::encode(['msg' => 'hello, site index']);
}
然后在控制器代码开始处添加以下代码,我们从控制器范围禁用csrf(也可以覆盖父类beforeAction对指定的action禁用csrf),可以参考 https://blog.csdn.net/u012979009/article/details/51791810
public $enableCsrfValidation = false; // 可以在配置文件、控制器或beforeAction禁用csrf
添加2个action,分别用于form表单测试和文件上传测试
public function actionWechatPost()
{
return Json::encode(['msg' => 'post OK', 'posted_data' => $_POST]);
}
public function actionWechatUpload()
{
return Json::encode(['msg' => 'upload OK', 'with_posted_data' => $_POST, 'Raw $_FILES' => $_FILES]);
}
用小程序开发工具新建应用gxlq_pig,用自带模拟器可以直接试验。
我们使用微信自己的WeUI框架,所以下载并引入该框架:从 https://github.com/Tencent/weui-wxss 下载zip压缩包,解压后将其中的style目录整个带目录放到项目根目录gxlq_pig下面 (WeUI框架自带了不少examples,所以可以参照学习),然后在小程序项目的全局应用样式表app.wxss头部添加以下代码 (可以参考 https://www.jianshu.com/p/fd423b6e17be )
@import "style/weui.wxss";
我们在项目根目录的pages下新建目录module1,然后在里面建立4个文件(这也是小程序基本“套路”):index.js,index.json,index.wxml,index.wxss
说明:简单理解,wxml对标html,wxss对标css,这两个部分组成了静态的可以看的部分(内容和样式),json文件是一个配置文件,json和wxss是可以覆盖应用的全局配置信息的,js文件相当于本模块的可执行代码。
小程序运行的大致逻辑是:app.json和app.wxss是全局的配置信息,后者是全局样式,前者最值得关心的是路由信息。app.js是应用程序主入口,就是加载启动小程序时需要做的工作,实际我们看到的“第一个画面”,其实是路由信息中的第一个路由到的页面。
现在,我们希望在第一个页面中加入“一点东西”,点击它之后“被动”转向我们新建的模块module1:
首先,必须在app.js的路由表添加module1中需要显示的页面module1/index的路由信息
"pages":[
"pages/index/index",
"pages/logs/logs",
"pages/module1/index"
],
其次,我们 pages/index/index.wxml 的底部添加一点代码(view class="mytest"部分),点击这个链接将路由到module1/index
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
<view class="mytest">
<navigator url="../module1/index">goto My Form</navigator>
</view>
为了避免这链接和上面的hello挤在一起,我们在 pages/index/index.wxss中添加样式(类mytest的样式)
.mytest {
margin-top: 20px;
background-color: pink;
}
最后是编写前面建立的4个文件的内容(可以参考 https://blog.csdn.net/anqingya/article/details/80348877 )
module1/index.wxml文件
<!--index.wxml-->
<form bindsubmit="formSubmit">
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">学号</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" name="no" placeholder="请输入学号" value='1635050131'/>
</view>
</view>
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">姓名</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" name="name" placeholder="请输入姓名" value='安庆雅'/>
</view>
</view>
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">性别</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" name="sex" value="{{sex}}"/>
</view>
<view class="weui-cell__ft">
<switch checked bindchange="switch1Change"/>
</view>
</view>
<view class="weui-cell weui-cell_input">
<view class="weui-cell__hd">
<view class="weui-label">年龄</view>
</view>
<view class="weui-cell__bd">
<input class="weui-input" name="age" placeholder="请输入年龄" value='21'/>
</view>
</view>
<view class="weui-cells">
<view class="weui-cell">
<view class="weui-cell__bd">
<view class="weui-uploader">
<view class="weui-uploader__hd">
<view class="weui-uploader__title">个人照片</view>
<view class="weui-uploader__info">0/1</view>
</view>
<view class="weui-uploader__bd">
<view class="weui-uploader__files">
<block wx:for="{{imageList}}" wx:for-item="image">
<view class="weui-uploader__file">
<image class="weui-uploader__img" src="{{image}}" data-src="{{image}}" bindtap="previewImage"></image>
</view>
</block>
</view>
<view class="weui-uploader__input-box">
<view class="weui-uploader__input" bindtap="chooseImage"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="weui-btn-area">
<button class="weui-btn" type="primary" form-type="submit">确定</button>
</view>
</form>
module1/index.js文件(作为测试另外2个文件不关键,不用写内容)
//index.js
//获取应用实例
Page({
data: {
sex: '男',
imageList:[]
},
switch1Change:function(e){
if(e.detail.value){
this.setData({sex:'男'})
}else{
this.setData({sex:'女'})
}
},
//表单提交
formSubmit: function (e) {
wx.request({
url: 'https://****/api/site/wechat-post', //仅为示例,并非真实的接口地址
data:e.detail.value,
method:'POST',
header: {
'content-type': 'application/x-www-form-urlencoded' // 默认值
},
success: (res)=>{
console.log(res)
if (res.error) {
wx.showToast({
title:res.data.msg, // 这个可能有问题,见下面 upload 部分
icon: 'none',
duration: 2000,
})
} else {
wx.showToast({
title: '添加成功',
icon: 'success',
duration: 2000,
})
this.upload();
}
}
})
},
//拍照
chooseImage: function () {
var that = this
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
console.log(res)
that.setData({
imageList: res.tempFilePaths
})
}
})
},
//预览照片
previewImage: function (e) {
var current = e.target.dataset.src
wx.previewImage({
current: current,
urls: this.data.imageList
})
},
//上传
upload:function(){
wx.uploadFile({
url: 'https://****/api/site/wechat-upload', //仅为示例,非真实的接口地址
filePath: this.data.imageList[0],
name: 'file',
formData: {
'user': 'test'
},
success: function (res) {
console.log(res);
//var data = res.data
//console.log(data);
var json=JSON.parse(res.data);
console.log(json);
console.log(typeof json); // 一次 JSON.parse 后,这里得到的json还是字符串!
json = JSON.parse(json);
console.log(json);
console.log(typeof json); // 到这里 json 才是Object
wx.showToast({
title: json.msg,
icon: 'none',
duration: 3000,
})
}
})
}
})
用微信开发工具的模拟器,进入后点击goto My Form,顺利跳转并显示表单页面。在表单里面输入各项信息,性别切换,然后选择电脑中的某个图片作为照片。在选择图片后,调试器console会输出 chooseImage的回调success内console.log语句的输出。
点击“确定”后,仔细观察调试器控制台的输出,确认信息是否正常,流程是否正确。同时,这些信息可以和后面服务端信息进行比对,例如上传的文件的大小是否一致。
观察 api/runtime/logs/app.log 尾部的信息,可以发现
$_POST = [
'no' => '20010414'
'name' => '张三'
'sex' => '男'
'age' => '42'
]
$_FILES = []
$_COOKIE = []
$_SERVER = [
// ......
]
//......
$_POST = [
'user' => 'test'
]
$_FILES = [
'file' => [
'name' => 'tmp_ec95aaf90fb6b7892ebbae76035c9981386612c029efcf2a.jpg'
'type' => 'image/jpeg'
'tmp_name' => 'C:\\wamp\\tmp\\php4A81.tmp'
'error' => 0
'size' => 36299
]
]
$_COOKIE = []
$_SERVER = [
//......
]
观察到这些信息,我们可以确信,表单数据提交和文件上传都是“达到了服务器端”的。接下来的工作无非是把上传文件或者处理表单改写成yii2风格的。