论坛 / 技术交流 / Typecho / 正文

Typecho 1.3 打赏功能开发教程:从零到一实现博客变现

引言

在内容创作日益普及的今天,打赏功能已成为许多博主与读者互动、获得经济支持的重要方式。Typecho作为一款轻量级、高性能的开源博客系统,虽然功能简洁,但通过插件扩展可以轻松实现打赏功能。本文将深入探讨如何在Typecho 1.3版本中开发一个完整的打赏功能模块,涵盖从需求分析、代码实现到界面优化的全过程。

打赏功能的实现不仅能够为博主带来额外收入,还能增强读者与博主之间的互动关系。相比其他博客系统,Typecho的插件开发相对简单,但需要一定的PHP和前端知识。本教程将带领你一步步构建一个功能完善、界面美观的打赏插件。

打赏功能需求分析与设计

功能需求分析

在开始编码之前,我们需要明确打赏功能的核心需求:

  1. 支付方式支持:至少支持微信支付和支付宝两种主流支付方式
  2. 金额设置:允许用户自定义打赏金额或选择预设金额
  3. 打赏记录:后台可查看所有打赏记录,包括时间、金额、支付方式等信息
  4. 界面展示:在前端文章页面合适位置展示打赏按钮和二维码
  5. 通知功能:打赏成功后向博主发送通知(邮件或站内信)
  6. 统计功能:统计总打赏金额、次数等数据

技术架构设计

基于Typecho 1.3的插件架构,我们设计以下技术方案:

  • 插件结构:遵循Typecho插件标准目录结构
  • 数据库设计:创建独立的数据表存储打赏记录
  • 支付接口:集成第三方支付SDK(如PayJS、Ping++等)
  • 前端实现:使用HTML/CSS/JavaScript创建响应式打赏界面
  • 安全性考虑:实现支付回调验证、防刷单机制

开发环境准备

环境要求

在开始开发前,请确保你的环境满足以下要求:

  • Typecho 1.3 或更高版本
  • PHP 7.0+(建议7.4以上)
  • MySQL 5.6+ 或 MariaDB 10.0+
  • 支持URL重写(mod_rewrite)
  • 一个可用的第三方支付账户(用于测试)

插件目录结构

创建插件目录结构如下:

Reward/
├── Plugin.php          # 插件主文件
├── Reward/
│   ├── Db/            # 数据库操作类
│   ├── Helper/        # 辅助函数
│   ├── Model/         # 数据模型
│   └── View/          # 视图文件
├── assets/            # 静态资源
│   ├── css/
│   ├── js/
│   └── images/
├── install.sql        # 安装SQL
└── uninstall.sql      # 卸载SQL

数据库设计与实现

创建数据表

我们需要创建一个数据表来存储打赏记录。在install.sql文件中添加以下SQL:

CREATE TABLE `typecho_reward_records` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `post_id` int(10) unsigned NOT NULL COMMENT '文章ID',
  `amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
  `payment_method` varchar(20) NOT NULL COMMENT '支付方式',
  `transaction_id` varchar(100) DEFAULT NULL COMMENT '交易ID',
  `payer` varchar(100) DEFAULT NULL COMMENT '支付者信息',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '支付状态',
  `created_at` int(10) unsigned NOT NULL COMMENT '创建时间',
  `updated_at` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_post_id` (`post_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打赏记录表';

数据模型类

创建Reward/Model/Record.php文件,定义数据模型:

<?php
class Reward_Model_Record extends Typecho_Db
{
    // 表名
    const TABLE_NAME = 'typecho_reward_records';
    
    // 支付状态常量
    const STATUS_PENDING = 0;    // 待支付
    const STATUS_SUCCESS = 1;    // 支付成功
    const STATUS_FAILED = 2;     // 支付失败
    
    /**
     * 添加打赏记录
     */
    public static function addRecord($data)
    {
        $db = Typecho_Db::get();
        $data['created_at'] = time();
        return $db->query($db->insert(self::TABLE_NAME)->rows($data));
    }
    
    /**
     * 更新打赏记录状态
     */
    public static function updateStatus($transactionId, $status, $payer = null)
    {
        $db = Typecho_Db::get();
        $updateData = [
            'status' => $status,
            'updated_at' => time()
        ];
        
        if ($payer) {
            $updateData['payer'] = $payer;
        }
        
        return $db->query($db->update(self::TABLE_NAME)
            ->rows($updateData)
            ->where('transaction_id = ?', $transactionId));
    }
    
    /**
     * 获取文章打赏统计
     */
    public static function getPostStats($postId)
    {
        $db = Typecho_Db::get();
        return $db->fetchRow($db->select('COUNT(*) as count', 'SUM(amount) as total')
            ->from(self::TABLE_NAME)
            ->where('post_id = ?', $postId)
            ->where('status = ?', self::STATUS_SUCCESS));
    }
}
?>

插件主文件开发

插件基本信息

Plugin.php文件中定义插件基本信息:

<?php
/**
 * Typecho打赏插件
 * 
 * @package Reward
 * @author YourName
 * @version 1.0.0
 * @link https://yourwebsite.com
 */
class Reward_Plugin implements Typecho_Plugin_Interface
{
    /**
     * 激活插件
     */
    public static function activate()
    {
        // 创建数据库表
        $db = Typecho_Db::get();
        $prefix = $db->getPrefix();
        
        try {
            $sql = file_get_contents(dirname(__FILE__) . '/install.sql');
            $sql = str_replace('typecho_', $prefix, $sql);
            $db->query($sql);
        } catch (Typecho_Db_Exception $e) {
            throw new Typecho_Plugin_Exception('安装失败:' . $e->getMessage());
        }
        
        // 注册文章输出钩子
        Typecho_Plugin::factory('Widget_Archive')->footer = array('Reward_Plugin', 'renderRewardBox');
        
        // 注册后台管理页面
        Typecho_Plugin::factory('admin/menu.php')->navBar = array('Reward_Plugin', 'renderAdminMenu');
        
        return '插件激活成功,请配置支付参数';
    }
    
    /**
     * 禁用插件
     */
    public static function deactivate()
    {
        // 可选:禁用时删除数据表
        // $db = Typecho_Db::get();
        // $db->query("DROP TABLE IF EXISTS `{$db->getPrefix()}reward_records`");
        
        return '插件已禁用';
    }
    
    /**
     * 插件配置面板
     */
    public static function config(Typecho_Widget_Helper_Form $form)
    {
        // 支付配置
        $payTitle = new Typecho_Widget_Helper_Form_Element_Text('wechat_appid', 
            null, null, _t('微信AppID'), _t('微信支付的AppID'));
        $form->addInput($payTitle);
        
        $alipayAppid = new Typecho_Widget_Helper_Form_Element_Text('alipay_appid',
            null, null, _t('支付宝AppID'), _t('支付宝的AppID'));
        $form->addInput($alipayAppid);
        
        // 金额设置
        $amounts = new Typecho_Widget_Helper_Form_Element_Text('reward_amounts',
            null, '2,5,10,20,50', _t('打赏金额选项'), _t('用逗号分隔的金额列表,单位:元'));
        $form->addInput($amounts);
        
        // 自定义金额
        $customAmount = new Typecho_Widget_Helper_Form_Element_Radio('allow_custom_amount',
            array('1' => _t('允许'), '0' => _t('禁止')), '1', _t('允许自定义金额'));
        $form->addInput($customAmount);
        
        // 显示位置
        $position = new Typecho_Widget_Helper_Form_Element_Select('display_position',
            array(
                'after_content' => _t('文章内容之后'),
                'before_content' => _t('文章内容之前'),
                'sidebar' => _t('侧边栏'),
                'floating' => _t('浮动按钮')
            ), 'after_content', _t('打赏框显示位置'));
        $form->addInput($position);
    }
    
    /**
     * 个人用户配置面板
     */
    public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
    
    /**
     * 在文章底部渲染打赏框
     */
    public static function renderRewardBox()
    {
        // 只在前台文章页面显示
        if (!Typecho_Widget::widget('Widget_Archive')->is('single')) {
            return;
        }
        
        // 获取配置
        $options = Helper::options();
        $pluginOptions = $options->plugin('Reward');
        
        // 加载CSS和JS
        $cssUrl = Helper::options()->pluginUrl . '/Reward/assets/css/reward.css';
        $jsUrl = Helper::options()->pluginUrl . '/Reward/assets/js/reward.js';
        
        echo '<link rel="stylesheet" href="' . $cssUrl . '">';
        
        // 渲染打赏HTML
        include dirname(__FILE__) . '/Reward/View/reward-box.php';
        
        echo '<script src="' . $jsUrl . '"></script>';
    }
}
?>

前端界面实现

CSS样式设计

创建assets/css/reward.css文件:

/* 打赏容器样式 */
.reward-container {
    margin: 40px 0;
    padding: 20px;
    border-radius: 8px;
    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
    text-align: center;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.reward-title {
    font-size: 1.5em;
    color: #333;
    margin-bottom: 15px;
    font-weight: bold;
}

.reward-desc {
    color: #666;
    margin-bottom: 20px;
    line-height: 1.6;
}

/* 金额选择按钮 */
.amount-buttons {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 20px;
}

.amount-btn {
    padding: 10px 20px;
    border: 2px solid #4CAF50;
    border-radius: 25px;
    background: white;
    color: #4CAF50;
    cursor: pointer;
    transition: all 0.3s ease;
    font-weight: bold;
}

.amount-btn:hover,
.amount-btn.active {
    background: #4CAF50;
    color: white;
}

/* 自定义金额输入 */
.custom-amount {
    margin: 15px 0;
}

.custom-amount input {
    padding: 10px;
    border: 2px solid #ddd;
    border-radius: 4px;
    width: 150px;
    text-align: center;
    font-size: 16px;
}

/* 支付方式选择 */
.payment-methods {
    display: flex;
    justify-content: center;
    gap: 20px;
    margin: 20px 0;
}

.payment-method {
    padding: 10px 20px;
    border: 2px solid #ddd;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.payment-method.active {
    border-color: #2196F3;
    background: #e3f2fd;
}

.payment-icon {
    width: 40px;
    height: 40px;
    margin-bottom: 5px;
}

/* 打赏按钮 */
.reward-btn {
    padding: 12px 40px;
    background: linear-gradient(135deg, #FF9800 0%, #FF5722 100%);
    color: white;
    border: none;
    border-radius: 25px;
    font-size: 18px;
    cursor: pointer;
    transition: transform 0.3s ease;
}

.reward-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 12px rgba(255, 87, 34, 0.3);
}

/* 二维码弹窗 */
.qrcode-modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    z-index: 9999;
    align-items: center;
    justify-content: center;
}

.qrcode-content {
    background: white;
    padding: 30px;
    border-radius: 10px;
    text-align: center;
    max-width: 400px;
    width: 90%;
}

.qrcode-img {
    width: 200px;
    height: 200px;
    margin: 20px auto;
}

.close-modal {
    position: absolute;
    top: 10px;
    right: 10px;
    font-size: 24px;
    cursor: pointer;
    color: #666;
}

JavaScript交互逻辑

创建assets/js/reward.js文件:

(function() {
    // 初始化打赏功能
    document.addEventListener('DOMContentLoaded', function() {
        initRewardSystem();
    });
    
    function initRewardSystem() {
        // 金额按钮点击事件
        document.querySelectorAll('.amount-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                document.querySelectorAll('.amount-btn').forEach(b => b.classList.remove('active'));
                this.classList.add('active');
                document.getElementById('reward-amount').value = this.dataset.amount;
            });
        });
        
        // 支付方式选择
        document.querySelectorAll('.payment-method').forEach(method => {
            method.addEventListener('click', function() {
                document.querySelectorAll('.payment-method').forEach(m => m.classList.remove('active'));
                this.classList.add('active');
            });
        });
        
        // 打赏按钮点击
        const rewardBtn = document.getElementById('reward-action');
        if (rewardBtn) {
            rewardBtn.addEventListener('click', function() {
                const amount = document.getElementById('reward-amount').value;
                const paymentMethod = document.querySelector('.payment-method.active');
                
                if (!amount || amount <= 0) {
                    alert('请选择或输入打赏金额');
                    return;
                }
                
                if (!paymentMethod) {
                    alert('请选择支付方式');
                    return;
                }
                
                // 显示二维码弹窗
                showQRCode(amount, paymentMethod.dataset.method);
            });
        }
        
        // 关闭弹窗
        const closeBtn = document.querySelector('.close-modal');
        if (closeBtn) {
            closeBtn.addEventListener('click', hideQRCode);
        }
        
        // 点击弹窗背景关闭
        const modal = document.getElementById('qrcode-modal');
        if (modal) {
            modal.addEventListener('click', function(e) {
                if (e.target === this) {
                    hideQRCode();
                }
            });
        }
    }
    
    function showQRCode(amount, method) {
        const modal = document.getElementById('qrcode-modal');
        const qrcodeImg = document.getElementById('qrcode-image');
        
        // 这里应该通过AJAX请求获取二维码
        // 为了简化示例,我们使用静态图片
        qrcodeImg.src = method === 'wechat' 
            ? 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=wechatpay://' + amount
            : 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=alipay://' + amount;
        
        document.getElementById('qrcode-amount').textContent = amount + '元';
        document.getElementById('qrcode-method').textContent = method === 'wechat' ? '微信支付' : '支付宝';
        
        modal.style.display = 'flex';
        
        // 模拟支付成功(实际开发中应该使用WebSocket或轮询检查支付状态)
        setTimeout(checkPaymentStatus, 3000);
    }
    
    function hideQRCode() {
        document.getElementById('qrcode-modal').style.display = 'none';
    }
    
    function checkPaymentStatus() {
        // 这里应该通过AJAX检查支付状态
        // 如果支付成功,显示成功提示并关闭弹窗
        const success = Math.random() > 0.5; // 模拟随机结果
        
        if (success) {
            alert('支付成功!感谢您的支持!');
            hideQRCode();
            
            // 更新打赏统计
            updateRewardStats();
        }
    }
    
    function updateRewardStats() {
        // 更新页面上的打赏统计信息
        const statsElement = document.getElementById('reward-stats');
        if (statsElement) {
            // 这里应该通过AJAX获取最新统计
            // 暂时使用模拟数据
            const currentCount = parseInt(statsElement.dataset.count) || 0;
            const currentTotal = parseFloat(statsElement.dataset.total) || 0;
            const amount = parseFloat(document.getElementById('reward-amount').value);
            
            statsElement.dataset.count = currentCount + 1;
            statsElement.dataset.total = currentTotal + amount;
            
            statsElement.innerHTML = `已有 ${currentCount + 1} 人打赏,累计 ${(currentTotal + amount).toFixed(2)} 元`;
        }
    }
})();

支付接口集成

微信支付集成示例

创建Reward/Helper/WechatPay.php文件:

<?php
class Reward_Helper_WechatPay
{
    private $appId;
    private $mchId;
    private $apiKey;
    
    public function __construct($appId, $mchId, $apiKey)
    {
        $this->appId = $appId;
        $this->mchId = $mchId;
        $this->apiKey = $apiKey;
    }
    
    /**
     * 生成支付二维码
     */
    public function createQRCode($amount, $description, $orderNo)
    {
        // 这里应该调用微信支付API
        // 由于微信支付需要商户资质,这里仅展示流程
        
        $params = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => $this->generateNonceStr(),
            'body' => $description,
            'out_trade_no' => $orderNo,
            'total_fee' => $amount * 100, // 微信支付单位为分
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'notify_url' => $this->getNotifyUrl(),
            'trade_type' => 'NATIVE',
            'product_id' => $orderNo
        ];
        
        $params['sign'] = $this->generateSign($params);
        
        // 调用统一下单API
        $result = $this->postXmlCurl($this->arrayToXml($params), 
            'https://api.mch.weixin.qq.com/pay/unifiedorder');
        
        $resultArray = $this->xmlToArray($result);
        
        if ($resultArray['return_code'] == 'SUCCESS' && 
            $resultArray['result_code'] == 'SUCCESS') {
            return $resultArray['code_url']; // 二维码链接
        }
        
        return false;
    }
    
    /**
     * 验证支付回调
     */
    public function verifyNotify($xml)
    {
        $data = $this->xmlToArray($xml);
        
        if (!isset($data['sign'])) {
            return false;
        }
        
        $sign = $data['sign'];
        unset($data['sign']);
        
        return $this->generateSign($data) == $sign;
    }
    
    // 其他辅助方法...
}
?>

后台管理界面

打赏记录管理

创建后台管理页面,显示打赏记录:

<?php
// 在Plugin.php中添加管理页面
public static function renderAdminMenu($nav)
{
    $nav['reward'] = array(
        'text' => '打赏管理',
        'url' => Typecho_Common::url('/extending.php?panel=Reward%2Fadmin%2Frecords.php', 
            Helper::options()->adminUrl),
        'icon' => 'heart'
    );
    return $nav;
}
?>

创建Reward/admin/records.php文件:

<?php
if (!isset($_GET['panel']) || !Typecho_Widget::widget('Widget_User')->hasLogin()) {
    exit;
}

require_once 'header.php';

// 获取打赏记录
$db = Typecho_Db::get();
$records = $db->fetchAll($db->select()
    ->from('table.reward_records')
    ->order('created_at', Typecho_Db::SORT_DESC)
    ->page(1, 20));

// 统计信息
$stats = $db->fetchRow($db->select('COUNT(*) as count', 'SUM(amount) as total')
    ->from('table.reward_records')
    ->where('status = ?', 1));
?>

<div class="typecho-page-title">
    <h2>打赏记录管理</h2>
</div>

<div class="typecho-page-main">
    <div class="typecho-table-wrap">
        <table class="typecho-table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>文章标题</th>
                    <th>金额</th>
                    <th>支付方式</th>
                    <th>支付者</th>
                    <th>状态</th>
                    <th>时间</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($records as $record): ?>
                <tr>
                    <td><?php echo $record['id']; ?></td>
                    <td>
                        <?php 
                        $post = $db->fetchRow($db->select('title')
                            ->from('table.contents')
                            ->where('cid = ?', $record['post_id']));
                        echo $post ? $post['title'] : '文章已删除';
                        ?>
                    </td>
                    <td><?php echo $record['amount']; ?>元</td>
                    <td><?php echo $record['payment_method']; ?></td>
                    <td><?php echo $record['payer'] ?: '匿名'; ?></td>
                    <td>
                        <?php 
                        $statusText = [
                            0 => '<span style="color:orange">待支付</span>',
                            1 => '<span style="color:green">成功</span>',
                            2 => '<span style="color:red">失败</span>'
                        ];
                        echo $statusText[$record['status']];
                        ?>
                    </td>
                    <td><?php echo date('Y-m-d H:i:s', $record['created_at']); ?></td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    
    <div class="typecho-table-info">
        <p>总计:<?php echo $stats['count']; ?> 笔打赏,共 <?php echo $stats['total'] ?: '0'; ?> 元</p>
    </div>
</div>

<?php require_once 'footer.php'; ?>

插件优化与安全考虑

性能优化建议

  1. 缓存机制:对打赏统计数据进行缓存,减少数据库查询
  2. 异步处理:支付回调使用队列异步处理,提高响应速度
  3. CDN加速:静态资源使用CDN加速
  4. 数据库索引:为常用查询字段添加索引

安全措施

  1. 输入验证:对所有用户输入进行严格验证和过滤
  2. SQL防注入:使用Typecho的查询构建器,避免SQL注入
  3. XSS防护:输出时使用htmlspecialchars转义
  4. CSRF防护:重要操作添加CSRF令牌验证
  5. 支付安全:验证支付回调签名,防止伪造请求
  6. 频率限制:限制同一IP的打赏频率,防止刷单

兼容性考虑

  1. 响应式设计:确保打赏界面在各种设备上正常显示
  2. 浏览器兼容:支持主流浏览器(Chrome、Firefox、Safari、Edge)
  3. Typecho版本:确保插件兼容Typecho 1.3及以上版本
  4. PHP版本:支持PHP 7.0及以上版本

测试与部署

测试要点

  1. 功能测试:测试所有打赏流程,包括支付成功、失败、取消等场景
  2. 兼容性测试:在不同浏览器和设备上测试界面显示
  3. 性能测试:测试高并发下的支付处理能力
  4. 安全测试:进行常见的安全漏洞测试

部署步骤

  1. 将插件文件夹上传到Typecho的usr/plugins/目录
  2. 在Typecho后台激活插件
  3. 配置支付参数(AppID、密钥等)
  4. 配置打赏金额选项和显示位置
  5. 测试打赏功能是否正常工作

总结

通过本教程,我们完整地开发了一个Typecho 1.3的打赏功能插件。从需求分析、数据库设计、代码实现到界面优化,我们涵盖了插件开发的各个环节。这个插件具有以下特点:

  1. 功能完善:支持微信支付和支付宝,包含完整的打赏流程
  2. 用户体验良好:美观的界面设计,流畅的交互体验
  3. 易于管理:后台可查看打赏记录和统计信息
  4. **

全部回复 (0)

暂无评论