FastAdmin 短信插件开发完整教程

本文详细介绍如何在 FastAdmin 框架下开发一个完整的短信插件,基于聚合数据 API 实现验证码和通知短信发送功能。文章涵盖插件架构设计、钩子系统、路由配置等核心内容,适合 FastAdmin 插件开发初学者参考。

目录

项目背景

插件架构设计

[2.1 目录结构](#2.1 目录结构)

[2.2 核心组件说明](#2.2 核心组件说明)

开发步骤

[3.1 创建插件元数据](#3.1 创建插件元数据)

[3.2 配置插件参数](#3.2 配置插件参数)

[3.3 开发插件主类](#3.3 开发插件主类)

[3.4 封装短信服务类](#3.4 封装短信服务类)

[3.5 开发前台控制器](#3.5 开发前台控制器)

[3.6 配置系统钩子和路由](#3.6 配置系统钩子和路由)

测试验证

[4.1 访问插件首页](#4.1 访问插件首页)

[4.2 测试短信发送](#4.2 测试短信发送)

核心原理

[5.1 钩子工作机制](#5.1 钩子工作机制)

[5.2 零侵入集成](#5.2 零侵入集成)

常见问题

[6.1 短信发送失败](#6.1 短信发送失败)

[6.2 钩子未注册](#6.2 钩子未注册)

总结

[7.1 技术要点](#7.1 技术要点)

[7.2 开发建议](#7.2 开发建议)


项目背景

在现代 Web 应用中,短信验证是用户注册、登录、找回密码等场景的必备功能。FastAdmin 作为一款流行的后台开发框架,提供了完善的短信钩子系统,允许开发者通过插件方式扩展短信功能。

本教程将带你从零开始,开发一个功能完整的短信插件,支持:

  • ✅ 验证码短信(注册/登录/重置密码/更换手机)

  • ✅ 通知短信

  • ✅ 防轰炸机制

  • ✅ 前台测试页面

  • ✅ 诊断修复工具

  • ✅ 详情文档页面

插件架构设计

2.1 目录结构

bash 复制代码
addons/juhesms/
├── Juhesms.php              # 插件主类
├── config.php               # 配置信息
├── info.ini                 # 插件元数据
├── controller/
│   └── Index.php            # 前台控制器
├── library/
│   └── Service.php          # 短信服务类
└── view/
    └── index/
        ├── index.html       # 前台首页
        └── detail.html      # 详情页面

2.2 核心组件说明

bash 复制代码
| 文件 | 作用 |
|------|------|
| `Juhesms.php` | 插件主类,包含钩子监听器 |
| `config.php` | 定义插件配置项 |
| `info.ini` | 插件元数据 |
| `Index.php` | 处理前台请求 |
| `Service.php` | 封装 API 调用 |

开发步骤

3.1 创建插件元数据

文件:`addons/juhesms/info.ini`

bash 复制代码
name = juhesms
title = 聚合数据短信
intro = 使用聚合数据短信接口发送验证码和通知短信
author = Yujl
website = https://www.juhe.cn/
version = 1.0.0
state = 1
url = /addons/juhesms

3.2 配置插件参数

文件:`addons/juhesms/config.php`

php 复制代码
<?php

return [
    [
        'name' => 'appkey',
        'title' => 'APPKEY',
        'type' => 'string',
        'value' => '',
        'rule' => 'required',
        'tip' => '在聚合数据个人中心查看',
    ],
    [
        'name' => 'tpl_register',
        'title' => '注册验证码模板 ID',
        'type' => 'string',
        'value' => '',
        'rule' => 'required',
        'tip' => '在聚合数据个人中心查看',
    ],
    [
        'name' => 'tpl_login',
        'title' => '登录验证码模板 ID',
        'type' => 'string',
        'value' => '',
        'rule' => 'required',
        'tip' => '在聚合数据个人中心查看',
    ],
    [
        'name' => 'tpl_resetpwd',
        'title' => '重置密码模板 ID',
        'type' => 'string',
        'value' => '',
        'rule' => 'required',
        'tip' => '在聚合数据个人中心查看',
    ],
    [
        'name' => 'tpl_changemobile',
        'title' => '更换手机模板 ID',
        'type' => 'string',
        'value' => '',
        'rule' => 'required',
        'tip' => '在聚合数据个人中心查看',
    ],
];

3.3 开发插件主类

文件:`addons/juhesms/Juhesms.php`

php 复制代码
<?php

namespace addons\juhesms;

use addons\juhesms\library\Service as SmsService;
use think\Addons;

/**
 * 聚合数据短信插件
 */
class Juhesms extends Addons
{
    /**
     * 插件安装
     */
    public function install()
    {
        $this->clearCache();
        return true;
    }

    /**
     * 插件卸载
     */
    public function uninstall()
    {
        $this->clearCache();
        return true;
    }
    
    /**
     * 清除缓存
     */
    protected static function clearCache()
    {
        $cacheFiles = [
            RUNTIME_PATH . 'config.php',
            RUNTIME_PATH . 'tags.php',
        ];
        
        foreach ($cacheFiles as $file) {
            if (file_exists($file)) {
                @unlink($file);
            }
        }
        
        if (function_exists('opcache_reset')) {
            @opcache_reset();
        }
    }

    /**
     * 短信发送(核心钩子)
     * @param \app\common\model\Sms $sms
     * @return bool
     */
    public function smsSend($sms)
    {
        if (!$sms || !$sms->mobile) {
            return false;
        }

        $code = $sms->code;
        $mobile = $sms->mobile;
        $event = $sms->event;

        // 调用服务类发送短信
        $result = SmsService::sendCode($mobile, $code, $event);
        
        return $result;
    }

    /**
     * 短信通知
     * @param array $params
     * @return bool
     */
    public function smsNotice(&$params)
    {
        $mobile = $params['mobile'] ?? '';
        $msg = $params['msg'] ?? '';

        if (!$mobile) {
            return false;
        }

        return SmsService::sendNotice($mobile, $msg);
    }

    /**
     * 获取验证码事件
     */
    public function smsGet($sms)
    {
        // 可选:记录日志
    }

    /**
     * 校验验证码事件
     */
    public function smsCheck($sms)
    {
        return true;
    }

    /**
     * 清空验证码事件
     */
    public function smsFlush()
    {
        // 可选:记录日志
    }
}

3.4 封装短信服务类

文件:`addons/juhesms/library/Service.php`

php 复制代码
<?php

namespace addons\juhesms\library;

use fast\Http;

/**
 * 聚合数据短信服务类
 */
class Service
{
    /**
     * API 接口地址
     */
    const API_URL = 'http://v.juhe.cn/sms/send';

    /**
     * 发送短信
     * @param string $mobile 手机号
     * @param array $vars 模板变量
     * @param int $tplId 模板 ID
     * @param string $appKey APPKEY
     * @return bool
     */
    public static function send($mobile, $vars = [], $tplId = null, $appKey = null)
    {
        if (!$appKey) {
            $config = get_addon_config('juhesms');
            $appKey = $config['appkey'] ?? '';
        }

        if (!$tplId) {
            return false;
        }

        $params = [
            'key'    => $appKey,
            'mobile' => $mobile,
            'tpl_id' => $tplId,
        ];

        if (!empty($vars)) {
            $params['vars'] = json_encode($vars, JSON_UNESCAPED_UNICODE);
        }

        try {
            $result = self::request($params);
            if ($result && $result['error_code'] == 0) {
                return true;
            } else {
                \think\Log::record('聚合短信发送失败:' . json_encode($result));
                return false;
            }
        } catch (\Exception $e) {
            \think\Log::record('聚合短信发送异常:' . $e->getMessage());
            return false;
        }
    }

    /**
     * 发送验证码短信
     * @param string $mobile 手机号
     * @param string $code 验证码
     * @param string $event 事件类型
     * @return bool
     */
    public static function sendCode($mobile, $code, $event)
    {
        $config = get_addon_config('juhesms');
        
        // 根据事件获取模板 ID
        $tplMap = [
            'register'     => 'tpl_register',
            'login'        => 'tpl_login',
            'resetpwd'     => 'tpl_resetpwd',
            'changemobile' => 'tpl_changemobile',
        ];

        $tplId = $config[$tplMap[$event] ?? ''] ?? null;

        return self::send($mobile, ['code' => $code], $tplId);
    }

    /**
     * 发送通知短信
     * @param string $mobile 手机号
     * @param string $msg 消息内容
     * @return bool
     */
    public static function sendNotice($mobile, $msg)
    {
        $config = get_addon_config('juhesms');
        $tplId = $config['tpl_notice'] ?? null;

        return self::send($mobile, ['msg' => $msg], $tplId);
    }

    /**
     * 发送 HTTP 请求
     */
    protected static function request($params)
    {
        $result = Http::sendRequest(self::API_URL, $params);
        
        if ($result['ret']) {
            return json_decode($result['msg'], true);
        }
        
        return false;
    }
}

3.5 开发前台控制器

**文件**:`addons/juhesms/controller/Index.php`

php 复制代码
<?php

namespace addons\juhesms\controller;

use think\addons\Controller;

/**
 * 前台控制器
 */
class Index extends Controller
{
    /**
     * 插件首页
     */
    public function index()
    {
        $config = get_addon_config('juhesms');
        $this->view->assign('config', $config);
        return $this->view->fetch();
    }

    /**
     * 详情页(渲染 README)
     */
    public function detail()
    {
        $readmeFile = __DIR__ . '/../README.md';
        
        if (file_exists($readmeFile)) {
            $content = file_get_contents($readmeFile);
            $content = $this->parseMarkdown($content);
        } else {
            $content = '<p>暂无详细说明</p>';
        }
        
        $this->view->assign('content', $content);
        return $this->view->fetch();
    }

    /**
     * 测试短信发送
     */
    public function test()
    {
        if (!$this->request->isPost()) {
            return json(['code' => 0, 'msg' => '请求方法错误']);
        }

        if (!\app\admin\library\Auth::instance()->id) {
            return json(['code' => 0, 'msg' => '请先登录']);
        }

        $mobile = $this->request->post('mobile', '', 'trim');
        
        if (!$mobile || !preg_match('/^1\d{10}$/', $mobile)) {
            return json(['code' => 0, 'msg' => '请输入正确的手机号']);
        }

        $code = \fast\Random::numeric(4);
        $result = \app\common\library\Sms::send($mobile, $code, 'login');
        
        if ($result) {
            return json(['code' => 1, 'msg' => '发送成功,验证码:' . $code]);
        } else {
            return json(['code' => 0, 'msg' => '发送失败']);
        }
    }

    /**
     * 诊断工具
     */
    public function diagnose()
    {
        $result = ['checks' => [], 'success' => 0, 'failed' => 0];

        // 清除缓存
        $cacheFiles = [RUNTIME_PATH . 'config.php', RUNTIME_PATH . 'tags.php'];
        foreach ($cacheFiles as $file) {
            if (file_exists($file) && @unlink($file)) {
                $result['checks'][] = ['item' => '清除缓存', 'status' => 'success'];
                $result['success']++;
            }
        }

        // 检查插件文件
        $requiredFiles = ['info.ini', 'Juhesms.php', 'config.php'];
        foreach ($requiredFiles as $file) {
            if (file_exists(__DIR__ . '/../' . $file)) {
                $result['checks'][] = ['item' => '文件检查', 'status' => 'success'];
                $result['success']++;
            } else {
                $result['checks'][] = ['item' => '文件检查', 'status' => 'failed'];
                $result['failed']++;
            }
        }

        return json([
            'code' => 1,
            'msg' => '诊断完成',
            'data' => $result
        ]);
    }

    /**
     * Markdown 解析
     */
    protected function parseMarkdown($markdown)
    {
        $markdown = preg_replace('/^### (.*+)$/m', '<h3>$1</h3>', $markdown);
        $markdown = preg_replace('/^## (.*+)$/m', '<h2>$1</h2>', $markdown);
        $markdown = preg_replace('/^# (.*+)$/m', '<h1>$1</h1>', $markdown);
        $markdown = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $markdown);
        $markdown = preg_replace('/`([^`]+)`/', '<code>$1</code>', $markdown);
        return $markdown;
    }
}

3.6 配置系统钩子和路由

文件:`application/extra/addons.php`

php 复制代码
<?php

return [
    'autoload' => false,
    
    // 钩子配置
    'hooks' => [
        'sms_send' => [
            'juhesms',
        ],
        'sms_notice' => [
            'juhesms',
        ],
        'sms_get' => [
            'juhesms',
        ],
        'sms_check' => [
            'juhesms',
        ],
        'sms_flush' => [
            'juhesms',
        ],
    ],
];

测试验证

4.1 访问插件首页

bash 复制代码
http://你的域名/juhesms

4.2 测试短信发送

  1. 输入测试手机号

  2. 点击"发送测试短信"

  3. 查看是否收到短信

核心原理

5.1 钩子工作机制

bash 复制代码
用户触发短信发送
    ↓
系统调用 Sms::send()
    ↓
触发 sms_send 钩子
    ↓
框架查找钩子注册表
    ↓
调用 Juhesms::smsSend()
    ↓
调用 Service::sendCode()
    ↓
请求聚合数据 API
    ↓
返回发送结果

5.2 零侵入集成

插件通过钩子系统自动接管短信发送,**无需修改任何业务代码**

php 复制代码
// 原有业务代码(无需修改)
Sms::send($mobile, $code, 'register');

// 系统内部会自动调用插件的 smsSend() 方法

常见问题

6.1 短信发送失败

**可能原因**

  1. APPKEY 不正确

  2. 模板 ID 错误

  3. 模板未审核

  4. 余额不足

**解决方案**

  • 检查配置是否正确

  • 查看 `runtime/log/` 日志文件

  • 登录聚合数据后台查看状态

6.2 钩子未注册

**症状**:提示"请在后台插件管理安装短信验证插件"

**解决方案**

  1. 检查 `addons.php` 配置

2.配置系统钩子和路由

  1. 后台重新启用插件

  2. 清除缓存

总结

7.1 技术要点

  1. **插件架构**:遵循 FastAdmin 插件开发规范

  2. **钩子系统**:利用钩子实现零侵入集成

  3. **服务封装**:独立 Service 类封装 API 调用

  4. **路由配置**:通过 addons.php 统一管理

7.2 开发建议

  1. **代码规范**:遵循 PSR 标准,使用命名空间

  2. **错误处理**:记录详细日志便于排查

  3. **安全防护**:防轰炸、防刷机制必不可少

  4. **文档完善**:提供详细的使用说明和示例

相关推荐
Ether IC Verifier2 小时前
SystemVerilog 数据类型详解
php·systemverilog·uvm·ic验证
弥树子2 小时前
踩坑记录:服务器内网调用接口,真实请求URL与官方公开URL不一致问题排查
开发语言·php
AugustRed4 小时前
Linux 运维常用命令大全(超全速查表)
运维·网络·php
剑神一笑10 小时前
Linux lsof 命令深度解析:从文件描述符到进程追踪
linux·运维·php
BingoGo11 小时前
免费可商用 PHP 管理后台 CatchAdmin V5.3.1 发布 后台打包直降 5s 内
后端·php
JaguarJack11 小时前
免费可商用 PHP 管理后台 CatchAdmin V5.3.1 发布 后台打包直降 5s 内
后端·php·laravel
ELI_He99912 小时前
Laravel Sail
php·laravel
傻啦嘿哟14 小时前
解决DNS污染:防止OpenClaw解析API域名到虚假地址
开发语言·php
dualven_in_csdn15 小时前
cmd切换到powershell (一)
服务器·开发语言·php
Cheng小攸15 小时前
实验九:防火墙安全认证和审计实验
开发语言·安全·php