init
commit
d1bf6f0cc1
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
phpunit.phar
|
||||
/vendor
|
||||
composer.phar
|
||||
composer.lock
|
||||
*.project
|
||||
.idea/
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Dcat Admin Extension
|
||||
|
||||
用户管理
|
||||
|
||||
## 安装
|
||||
|
||||
- `composer config repositories.peidikeji/dcat-admin-user git git@gitee.com:paddy_technology/dcat-admin-user.git`
|
||||
- `composer require peidikeji/dcat-admin-user:dev-develop`
|
||||
- `php artisan vendor:publish --tag=dcat-admin-user-migrations`
|
||||
|
||||
## 事件
|
||||
|
||||
- 用户注册成功: `Peidikeji\User\Events\UserRegister`
|
||||
|
||||
## 数据表
|
||||
|
||||
### 用户表: users
|
||||
|
||||
| column | type | nullable | default | comment |
|
||||
| - | - | - | - | - |
|
||||
| id | bigint | not null | - | 主键 |
|
||||
| username | varchar(191) | null | - | 用户名 |
|
||||
| phone | varchar(191) | null | - | 手机号 |
|
||||
| name | varchar(191) | null | - | 昵称 |
|
||||
| avatar | varchar(191) | null | - | 头像 |
|
||||
| balance | decimal(12, 2) | not null | 0 | 余额 |
|
||||
| invite_code | varchar(191) | not null | - | 邀请码 |
|
||||
| inviter_id | bigint | null | - | 邀请人 |
|
||||
| inviter_path | varchart(191) | null | - | 所有的上级邀请人(-1-2-3-) |
|
||||
| created_at | timestamp | null | - | 创建时间 |
|
||||
| updated_at | timestamp | null | - | 更新时间 |
|
||||
|
||||
### 第三方登录信息: user_socialites
|
||||
|
||||
| column | type | nullable | default | comment |
|
||||
| - | - | - | - | - |
|
||||
| id | bigint | not null | - | 主键 |
|
||||
| user_id | bigint | not null | - | 外键关联 users.id |
|
||||
| type | varchar(191) | not null | - | 类型(SocialiteType) |
|
||||
| openid | varchar(191) | not null | - | 第三方唯一凭证 |
|
||||
| unionid | varchar(191) | null | - | 第三方唯一凭证 |
|
||||
| data | json | null | - | 第三方数据 |
|
||||
| created_at | timestamp | null | - | 创建时间 |
|
||||
| updated_at | timestamp | null | - | 更新时间 |
|
||||
|
||||
### 用户余额变动记录: user_balance_logs
|
||||
|
||||
| column | type | nullable | default | comment |
|
||||
| - | - | - | - | - |
|
||||
| id | bigint | not null | - | 主键 |
|
||||
| user_id | bigint | not null | - | 外键关联 users.id |
|
||||
| cate | varchar(191) | not null | - | 类别 |
|
||||
| description | varchar(191) | not null | - | 描述 |
|
||||
| amount | decimal(12, 2) | not null | - | 变动数量(正数为增加, 负数为减少) |
|
||||
| balance | decimal(12, 2) | not null | - | 变动后的余额 |
|
||||
| remarks | varchart(191) | null | - | 备注 |
|
||||
| source_type | varchart(191) | null | - | 来源(多态关联) |
|
||||
| source_id | bigint | null | - | 来源(多态关联) |
|
||||
| created_at | timestamp | null | - | 创建时间 |
|
||||
| updated_at | timestamp | null | - | 更新时间 |
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "peidikeji/dcat-admin-user",
|
||||
"alias": "用户管理",
|
||||
"description": "用户管理",
|
||||
"type": "library",
|
||||
"keywords": ["dcat-admin", "extension", "user"],
|
||||
"homepage": "https://gitee.com/paddy_technology/dcat-admin-user",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "panliang",
|
||||
"email": "1163816051@qq.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0",
|
||||
"peidikeji/dcat-admin": "*",
|
||||
"tucker-eric/eloquentfilter": "^3.1",
|
||||
"laravel/framework": "^9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Peidikeji\\User\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Peidikeji\\User\\UserServiceProvider"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('username');
|
||||
$table->string('password')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->string('name')->nullable();
|
||||
$table->string('avatar')->nullable();
|
||||
$table->decimal('balance', 12, 2)->default(0)->comment('余额');
|
||||
$table->string('invite_code')->comment('邀请码');
|
||||
$table->unsignedBigInteger('inviter_id')->nullable()->comment('邀请人');
|
||||
$table->string('inviter_path')->nullable()->comment('所有上级邀请人');
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('用户表');
|
||||
});
|
||||
|
||||
Schema::create('user_socialites', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('type');
|
||||
$table->string('openid');
|
||||
$table->string('unionid')->nullable();
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('用户-第三方登录');
|
||||
});
|
||||
|
||||
Schema::create('user_balance_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('cate')->comment('类别');
|
||||
$table->string('description')->comment('描述');
|
||||
$table->decimal('amount', 12, 2)->comment('变动数量, 正数为增加, 负数为减少');
|
||||
$table->decimal('balance', 12, 2)->comment('变更后的余额');
|
||||
$table->string('remarks')->nullable()->comment('备注');
|
||||
$table->nullableMorphs('source');
|
||||
$table->timestamps();
|
||||
|
||||
$table->comment('用户-余额流水记录');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_balance_logs');
|
||||
Schema::dropIfExists('user_socialites');
|
||||
Schema::dropIfExists('users');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
return [];
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
];
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'UserBalance' => '余额流水',
|
||||
'user-balance' => '余额流水',
|
||||
],
|
||||
'fields' => [
|
||||
'user_id' => '用户',
|
||||
'cate' => '类别',
|
||||
'amount' => '金额',
|
||||
'description' => '描述',
|
||||
'created_at' => '时间',
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'User' => '用户管理',
|
||||
'user' => '用户',
|
||||
'users' => '用户',
|
||||
],
|
||||
'fields' => [
|
||||
'id' => 'ID',
|
||||
'name' => '姓名',
|
||||
'gender' => '性别',
|
||||
'phone' => '手机号',
|
||||
'avatar' => '头像',
|
||||
'balance' => '余额',
|
||||
'profit' => 'e品额',
|
||||
'inviter_id' => '邀请人',
|
||||
'inviter' => [
|
||||
'name' => '邀请人',
|
||||
'phone' => '邀请人',
|
||||
],
|
||||
'invite_code' => '邀请码',
|
||||
'username' => '用户名',
|
||||
'password' => '密码',
|
||||
'created_at' => '注册时间',
|
||||
]
|
||||
];
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Admin;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group([
|
||||
'prefix' => config('admin.route.prefix'),
|
||||
'middleware' => config('admin.route.middleware'),
|
||||
], function () {
|
||||
Route::get('api/user', [UserController::class, 'list'])->name('dcat.admin.api.users');
|
||||
|
||||
Route::resource('users', UserController::class)->names('dcat.admin.users');
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Api;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::group([
|
||||
'middleware' => ['api'],
|
||||
'prefix' => 'api',
|
||||
], function () {
|
||||
Route::group(['prefix' => 'auth'], function () {
|
||||
Route::post('login', [AuthController::class, 'login']);
|
||||
Route::post('register', [AuthController::class, 'register']);
|
||||
|
||||
Route::post('login-by-sms', [AuthController::class, 'loginBySms']);
|
||||
|
||||
Route::post('login-by-wxmini', [AuthController::class, 'loginByWxMini']);
|
||||
Route::post('wx-bind-phone', [AuthController::class, 'wxbindPhone']);
|
||||
|
||||
Route::post('reset', [AuthController::class, 'reset']);
|
||||
Route::post('reset-pwd', [AuthController::class, 'resetPwd']);
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'user', 'middleware' => ['auth:api']], function () {
|
||||
Route::get('profile', [UserController::class, 'profile']);
|
||||
Route::put('profile', [UserController::class, 'update']);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Enums;
|
||||
|
||||
use Dcat\Admin\Admin;
|
||||
|
||||
enum SocialiteType: string
|
||||
{
|
||||
case WxMini = 'wx-mini';
|
||||
|
||||
public static function options()
|
||||
{
|
||||
return [
|
||||
self::WxMini->value => '微信小程序',
|
||||
];
|
||||
}
|
||||
|
||||
public function text()
|
||||
{
|
||||
return data_get(self::options(), $this->value);
|
||||
}
|
||||
|
||||
public function label()
|
||||
{
|
||||
$color = match ($this) {
|
||||
static::WxMini => 'success',
|
||||
};
|
||||
|
||||
$background = Admin::color()->get($color, $color);
|
||||
|
||||
$name = static::options()[$this->value] ?? '其它';
|
||||
|
||||
return "<span class='label' style='background: $background;'>{$name}</span>";
|
||||
}
|
||||
|
||||
public function dot()
|
||||
{
|
||||
$color = match ($this) {
|
||||
static::WxMini => 'success',
|
||||
};
|
||||
|
||||
$background = Admin::color()->get($color, $color);
|
||||
|
||||
$name = static::options()[$this->value] ?? '其它';
|
||||
|
||||
return "<i class='fa fa-circle' style='font-size: 13px;color: $background;'> {$name}</span>";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Peidikeji\User\Models\User;
|
||||
|
||||
class UserRegister
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Filters;
|
||||
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class UserFilter extends ModelFilter
|
||||
{
|
||||
public function key($key)
|
||||
{
|
||||
$this->where(function ($q) use ($key) {
|
||||
$q->orWhere('phone', 'like', "%$key%")->orWhere('username', 'like', "%$key%");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Admin;
|
||||
|
||||
use App\Admin\Renderable\BalanceLogTable;
|
||||
use App\Models\BalanceLog;
|
||||
use Dcat\Admin\Admin;
|
||||
use Dcat\Admin\Form;
|
||||
use Dcat\Admin\Grid;
|
||||
use Dcat\Admin\Grid\Filter;
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use Dcat\Admin\Layout\Row;
|
||||
use Dcat\Admin\Show;
|
||||
use Dcat\Admin\Show\Tools;
|
||||
use Dcat\Admin\Widgets\Card;
|
||||
use Dcat\Admin\Widgets\Tab;
|
||||
use Dcat\Admin\Widgets\Table;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
use Peidikeji\Order\Models\Order;
|
||||
use Peidikeji\User\Models\User;
|
||||
use Peidikeji\User\Models\UserSocialite;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Admin\Actions\Grid\UserBalanceChange;
|
||||
|
||||
class UserController extends AdminController
|
||||
{
|
||||
protected $translation = 'dcat-admin-user::user';
|
||||
|
||||
public function list(Request $request)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
if ($request->filled('q')) {
|
||||
$search = '%' . $request->input('q'). '%';
|
||||
$query->where(fn($q) => $q->where('phone', 'like', $search)->orWhere('name', 'like', $search));
|
||||
}
|
||||
|
||||
if ($request->filled('none_merchant') || $request->filled('amp;none_merchant')) {
|
||||
$query->whereDoesntHave('merchants');
|
||||
}
|
||||
|
||||
$query->select(['id', 'phone as text']);
|
||||
|
||||
if ($request->filled('_paginate')) {
|
||||
$list = $query->paginate();
|
||||
} else {
|
||||
$list = $query->get();
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(User::with(['inviter']), function (Grid $grid) {
|
||||
$grid->model()->sort();
|
||||
|
||||
$grid->disableRowSelector();
|
||||
|
||||
$grid->column('id');
|
||||
$grid->column('name')->display(function () {
|
||||
return ($this->avatar ? '<img src="'.$this->avatar.'" width="60" class="img-thumbnail" />' : '') . $this->name;
|
||||
});
|
||||
$grid->column('phone');
|
||||
$grid->column('balance')->modal(__('dcat-admin-user::user.fields.balance') . '变更记录', fn() => BalanceLogTable::make(['type' => BalanceLog::TYPE_BALANCE, 'subject_id' => $this->id, 'subject_type' => 'user']));
|
||||
$grid->column('profit')->modal(__('dcat-admin-user::user.fields.profit') . '变更记录', fn() => BalanceLogTable::make(['type' => BalanceLog::TYPE_PROFIT, 'subject_id' => $this->id, 'subject_type' => 'user']));
|
||||
$grid->column('inviter.phone');
|
||||
$grid->column('created_at');
|
||||
|
||||
$user = Admin::user();
|
||||
$grid->showCreateButton($user->can('dcat.admin.users.create'));
|
||||
$grid->showViewButton($user->can('dcat.admin.users.show'));
|
||||
$grid->showEditButton($user->can('dcat.admin.users.edit'));
|
||||
$grid->showDeleteButton($user->can('dcat.admin.users.destroy'));
|
||||
$grid->actions(function (Grid\Displayers\Actions $actions) use ($user) {
|
||||
//余额变动
|
||||
if($user->can('dcat.admin.users.change_balance')){
|
||||
$actions->append(new UserBalanceChange());
|
||||
}
|
||||
});
|
||||
|
||||
$grid->filter(function (Filter $filter) {
|
||||
$filter->panel();
|
||||
$filter->where('name', function ($q) {
|
||||
$search = '%'.$this->input.'%';
|
||||
$q->where(fn($q) => $q->where('username', 'like', $search)->orWhere('name', 'like', $search)->orWhere('phone', 'like', $search));
|
||||
}, '搜索')->placeholder('姓名/手机号')->width(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new User(), function (Form $form) {
|
||||
$uniquePhone = Rule::unique('users', 'phone');
|
||||
if ($form->isEditing()) {
|
||||
$uniquePhone->ignore($form->model()->id);
|
||||
}
|
||||
$form->text('phone')->rules([$uniquePhone])->required();
|
||||
$form->text('name')->required();
|
||||
$form->image('avatar')->autoUpload()->saveFullUrl()->move('user/avatar');
|
||||
$form->select('inviter_id')->model(User::class, 'id', 'name')->ajax('api/user?_paginate=1');
|
||||
$form->hidden('inviter_path');
|
||||
$form->hidden('invite_code');
|
||||
|
||||
$form->saving(function (Form $form) {
|
||||
$form->inviter_path = $form->inviter_id ? User::where('id', $form->inviter_id)->value('inviter_path') . $form->inviter_id.'-' : null;
|
||||
if (!$form->invite_code) {
|
||||
do {
|
||||
$invite_code = strtoupper(Str::random(6));
|
||||
} while(User::where('invite_code', $invite_code)->exists());
|
||||
$form->invite_code = $invite_code;
|
||||
}
|
||||
});
|
||||
|
||||
$form->deleting(function (Form $form) {
|
||||
$data = $form->model()->toArray();
|
||||
$ids = array_column($data, 'id');
|
||||
if (Merchant::whereIn('user_id', $ids)->exists()) {
|
||||
return $form->response()->error('已经关联店铺, 无法删除');
|
||||
}
|
||||
if (Order::whereIn('user_id', $ids)->exists()) {
|
||||
return $form->response()->error('已经关联订单, 请先删除订单');
|
||||
}
|
||||
// 删除用户的登录信息
|
||||
UserSocialite::whereIn('user_id', $ids)->delete();
|
||||
foreach($ids as $id) {
|
||||
$current = User::findOrFail($id);
|
||||
// 删除用户的邀请信息
|
||||
$users = User::where('inviter_path', 'like', '%-'.$id.'-%')->get();
|
||||
foreach($users as $user) {
|
||||
$user->inviter_path = str_replace('-'.$id.'-', '-', $user->inviter_path);
|
||||
$ids = explode('-', $user->inviter_path);
|
||||
$user->inviter_id = count($ids) > 2 ? $ids[count($ids) - 2] : null;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
$info = User::with(['inviter', 'inviteUsers', 'socialites'])->findOrFail($id);
|
||||
$show = Show::make($info);
|
||||
$show->field('name');
|
||||
$show->field('gender');
|
||||
$show->field('phone');
|
||||
$show->field('avatar')->image('', 64);
|
||||
$show->field('inviter.phone');
|
||||
$show->field('invite_code');
|
||||
$show->field('balance');
|
||||
$show->field('profit');
|
||||
$show->field('created_at');
|
||||
$show->tools(function (Tools $tools) {
|
||||
$tools->disableBack();
|
||||
$tools->disableList(false);
|
||||
});
|
||||
|
||||
$tab = new Tab();
|
||||
|
||||
$keys = ['id', 'username', 'name', 'phone', 'created_at'];
|
||||
$inviteUsers = $info->inviteUsers->map(function ($item) {
|
||||
return [$item->id, $item->username, $item->name, $item->phone, $item->created_at->format('Y-m-d H:i:s')];
|
||||
});
|
||||
$headers = array_map(fn($v) => __($this->translation . '.fields.' . $v), $keys);
|
||||
$table = new Table($headers, $inviteUsers, ['table-hover']);
|
||||
$tab->add('邀请用户', $table);
|
||||
|
||||
$keys = ['type', 'openid', 'created_at'];
|
||||
$socialites = $info->socialites->map(function ($item) {
|
||||
return [$item->type->text(), $item->openid, $item->created_at->format('Y-m-d H:i:s')];
|
||||
});
|
||||
$table = new Table($keys, $socialites, ['table-hover']);
|
||||
$tab->add('绑定账户', $table);
|
||||
|
||||
$row = new Row();
|
||||
$row->column(6, $show);
|
||||
$row->column(6, (new Card('', $tab)));
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Api;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Sms;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Overtrue\LaravelWeChat\EasyWeChat;
|
||||
use Peidikeji\User\Enums\SocialiteType;
|
||||
use Peidikeji\User\Events\UserRegister;
|
||||
use Peidikeji\User\Models\User;
|
||||
use Peidikeji\User\Models\UserSocialite;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required',
|
||||
'password' => 'required',
|
||||
]);
|
||||
|
||||
$user = User::filter(['key' => $request->input('username')])->first();
|
||||
if (!$user) {
|
||||
return $this->error('用户名或密码错误');
|
||||
}
|
||||
if (!Hash::check($request->input('password'), $user->password)) {
|
||||
return $this->error('用户名或密码错误1');
|
||||
}
|
||||
|
||||
return $this->attemptUser($user);
|
||||
}
|
||||
|
||||
public function loginBySms(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required',
|
||||
'code' => 'required',
|
||||
]);
|
||||
$phone = $request->input('phone');
|
||||
|
||||
$result = Sms::checkCode('login', $phone, $request->input('code'));
|
||||
if (!$result) {
|
||||
return $this->error('验证码不正确或已过期');
|
||||
}
|
||||
|
||||
$user = User::where('phone', $phone)->first();
|
||||
if (!$user) {
|
||||
$user = $this->createUser(['phone' => $phone], $request->input('invite_code'));
|
||||
}
|
||||
|
||||
return $this->attemptUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小程序登录
|
||||
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
|
||||
*/
|
||||
public function loginByWxMini(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'code' => 'required',
|
||||
]);
|
||||
$app = EasyWeChat::miniApp();
|
||||
$response = $app->getClient()->get('/sns/jscode2session', [
|
||||
'appid' => $app->getAccount()->getAppId(),
|
||||
'secret' => $app->getAccount()->getSecret(),
|
||||
'js_code' => $request->input('code'),
|
||||
'grant_type' => 'authorization_code',
|
||||
]);
|
||||
$result = $response->toArray();
|
||||
$openid = data_get($result, 'openid');
|
||||
if (!$openid) {
|
||||
return $this->error('未获取到 openid');
|
||||
}
|
||||
$unionid = data_get($result, 'unionid');
|
||||
$socialite = UserSocialite::query()->updateOrCreate([
|
||||
'openid' => $openid,
|
||||
'type' => SocialiteType::WxMini,
|
||||
], [
|
||||
'data' => $result,
|
||||
'unionid' => $unionid,
|
||||
]);
|
||||
if ($socialite->user) {
|
||||
return $this->attemptUser($socialite->user);
|
||||
}
|
||||
|
||||
return $this->json(['openid' => $openid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小程序获取手机号
|
||||
* https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
|
||||
*/
|
||||
public function wxbindPhone(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'openid' => 'required',
|
||||
'code' => 'required',
|
||||
]);
|
||||
$response = EasyWeChat::miniApp()->getClient()->postJson('/wxa/business/getuserphonenumber', ['code' => $request->input('code')]);
|
||||
$result = $response->toArray();
|
||||
if (data_get($result, 'errcode') !== 0) {
|
||||
return $this->error(data_get($result, 'errmsg'));
|
||||
}
|
||||
$phone = data_get($result, 'phone_info.purePhoneNumber');
|
||||
if (!$phone) {
|
||||
return $this->error('未获取到手机号');
|
||||
}
|
||||
|
||||
$user = User::where('phone', $phone)->first();
|
||||
if (!$user) {
|
||||
$user = $this->createUser(['phone' => $phone], $request->input('invite_code'));
|
||||
} elseif ($user->socialites()->where('type', SocialiteType::WxMini)->exists()) {
|
||||
return $this->error('手机号已经绑定过了');
|
||||
}
|
||||
|
||||
$openid = $request->input('openid');
|
||||
UserSocialite::updateOrCreate([
|
||||
'openid' => $openid,
|
||||
'type' => SocialiteType::WxMini,
|
||||
], [
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
|
||||
return $this->attemptUser($user);
|
||||
}
|
||||
|
||||
public function register(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required',
|
||||
'code' => 'required',
|
||||
]);
|
||||
$phone = $request->input('phone');
|
||||
|
||||
if (User::where('phone', $phone)->exists()) {
|
||||
return $this->error('用户已经注册');
|
||||
}
|
||||
|
||||
$result = Sms::checkCode('register', $phone, $request->input('code'));
|
||||
if (!$result) {
|
||||
return $this->error('验证码不正确或已过期');
|
||||
}
|
||||
|
||||
$user = $this->createUser(['phone' => $phone], $request->input('invite_code'));
|
||||
|
||||
return $this->attemptUser($user);
|
||||
}
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required',
|
||||
'code' => 'required',
|
||||
'password' => ['required', Password::min(6)],
|
||||
]);
|
||||
$phone = $request->input('phone');
|
||||
|
||||
$result = Sms::checkCode('reset', $phone, $request->input('code'));
|
||||
if (!$result) {
|
||||
return $this->error('验证码不正确或已过期');
|
||||
}
|
||||
|
||||
$user = User::where('phone', $phone)->first();
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在');
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'password' => Hash::make($request->input('password')),
|
||||
]);
|
||||
|
||||
return $this->success('密码重置成功');
|
||||
}
|
||||
|
||||
protected function attemptUser(User $user, $name = 'api')
|
||||
{
|
||||
$token = $user->createToken($name)->plainTextToken;
|
||||
return $this->json(['token' => $token, 'id' => $user->id]);
|
||||
}
|
||||
|
||||
protected function createUser($attributes = [], $invite_code = null)
|
||||
{
|
||||
if ($invite_code) {
|
||||
$inviterId = User::where('invite_code', $invite_code)->value('id');
|
||||
if (!$inviterId) {
|
||||
throw new BizException('邀请码错误');
|
||||
}
|
||||
$attributes['inviter_id'] = $inviterId;
|
||||
}
|
||||
$user = User::create($attributes);
|
||||
|
||||
event(new UserRegister($user));
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function resetPwd(Request $request)
|
||||
{
|
||||
$input = $request->validate([
|
||||
'password' => 'required|current_password:api',
|
||||
'new_password' => 'required',
|
||||
], [
|
||||
'password.current_password' => '密码错误',
|
||||
]);
|
||||
$user = auth('api')->user();
|
||||
if (!$user || !Hash::check($input['password'], $user->password)) {
|
||||
throw new BizException('密码错误');
|
||||
}
|
||||
|
||||
$user->password = bcrypt($input['new_password']);
|
||||
$user->save();
|
||||
|
||||
$user->tokens()->delete();
|
||||
return $this->success('修改成功');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Peidikeji\User\Http\Resources\UserResource;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function profile()
|
||||
{
|
||||
$user = auth('api')->user();
|
||||
|
||||
return UserResource::make($user);
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$update = $request->validate([
|
||||
'name' => 'string',
|
||||
'avatar' => 'string',
|
||||
'gender' => 'string',
|
||||
'password' => 'confirmed',
|
||||
]);
|
||||
$user = auth('api')->user();
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$validatePassword = !is_null($user->password);
|
||||
if ($validatePassword) {
|
||||
$request->validate(['password_old' => 'current_password:api'], ['password_old.current_password' => '原密码错误']);
|
||||
}
|
||||
$update['password'] = Hash::make($request->input('password'));
|
||||
}
|
||||
|
||||
$user->update($update);
|
||||
return $this->success('修改成功');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class UserResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'username' => $this->username,
|
||||
'avatar' => $this->avatar,
|
||||
'balance' => $this->balance,
|
||||
'profit' => $this->profit,
|
||||
'invite_code' => $this->invite_code,
|
||||
'inviter_id' => $this->inviter_id,
|
||||
'phone' => $this->phone,
|
||||
'gender' => $this->gender,
|
||||
'vip_expired_at' => $this->vip_expired_at?->timestamp,
|
||||
'created_at' => $this->created_at?->timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
public function with($request)
|
||||
{
|
||||
return ['code' => Response::HTTP_OK, 'message' => ''];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class UserTinyResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'username' => $this->username,
|
||||
'avatar' => $this->avatar,
|
||||
'invite_code' => $this->invite_code,
|
||||
'inviter_id' => $this->inviter_id,
|
||||
'phone' => $this->phone ? substr_replace($this->phone, '****', 3, 4) : $this->phone,
|
||||
'created_at' => $this->created_at?->timestamp,
|
||||
'is_vip' => $this->isVip(),
|
||||
'vip_expired_at' => $this->vip_expired_at?->timestamp,
|
||||
];
|
||||
}
|
||||
|
||||
public function with($request)
|
||||
{
|
||||
return ['code' => Response::HTTP_OK, 'message' => ''];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Models;
|
||||
|
||||
use App\Models\BalanceLog;
|
||||
use App\Models\CashOutLog;
|
||||
use App\Models\UserAddress;
|
||||
use App\Models\UserLike;
|
||||
use EloquentFilter\Filterable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use Peidikeji\User\Filters\UserFilter;
|
||||
use Peidikeji\Merchant\Models\Merchant;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Support\Str;
|
||||
use Peidikeji\Goods\Models\GoodsCart;
|
||||
use Peidikeji\Order\Models\Order;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens;
|
||||
use HasDateTimeFormatter;
|
||||
use Filterable;
|
||||
|
||||
protected $fillable = ['username', 'password', 'avatar', 'balance', 'profit', 'invite_code', 'inviter_id', 'inviter_path', 'name', 'phone', 'vip_expired_at', 'gender'];
|
||||
|
||||
protected $dates = ['vip_expired_at'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::creating(function ($model) {
|
||||
if (!$model->invite_code) {
|
||||
do {
|
||||
$invite_code = strtoupper(Str::random(6));
|
||||
} while(User::where('invite_code', $invite_code)->exists());
|
||||
$model->invite_code = $invite_code;
|
||||
}
|
||||
if ($model->inviter_id && !$model->inviter_path) {
|
||||
$inviter_path = User::where('id', $model->inviter_id)->value('inviter_path') ?: '-';
|
||||
$model->inviter_path = $inviter_path . $model->inviter_id . '-';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function modelFilter()
|
||||
{
|
||||
return UserFilter::class;
|
||||
}
|
||||
|
||||
public function inviter()
|
||||
{
|
||||
return $this->belongsTo(static::class, 'inviter_id');
|
||||
}
|
||||
|
||||
public function inviteUsers()
|
||||
{
|
||||
return $this->hasMany(static::class, 'inviter_id')->sort();
|
||||
}
|
||||
|
||||
public function socialites()
|
||||
{
|
||||
return $this->hasMany(UserSocialite::class, 'user_id');
|
||||
}
|
||||
|
||||
public function balanceLogs()
|
||||
{
|
||||
return $this->morphMany(BalanceLog::class, 'subject');
|
||||
}
|
||||
|
||||
public function cashOutLogs()
|
||||
{
|
||||
return $this->hasMany(CashOutLog::class, 'user_id');
|
||||
}
|
||||
|
||||
public function merchants()
|
||||
{
|
||||
return $this->hasMany(Merchant::class, 'user_id');
|
||||
}
|
||||
|
||||
public function scopeSort($q)
|
||||
{
|
||||
return $q->latest('id');
|
||||
}
|
||||
|
||||
public function isVip(): bool
|
||||
{
|
||||
return $this->vip_expired_at && $this->vip_expired_at->gt(now()) ? true : false;
|
||||
}
|
||||
|
||||
public function getSubPhone()
|
||||
{
|
||||
// 18223350967 => 0967
|
||||
return $this->phone ? substr($this->phone, 7, 4) : '';
|
||||
}
|
||||
|
||||
public function getHidePhone()
|
||||
{
|
||||
// 18223350967 => 182****0967
|
||||
return $this->phone ? substr_replace($this->phone, '****', 3, 4) : '';
|
||||
}
|
||||
|
||||
public function addresses(){
|
||||
return $this->hasMany(UserAddress::class, 'user_id');
|
||||
}
|
||||
|
||||
// 购物车
|
||||
public function carts()
|
||||
{
|
||||
return $this->hasMany(GoodsCart::class, 'user_id');
|
||||
}
|
||||
|
||||
// 订单
|
||||
public function orders()
|
||||
{
|
||||
return $this->hasMany(Order::class, 'user_id');
|
||||
}
|
||||
|
||||
public function likes()
|
||||
{
|
||||
return $this->hasMany(UserLike::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Peidikeji\User\Enums\SocialiteType;
|
||||
|
||||
class UserSocialite extends Model
|
||||
{
|
||||
protected $fillable = ['data', 'openid', 'type', 'user_id'];
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
'type' => SocialiteType::class
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Peidikeji\User;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class UserServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/api.php');
|
||||
|
||||
// $this->loadMigrationsFrom(__DIR__.'/../database/');
|
||||
|
||||
$this->publishes([
|
||||
__DIR__.'/../database/' => database_path('migrations')
|
||||
], 'dcat-admin-user-migrations');
|
||||
|
||||
$this->loadTranslationsFrom(__DIR__.'/../lang', 'dcat-admin-user');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue