增加 推介系统
This commit is contained in:
parent
6a18314e06
commit
16cec5c737
102
app/Http/Controllers/Web/AffiliateController.php
Normal file
102
app/Http/Controllers/Web/AffiliateController.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Affiliate\Affiliates;
|
||||
use App\Models\Affiliate\AffiliateUser;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AffiliateController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): View|RedirectResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
$user->load('affiliate');
|
||||
|
||||
$affiliate = $user->affiliate;
|
||||
|
||||
// 检测用户是否激活了推介计划
|
||||
if (! $affiliate) {
|
||||
return redirect()->route('affiliates.create');
|
||||
}
|
||||
|
||||
$affiliateUsers = auth()->user()->affiliateUsers()->paginate(10);
|
||||
|
||||
return view('affiliates.index', compact('affiliateUsers', 'affiliate'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create(): View|RedirectResponse
|
||||
{
|
||||
$user = auth('web')->user();
|
||||
$user->load('affiliate', 'affiliateUser.affiliate.user');
|
||||
|
||||
if ($user->affiliate) {
|
||||
return redirect()->route('affiliates.index')->with('error', '您已经激活了推介计划。');
|
||||
}
|
||||
|
||||
return view('affiliates.create', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (auth()->user()->affiliate) {
|
||||
return redirect()->route('affiliates.index')->with('error', '您已经激活了推介计划。');
|
||||
}
|
||||
|
||||
$request->user('web')->affiliate()->create();
|
||||
|
||||
return redirect()->route('affiliates.index')->with('success', '欢迎您,并感谢您。');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Affiliates $affiliate): RedirectResponse
|
||||
{
|
||||
if (auth('web')->guest()) {
|
||||
// save the affiliate id in the session
|
||||
session()->put('affiliate_id', $affiliate->id);
|
||||
|
||||
$cache_key = 'affiliate_ip:'.$affiliate->id.':'.request()->ip();
|
||||
|
||||
if (! Cache::has($cache_key)) {
|
||||
$affiliate->increment('visits');
|
||||
Cache::put($cache_key, true, now()->addHour());
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Affiliates $affiliate): RedirectResponse
|
||||
{
|
||||
// 检测是不是自己的推介计划
|
||||
if ($affiliate->user_id !== auth()->id()) {
|
||||
return redirect()->route('affiliates.index')->with('error', '您没有权限删除此推介计划。');
|
||||
}
|
||||
|
||||
AffiliateUser::where('affiliate_id', $affiliate->id)->delete();
|
||||
User::where('affiliate_id', $affiliate->id)->update(['affiliate_id' => null]);
|
||||
|
||||
$affiliate->delete();
|
||||
|
||||
return redirect()->route('affiliates.create')->with('success', '推介计划已经成功删除。');
|
||||
}
|
||||
}
|
85
app/Models/Affiliate/AffiliateUser.php
Normal file
85
app/Models/Affiliate/AffiliateUser.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Affiliate;
|
||||
|
||||
use App\Models\User;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AffiliateUser extends Model
|
||||
{
|
||||
use Cachable;
|
||||
|
||||
public $fillable = [
|
||||
'revenue',
|
||||
'affiliate_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
public $casts = [
|
||||
'revenue' => 'decimal:2',
|
||||
];
|
||||
|
||||
public $with = [
|
||||
'user',
|
||||
'originalAffiliateUser',
|
||||
];
|
||||
|
||||
public function affiliate(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Affiliates::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function originalAffiliateUser(): HasOneThrough
|
||||
{
|
||||
return $this->hasOneThrough(
|
||||
User::class,
|
||||
Affiliates::class,
|
||||
'user_id',
|
||||
'id',
|
||||
'affiliate_id',
|
||||
'user_id'
|
||||
);
|
||||
}
|
||||
|
||||
// 给用户添加佣金
|
||||
public function addRevenue(string $revenue): void
|
||||
{
|
||||
$this->load('user');
|
||||
|
||||
if (! $this->user->isRealNamed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load('affiliate.user');
|
||||
|
||||
// 给 affiliate_id 中的 user_id 添加佣金
|
||||
Cache::lock('affiliate_user_'.$this->id, 10)->block(10, function () use ($revenue) {
|
||||
// 计算应得
|
||||
$commission_referral = config('settings.billing.commission_referral') * 100;
|
||||
|
||||
$revenue = bcdiv($revenue, $commission_referral, 2);
|
||||
|
||||
$this->update([
|
||||
'revenue' => bcadd($this->revenue, $revenue, 2),
|
||||
]);
|
||||
|
||||
// 给上级添加佣金
|
||||
if ($this->affiliate->user_id) {
|
||||
$this->affiliate->update([
|
||||
'revenue' => bcadd($this->affiliate->revenue, $revenue, 2),
|
||||
]);
|
||||
|
||||
$this->affiliate->user->charge($revenue, 'affiliate', '下属用户 '.$this->user->name.'#'.$this->user_id.' 充值所获得的佣金。');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
50
app/Models/Affiliate/Affiliates.php
Normal file
50
app/Models/Affiliate/Affiliates.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Affiliate;
|
||||
|
||||
use App\Models\User;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Affiliates extends Model
|
||||
{
|
||||
use Cachable;
|
||||
|
||||
public $fillable = [
|
||||
'uuid',
|
||||
'visits',
|
||||
'revenue',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
public $casts = [
|
||||
'visits' => 'integer',
|
||||
'revenue' => 'decimal:2',
|
||||
];
|
||||
|
||||
public static function booted()
|
||||
{
|
||||
static::creating(function (self $affiliate) {
|
||||
$affiliate->uuid = Str::ulid();
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeThisUser(Builder $query): Builder
|
||||
{
|
||||
return $query->where('user_id', auth('web')->id());
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(AffiliateUser::class);
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
|
||||
use App\Events\Users;
|
||||
use App\Exceptions\User\BalanceNotEnoughException;
|
||||
use App\Models\Affiliate\Affiliates;
|
||||
use App\Models\Affiliate\AffiliateUser;
|
||||
use GeneaLabs\LaravelModelCaching\CachedBuilder;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
@ -14,6 +16,9 @@
|
||||
use Illuminate\Database\Eloquent\Prunable;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@ -48,6 +53,7 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
'email',
|
||||
'password',
|
||||
'receive_marketing_email',
|
||||
'affiliate_id',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -82,6 +88,27 @@ public function hosts(): HasMany
|
||||
return $this->hasMany(Host::class);
|
||||
}
|
||||
|
||||
public function affiliate(): HasOne
|
||||
{
|
||||
return $this->hasOne(Affiliates::class);
|
||||
}
|
||||
|
||||
public function affiliateUsers(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(AffiliateUser::class, Affiliates::class, 'user_id', 'affiliate_id');
|
||||
}
|
||||
|
||||
public function affiliateUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AffiliateUser::class, 'affiliate_id');
|
||||
}
|
||||
|
||||
// 通过 affiliate_id 获取到 affiliates 中的 user_id
|
||||
public function promoter(): HasOneThrough
|
||||
{
|
||||
return $this->hasOneThrough(User::class, Affiliates::class, 'id', 'id', 'affiliate_id', 'user_id');
|
||||
}
|
||||
|
||||
public function getBirthdayFromIdCard(string|null $id_card = null): Carbon
|
||||
{
|
||||
if (empty($id_card)) {
|
||||
|
@ -24,10 +24,17 @@ public function updated(Balance $balance): void
|
||||
{
|
||||
if ($balance->isDirty('paid_at')) {
|
||||
if ($balance->paid_at) {
|
||||
$balance->load('user');
|
||||
$balance->load('user.affiliateUser');
|
||||
|
||||
$balance->notify(new UserCharged());
|
||||
broadcast(new Users($balance->user, 'balance.updated', $balance));
|
||||
|
||||
$balance->user->charge($balance->amount, $balance->payment, $balance->order_id);
|
||||
|
||||
if ($balance->user->affiliate_id) {
|
||||
$balance->user->affiliateUser->addRevenue($balance->amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,39 @@
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Affiliate\AffiliateUser;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class UserObserver
|
||||
{
|
||||
/**
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function creating(User $user): void
|
||||
{
|
||||
$user->email_md5 = md5($user->email);
|
||||
$user->uuid = Str::uuid();
|
||||
|
||||
// if session has affiliate_id, then set it to user
|
||||
if (session()->has('affiliate_id')) {
|
||||
$user->affiliate_id = session()->get('affiliate_id');
|
||||
}
|
||||
}
|
||||
|
||||
public function created(User $user): void
|
||||
{
|
||||
// if user has affiliate_id, then create an affiliate_user record
|
||||
if ($user->affiliate_id) {
|
||||
AffiliateUser::create([
|
||||
'affiliate_id' => $user->affiliate_id,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updating(User $user): void
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('affiliates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->ulid()->index();
|
||||
|
||||
// 访问数量
|
||||
$table->unsignedBigInteger('visits')->default(0);
|
||||
|
||||
// 累计收益
|
||||
$table->decimal('revenue', 10)->default(0);
|
||||
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('affiliate_users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// 从中盈利
|
||||
$table->decimal('revenue', 10)->default(0);
|
||||
|
||||
$table->foreignId('affiliate_id')->constrained('affiliates')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->constrained('users')->cascadeOnDelete();
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->foreignId('affiliate_id')->nullable()->after('user_group_id')->constrained('affiliates')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropForeign(['affiliate_id']);
|
||||
$table->dropColumn('affiliate_id');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('affiliate_users');
|
||||
|
||||
Schema::dropIfExists('affiliates');
|
||||
}
|
||||
};
|
24
resources/views/affiliates/create.blade.php
Normal file
24
resources/views/affiliates/create.blade.php
Normal file
@ -0,0 +1,24 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '推介计划')
|
||||
|
||||
@section('content')
|
||||
|
||||
<h3>加入推介计划</h3>
|
||||
<p>
|
||||
让更多人用上我们的产品,您将从他们实名认证成功之后的每笔充值中获取 {{ config('settings.billing.commission_referral') * 100 . '%' }}
|
||||
的佣金。</p>
|
||||
|
||||
@if ($user->affiliate_id)
|
||||
<span>您被 {{ $user->affiliateUser->affiliate->user->name }}#{{ $user->affiliateUser->affiliate->user_id }} 引荐。</span>
|
||||
@if ($user->affiliateUser->affiliate->revenue > 5)
|
||||
<span>您的推介人已经获得了 {{ $user->affiliateUser->affiliate->revenue }} 元的佣金。</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<form class="mt-3" method="post" action="{{ route('affiliates.store') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-primary">加入推介计划</button>
|
||||
</form>
|
||||
|
||||
@endsection
|
52
resources/views/affiliates/index.blade.php
Normal file
52
resources/views/affiliates/index.blade.php
Normal file
@ -0,0 +1,52 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '推介计划')
|
||||
|
||||
@section('content')
|
||||
|
||||
<h3>推介计划</h3>
|
||||
|
||||
<p>
|
||||
访问量:{{ $affiliate->visits }}
|
||||
</p>
|
||||
<p>
|
||||
盈利:{{ $affiliate->revenue }} 元
|
||||
</p>
|
||||
|
||||
<p>推介 URL: {{ \Illuminate\Support\Facades\URL::route('affiliates.show', $affiliate->uuid) }}</p>
|
||||
|
||||
<h3>用户列表</h3>
|
||||
@php($count = $affiliateUsers->count())
|
||||
@if ($count)
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">用户名</th>
|
||||
<th scope="col">盈利</th>
|
||||
<th scope="col">注册时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($affiliateUsers as $user)
|
||||
<tr>
|
||||
<td>{{ $user->user->name }}</td>
|
||||
<td>{{ $user->revenue }} 元</td>
|
||||
<td>{{ $user->created_at }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
{{ $affiliateUsers->links() }}
|
||||
@else
|
||||
<p>您还没有推介用户。</p>
|
||||
@endif
|
||||
|
||||
<h4>离开推介计划</h4>
|
||||
<form method="post" action="{{ route('affiliates.destroy', $affiliate->id) }}"
|
||||
onclick="return confirm('删除后将不会获得收益,推介数据也会被删除。')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger">删除推介计划</button>
|
||||
</form>
|
||||
|
||||
@endsection
|
@ -55,6 +55,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('transactions') }}">记录</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('affiliates.index') }}">推介</a>
|
||||
</li>
|
||||
@endauth
|
||||
|
||||
<li class="nav-item">
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Web\AffiliateController;
|
||||
use App\Http\Controllers\Web\Auth\ConfirmPasswordController;
|
||||
use App\Http\Controllers\Web\Auth\ForgotPasswordController;
|
||||
use App\Http\Controllers\Web\Auth\LoginController;
|
||||
@ -83,6 +84,10 @@ function () {
|
||||
Route::post('real_name', [RealNameController::class, 'store'])->name('real_name.store');
|
||||
/* End 实名认证 */
|
||||
|
||||
/* Start 推介 */
|
||||
Route::resource('affiliates', AffiliateController::class)->only(['index', 'create', 'store', 'destroy']);
|
||||
/* End 推介 */
|
||||
|
||||
/* Start 匿名登录 */
|
||||
Route::get('auth_request/{auth_request}', [AuthController::class, 'showAuthRequest'])->withoutMiddleware(['auth:web', 'verified'])->name('auth_request.show');
|
||||
Route::post('auth_request', [AuthController::class, 'storeAuthRequest'])->name('auth_request.store');
|
||||
@ -98,3 +103,5 @@ function () {
|
||||
|
||||
// 维护
|
||||
Route::get('maintenance', MaintenanceController::class)->name('maintenances');
|
||||
|
||||
Route::middleware('guest')->get('affiliates/{affiliate:uuid}', [AffiliateController::class, 'show'])->name('affiliates.show');
|
||||
|
Loading…
Reference in New Issue
Block a user