国产+高潮+在线,国产 av 仑乱内谢,www国产亚洲精品久久,51国产偷自视频区视频,成人午夜精品网站在线观看

添加功能模塊

img.png

接下來我們將介紹如何添加如圖所示的邀請功能模塊。

邀請功能介紹及原理

  • 每個(gè)注冊用戶有一個(gè)邀請鏈接,鏈接中包含了邀請人的user_id,例如 https://bla.cn/ai/101
  • 受邀者訪問這個(gè)鏈接時(shí)系統(tǒng)會記錄一個(gè)cookie ai_inviter=邀請人user_id
  • 受邀者注冊時(shí)讀取這個(gè)cookie,將邀請人和被邀請人記錄到 ai_invite 表
  • 被邀請人購買會員時(shí)邀請人獲得獎勵,獎勵記錄到 ai_invite_rewards 表

注意
為了避免和其插件表名沖突,AI系統(tǒng)相關(guān)的表格一律以ai_開頭

創(chuàng)建項(xiàng)目

 php webman app-plugin:create ai_invite

建表

module-invite.png

img.png

img.png

一鍵菜單生成后臺

給新建的兩個(gè)表分別生成菜單

ai_inviteb表

img.png

img.png
控制器 /plugin/ai_invite/app/admin/controller/AiInviteController.php
模型 /plugin/ai_invite/app/model/AiInvite.php

ai_invite_rewards表

img.png

img.png
控制器 /plugin/ai_invite/app/admin/controller/AiInviteRewardController.php
模型 /plugin/ai_invite/app/model/AiInviteReward.php

生成結(jié)果

刷新后能看到生成的菜單如下
img.png

以上操作會生成兩個(gè)模型AiInvite AiInviteReward,開發(fā)者可以在后續(xù)代碼中直接使用。

邀請鏈接設(shè)計(jì)及實(shí)現(xiàn)

邀請鏈接類似 http://bla.cn/ai/101 ,其中101是邀請人的用戶ID,進(jìn)入這個(gè)鏈接后實(shí)際上就是顯示AI的主頁(用戶不會感到任何區(qū)別),但是會記錄一個(gè)cookie ai_inviter,值為101。
為了不入侵現(xiàn)有AI系統(tǒng)代碼,我們需要使用中間件來做這個(gè)事情。

新建中間件 plugin/ai_invite/app/middleware/Invite.php 內(nèi)容如下

<?php
namespace plugin\ai_invite\app\middleware;

use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;

class Invite implements MiddlewareInterface
{
    public function process(Request $request, callable $handler) : Response
    {
        $response = $handler($request);
        if ($request->route && $aiInviter = $request->route->param('ai_inviter')) {
            if (!$request->cookie('ai_inviter')) {
                $response->cookie('ai_inviter', $aiInviter, 3600 * 24 * 365, '/');
            }
        }
        return $response;
    }
}

打開 plugin/ai_invite/config/route.php 添加以下路由

<?php

use plugin\ai_invite\app\middleware\Invite;
use plugin\ai\app\controller\IndexController;
use Webman\Route;

Route::any('/ai/{ai_inviter:\d+}', [IndexController::class, 'index'])->middleware(Invite::class);

這樣當(dāng)訪問 http://bla.cn/ai/101 時(shí)會記錄一個(gè)cookie ai_inviter,值為101。

通過事件系統(tǒng)實(shí)現(xiàn)相關(guān)功能

系統(tǒng)有新用戶注冊時(shí)會觸發(fā)一個(gè)user.register事件,用戶購買會員時(shí)會觸發(fā)一個(gè)ai.payment.success事件,左側(cè)圖標(biāo)菜單渲染事件為ai.menu.list,我們可以通過監(jiān)聽這些事件來實(shí)現(xiàn)對應(yīng)的功能,并且不會入侵現(xiàn)有AI系統(tǒng)源碼。
如果你想要了解系統(tǒng)的其它事件請參考 事件列表

創(chuàng)建 plugin/ai_invite/config/event.php 內(nèi)容如下

<?php

use plugin\ai\app\event\data\EventData;
use plugin\ai\app\model\AiModel;
use plugin\ai_invite\app\model\AiInvite;
use plugin\ai_invite\app\model\AiInviteReward;
use plugin\ai_invite\app\service\Invite;
use plugin\user\app\model\User;
use plugin\ai\api\User as ApiUser;

return [
    // 用戶注冊時(shí)觸發(fā)
    'user.register' => [
        function (User $user) {
            $request = request();
            if (!$request) {
                return;
            }
            $inviter = $request->cookie('ai_inviter');
            if ($inviter && is_numeric($inviter) && User::find($inviter)) {
                $aiInvite = new AiInvite();
                $aiInvite->inviter = $inviter;
                $aiInvite->invitee = $user->id;
                $aiInvite->percent = Invite::getSetting()['percent'] ?? 10;
                $aiInvite->save();
            }
        }
    ],
    // 在ai系統(tǒng)支付時(shí)觸發(fā)
    'ai.payment.success' => [
        function ($paymentData) {
            $userId = $paymentData->userId;
            $data = $paymentData->data;
            $aiInvite = AiInvite::where('invitee', $userId)->first();
            if (!$aiInvite) {
                return;
            }
            $percent = 0.1;
            $inviter = $aiInvite->inviter;
            $reward = new AiInviteReward;
            $reward->inviter = $inviter;
            $reward->invitee = $userId;
            $reward->percent = $percent;
            $reward->data = json_encode($data);
            $reward->amount = round($data['price'] * $percent, 2);
            $reward->type = 'recharge';
            $reward->save();
            // 自動給邀請人增加余額
            $modelTypes = AiModel::pluck('type')->toArray();
            $data['days'] = isset($data['months']) ? $data['months'] * 30 : $data['days'];
            $data['days'] = ceil($data['days'] * $percent);
            unset($data['months']);
            foreach ($data as $key => $value) {
                if (in_array($key, $modelTypes)) {
                    $data[$key] = ceil($value * $percent);
                }
            }
            ApiUser::addBalanceByPlanData($inviter, $data);
        }
    ],
    // 渲染左側(cè)圖標(biāo)菜單時(shí)觸發(fā)
    'ai.menu.list' => [
        function (EventData $object) {
            $data = $object->data;
            $data['invite'] = [
                'enabled' => true, // 是否啟用
                'title' => '邀請好友', // 標(biāo)題
                'icon' => [
                    'light' => '<i class="bi bi-share"></i>', // 明亮主題下的圖標(biāo)
                    'dark' => '<i class="bi bi-share"></i>',  // 暗黑主題下的圖標(biāo)
                    'active' => '<i class="bi bi-share-fill"></i>' // 被選中后的圖標(biāo)
                ],
                'url' => '/app/ai_invite', // iframe url 地址
                'mobile' => true, // 是否在移動端顯示圖標(biāo)菜單
            ];
            $object->data = $data;
        }
    ]
];

開發(fā) /app/ai_invite 頁面

plugin/ai_invite/app/controller/IndexController.php 控制器內(nèi)容如下

<?php

namespace plugin\ai_invite\app\controller;

use plugin\ai_invite\app\model\AiInvite;
use plugin\ai_invite\app\model\AiInviteReward;
use plugin\ai_invite\app\service\Invite;
use plugin\user\api\User;
use support\Request;

class IndexController
{
    // 首頁
    public function index(Request $request)
    {
        if (!session('user')) {
            return redirect('/app/ai/user/login?redirect=' . urlencode($request->uri()));
        }
        $userId = session('user.uid') ?? session('user.id');
        $totalAmount = AiInviteReward::where('inviter', $userId)->sum('amount');
        $totalRewords = AiInviteReward::where('inviter', $userId)->count();
        $totalInvitees = AiInvite::where('inviter', $userId)->count('invitee');
        return view('index/index', [
            'totalAmount' => $totalAmount,
            'totalRewords' => $totalRewords,
            'totalInvitees' => $totalInvitees,
        ]);
    }

