小程序Lumen API开发

Lumen API开发

Lumen.png

Laravel和Lumen的区别:Lumen轻量级框架,集合了Laravel的优美语法,支持ORM没有Blade模板引擎,我个人觉得这个框架适用于API的开发,对于相比Laravel性能,如果Lumen打开了门面就缺失了它自己的特性,就和Laravel没什么区别了。

安装Lumen

  • 安装Composer
cd /usr/local/bin

php -r“copy('https://getcomposer.org/installer','composer-setup.php');”
php -r“if(hash_file('SHA384','composer-setup.php')==='544e09ee996cdf60ece3804abc52599c22f1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061'){echo'Installer verified';} else {echo'Installer corrupt'; unlink('composer-setup。 php');} echo PHP_EOL;“
php composer-setup.php
php -r“unlink('composer-setup.php');”

composer --version 或 composer -v  // 查看是否安装成功?
  • Mac配置Path路径
sudo vim ~/.bash_profile
export PATH=~/.composer/vendor/bin:$PATH  // 黏贴这段代码
执行source ~/.bash_profile                // 更新配置文件
  • 安装Lumen项目(两种方式)
第一种方式:通过 Lumen 安装器 (不支持选择版本默认最新的)
【帮助】lumen help new
【Lumen】lumen new project // 直接new一个项目就可以了

第二种方式:Composer 安装(速度慢但可指定安装版本)
【安装指定版本】composer create-project laravel/lumen Lumen-5.5 --prefer-dist  "5.5.*"

// 目前只有通过 composer create-project 方式支持选择版本, lumen new 方式不支持。

配置完域名直接访问Lumen

Lumen-5.5.png

添加缺失的Artisan命令

执行php artisan list会发现,Lumen相比Laravel缺失了一部分make···等命令,像我这种懒人必须得想办法解决 gitHub:lumen-generators

not have-artisan.jpg
  • 执行Composer安装
composer require wn/lumen-generators
  • 注册服务提供者Providers
// 在app/Providers/AppServiceProvider.php
public function register()
{
    if ($this->app->environment() == 'local') {
        $this->app->register('Wn\Generators\CommandsServiceProvider');
    }
}
  • 展示渲染Providers
// 在bootstrap/app.php找到Register Service Providers添加下面代码
$app->register(Wn\Generators\CommandsServiceProvider::class);
  • 查看命令php artisan list
    wn-artisan.png
  • 修改Controller和Model模板源码(不改你会后悔的!)


    templates-path.png

    控制器模板:

<?php

namespace App\Http\Controllers\{{name}};

use Laravel\Lumen\Routing\Controller;

class {{name}} extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Display a listing of the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     * @author
     */
    public function index()
    {
        //
    }
}

model 模板:

<?php

namespace {{namespace}}\Models;

use Illuminate\Database\Eloquent\Model;

class {{name}} extends Model {

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [];

}
  • wn配置完的artisan操作命令
// 有个bug需要自己去控制器里再编辑下名称,着急开发懒得去翻源码了
【生成控制器】php artisan wn:controller Static/Static

开启Lumen的各种扩展必须组件 (门面操作,谨慎开启)

// 开启 Eloquent
$app->withEloquent();
// 开启 Session
去掉 $app->middleware(); 的 StartSession 中间件的注释
// 开启 路由中间件
去掉 routeMiddleware 的注释
// 配置 数据库 ,复制 /config文件
去源码vendor/laravel/lumen-framework/config 复制到根目录下
  • 对于Lumen的dd打印

什么?哦,上帝!真是见鬼!怎么会有人在 Laravel 中还在用 echo + die() ?好家伙,我敢打赌,他一定没有好好看文档,我向圣母玛利亚保证。如果让我看到这群愚蠢的土拨鼠,看在上帝的份上,我会用靴子狠狠地踢他们的屁股,我发誓我绝对会。 (来自一个逗比的Lumen dd() 打印解释)

// 安装 dd() 打印样式组件
composer require symfony/var-dumper

微信小程序【第三方平台账号登录流程图】

微信授权登录流程图.png

图片来源PHP中文网:图片中的3rd_session类似于Auth2的认证_token,所以后端需要写有状态的接口时每次验证这个值(是否过期,是否正确)自己封装一个继承类去验证,请看下面代码。

  • 小程序继承类代码:
<?php

namespace App\Http\Controllers\Applets;

use App\Http\Controllers\Controller;
use App\Traits\AppletsTrait;

/**
 * 小程序获取token基类认证
 *
 * Class BasisController
 * @package App\Http\Controllers\Applets
 * @author SuperHao - 619596123@qq.com
 */
class BasisController extends Controller
{
    use AppletsTrait;

    protected $session3rd;

    /**
     * BasisController constructor.
     * @author SuperHao - 619596123@qq.com
     */
    public function __construct()
    {
        $this->getSession3rd($session3rd);
        $this->session3rd = $session3rd;
    }
}
  • 小程序 TraitgetSession3rd 方法:
/**
 * 获取session3rd参数
 *
 * @return boolean
 * @author SuperHao - 619596123@qq.com
 */
 public function getSession3rd(&$session3rd)
 {
     $result = array_key_exists('HTTP_SESSION3RD', $_SERVER);
     if (!$result) return false;
     $session3rd = $_SERVER['HTTP_SESSION3RD'];
     return true;
 }
  • 继承基类控制器里使用:
<?php

namespace App\Http\Controllers\Applets;

use Illuminate\Http\Request;

class TestController extends BasisController
{
    /**
     * Display a listing of the resource
     *
     * @throws \Exception
     * @author SuperHao - 619596123@qq.com
     */
    public function index()
    {
        // 获取 Session3rd (这里直接使用基类的参数)
        $session3rd = $this->session3rd;

        // 获取 redis 里的用户信息
        $userInfo = cache($session3rd);
        
        // 数据打印
        // array:3 [
        //   "session_key" => "2C7rPDnhXAy5dUAdpS==..."
        //   "openid" => "odLil5N4yHjt2nUZfnjuIGmca..."
        //   "unionid" => "oKMyGwZjvi0WigertI0XWOP2..."
        // ]
        dd($userInfo);
    }
}
  • 中间件拦截基类Controller代码:
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class AppletsTokenMiddleware
{
    /**
     * 获取请求头Session3rd.
     *
     * @param $request
     * @param Closure $next
     * @return \Illuminate\Http\JsonResponse|mixed
     * @author SuperHao - 619596123@qq.com
     */
    public function handle($request, Closure $next)
    {
        $result = array_key_exists('HTTP_SESSION3RD', $_SERVER);;
        if ($result) {
            if (Cache::has($_SERVER['HTTP_SESSION3RD'])) return $next($request);
        }
        return response()->json(['status' => false, 'code' => 401, 'message' => '账户过期,请重新授权登陆']);
    }
}
小程序后端登录凭证校验:

小程序接口官网地址,找到登陆凭证,里面的wx.login是前端的,后台不用管。(前提是必须了解上面的流程图)
然后找到 登录凭证校验 这个接口上面写的很详细,好像没有说是异步还是同步,但是网页打开调用了一下你会发现这是个同步的接口(同步的接口就是发送参数后微信立马回调给你参数,异步的话则需要配置回调域名并且还得打印写入到日志或者文件中才能调试参数)下面贴一下代码demo:

  • 微信接口工具类
<?php

namespace App\Tools;

/**
 * 微信接口工具类
 *
 * Class WeChatTool
 * @package App\Tools
 * @author SuperHao - 619596123@qq.com
 */
class WeChatTool
{
    const OK = 0;                       // success
    const IllegalAesKey = -41001;       // encodingAesKey 非法
    const IllegalIv = -41002;           // appId missing (缺少appId参数)
    const IllegalBuffer = -41003;       // aes 解密失败
    const DecodeBase64Error = -41004;   // 解密后得到的buffer非法

    /**
     * 返回转译 constCodeMessage
     *
     * @param $code
     * @return string
     * @author SuperHao - 619596123@qq.com
     */
    static public function constCodeMsg($code)
    {
        switch ($code) {
            case 0:
                return 'success';
                break;
            case -41001:
                return 'encodingAesKey 非法';
                break;
            case -41002:
                return 'appId missing (缺少appId参数)';
                break;
            case -41003:
                return 'aes 解密失败';
                break;
            case -41004:
                return '解密后得到的buffer非法';
                break;
            default:
                return '未知解密错误';
                break;
        }
    }

    /**
     * 获取168位的随机字符串
     *
     * @return string
     * @author SuperHao - 619596123@qq.com
     */
    static public function getSession3rd()
    {
        /**
         * 微信的官方文档推荐用这种操作系统提供真正随机数的方法
         * /dev/urandom 和 /dev/random 已经算是很接近真随机数的随机数了
         */
        return shell_exec('head -n 80 /dev/urandom | tr -dc A-Za-z0-9 | head -c 168');
    }

    /**
     * 微信接口报错code
     *
     * @param $code
     * @return array|\Illuminate\Contracts\Translation\Translator|null|string
     * @author SuperHao - 619596123@qq.com
     */
    static public function codeErrorMessage($code)
    {
        /**
         * 判断是否是类型中的
         */
        if (array_key_exists($code, trans('applets'))) {
            $message = $code . ': ' . trans('applets.' . $code);
        } else {
            // 未知错误
            $message = $code . ': ' . trans('applets.unknown');
        }

        return $message;
    }

