開發了好幾個小程式,將我開發小程式的一些前期準備工作分享在這裡。大家可以參考一下。如果有好的提議,可以分享在下面。
第一步:準備基礎的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 為技術新手提供服務