    // 加載邀請獎勵數(shù)據(jù)
    public function invitees()
    {
        $userId = session('user.uid') ?? session('user.id');
        $items = AiInviteReward::where('inviter', $userId)->orderBy('id')->get();
        $userIdArray = array_unique($items->pluck('invitee')->toArray());
        $users = User::whereIn('id', $userIdArray)->get()->keyBy('id');
        $data = [];
        $typeMap = [
            'recharge' => '充值獎勵',
            'register' => '注冊獎勵',
        ];
        foreach ($items as $item) {
            $user = $users[$item->invitee];
            $data[] = [
                'id' => $user->id,
                'nickname' => $user->nickname,
                'avatar' => $user->avatar,
                'amount' => $item->amount,
                'created_at' => $item->created_at,
                'type' => $typeMap[$item->type],
            ];
        }
        return json([
            'code' => 0,
            'msg' => 'ok',
            'data' => $data,
        ]);
    }

}

plugin/ai_invite/app/view/index/index.html 頁面內(nèi)容如下

<!doctype html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
    <link rel="shortcut icon" href="/favicon.ico" />
    <link href="/app/ai/css/bootstrap.min.css?v=5.3" rel="stylesheet">
    <link href="/app/ai/css/app.css?v=<?=$css_version??1?>" rel="stylesheet">
    <script src="/app/ai/js/jquery.min.js"></script>
    <script src="/app/ai/js/bootstrap.bundle.min.js?v=5.3"></script>
    <title>webman AI - 邀請</title>
</head>

<body data-bs-theme="light">

<div class="header">邀請好友</div>
<div class="container-fluid p-4 overflow-scroll" style="height: calc(100% - 45px)">
    <div class="row">
        <div class="col-12 pt-2" id="app">
            <div class="rounded white-bg py-4 px-3">
                <div class="d-flex justify-content-center">
                    <div class="d-flex justify-content-around f16" style="max-width:400px; min-width: 380px;">
                        <div class="d-flex flex-column justify-content-center align-items-center">
                            <div class="text-secondary mb-2">總收益</div>
                            <div>¥<?=$totalAmount?></div>
                        </div>
                        <div class="d-flex flex-column justify-content-center align-items-center">
                            <div class="text-secondary mb-2">獎勵次數(shù)</div>
                            <div><?=$totalRewords?></div>
                        </div>
                        <div class="d-flex flex-column justify-content-center align-items-center">
                            <div class="text-secondary mb-2">邀請人數(shù)</div>
                            <div><?=$totalInvitees?></div>
                        </div>
                    </div>
                </div>

                <div class="d-flex flex-column align-items-center justify-content-center mt-4">
                    <div>
                        <span class="me-2">邀請鏈接</span>
                        <input ref="inputElement" class="form-control me-2 d-inline-block border text-secondary mb-1" type="text" v-model="inviteUrl" readonly style="width:18em">
                        <button class="btn btn-primary" @click="copyToClipboard">復(fù)制</button>
                    </div>
                    <div class="mt-3 text-success">
                        你將獲得好友充值的<b>10%</b>的獎勵,獎勵將自動兌換為相應(yīng)的vip會員額度。
                    </div>
                </div>

            </div>

            <div class="row" v-cloak>
                <div v-for="invitee in invitees" class="col-12 col-sm-6 col-md-6 col-lg-4 col-xl-3">
                    <div class="white-bg shadow-sm mt-3 rounded p-3">
                        <div class="d-flex align-items-center justify-content-between role position-relative">
                            <div class="d-flex align-items-center">
                                <img :src="invitee.avatar" class="avatar me-2"/>
                                <div>
                                    <div>{{invitee.nickname}}</div>
                                    <div class="text-secondary-sm">{{invitee.type}}</div>
                                </div>
                            </div>
                            <div class="text-secondary">
                                {{invitee.amount}}¥
                            </div>
                        </div>
                        <div class="mt-3 d-flex justify-content-between align-items-center">
                            <span class="text-secondary-sm">已自動兌換</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- vue -->