    /**
     * 解密微信数据. (检验数据的真实性,并且获取解密后的明文)
     *
     * @param $sessionKey string session_key
     * @param $encryptedData string 加密的用户数据
     * @param $iv string 与用户数据一同返回的初始向量
     * @param $data array 解密后的原文
     * @return array
     * @author SuperHao - 619596123@qq.com
     */
    static public function decryptData($sessionKey, $encryptedData, $iv, &$data)
    {
        if (strlen($sessionKey) != 24) {
            return ['status' => false, 'code' => self::IllegalAesKey, 'message' => self::constCodeMsg(self::IllegalAesKey)];
        }
        $aesKey = base64_decode($sessionKey);
        if (strlen($iv) != 24) {
            return ['status' => false, 'code' => self::IllegalIv, 'message' => self::constCodeMsg(self::IllegalIv)];
        }

        $aesIV = base64_decode($iv);
        $aesCipher = base64_decode($encryptedData);
        $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
        $dataObj = json_decode($result);

        if ($dataObj == NULL) {
            return ['status' => false, 'code' => self::IllegalBuffer, 'message' => self::constCodeMsg(self::IllegalBuffer)];
        }
        if ($dataObj->watermark->appid != config('applets.appId')) {
            return ['status' => false, 'code' => self::IllegalBuffer, 'message' => self::constCodeMsg(self::IllegalBuffer)];
        }

        $data = $result;

        return ['status' => true, 'code' => self::OK, 'message' => self::constCodeMsg(self::OK)];
    }
}
  • 微信登录获取Code处理代码
// 前端传输code换取sessionkey (get code2Session).
public function index(Request $request)
{
    /**
     * 验证登陆信息
     */
    $validator = Validator::make($request->all(), [
        'js_code' => 'required|alpha_dash',
    ]);
    if ($validator->fails()) {
        return $this->response->error($validator->errors()->first());
    }

    $jsCode = $request->input('js_code');

    /**
     * getJsCode
     */
    $url = sprintf(config('applets.url.getJsCode'), config('applets.appId'), config('applets.appSecret'), $jsCode);
    /**
     * send Http
     */
    $result = CurlTool::httpsRequest($url);
    $result = json_decode($result, true);
    /**
     * Exists WeChat Error
     */
    if (array_key_exists('errcode', $result)) {
        /**
         * 微信接口报错代码转译
         */
        $message = WeChatTool::codeErrorMessage($result['errcode']);
        return $this->response->error($message);
    }

    return $this->response->success($result);
}
  • 微信登录解密数据代码
public function store(LoginStoreRequest $request)
{
    $json = $request->input('json');
    $sessionKey = $request->input('session_key');

    /**
     * 解密数据
     */
    $result = WeChatTool::decryptData($sessionKey, $json['encryptedData'], $json['iv'], $data);
    if (!$result['status']) {
        $data = [
            'session_key' => $sessionKey,
            'json' => $json,
        ];
        filePutData('storage/wechat.log', $data);
        $message = $result['code'] . ':' . $result['message'];
        return $this->response->error($message);
    }

    /**
     * 检测过滤微信数据是否存在
     */
    $data = json_decode($data, true);
    if (!array_key_exists('openId', $data) && !array_key_exists('unionId', $data)) {
        return $this->response->error(trans('request.arrayKeyExists'));
    }

    /**
     * build
     */
    $build = [
        'nickname' => $data['nickName'],
        'gender' => $data['gender'],
        'openid' => $data['openId'],
        'unionid' => $data['unionId'],
        'avatarUrl' => $data['avatarUrl'],
        'country' => $data['country'],
        'province' => $data['province'],
        'city' => $data['city'],
        'language' => $data['language'],
    ];

    /**
     * 判断是否创建授权过
     */
    $weChatUser = $this->wechatUser->where(['unionid' => $data['unionId']])->first();
    if (!empty($weChatUser)) {
        /**
         * save userInfo
         */
        $weChatUser->save($build);
        $data['wechat_id'] = $weChatUser->id;
    } else {
        /**
         * create weChatUser Data
         */
        $create = $this->wechatUser->create($build);
        $data['wechat_id'] = $create->id;
    }

    /**
     * 获取 3rd_session (168位的随机字符串)
     */
    $response = [
        'unionid' => $data['unionId'],
        'Authorization' => 'Bearer ' . WeChatTool::getSession3rd(), // session3rd
    ];
    // 编写redis值
    $session3rdRedis = session3rd($response['Authorization']);

    /**
     * Redis 储存微信用户信息
     */
    Cache::remember($session3rdRedis, config('applets.cacheSession3rd'), function () use ($data) {
        return [
            'wechat_id' => $data['wechat_id'],
            'unionId' => $data['unionId'],
            'openId' => $data['openId'],
            'nickName' => $data['nickName'],
            'avatarUrl' => $data['avatarUrl'],
            'gender' => $data['gender'],
        ];
    });

    return $this->response->created(trans('response.decryptData'), $response);
}
小程序调试必备:
  • Sunny-Ngrok

Sunny-Ngrok内网穿透工具,是一个很简单还不收费的软件。里面有体验隧道,用这个足够了。具体使用教程官方有详细的视频介绍

  • Wampserver (或者可以使用 Vagrant 自带的网络配置 public_network)

为什么推荐这个?因为每次都得开本地Vagrant虚拟机才能访问,如果写前后台分离为了和前端人员调试方便,开启Wampserver直接指向自己的Vagrant目录访问自己的Api会更方便别人,即便是平时自己的Vagrant不启动前端也可以访问接口。

1.配置局域网访问

Win +R 打开 Cmd 输入 ipconfig 查看自己的以太网局域网IPv4
ipconfig.png
打开 httpd-vhosts 找到 Require local 替换成 Require all granted
如下:
<VirtualHost *:80>
    ServerName 192.168.10.72
    DocumentRoot D:/Vagrant/
    <Directory  "D:/Vagrant/">
        Options +Indexes +FollowSymLinks +MultiViews
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
# 借鉴地址:
// https://jingyan.baidu.com/article/acf728fd556c93f8e410a344.html

注意:记得去自己的网络里查看是否是固定的 以太网 IPv4,如果不是请如下设置:

IP地址:192.168.10.110
子网掩码:255.255.255.0
默认网关:192.168.10.1  //里面的10 是自己公司的网段

DNS:
首选DNS服务器:114.114.114.114
备用DNS服务器:8.8.8.8

2.配置 Wampserver 的 Redis

虽然你的vagrant装了redis,但是Wampserver 是没有的,所以需要装一下php扩展,找了半天找到靠谱的:CSDN-Wampserver安装redis扩展,我就不搬过来了。
但是总不能一直开着cmd运行窗,太累太low -> Windows下Redis一直后台运行

- 到Redis目录下 安装redis服务:
redis-server --service-install redis.windows.conf --loglevel verbose
# 提示(Redis已成功安装为服务) :
[13808] 20 Sep 15:04:30.063 # Granting read/write access to 'NT AUTHORITY\NetworkService' on: "D:\Redis" "D:\Redis\"
[13808] 20 Sep 15:04:30.064 # Redis successfully installed as a service.
- 启动Redis:redis-server --service-start  关闭 -stop

一个很好用的 Laravel sql 记录日志,它的功能大体有:
1. 记录框架中的所有执行过的SQL
2. 展示每条执行速度,用来分析SQL的问题,默认情况下以毫秒为单位
3. 可以日志记录慢查询SQL (也以毫秒为单位,可设置慢查询时间)

composer require mnabialek/laravel-sql-logger --dev
  • 执行扩展包资源选择对应数字服务类
php artisan vendor:publish
  • 组件配置文件路径在:config/sql_logger.php
Laravel-模型关联-远程一对多(特殊需求)

有个需求,需要远程查表。用的模型关联所以尝试了期待已久的 远程一对多,问题就是参数有点乱,官方也没有详细的讲解。所以亲自试了好久,总结写出以下注释:

情况说明:(现在有3张表)
1是主表(文章详情表)     2是中间表(评论表)    3是远程表(评论人的user表)
id,comment_id            id,user_id            id,name
需求:1想拿到评论他文章的用户名称,也就是3表的username
/**
 * 文章查询评论人信息
 * 远程一对多
 */
 public function commentUser()
 {
     return $this->hasManyThrough(
      1   'App\Applets\user',     // 远程表
      2   'App\Applets\comment',  // 中间表
      3   'id',                   // 中间表对主表的关联字段(2.id)
      4   'id',                   // 远程表对中间表的关联字段(3.id)
      5   'comment_id',           // 主表对中间表的关联字段(1.comment_id)
      6   'user_id'               // 中间表对远程表的关联字段(2.user_id)
     );
 }

* 如果把参数分为 1、2、3、4、5、6 就是:
* 1 和 2 对应
* 3 和 5 对应
* 4 和 6 对应

借着上面的分析顺道说一下别的模型关联参数:
(参数都是数字代表,有时候也可不填参数,Laravel 会根据规则自己找关联)
【hasOne】    一对一            1关联模型  2关联表的关联字段     3自己主表的id
【hasMany】   一对多            1关联模型  2关联表的关联字段     3自己主表的id
【belongsTo】 反向一对一(多)   1关联表    2主表关联的关联表ID   3关联表ID

如果有什么不太清楚,或者我写的不好的。请留言评论,随时都回复哈,谢谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269