1
0
Fork 0
panliang 2023-12-09 14:10:56 +08:00
parent 15463a5b90
commit e258d50fbb
28 changed files with 1747 additions and 55 deletions

View File

@ -60,7 +60,7 @@ class PartyUserController extends AdminController
]);
}
public function detail($id): Form
public function detail(): Form
{
return $this->baseDetail()->title('')->body([
amisMake()->TextControl()->name('id')->label(__('party_user.id'))->static(),

View File

@ -20,6 +20,7 @@ class UserScoreService extends BaseService
public function listQuery()
{
$model = $this->getModel();
$filter = $this->getModelFilter();
$query = $this->query();

View File

@ -0,0 +1,24 @@
<?php
namespace App\Enums;
use Dcat\Admin\Admin;
enum SocialiteType: string
{
case WxMini = 'wx-mini';
case WxOfficial = 'wx-official';
public static function options()
{
return [
self::WxMini->value => '微信小程序',
self::WxOfficial->value => '微信公众号',
];
}
public function text()
{
return data_get(self::options(), $this->value);
}
}

View File

@ -28,7 +28,7 @@ class AccountController extends Controller
{
$user = auth('api')->user();
$service = PartyUserService::make();
if ($service->update($user->id, $request->all())) {
if ($service->update($user->id, $request->only(['name', 'avatar']))) {
return $this->response()->success('保存成功');
}
return $this->response()->fail($service->getError());

View File

@ -4,8 +4,9 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\PartyUser;
use App\Models\{PartyUser, UserSocialite};
use Illuminate\Support\Facades\Hash;
use App\Enums\SocialiteType;
class AuthController extends Controller
{
@ -21,29 +22,29 @@ class AuthController extends Controller
return $this->response()->fail('用户名或密码错误');
}
// 更新第三方账户
// $openid = $request->input('openid');
// $open_type = $request->input('open_type');
// if ($openid && $open_type) {
// $this->updateUserSocialite($user, $openid, SocialiteType::from($open_type));
// }
$token = $user->createToken('client')->plainTextToken;
$openid = $request->input('openid');
$open_type = $request->input('open_type');
if ($openid && $open_type) {
$this->updateUserSocialite($user, $openid, SocialiteType::from($open_type));
}
$token = $user->createToken('api')->plainTextToken;
return $this->response()->success(['token' => $token, 'user' => $user]);
}
// protected function updateUserSocialite($user, $openid, $type)
// {
// // 清空以前绑定的
// UserSocialite::where([
// 'type' => $type,
// 'user_type' => $user->getMorphClass(),
// 'user_id' => $user->id,
// ])->update(['user_id' => null]);
// UserSocialite::updateOrCreate([
// 'type' => $type,
// 'user_type' => $user->getMorphClass(),
// 'openid' => $openid,
// ], [
// 'user_id' => $user->id,
// ]);
// }
protected function updateUserSocialite($user, $openid, $type)
{
// 清空以前绑定的
UserSocialite::where([
'type' => $type,
'user_type' => $user->getMorphClass(),
'user_id' => $user->id,
])->update(['user_id' => null]);
UserSocialite::updateOrCreate([
'type' => $type,
'user_type' => $user->getMorphClass(),
'openid' => $openid,
], [
'user_id' => $user->id,
]);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\UserScoreResource;
use App\Admin\Services\UserScoreService;
use App\Enums\CheckStatus;
class UserScoreController extends Controller
{
public function index(Request $request)
{
$user = auth('api')->user();
$list = $user->scores()->filter($request->all())->sort()->paginate($request->input('per_page'));
return $this->response()->success(UserScoreResource::collection($list));
}
public function show($id)
{
$user = auth('api')->user();
$info = $user->scores()->with(['type', 'checkUser'])->findOrFail($id);
return $this->response()->success(UserScoreResource::make($info));
}
public function store(Request $request)
{
$user = auth('api')->user();
$params = $request->all();
$params['user_id'] = $user->id;
$service = UserScoreService::make();
$result = $service->store($params);
if ($result) {
return $this->response()->success();
}
return $this->response()->fail($service->getError());
}
public function update(Request $request)
{
$user = auth('api')->user();
$info = $user->scores()->findOrFail($id);
if ($info->check_status == CheckStatus::Success) {
return $this->response()->fail('审核已通过, 无法修改');
}
$info->update($request->only(['title', 'content', 'images', 'file']));
return $this->response()->success();
}
public function destroy($id)
{
$user = auth('api')->user();
$info = $user->scores()->findOrFail($id);
if ($info->check_status == CheckStatus::Success) {
return $this->response()->fail('审核已通过, 无法删除');
}
UserScoreService::make()->delete($id);
return $this->response()->success();
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Http\Controllers;
use App\Models\PartyUser;
use Illuminate\Http\Request;
use App\Models\UserSocialite;
use App\Enums\SocialiteType;
use Slowlyo\OwlAdmin\Admin;
use Overtrue\LaravelWeChat\EasyWeChat;
class WechatController extends Controller
{
public function oauth(Request $request)
{
$user = session('easywechat.oauth_user.default');
$openid = $user->getId();
$socialite = UserSocialite::updateOrCreate([
'type' => SocialiteType::WxOfficial,
'user_type' => (new PartyUser)->getMorphClass(),
'openid' => $openid,
], [
'data' => $user,
]);
$user = $socialite->user;
if (!$user) {
return redirect(url('/h5/pages/auth/login') . '?' . http_build_query(['openid' => $openid, 'open_type' => SocialiteType::WxOfficial->value]));
}
$token = $user->createToken('api')->plainTextToken;
return redirect(url('/h5/pages/index/welcome') . '?token=' . $token);
}
public function officialAccessToken()
{
$app = EasyWeChat::officialAccount();
$token = $app->getAccessToken();
return response()->json([
'code' => 200,
'data' => [
'token' => $token->getToken(),
],
'message' => ''
]);
}
}

View File

@ -64,5 +64,6 @@ class Kernel extends HttpKernel
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'easywechat.oauth' => \App\Http\Middleware\UserSocialite::class,
];
}

View File

@ -0,0 +1,105 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use JetBrains\PhpStorm\Pure;
use Overtrue\LaravelWeChat\Events\WeChatUserAuthorized;
use Overtrue\LaravelWeChat\EasyWeChat;
use Overtrue\LaravelWeChat\Middleware\OAuthAuthenticate;
use Overtrue\Socialite\User as SocialiteUser;
class UserSocialite extends OAuthAuthenticate
{
public function handle(
Request $request,
Closure $next,
string $account = 'default',
string $scope = null,
?string $type = 'service'
): mixed {
if (config('app.debug')) {
$user = new SocialiteUser([
'id' => 'omDRm6ial5hRdys0NpnHQpYJ44aY',
'name' => '潘亮',
'nickname' => '潘亮',
'avatar' => 'https://via.placeholder.com/64x64.png',
'email' => null,
'original' => [],
'provider' => 'WeChat',
]);
session(['easywechat.oauth_user.default' => $user]);
return $next($request);
}
// 保证兼容性参数处理
$prefix = ('work' !== $type) ? 'official_account' : 'work';
$sessionKey = \sprintf('easywechat.oauth_user.%s', $account);
$name = \sprintf('easywechat.%s.%s', $prefix, $account);
$config = config($name, []);
$service = $this->getWechatApp($name);
$scope = $scope ?: Arr::get($config, 'oauth.scopes', ['snsapi_base']);
if (\is_string($scope)) {
$scope = \array_map('trim', explode(',', $scope));
}
if (Session::has($sessionKey)) {
event(new WeChatUserAuthorized(session($sessionKey), false, $account));
return $next($request);
}
// 是否强制使用 HTTPS 跳转
$enforceHttps = Arr::get($config, 'oauth.enforce_https', false);
if ($request->has('code')) {
session([$sessionKey => $service->getOAuth()->userFromCode($request->query('code'))]);
event(new WeChatUserAuthorized(session($sessionKey), true, $account));
return redirect()->to($this->getIntendUrl($request, $enforceHttps));
}
session()->forget($sessionKey);
// 跳转到微信授权页
return redirect()->away(
$service->getOAuth()->scopes($scope)->redirect($this->getRedirectUrl($request, $enforceHttps))
);
}
// public function handle(Request $request, Closure $next): Response
// {
// if (config('app.debug')) {
// $user = new SocialiteUser([
// 'id' => 'omDRm6ial5hRdys0NpnHQpYJ44aY',
// 'name' => '潘亮',
// 'nickname' => '潘亮',
// 'avatar' => 'https://via.placeholder.com/64x64.png',
// 'email' => null,
// 'original' => [],
// 'provider' => 'WeChat',
// ]);
// session(['easywechat.oauth_user.default' => $user]);
// return $next($request);
// }
// }
protected function getWechatApp($name)
{
$app = EasyWeChat::officialAccount($name);
$app->setAccessToken(new \App\Services\WechatOfficialAccessToken(
appId: $app->getAccount()->getAppId(),
secret: $app->getAccount()->getSecret(),
cache: $app->getCache(),
httpClient: $app->getHttpClient(),
stable: $app->getConfig()->get('use_stable_access_token', false),
));
}
}

View File

@ -16,7 +16,7 @@ class KeywordResource extends JsonResource
{
return [
'id' => $this->id,
'name' => $this->id,
'name' => $this->name,
'key' => $this->key,
'value' => $this->value,
'parent_id' => $this->parent_id,

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserScoreResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'type_id' => $this->type_id,
'type' => $this->whenLoaded('type'),
'cate_id' => $this->cate_id,
'user_id' => $this->user_id,
'title' => $this->title,
'content' => $this->content,
'images' => $this->images,
'file' => $this->file,
'check_status' => $this->check_status,
'check_user' => $this->whenLoaded('checkUser'),
'check_status_text' => $this->check_status_text,
'check_remarks' => $this->check_remarks,
'check_at' => $this->check_at?->format('Y-m-d H:i:s'),
'score' => $this->score,
'created_at' => $this->created_at->format('Y-m-d H:i:s')
];
}
}

View File

@ -29,4 +29,9 @@ class PartyUser extends Authenticatable
{
return $this->belongsTo(PartyCate::class, 'cate_id');
}
public function scores()
{
return $this->hasMany(UserScore::class, 'user_id');
}
}

View File

@ -67,6 +67,6 @@ class UserScore extends Model
public function scopeSort($q)
{
$this->orderBy('check_status')->latest('created_at');
$q->orderBy('check_status')->orderBy('created_at', 'desc');
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Enums\SocialiteType;
use App\Traits\HasDateTimeFormatter;
class UserSocialite extends Model
{
use HasDateTimeFormatter;
protected $fillable = ['type', 'user_type', 'user_id', 'openid', 'data'];
protected $casts = [
'type' => SocialiteType::class,
'data' => 'json'
];
public function user()
{
return $this->morphTo(__FUNCTION__, 'user_type', 'user_id');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Services;
use EasyWeChat\OfficialAccount\AccessToken;
use Illuminate\Support\Facades\Http;
/**
* 自定义微信AccessToken
*/
class WechatOfficialAccessToken extends AccessToken
{
public function getToken(): string
{
if ($url = config('easywechat.official_account.access_token_url')) {
$response = Http::post($url);
if ($response->successful()) {
$token = data_get($response->json(), 'data.token');
return $token;
}
}
return parent::getToken();
}
}

View File

@ -11,6 +11,7 @@
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"maatwebsite/excel": "^3.1",
"overtrue/laravel-wechat": "^7.2",
"slowlyo/owl-admin": "^2.8",
"tucker-eric/eloquentfilter": "^3.2"
},

1164
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,171 @@
<?php
return [
/*
* 默认配置,将会合并到各模块中
*/
'defaults' => [
'http' => [
'timeout' => 5.0,
'throw' => false,
'retry' => false,
],
],
/*
* 公众号
*/
'official_account' => [
'access_token_url' => env('WECHAT_OFFICIAL_ACCOUNT_ACCESS_TOKEN_URL', ''),
'default' => [
'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''), // AppID
'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', ''), // AppSecret
'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''), // Token
'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''), // EncodingAESKey
/*
* OAuth 配置
*
* scopes公众平台snsapi_userinfo / snsapi_base开放平台snsapi_login
* callbackOAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。)
* enforce_https是否强制使用 HTTPS 跳转
*/
'oauth' => [
'scopes' => array_map('trim', explode(',', env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_SCOPES', 'snsapi_base'))),
'callback' => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/examples/oauth_callback.php'),
'enforce_https' => false,
],
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
//'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
//],
],
],
/*
* 开放平台第三方平台
*/
// 'open_platform' => [
// 'default' => [
// 'app_id' => env('WECHAT_OPEN_PLATFORM_APPID', ''),
// 'secret' => env('WECHAT_OPEN_PLATFORM_SECRET', ''),
// 'token' => env('WECHAT_OPEN_PLATFORM_TOKEN', ''),
// 'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
/*
* 小程序
*/
// 'mini_app' => [
// 'default' => [
// 'app_id' => env('WECHAT_MINI_APP_APPID', ''),
// 'secret' => env('WECHAT_MINI_APP_SECRET', ''),
// 'token' => env('WECHAT_MINI_APP_TOKEN', ''),
// 'aes_key' => env('WECHAT_MINI_APP_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
/*
* 微信支付
*/
// 'pay' => [
// 'default' => [
// 'app_id' => env('WECHAT_PAY_APPID', ''),
// 'mch_id' => env('WECHAT_PAY_MCH_ID', 'your-mch-id'),
// 'private_key' => '/data/private/certs/apiclient_key.pem',
// 'certificate' => '/data/private/certs/apiclient_cert.pem',
// 'notify_url' => 'http://example.com/payments/wechat-notify', // 默认支付结果通知地址
// /**
// * 证书序列号,可通过命令从证书获取:
// * `openssl x509 -in application_cert.pem -noout -serial`
// */
// 'certificate_serial_no' => '6F2BADBE1738B07EE45C6A85C5F86EE343CAABC3',
//
// 'http' => [
// 'base_uri' => 'https://api.mch.weixin.qq.com/',
// ],
//
// // v2 API 秘钥
// //'v2_secret_key' => '26db3e15cfedb44abfbb5fe94fxxxxx',
//
// // v3 API 秘钥
// //'secret_key' => '43A03299A3C3FED3D8CE7B820Fxxxxx',
//
// // 注意 此处为微信支付平台证书 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml
// 'platform_certs' => [
// '/data/private/certs/platform_key.pem',
// ],
// ],
// ],
/*
* 企业微信
*/
// 'work' => [
// 'default' => [
// 'corp_id' => env('WECHAT_WORK_CORP_ID', ''),
// 'secret' => env('WECHAT_WORK_SECRET', ''),
// 'token' => env('WECHAT_WORK_TOKEN', ''),
// 'aes_key' => env('WECHAT_WORK_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
/*
* 企业微信开放平台
*/
// 'open_work' => [
// 'default' => [
// 'corp_id' => env('WECHAT_OPEN_WORK_CORP_ID', ''),
// 'provider_secret' => env('WECHAT_OPEN_WORK_SECRET', ''),
// 'token' => env('WECHAT_OPEN_WORK_TOKEN', ''),
// 'aes_key' => env('WECHAT_OPEN_WORK_AES_KEY', ''),
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* https://github.com/symfony/symfony/blob/6.0/src/Symfony/Contracts/HttpClient/HttpClientInterface.php#L26
*/
// 'http' => [
// 'timeout' => 5.0,
// // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
// 'base_uri' => 'https://api.weixin.qq.com/',
// ],
// ],
// ],
];

View File

@ -0,0 +1,31 @@
<?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('user_socialites', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->nullableMorphs('user');
$table->string('openid');
$table->json('data')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_socialites');
}
};

View File

@ -15,7 +15,6 @@ class KeywordSeeder extends Seeder
*/
public function run(): void
{
$now = now();
Keyword::truncate();
$list = [
@ -58,6 +57,7 @@ class KeywordSeeder extends Seeder
protected function createBanner()
{
$now = now();
Banner::truncate();
$banners = [
'banner_1' => [

View File

@ -18,10 +18,18 @@ class PartyUserSeeder extends Seeder
*/
public function run(): void
{
// PartyCate::truncate();
// PartyUser::truncate();
// (new PartyCateFactory())->count(20)->create();
// (new PartyUserFactory())->count(100)->create();
PartyCate::truncate();
PartyUser::truncate();
(new PartyCateFactory())->count(20)->create();
(new PartyUserFactory())->count(100)->create([
'scores' => [
'score_cate_1' => 0,
'score_cate_2' => 0,
'score_cate_3' => 0,
'score_cate_4' => 0,
'score_cate_5' => 0,
]
]);
UserScore::truncate();
UserScore::factory()->count(100)->create();

View File

@ -1,5 +0,0 @@
const toastElement = document.getElementById('toast')
if (toastElement) {
const toast = bootstrap.Toast.getOrCreateInstance(toastElement)
toast.show()
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,4 +38,6 @@ Route::group(['middleware' => 'auth:api'], function () {
Route::get('rank/current', [\App\Http\Controllers\Api\RankController::class, 'current']);
Route::get('rank/current-list', [\App\Http\Controllers\Api\RankController::class, 'currentList']);
Route::apiResource('user/score', \App\Http\Controllers\Api\UserScoreController::class);
});

View File

@ -13,4 +13,8 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::view('/', 'admin');
Route::redirect('/', '/admin');
Route::group(['middleware' => ['easywechat.oauth:default,snsapi_base']], function () {
Route::get('oauth', [App\Http\Controllers\WechatController::class, 'oauth']);
});