<script type="text/javascript" src="/app/ai/js/vue.global.js"></script>

<script>
    const App = {
        data() {
            return {
                invitees: [],
                inviteUrl: '',
            }
        },
        mounted() {
            this.loadInvitees();
            this.inviteUrl = location.origin + '/ai/' + window.parent.ai.loginUser.userid;
        },
        methods: {
            copyToClipboard() {
                const inputElement = this.$refs.inputElement;
                const item = new ClipboardItem({ 'text/plain': new Blob([inputElement.value], { type: 'text/plain' }) });
                navigator.clipboard.write([item]).then(() => {
                    webman.success('復(fù)制成功');
                }).catch((error) => {
                    console.error('復(fù)制失敗', error);
                });
            },
            loadInvitees() {
                $.ajax({
                    url: "/app/ai_invite/index/invitees",
                    success: (res) => {
                        if (res.code) {
                            return alert(res.msg);
                        }
                        this.invitees = res.data;
                    }
                });
            }
        }
    }
    Vue.createApp(App).mount('#app');

</script>

<script src="/app/user/js/webman.js"></script>

</body>
</html>

最終效果類似如下

module-invite.png

install.sql

如果你想發(fā)布插件給其他人使用,需要在插件目錄下創(chuàng)建一個(gè)install.sql文件,里面的SQL會在插件安裝或升級時(shí)執(zhí)行,用來初始化數(shù)據(jù)庫表結(jié)構(gòu)及升級修改表結(jié)構(gòu)。
plugin/ai_invite/install.sql

CREATE TABLE `ai_invite` (
 `id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵',
 `created_at` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
 `updated_at` datetime DEFAULT NULL COMMENT '更新時(shí)間',
 `inviter` int DEFAULT NULL COMMENT '邀請人',
 `invitee` int DEFAULT NULL COMMENT '受邀人',
 `percent` int DEFAULT '10' COMMENT '獎勵百分比',
 PRIMARY KEY (`id`),
 UNIQUE KEY `invitee` (`invitee`),
 KEY `inviter` (`inviter`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='邀請';

CREATE TABLE `ai_invite_rewards` (
 `id` int NOT NULL AUTO_INCREMENT COMMENT '主鍵',
 `created_at` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
 `updated_at` datetime DEFAULT NULL COMMENT '更新時(shí)間',
 `inviter` int DEFAULT NULL COMMENT '邀請人',
 `invitee` int DEFAULT NULL COMMENT '受邀人',
 `amount` decimal(8,2) DEFAULT NULL COMMENT '獎勵',
 `percent` int DEFAULT NULL COMMENT '百分比',
 `data` text COLLATE utf8mb4_general_ci COMMENT '數(shù)據(jù)',
 `type` enum('register','recharge') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'recharge' COMMENT '類型',
 PRIMARY KEY (`id`),
 KEY `inviter` (`inviter`),
 KEY `invitee` (`invitee`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='邀請獎勵';

注意
每個(gè)語句以;結(jié)束

特別注意
如果你的插件后續(xù)版本需要修改表結(jié)構(gòu),只能通過追加的方式將alter table語句放再install.sql末尾,千萬不要直接修改建表語句,否則老用戶無法實(shí)現(xiàn)升級。
這部分的說明參考 webman應(yīng)用插件自動導(dǎo)入數(shù)據(jù)庫

發(fā)布插件并獲取收益

參考 發(fā)布插件

編輯于2024-04-25 15:47:24 完善本頁 +發(fā)起討論
贊助商