使用Laravel构建小程序Api服务器的准备工作
开发了好几个小程序,将我开发小程序的一些前期准备工作分享在这里。大家可以参考一下。如果有好的提议,可以分享在下面。
第一步:准备基础的api构建工作
第二步:安装easywechat
建议安装overtrue/laravel-wechat
,和easywechat是一个作者,laravel-wechat
依赖了easywechat
。所以直接安装即可:
composer require "overtrue/laravel-wechat"
- 创建配置文件:
php artisan vendor:publish --provider="Overtrue\LaravelWeChat\ServiceProvider"
- 可选,添加别名
'aliases' => [
// ...
'EasyWeChat' => Overtrue\LaravelWeChat\EasyWeChat::class,
],
- 修改相关配置
打开 config/easywechat.php 配置好相应的信息即可
用户在小程序进行注册的逻辑
- 创建注册认证的控制器
php artisan make:controller Api/Weapp/AuthorizationsController
代码如下:
<?php
namespace App\Http\Controllers\Api\Weapp;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Auth\AuthenticationException;
use App\Http\Requests\Api\Weapp\AuthorizationRequest;
use Overtrue\LaravelWeChat\ServiceProvider;
use Overtrue\LaravelWeChat\EasyWeChat;
class AuthorizationsController extends Controller
{
/**
* @param AuthorizationRequest $request
* @return array
*/
public function store(AuthorizationRequest $request)
{
(new ServiceProvider(app()))->register();
$code = $request->code;
$miniProgram = EasyWeChat::miniApp();
$utils = $miniProgram->getUtils();
$data = $utils->codeToSession($code);
// 找到 openid 对应的用户
$user = User::where('weapp_openid', $data['openid'])->first();
$attributes['weixin_session_key'] = $data['session_key'];
// 未找到对应用户则注册新用户
if (!$user) {
$attributes['weapp_openid'] = $data['openid'];
$user = User::create($attributes);
} else {
// 更新用户数据
$user->update($attributes);
}
// 为对应用户创建 token
$user->tokens()->delete();
$token = $user->createToken($request->device_name);
return ['token' => $token->plainTextToken];
}
/**
* @param User $user
* @param $name
* @return array
*/
public function responseToken(User $user, $name = '')
{
// 为对应用户创建 token
// $user->tokens()->delete();
$token = $user->createToken($name);
return [
'token' => $token->plainTextToken,
'expire_in' => config('sanctum.expiration')
];
}
}
需要注意:这里的控制器继承的Controller
是基于这个帖子中写的自己建立的控制器基类。验证类同样也是。如果你之前的api准备工作没有按照我之前的教程进行操作。那么这里自行修改下相应的继承。类方法没区别。
增加小程序注册路由
// 小程序登录
Route::post('authorizations', 'AuthorizationsController@store')->name('authorizations.store');
将这个路由放在登录路由组里即可。路由分组见 wyz.xyz/d/344-laravelapi
原生小程序的请求封装
我的小程序全部是基于原生写法。其中因为有大量的请求需要使用。所以将所有的请求封装成了一个请求文件。这里将这个请求文件分享出来。
在小程序建立以下文件
utils/request.js
代码如下:
/************ API 定义 ************/
/**
* api入口定义
*/
const host = 'http://supply.test',
api_entry = host + '/api/weapp'
/**
* 登录
* @param {*} data
* @returns
*/
async function login() {
// 登录参数
async function getLoginParams() {
const wxLogin = () => new Promise((res, rej) => wx.login({ success: r => res(r), fail: e => rej(e) }))
try {
const { code } = await wxLogin(),
{ model: device_name } = wx.getSystemInfoSync()
return { code, device_name }
} catch (e) {
wx.showModal({ title: '登录失败,请稍后再试' })
DEBUG && console.log('[request] getLoginParams, wxLogin fail:', e)
return;
}
}
const login_params = await getLoginParams()
// 登录接口
const { token, expire_in = 604800 } = await repository('/authorizations', post(login_params))
// 登录成功并设置token缓存
setAccessToken(token, expire_in)
return token
}
/**
* 更新token
* @param {*} data
* @returns
*/
async function refreshToken() {
const { token, expire_in = 604800 } = await repository('/authorizations/current', post('', withToken))
// 登录成功并设置token缓存
setAccessToken(token, expire_in)
return token
}
// 这里是方法列表,你可以在下面增加你的方法,这里只是做个示范
async function getCustom() {
return await repository('/settings/custom', get(), { fresher: false, useCache: true })
}
// 这里导出上面的方法列表
export {
host,
getAccessToken,
getCustom,
login
}
/************ API 定义结束 ************/
/************ 可修改部分 ************/
const DEBUG = false
// 缓存 key 定义
const KEY_ACCESS_TOKEN = 'access_token',
KEY_TOKEN_EXPIRE = 'access_token_expired_at'
/**
* 缓存提供者
* 可以使用其它缓存接口,需要实现get、set方法
*/
const cacheProvider = {
get: async (key) => {
return await wx.getStorage({ key: 'repository/' + key }).then(res => res.data).catch(e => null)
},
set: async (key, data) => {
await wx.setStorage({ key: 'repository/' + key, data })
}
}
// repository 配置
const repositoryConfig = {
// 缓存提供者
cacheProvider,
// 需要更新数据
fresher: true,
// 使用系统缓存
useCache: false,
// 验证结果
validate: true,
// 只返回结果
onlyFetchedData: true,
// 刷新缓存
refreshCache: false,
}
/**
* 定义获取器
*/
function fetcher(method, data, before) {
method = method.toUpperCase()
if (!['GET', 'POST', 'PUT', 'DELETE'].includes(method))
throw new Error('[request] not allow method: ' + method)
return async (url) => {
let option = {
url, data, method, header: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'Application/json',
}
}
if (!Array.isArray(before)) before = [before]
for (const i in before) {
if (typeof before[i] === 'function')
option = await before[i](option)
}
return wxRequest(option).then(responseHandler)
}
}
const get = (d, be) => fetcher('get', d || '', [urlCon, be])
const post = (d, be) => fetcher('post', d || '', [urlCon, be])
const put = (d, be) => fetcher('put', d || '', [urlCon, be])
const del = (d, be) => fetcher('delete', d || '', [urlCon, be])
const urlCon = (option) => ({ ...option, url: api_entry + option.url })
const withToken = async (option) => ({ ...option, header: { 'Authorization': 'Bearer ' + await getAccessToken() } })
/**
* 响应错误处理
* 返回对象 { error }
* @param {Object} err
*/
function errorHandler(err) {
if (err.response.statusCode == 422) {
wx.showToast({ title: '提交内容错误', icon: 'error' })
return { error: err }
} else {
wx.showToast({ title: err.message, icon: 'error' })
return { error: err }
}
}
/**
*
* @param {data, error, response} params
* @returns
*/
function checkError(result) {
let { error, response } = result
return new Promise(resolve => error ? errorHandler({ ...error, response }) : resolve(result))
}
/**
* 响应处理方法
* 根据HTTP响应状态码处理响应内容,当错误时 error 不为空,即`!!error === true`
* 返回对象 { data, error, response }
* @param {*} response
* @returns
*/
function responseHandler(response) {
// DEBUG && console.log('[request] responseHandler; response:', response)
if (200 <= response.statusCode && response.statusCode < 300) {
return { data: response.data, error: null, response }
}
if (400 <= response.statusCode && response.statusCode < 500) {
DEBUG && console.log('[request] responseHandler, request error; response:', response)
// 当未提供正确token时,响应码为401
if (response.statusCode === 401) {
DEBUG && console.log('[request] responsehandler, token 无效或过期')
}
return { data: null, error: response.data, response }
}
if (500 <= response.statusCode && response.statusCode < 600) {
DEBUG && console.log('[request] responseHandler, server error; response:', response)
return { data: null, error: response.data, response }
}
}
/************ 可修改部分结束 ************/
/************ 约定部分 ************/
/**
* 数据仓库
* 返回内容为fetcher的结果
* @param {*} key
* @param {*} fetcher
* @param {*} _option
* @returns
*/
async function repository(key, fetcher, _option) {
_option = repositoryConfigure(_option || {})
const fresher = _option.fresher === true,
useCache = _option.useCache === true,
validate = _option.validate === true,
cacheProvider = useCache && _option.cacheProvider ? _option.cacheProvider : defaultRepositories,
onlyFetchedData = _option.onlyFetchedData === true,
refreshCache = _option.refreshCache === true
// key = stringifyKey(key)
if (typeof key === 'function') key = key()
if (Array.isArray(key)) key = JSON.stringify(key)
let result
if (!fresher) {
result = await cacheProvider.get(key)
}
// 获取数据
if (!result || refreshCache) {
result = await fetcher(key).then(r => validate ? checkError(r) : r)
DEBUG && console.log('[request] repository, fetched: ', key, result)
await cacheProvider.set(key, result)
} else {
DEBUG && console.log('[request] repository, get from cache:', key, result)
}
function mutate(data) {
DEBUG && console.log('[request] repository, mutate', data)
return repository(key, async () => ({ data }), { ..._option, refreshCache: true, onlyFetchedData: true })
}
function refresh() {
DEBUG && console.log('[request] repository, refresh')
return repository(key, fetcher, { ..._option, refreshCache: true })
}
return onlyFetchedData ? result.data : { ...result, mutate, refresh };
}
const repositoryConfigure = (config) => ({ ...repositoryConfig, ...config })
const defaultRepositories = new Map()
/**
* 微信请求promise封装
* @param {Object} options
* @returns {Object}
*/
async function wxRequest({ url, data, method, header }) {
return await new Promise((resolve, reject) =>
wx.request({ url, data, method, header, success: res => resolve(res), fail: err => reject(err) })
)
}
/**
* 获取token
* @param {Boolean} fresher
*/
async function getAccessToken(retry = 1) {
// 获取缓存 token
let token = (await cacheProvider.get(KEY_ACCESS_TOKEN)),
expire = await cacheProvider.get(KEY_TOKEN_EXPIRE)
DEBUG && console.log('[request] getAccessToken, after cacheProvider; token, expire: ', token, expire)
if (!token) {
DEBUG && console.log('[request] getAccessToken, login')
// 登录获取token
await login()
}
// 检查过期时间
else if (expire <= timestramp()) {
DEBUG && console.log('[request] getAccessToken, refresh')
// 刷新token
await refreshToken()
}
else {
return token
}
return retry > 0 ? getAccessToken(0) : 'no_token'
}
/**
* 保存 token
* @param {String} token
* @param {Number} expire_in
*/
function setAccessToken(token, expire_in) {
cacheProvider.set(KEY_ACCESS_TOKEN, token)
cacheProvider.set(KEY_TOKEN_EXPIRE, timestramp() + expire_in)
}
/**
* 获取时间戳
* 单位:秒
* @returns
*/
function timestramp() {
return parseInt((new Date().getTime() / 1000).toFixed(0))
}
/************ 约定部分结束 ************/
使用的时候只需引入在当前文件export
的的方法即可。
小程序开发交流QQ群:156516399
本作品采用《CC 协议》,转载必须注明作者和本文链接
乌鸦嘴社区 wyz.xyz 来玩。
推荐文章: