Commit a4671c53 a4671c53f6497be3923bd8359c08a222313b19c5 by 杨俊

feat(master): 初始化

0 parents
Showing 507 changed files with 4944 additions and 0 deletions
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
JPUSH_KEY=860a3d4ecfaf3c38eb539c4e
JPUSH_SECRET=df208c0eb485ed3ab3b18ff2
IM_KEY=
IM_SECRET=
WECHAT_OFFICIAL_ACCOUNT_APPID=
WECHAT_OFFICIAL_ACCOUNT_SECRET=
WECHAT_OFFICIAL_ACCOUNT_TOKEN=
WECHAT_OFFICIAL_ACCOUNT_AES_KEY=
WECHAT_MINI_APP_APPID=
WECHAT_MINI_APP_SECRET=
WECHAT_MINI_APP_TOKEN=
WECHAT_MINI_APP_AES_KEY=
OCTANE_SERVER=swoole
OCTANE_HTTPS=true
* text=auto
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore
/node_modules
/public/build
/public/vendor/app
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
supervisord.pid
/.fleet
/.idea
/.vscode
package-lock.json
# ChangeLog
### feat
* 调整telescope的访问权限 ([4664ae7](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/4664ae7))
* 歌曲分享,厂牌管理员 ([37fc0c9](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/37fc0c9))
* 所有Token接口增加用户状态过滤中间件 ([e8fa624](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/e8fa624))
* 【平台端】Banner浏览用户模块重构 ([bea107a](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/bea107a))
* 【平台端】明星标签配置首页筛选 ([c0df0e2](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/c0df0e2))
* 【平台端】系统推送通知对象调整 ([11ec662](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/11ec662))
* 【平台端】用户详情补充“注册来源” ([923ac36](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/923ac36))
* 【平台端】认证待审核补充“注册来源” ([87ef34f](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/87ef34f))
* 【平台端】用户详情补充“注册来源” ([d64e76d](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/d64e76d))
* 【平台端】认证待审核补充“注册来源” ([2d83fb8](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/2d83fb8))
* 【应用前台】增加推荐用户列表 ([874d2cf](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/874d2cf))
* 【应用前台】审核认证补充字段“来源渠道” ([7c1502a](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/7c1502a))
* 【应用前台】聊天列表系统通知,邀请注册消息 ([704e352](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/704e352))
* 【应用前台】用户注册 ([85d2a56](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/85d2a56))
* 【应用前台】用户注册短信申请 ([a0fc305](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/a0fc305))
* 【应用前台】Demo详情 ([96d9e53](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/96d9e53))
* 【应用前台】Demo库 ([b679b8b](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/b679b8b))
* 【应用前台】首页音乐人筛选(明星标签) ([f482258](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/f482258))
### fix
* 修复Banner异常 ([f3deedf](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/f3deedf))
* 修复普通权限用户浏览Banner异常bug ([92c42de](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/92c42de))
* 修复登录账号排序问题 ([892393c](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/892393c))
* 修复手机号登录显示区号不正确提示 ([ce3d123](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/ce3d123))
* 修复PHP发布JWT,Python无法通过 ([0892d1e](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/0892d1e))
* 修复邮件附件过大导致邮件无法发送bug ([7d0b5ee](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/7d0b5ee))
* 修复极光推送脚本参数名错误 ([278fa45](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/278fa45))
* 修复Demo详情分享数不正确 ([201dbbf](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/201dbbf))
* 修复线上groupMember接口app漏传pageSizebug,暂时调整默认分页数为30 ([2581fd4](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/2581fd4))
* 修复活动分享用户详情取消去重 ([f56c7ae](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/f56c7ae))
* 修复活动(试听|分享)用户标签数据错误 ([c018751](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/c018751))
* 修复音乐人列表star参数名 ([78bba46](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/78bba46))
* 修复厂牌主页下添加管理员日志记录错误bug ([8417c68](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/8417c68))
* 修复因为负载均衡无法获取真实IP问题 ([dcbd675](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/dcbd675))
### docs
* 2.0.6.sql ([0553910](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/0553910))
### refactor
* 重构Http Log记录方式 ([6c5454d](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/6c5454d))
### other
* 2.0.7 sql文件 ([b796e8b](http://gitlab.hikoon.com/hi-sing/serviceWorker/commit/b796e8b))
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
<?php
namespace App\Actions;
use App\Jobs\WechatTemplateMessageJob;
use App\Models\Activity;
use App\Models\SystemRole;
use App\Models\User;
use Carbon\Carbon;
use Lorisleiva\Actions\Concerns\AsAction;
use Lorisleiva\Actions\Concerns\AsJob;
class ActivityPublishApply
{
use AsAction, AsJob;
protected string $template = 'o9i6qtgr4ghtGvU-piimB9_mAaSOAXDDMzqx9zXYkss';
public function handle(Activity $activity): void
{
$activity->loadMissing('user:id,nick_name,real_name');
$roleIds = SystemRole::query()
->whereRelation('permissions', fn($query) => $query->where('guard', 'Admin')->where('system_permissions.name', 'audition-activity-audit-audit'))
->where('system_roles.status', 1)
->pluck('system_roles.id')->toArray();
User::query()
->join('user_has_roles', 'user_has_roles.user_id', 'users.id')
->whereIn('user_has_roles.role_id', $roleIds)
->where('users.status', 1)->where('users.official_status', 1)
->distinct()->pluck('users.official_id')
->each(fn($item) => WechatTemplateMessageJob::dispatch($item, $this->template, [
'first' => '厂牌提交试唱发布申请',
'keyword1' => '海星试唱提交了上架申请',
'keyword2' => sprintf('%s(%s)', data_get($activity, 'user.nick_name'), data_get($activity, 'user.real_name')),
'keyword3' => (string)data_get($activity, 'created_at', Carbon::now()->toDateTimeString()),
'keyword4' => sprintf('【%s】活动上架需要您的审核', $activity->getAttribute('song_name')),
'remark' => ''
]));
}
}
<?php
namespace App\Actions;
use App\Enums\ActivityWorkStatusEnum;
use App\Models\ActivityUser;
use App\Models\Views\ActivityWork;
use App\Notifications\ActivitySingerSubmitWorkRejectNotification;
use App\Notifications\ActivitySubmitWorkRejectNotification;
use Lorisleiva\Actions\Concerns\AsAction;
use Lorisleiva\Actions\Concerns\AsJob;
class ActivityWorkAuditFail
{
use AsAction, AsJob;
public function handle(ActivityWork $work): void
{
ActivityUser::query()->whereKey($work->getKey())->update(['status' => ActivityWorkStatusEnum::REJECT]);
$this->pushUserNotification($work);
$this->pushBusinessNotification($work);
}
/**
* @param \App\Models\Views\ActivityWork $work
* @return void
*/
private function pushUserNotification(ActivityWork $work): void
{
$work->user?->notify(new ActivitySubmitWorkRejectNotification($work));
}
/**
* @param \App\Models\Views\ActivityWork $work
* @return void
*/
private function pushBusinessNotification(ActivityWork $work): void
{
$work->business?->notify(new ActivitySingerSubmitWorkRejectNotification($work));
}
}
<?php
namespace App\Actions;
use App\Enums\ActivityStatusEnum;
use App\Enums\ActivityWorkStatusEnum;
use App\Enums\ActivityWorkTypeEnum;
use App\Enums\BrokerPushMatchRecordStatusEnum;
use App\Jobs\BrokerPushMatchRecordJob;
use App\Models\Activity;
use App\Models\ActivityUser;
use App\Models\BrokerPushConfig;
use App\Models\BrokerPushMatchRecord;
use App\Models\BrokerUserConfig;
use App\Models\BrokerUserConfigItem;
use App\Models\SystemConfig;
use App\Models\Views\ActivityWork;
use App\Notifications\ActivitySingerSubmitWorkPassNotification;
use App\Notifications\ActivitySubmitWorkPassNotification;
use App\Support\Model;
use DB;
use Lorisleiva\Actions\Concerns\AsAction;
class ActivityWorkAuditSuccess
{
use AsAction;
private int $brokerId;
private BrokerUserConfig|Model $brokerUserConfig;
private string $brokerUserLevel;
/**
* @param \App\Models\Views\ActivityWork $work
* @param string $remark
* @return void
*/
public function handle(ActivityWork $work): void
{
$this->brokerId = (int)$work->getAttribute('business_id');
DB::transaction(function () use ($work) {
Activity::query()->whereKey($work->getAttribute('activity_id'))
->update(['status' => ActivityStatusEnum::MATCH, 'match_at' => now()->toDateTimeString()]);
ActivityUser::query()->whereKey($work->getKey())
->update(['status' => ActivityWorkStatusEnum::SUCCESS, 'broker_id' => $this->brokerId, 'broker_level' => '']);
ActivityWork::query()->where('activity_id', $work->getAttribute('activity_id'))
->where('type', ActivityWorkTypeEnum::SUBMIT)->where('status', ActivityWorkStatusEnum::WAIT)
->eachById(fn(ActivityWork $activityWork) => ActivityWorkAuditFail::dispatch($activityWork));
});
$this->pushUserNotification($work);
$this->pushBusinessNotification($work);
}
/**
* @param \App\Models\Views\ActivityWork $work
* @param string $remark
* @return void
*/
private function pushUserNotification(ActivityWork $work): void
{
$work->user?->notify(new ActivitySubmitWorkPassNotification($work));
}
/**
* @param \App\Models\Views\ActivityWork $work
* @return void
*/
private function pushBusinessNotification(ActivityWork $work): void
{
$work->business?->notify(new ActivitySingerSubmitWorkPassNotification($work));
if ($config = $this->getUserConfig()) {
$level = $this->getUserLevel($config);
ActivityUser::query()->whereKey($work->getKey())->update(['broker_id' => $this->brokerId, 'broker_level' => $level]);
if ($pushConfig = BrokerPushConfig::query()->where('identifier', $level)->where('match_title', '!=', '')->first()) {
BrokerPushMatchRecord::created(static fn($record) => BrokerPushMatchRecordJob::dispatch($record));
BrokerPushMatchRecord::query()->create([
'broker_id' => $this->brokerId,
'broker_level' => str_replace('对应分类:', '', $level),
'user_id' => $work->getAttribute('user_id'),
'user_tag' => $work->user->authTags()->pluck('name')?->join('|') ?? '',
'activity_id' => $work->getAttribute('activity_id'),
'project_id' => $work->getAttribute('project_id'),
'title' => $pushConfig->getAttribute('match_title'),
'content' => str_replace(
['{border}', '{singer}', '{start}', '{end}', '{type}', '{brand}', '{song}'],
[
$work->business->getAttribute('nick_name'),
$work->user->getAttribute('nick_name'),
$config->getAttribute('begin_at'),
$config->getAttribute('end_at'),
str_replace('对应分类:', '', $level),
$work->project()->value('name'),
$work->getAttribute('activity_name')
],
$pushConfig->getAttribute('match_intro')
),
'send_at' => now()->toDateTimeString(),
'status' => BrokerPushMatchRecordStatusEnum::PROCESSING
]);
}
}
}
/**
* @return \App\Support\Model|null
*/
private function getUserConfig(): ?Model
{
return BrokerUserConfig::query()->where('begin_at', '<=', now()->toDateTimeString())->where('end_at', '>=', now()->toDateTimeString())->first();
}
/**
* @param \App\Support\Model $config
* @return string
*/
private function getUserLevel(Model $config): string
{
if (!$level = BrokerUserConfigItem::query()->where('config_id', $config->getKey())->where('user_id', $this->brokerId)->value('identifier')) {
$level = SystemConfig::query()->where('identifier', 'g65owmrgKSUhxV2afndUg')->value('content') ?? '';
$level = (empty($level) ? '' : '对应分类:' . $level);
}
return $level;
}
}
<?php
namespace App\Actions;
use App\Helpers\IMHelper;
use App\Models\User;
use Lorisleiva\Actions\Concerns\AsAction;
class UserRegisterAction
{
use AsAction;
public string $jobQueue = 'im';
/**
* @throws \JsonException
* @throws \Exception
*/
public function handle(User $user): void
{
$content = '您邀请的用户:' . $user->getAttribute('nick_name') . ',已完成账号注册。感谢您对平台的支持!';
IMHelper::sendSingleChatMessage(IMHelper::SYSTEM_USER, $user->getAttribute('inviter_id'), [
"MsgBody" => [
IMHelper::createCustomMessage([
"Desc" => $content,
"Data" => [
'businessID' => 'user_register',
'MsgType' => 'text',
'MsgEvent' => 'user_register',
'MsgAttribute' => ['Id' => $user->getKey(), 'NickName' => $user->getAttribute('nick_name')],
'Content' => $content,
],
])
],
"CloudCustomData" => [],
"OfflinePushInfo" => IMHelper::createOfflineMessage('系统消息', $content)
]);
}
}
<?php
namespace App\Channels;
use App\Jobs\JPushMessageJob;
use App\Models\User;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Arr;
class JPushNotificationChannel
{
public function send(User $notifiable, Notification $notification): void
{
// @phpstan-ignore-next-line
$message = $notification->toJPush($notifiable);
JPushMessageJob::dispatchSync(
Arr::get($message, 'user', []),
Arr::get($message, 'title', ''),
Arr::get($message, 'content', ''),
Arr::get($message, 'data', [])
);
}
}
<?php
namespace App\Channels;
use App\Jobs\SmsMessageJob;
use App\Models\User;
use Arr;
use Illuminate\Notifications\Notification;
class SmsNotificationChannel
{
public function send(User $notifiable, Notification $notification): void
{
// @phpstan-ignore-next-line
$message = $notification->toSms($notifiable);
$phone = Arr::get($message, 'phone');
$data = Arr::get($message, 'data', []);
SmsMessageJob::dispatchSync($phone, $data);
}
}
<?php
namespace App\Channels;
use App\Jobs\WechatTemplateMessageJob;
use App\Models\User;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Arr;
class WechatNotificationChannel
{
public function send(User $notifiable, Notification $notification): void
{
// @phpstan-ignore-next-line
$message = $notification->toWechat($notifiable);
WechatTemplateMessageJob::dispatchSync(
openId: Arr::get($message, 'openId', ''),
template: Arr::get($message, 'template', ''),
data: Arr::get($message, 'data', []),
url: Arr::get($message, 'url', ''),
page: Arr::get($message, 'page', '')
);
}
}
<?php
namespace App\Console\Commands;
use App\Enums\ActivityStatusEnum;
use App\Jobs\ActivityMakeMediaJob;
use App\Models\Activity;
use Illuminate\Console\Command;
class ActivityMediaFixCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'activity:Media';
/**
* The console command description.
*
* @var string
*/
protected $description = '修复歌曲音频文件';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$id = $this->ask('请输入修复歌曲ID?');
$status = $this->ask('请选择修复歌曲状态');
ActivityMakeMediaJob::dispatchSync(Activity::find($id), ActivityStatusEnum::tryFrom($status), 1);
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Enums\ActivityAuditStatusEnum;
use App\Enums\ActivityStatusEnum;
use App\Models\Activity;
use DB;
use Illuminate\Console\Command;
class ActivityStatusRecordCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'activity:status-record';
/**
* The console command description.
*
* @var string
*/
protected $description = '记录活动状态节点';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
DB::table('activity_status_records')->updateOrInsert(
['time' => today()->toDateString()],
[
'up' => Activity::query()->where('activitys.audit_status', ActivityAuditStatusEnum::SUCCESS)->where('activitys.status', ActivityStatusEnum::UP)->count(),
'down' => Activity::query()->where('activitys.audit_status', ActivityAuditStatusEnum::SUCCESS)->where('activitys.status', ActivityStatusEnum::DOWN)->count(),
'match' => Activity::query()->where('activitys.audit_status', ActivityAuditStatusEnum::SUCCESS)->where('activitys.status', ActivityStatusEnum::MATCH)->count(),
'send' => Activity::query()->where('activitys.audit_status', ActivityAuditStatusEnum::SUCCESS)->where('activitys.status', ActivityStatusEnum::SEND)->count(),
]
);
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Enums\BrokerPushLevelRecordStatusEnum;
use App\Jobs\BrokerPushLevelRecordSendJob;
use App\Models\BrokerPushLevelRecord;
use App\Models\BrokerUserConfig;
use App\Models\BrokerUserConfigItem;
use DB;
use Illuminate\Console\Command;
class BrokerUserConfigCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'broker:user-config {method}';
/**
* The console command description.
*
* @var string
*/
protected $description = '经纪人运营-用户配置';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$method = $this->argument('method');
switch ($method) {
case 'singer_num':
BrokerUserConfigItem::query()
->leftJoin('users', 'users.id', 'broker_user_config_items.user_id')
->where('config_id', BrokerUserConfig::query()->whereDate('begin_at', now()->toDateString())->value('id'))
->where('broker_user_config_items.status', 1)
->where('broker_user_config_items.is_sync', 0)
->update([
'users.business_singer_limit' => DB::raw('CASE WHEN broker_user_config_items.singer_num = "" THEN users.business_singer_limit ELSE broker_user_config_items.singer_num END'),
'broker_user_config_items.is_sync' => 1
]);
break;
case 'level_message':
BrokerPushLevelRecord::query()
->where('status', BrokerPushLevelRecordStatusEnum::WAITING)
->whereBetween('publish_at', [now()->subHour()->toDateTimeString(), now()->toDateTimeString()])
->eachById(fn(BrokerPushLevelRecord $record) => BrokerPushLevelRecordSendJob::dispatchSync($record));
break;
}
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Helpers\IMHelper;
use App\Http\Service\UserService;
use App\Models\GroupHasMember;
use App\Models\SystemConfig;
use Illuminate\Console\Command;
class GroupOut extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'group:out';
/**
* The console command description.
*
* @var string
*/
protected $description = '自动退出团队';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
GroupHasMember::with(['group:id,user_id', 'member:id,business_id'])->where('status', 2)
->where('updated_at', '<=', now()->subDays($this->getLimitDay()))
->eachById(function (GroupHasMember $member) {
$response = IMHelper::sendSingleChatMessage($member->getAttribute('member_id'), $member->getRelation('group')?->getAttribute('user_id') ?? 0, [
"MsgBody" => IMHelper::createOnlineMessage('TIMCustomElem', [
"Data" => ['businessID' => 'invite_tip', 'content' => '', 'text' => '自动退出团队', 'tipStatus' => 8, 'title' => '', 'version' => 4],
"Desc" => "notification", "Ext" => "url"
]),
"OfflinePushInfo" => IMHelper::createOfflineMessage('', '自动退出团队')
]);
UserService::deleteGroupMember($member->getRelation('member'));
info('自动退出团队', $response->json());
});
return self::SUCCESS;
}
public function getLimitDay()
{
return SystemConfig::query()->where('identifier', 'F_bk65qzuTXvyemFAl4DR')->value('content') ?? 7;
}
}
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class HttpLogClearCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'http-log:clear';
/**
* The console command description.
*
* @var string
*/
protected $description = '清理请求系统日志';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$this->line('clear start ...');
do {
$deleted = DB::table('system_http_logs')->where('created_at', '<', now()->subMonth()->toDateTimeString())->take(1000)->delete();
} while ($deleted !== 0);
$this->line('clear end ...');
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Helpers\IMHelper;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
class ImFixCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'im:fix';
/**
* The console command description.
*
* @var string
*/
protected $description = 'IM 用户同步';
/**
* Execute the console command.
*
* @return int
* @throws \Exception
*/
public function handle(): int
{
User::with(['business:id,nick_name,avatar', 'artTags:id,name'])->select(['id', 'nick_name', 'avatar', 'business_id', 'chat_mode', 'identity'])
->where('audit_status', 1)->chunkById(100, function (Collection $collection) {
$result = IMHelper::checkAccount($collection->modelKeys())->json('ResultItem', []);
$checkUser = Arr::pluck($result, 'AccountStatus', 'UserID');
$collection->each(function (User $user) use ($checkUser) {
if (isset($checkUser[IMHelper::userKeyToAccount($user->getKey())]) && $checkUser[IMHelper::userKeyToAccount($user->getKey())] === 'NotImported') {
$data = IMHelper::importSingleAccount($user->getKey(), [
'chat_mode' => $user->getAttribute('chat_mode'),
'nickName' => $user->getAttribute('nick_name'),
'business' => $user->getAttribute('business'),
'artTag' => $user->getAttribute('artTags'),
'identity' => $user->getAttribute('identity')
], $user->getAttribute('avatar') ?? '');
$this->info($user->getKey() . ':' . json_encode($data->json(), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE));
}
});
});
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Enums\NotificationStatusEnum;
use App\Jobs\SystemNotificationSendJob;
use App\Models\Notification;
use Illuminate\Console\Command;
class NotifyMonitorCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'notify:monitor';
/**
* The console command description.
*
* @var string
*/
protected $description = '监控系统推送消息';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
Notification::query()
->where('status', NotificationStatusEnum::WAITING)
->where('publish_type', 2)
->where('publish_at', '<=', now()->toDateTimeString())
->eachById(fn(Notification $notification) => SystemNotificationSendJob::dispatchSync($notification));
return self::SUCCESS;
}
}
<?php
namespace App\Console\Commands;
use App\Http\Service\ImSign;
use App\Http\Service\ImSendService;
use App\Models\Pivots\ProjectMemberPivot;
use App\Models\Pivots\UserProjectPivot;
use App\Models\Project;
use App\Models\SystemConfig;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Auth;
class ProjectOut extends Command
{
protected $signature = 'project:out';
/**
* The console command description.
*
* @var string
*/
protected $description = '自动退出厂牌';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$members = ProjectMemberPivot::query()->where('status', 3)->where('updated_at', '<=', now()->subDays($this->getLimitDay()))->get();
foreach ($members as $member){
$MsgBody = [
[
"MsgType" => "TIMCustomElem",
"MsgContent" => [
"Data" => "{\"businessID\": \"system_notice\",\"text\": \"厂牌成员提醒\",\"title\": \"厂牌成员提醒\",\"content\": \"到期自动退出厂牌\",\"version\": 4}",
"Desc" => "notification",
"Ext" => "url",
"Sound" => "dingdong.aiff"
]
]
];
$OfflinePushInfo = [
"PushFlag" => 0,
"Title" =>'厂牌提醒',
"Desc" => "到期自动退出厂牌",
"Ext" => "到期自动退出厂牌",
"AndroidInfo" => [
"Sound" => "android.mp3"
],
"ApnsInfo" => [
"Sound" => "apns.mp3",
"BadgeMode" => 1,
"Image" => "www.image.com"
]
];
$sendData = ['MsgBody'=>$MsgBody,'OfflinePushInfo'=>$OfflinePushInfo];
$getSign = new ImSign();
$sendService = new ImSendService();
$sign = $getSign->genUserSig('administrator');
$sendService->batchSend($sign,[$member->user_id],"system",$sendData,2);
$member->delete();
}
return self::SUCCESS;
}
public function getLimitDay()
{
return SystemConfig::query()->where('identifier', 'i9V0wD_FitcGXR3HxUK5C')->value('content') ?? 7;
}
}
<?php
namespace App\Console\Commands;
use App\Helpers\ConfigHelper;
use App\Models\SystemConfig;
use App\Models\User;
use Illuminate\Console\Command;
class SyncBusinessDemoCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'sync:businessDemo';
/**
* The console command description.
*
* @var string
*/
protected $description = '同步经纪人Demo权限';
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
SystemConfig::query()->where('identifier', ConfigHelper::DEMO_USER_KEY)->update([
'content' => User::query()->whereIn('identity', [2, 3])->pluck('id')->implode(',') ?? ''
]);
return self::SUCCESS;
}
}
<?php
namespace App\Console;
use App\Console\Commands\ActivityStatusRecordCommand;
use App\Console\Commands\BrokerUserConfigCommand;
use App\Console\Commands\GroupOut;
use App\Console\Commands\HttpLogClearCommand;
use App\Console\Commands\NotifyMonitorCommand;
use App\Console\Commands\ProjectOut;
use App\Console\Commands\SyncBusinessDemoCommand;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan;
/**
* @method comment(string $quote)
*/
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule): void
{
$schedule->command('telescope:prune --hours=336')->daily()->onOneServer();
$schedule->command('queue:prune-batches --hours=72')->daily()->onOneServer();
$schedule->command(SyncBusinessDemoCommand::class)->everyMinute()->onOneServer();
$schedule->command(HttpLogClearCommand::class)->withoutOverlapping(60)->lastDayOfMonth('03:00')->onOneServer();
$schedule->command(GroupOut::class)->everyOddHour()->onOneServer();
$schedule->command(ProjectOut::class)->everyOddHour()->onOneServer();
$schedule->command(ActivityStatusRecordCommand::class)->dailyAt('23:55')->onOneServer();
$schedule->command(NotifyMonitorCommand::class)->withoutOverlapping(5)->everyMinute()->onOneServer();
$schedule->command(BrokerUserConfigCommand::class, ['singer_num'])->dailyAt('01:00')->onOneServer();
$schedule->command(BrokerUserConfigCommand::class, ['level_message'])->withoutOverlapping(5)->everyMinute()->onOneServer();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');
Artisan::command('inspire', function () {
$this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote');
}
}
<?php
namespace App\Enums;
enum ActivityAuditStatusEnum: int
{
case PROCESS = 0;
case SUCCESS = 1;
case FAIL = 2;
public function label(): string
{
return match ($this) {
self::PROCESS => '处理中',
self::SUCCESS => '通过',
self::FAIL => '不通过',
};
}
}
<?php
namespace App\Enums;
enum ActivityCreateFormEnum: int
{
case PROJECT = 0;
case ADMIN = 1;
case USER = 2;
}
<?php
namespace App\Enums;
enum ActivitySongTypeEnum: int
{
case SONG = 1;
case DEMO = 2;
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::SONG => '歌曲',
self::DEMO => 'Demo',
default => '未知'
};
}
}
<?php
namespace App\Enums;
enum ActivityStatusEnum: int
{
case PROCESS = 0;
case UP = 1;
case DOWN = 2;
case MATCH = 3;
case FAIL = 4;
case SEND = 5;
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::PROCESS => '处理中',
self::UP => '上架',
self::DOWN => '下架',
self::MATCH => '已匹配',
self::FAIL => '处理失败',
self::SEND => '已发行'
};
}
}
<?php
namespace App\Enums;
enum ActivityWorkModeEnum: int
{
case ONLINE = 0;
case OFFLINE = 1;
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::ONLINE => '在线演唱',
self::OFFLINE => '自主上传'
};
}
}
<?php
namespace App\Enums;
enum ActivityWorkSingTypeEnum: string
{
case FULL = 'Full';
case PART = 'Part';
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::FULL => '唱整首',
self::PART => '唱片段'
};
}
}
<?php
namespace App\Enums;
enum ActivityWorkStatusEnum: int
{
case WAIT = 0;
case SUCCESS = 1;
case REJECT = 2;
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::WAIT => '待采纳',
self::SUCCESS => '已采纳',
self::REJECT => '不合适',
};
}
}
<?php
namespace App\Enums;
enum ActivityWorkTypeEnum: string
{
case SUBMIT = 'Submit';
case SAVE = 'Save';
/**
* @return string
*/
public function label(): string
{
return match ($this) {
self::SUBMIT => '提交',
self::SAVE => '保存'
};
}
}
<?php
namespace App\Enums;
enum BannerStatusEnum: int
{
case DISABLE = 0;
case ACTIVATE = 1;
public function label(): string
{
return match ($this) {
self::DISABLE => '下架',
self::ACTIVATE => '上架',
};
}
}
<?php
namespace App\Enums;
enum BrokerPushLevelRecordStatusEnum: int
{
case FAIL = -1;
case WAITING = 0;
case PROCESSING = 1;
case SUCCESS = 2;
case ROLLBACK = 3;
public function label(): string
{
return match ($this) {
self::WAITING => '待发送',
self::PROCESSING => '发送中',
self::SUCCESS => '已发送',
self::FAIL => '发送失败',
self::ROLLBACK => '已撤销',
};
}
}
<?php
namespace App\Enums;
enum BrokerPushMatchRecordStatusEnum: int
{
case FAIL = -1;
case PROCESSING = 0;
case SUCCESS = 1;
case ROLLBACK = 2;
public function label(): string
{
return match ($this) {
self::PROCESSING => '发送中',
self::SUCCESS => '已发送',
self::FAIL => '发送失败',
self::ROLLBACK => '已撤销',
};
}
}
<?php
namespace App\Enums;
enum NotificationStatusEnum: int
{
case CANCEL = -2;
case FAIL = -1;
case WAITING = 0;
case PROCESSING = 1;
case SUCCESS = 2;
case ROLLBACK = 3;
public function label(): string
{
return match ($this) {
self::WAITING => '待发送',
self::PROCESSING => '发送中',
self::SUCCESS => '已发送',
self::FAIL => '发送失败',
self::CANCEL => '发送取消',
self::ROLLBACK => '已撤销',
};
}
}
<?php
namespace App\Enums;
enum RoleStatusEnum: int
{
case DISABLE = 0;
case ACTIVATE = 1;
public function label(): string
{
return match ($this) {
self::DISABLE => '禁用',
self::ACTIVATE => '启用',
};
}
}
<?php
namespace App\Enums;
enum UserOfficialStatusEnum: int
{
case SUBSCRIBE = 1;
case UN_SUBSCRIBE = 0;
public function label(): string
{
return match ($this) {
self::SUBSCRIBE => '已关注',
self::UN_SUBSCRIBE => '未关注',
};
}
}
<?php
namespace App\Enums;
enum UserScopeEnum: int
{
case UNSET = 0;
case SYSTEM = 1;
case PROJECT = 2;
public function label(): string
{
return match ($this) {
self::UNSET => '无',
self::SYSTEM => '平台管理员',
self::PROJECT => '厂牌管理员'
};
}
}
<?php
namespace App\Enums;
enum UserSexEnum: int
{
case UNSET = 0;
case MALE = 1;
case FEMALE = 2;
public function label(): string
{
return match ($this) {
self::UNSET => '无',
self::MALE => '男',
self::FEMALE => '女'
};
}
}
<?php
namespace App\Enums;
enum UserStatusEnum: int
{
case DISABLE = 0;
case ACTIVATE = 1;
case UNSET = 2;
public function label(): string
{
return match ($this) {
self::DISABLE => '禁用',
self::ACTIVATE => '启用',
self::UNSET => '注销'
};
}
}
<?php
namespace App\Excel;
use App\Support\ExcelExport;
class ActivityDemoExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->with(['project:id,name', 'user:id,real_name,nick_name,identity', 'tags:id,name', 'links:id,nick_name,real_name']);
$this->builder->withCount(['viewUsers', 'collectionUsers']);
$this->setHeader(['歌曲名称', '曲风标签', '关联厂牌', '词作者', '曲作者', '试听人数', '收藏人数', '创建人艺名', '创建人真名', '创建时间', '通过时间 ', '状态']);
$this->setColumns(['A:G' => 18, 'H:M' => 20]);
}
protected function setData($item): array
{
$lyricistIds = data_get($item, 'expand.lyricist.ids', []);
$lyricistNames = data_get($item, 'expand.lyricist.supplement', []);
$composerIds = data_get($item, 'expand.composer.ids', []);
$composerNames = data_get($item, 'expand.composer.supplement', []);
return [
$item->song_name,
$item->getRelation('tags')?->implode('name', ',') ?? '',
$item->getRelation('project')?->getAttribute('name') ?? '无关联厂牌',
$item->getRelation('links')->whereIn('id', $lyricistIds)->pluck('nick_name')?->unique()?->merge($lyricistNames)?->implode(','),
$item->getRelation('links')->whereIn('id', $composerIds)->pluck('nick_name')?->unique()?->merge($composerNames)?->implode(','),
$item->view_users_count ?? 0,
$item->collection_users_count ?? 0,
$item->getRelation('user')?->getAttribute('nick_name'),
$item->getRelation('user')?->getAttribute('real_name'),
(string)$item->created_at,
(string)$item->audit_at,
$item->status?->label(),
];
}
}
<?php
namespace App\Excel;
use App\Models\SystemConfig;
use App\Support\ExcelExport;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class ActivityExcel extends ExcelExport
{
protected Collection $config;
protected function beforeHandle(): void
{
$this->builder->with(['project:id,name', 'user:id,real_name,nick_name,identity', 'tags:id,name', 'submitUsers:id,nick_name,real_name', 'links:id,nick_name,real_name']);
$this->builder->withCount(['viewUsers', 'collectionUsers', 'submitUsers']);
$this->setHeader(['歌曲名称', '推荐描述', '曲风标签', '歌曲语种', '歌曲速度', '关联厂牌', '词作者', '曲作者', '编曲', '性别要求', '预期发行日期', '试听人数', '收藏人数', '提交人数', '提交用户', '创建人艺名', '创建人真名', '创建时间', '通过时间 ', '确认时间', '匹配时间', '状态']);
$this->setColumns(['A:I' => 18, 'J:U' => 20]);
$this->config = SystemConfig::query()
->whereHas('parent', fn(Builder $builder) => $builder->whereIn('identifier', ['activity_lang', 'activity_speed', 'activity_sex']))
->pluck('name', 'identifier');
}
protected function setData($item): array
{
$lyricistIds = data_get($item, 'expand.lyricist.ids', []);
$lyricistNames = data_get($item, 'expand.lyricist.supplement', []);
$composerIds = data_get($item, 'expand.composer.ids', []);
$composerNames = data_get($item, 'expand.composer.supplement', []);
$arrangerIds = data_get($item, 'expand.arranger.ids', []);
$arrangerNames = data_get($item, 'expand.arranger.supplement', []);
$lang = data_get($item, 'lang', []);
return [
$item->song_name,
$item->sub_title,
$item->getRelation('tags')?->implode('name', ',') ?? '',
$this->config->only($lang)?->implode(','),
$this->config->get($item->speed),
$item->getRelation('project')?->getAttribute('name') ?? '无关联厂牌',
$item->getRelation('links')->whereIn('id', $lyricistIds)->pluck('nick_name')?->unique()?->merge($lyricistNames)?->implode(','),
$item->getRelation('links')->whereIn('id', $composerIds)->pluck('nick_name')?->unique()?->merge($composerNames)?->implode(','),
$item->getRelation('links')->whereIn('id', $arrangerIds)->pluck('nick_name')?->unique()?->merge($arrangerNames)?->implode(','),
$this->config->get($item->sex),
$item->estimate_release_at,
$item->view_users_count ?? 0,
$item->collection_users_count ?? 0,
$item->submit_users_count ?? 0,
$item->getRelation('submitUsers')?->implode('nick_name', ','),
$item->getRelation('user')?->getAttribute('nick_name'),
$item->getRelation('user')?->getAttribute('real_name'),
(string)$item->created_at,
(string)$item->audit_at,
(string)$item->match_at,
$item->match_day . ' 天',
$item->status?->label(),
];
}
}
<?php
namespace App\Excel;
use App\Support\ExcelExport;
class ActivityReListRecordExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->with(['activity:id,song_name', 'project:id,name', 'user:id,nick_name', 'broker:id,nick_name', 'operator:id,nick_name']);
$this->setHeader(['歌曲ID', '歌曲名', '歌曲厂牌', '原合作用户ID', '原合作用户', '经纪人', '经纪人分类', '操作人', '操作时间'], 'A1:I1');
$this->setColumns(['A:I' => 28]);
}
protected function setData($item): array
{
return [
$item->activity?->id,
$item->activity?->song_name,
$item->project?->name,
$item->user?->id,
$item->user?->nick_name,
$item->broker?->nick_name,
$item->broker_level ?? '无',
$item->operator?->nick_name,
(string)$item->created_at
];
}
}
<?php
namespace App\Excel;
use App\Models\SystemConfig;
use App\Support\ExcelExport;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class ActivitySongExcel extends ExcelExport
{
protected Collection $config;
protected function beforeHandle(): void
{
$this->builder->with(['project:id,name', 'user:id,real_name,nick_name,identity', 'tags:id,name', 'submitUsers:id,nick_name,real_name', 'links:id,nick_name,real_name']);
$this->builder->withCount(['viewUsers', 'collectionUsers', 'submitUsers']);
$this->setHeader(['歌曲名称', '推荐描述', '曲风标签', '歌曲语种', '歌曲速度', '关联厂牌', '词作者', '曲作者', '编曲', '性别要求', '预期发行日期', '试听人数', '收藏人数', '提交人数', '提交用户', '创建人艺名', '创建人真名', '创建时间', '通过时间 ', '确认时间', '匹配时间', '状态']);
$this->setColumns(['A:I' => 18, 'J:U' => 20]);
$this->config = SystemConfig::query()
->whereHas('parent', fn(Builder $builder) => $builder->whereIn('identifier', ['activity_lang', 'activity_speed', 'activity_sex']))
->pluck('name', 'identifier');
}
protected function setData($item): array
{
$lyricistIds = data_get($item, 'expand.lyricist.ids', []);
$lyricistNames = data_get($item, 'expand.lyricist.supplement', []);
$composerIds = data_get($item, 'expand.composer.ids', []);
$composerNames = data_get($item, 'expand.composer.supplement', []);
$arrangerIds = data_get($item, 'expand.arranger.ids', []);
$arrangerNames = data_get($item, 'expand.arranger.supplement', []);
$lang = data_get($item, 'lang', []);
return [
$item->song_name,
$item->sub_title,
$item->getRelation('tags')?->implode('name', ',') ?? '',
$this->config->only($lang)?->implode(','),
$this->config->get($item->speed),
$item->getRelation('project')?->getAttribute('name') ?? '无关联厂牌',
$item->getRelation('links')->whereIn('id', $lyricistIds)->pluck('nick_name')?->unique()?->merge($lyricistNames)?->implode(','),
$item->getRelation('links')->whereIn('id', $composerIds)->pluck('nick_name')?->unique()?->merge($composerNames)?->implode(','),
$item->getRelation('links')->whereIn('id', $arrangerIds)->pluck('nick_name')?->unique()?->merge($arrangerNames)?->implode(','),
$this->config->get($item->sex),
$item->estimate_release_at,
$item->view_users_count ?? 0,
$item->collection_users_count ?? 0,
$item->submit_users_count ?? 0,
$item->getRelation('submitUsers')?->implode('nick_name', ','),
$item->getRelation('user')?->getAttribute('nick_name'),
$item->getRelation('user')?->getAttribute('real_name'),
(string)$item->created_at,
(string)$item->audit_at,
(string)$item->match_at,
$item->match_day . ' 天',
$item->status?->label(),
];
}
}
<?php
namespace App\Excel;
use App\Enums\ActivityWorkStatusEnum;
use App\Support\ExcelExport;
class ActivityWorkExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->with(['user:id,nick_name,real_name,email,company,rate', 'business:id,nick_name,real_name', 'share:id,nick_name,real_name']);
$this->setHeader(['艺名', '真名', '邮箱', '经济公司', '分成模式', '关联队长', '关联分享人', '参与方式', '类型', '状态', '保存/提交时间', 'Demo地址']);
$this->setColumns(['A:K' => 18, 'L:L' => 100]);
}
protected function setData($item): array
{
return [
$item->getRelation('user')?->getAttribute('nick_name'),
$item->getRelation('user')?->getAttribute('real_name'),
$item->getRelation('user')?->getAttribute('email'),
$item->getRelation('user')?->getAttribute('company'),
$item->getRelation('user')?->getAttribute('rate'),
$item->getRelation('business')?->getAttribute('nick_name'),
$item->getRelation('share')?->getAttribute('nick_name'),
$item->getAttribute('mode')?->label(),
$item->getAttribute('type')?->label(),
$item->getAttribute('status')?->label(),
$item->getAttribute('status') === ActivityWorkStatusEnum::SUCCESS ? (string)$item->getAttribute('submit_at') : (string)$item->getAttribute('created_at'),
$item->getAttribute('demo_url'),
];
}
}
<?php
namespace App\Excel;
use App\Enums\BrokerPushMatchRecordStatusEnum;
use App\Support\ExcelExport;
class BrokerPushMatchRecordExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->with(['activity:id,song_name', 'project:id,name', 'user:id,nick_name', 'broker:id,nick_name']);
$this->setHeader(['经纪人ID', '经纪人', '分类', '试唱用户ID', '试唱用户', '试唱用户认证', '歌曲厂牌', '关联歌曲ID', '关联歌曲', '通知内容', '发送时间', '发送状态', '用户行为'], 'A1:M1');
$this->setColumns(['A:E' => 20, 'F:K' => 32, 'L:M' => 20]);
}
protected function setData($item): array
{
return [
$item->broker?->id,
$item->broker?->nick_name,
$item->broker_level,
$item->user?->id,
$item->user?->nick_name,
$item->user_tag,
$item->project?->name,
$item->activity?->id,
$item->activity?->song_name,
$item->content,
$item->send_at,
BrokerPushMatchRecordStatusEnum::tryFrom($item->status)?->label(),
$this->formatReadStatus($item->read_at)
];
}
/**
* @param $status
* @return string
*/
protected function formatReadStatus($status): string
{
return is_null($status) ? '未查看' : '已查看';
}
}
<?php
namespace App\Excel;
use App\Enums\UserSexEnum;
use App\Support\ExcelExport;
use Illuminate\Support\Arr;
class BusinessExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->addSelect(['company'])->with(['styleTags:id,name', 'authTags:id,name']);
$this->setHeader([
'用户艺名', '用户真名', '性别', '用户邮箱', '手机号码', '注册时间', '认证通过时间', '用户权限',
'音乐人能力', '是否关注服务号', '公司', '常居地', '擅长的曲风', '状态'
]);
$this->setColumns(['A:N' => 20]);
}
protected function setData($item): array
{
return Arr::map([
$item->getAttribute('nick_name'),
$item->getAttribute('real_name'),
UserSexEnum::tryFrom($item->getAttribute('sex'))?->label(),
$item->getAttribute('email'),
sprintf('(%s)%s', $item->getFullPhone()->getPrefixedIDDCode('+'), $item->getFullPhone()->getNumber()),
(string)$item->getAttribute('created_at'),
(string)$item->getAttribute('audit_at'),
$item->getAttribute('scope')?->label(),
$item->getRelation('authTags')?->implode('name', '|'),
$item->getAttribute('official_status')?->label(),
$item->getAttribute('company'),
$item->getFullAddress(),
$item->getRelation('styleTags')?->implode('name', ','),
$item->getAttribute('status')?->label(),
], static fn($value) => empty($value) ? '无' : $value);
}
}
<?php
namespace App\Excel;
use App\Support\ExcelExport;
class ProjectExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->with('master:id,nick_name');
$this->setHeader(["厂牌名称", '主理人', "厂牌管理员人数", "厂牌成员人数", "上架歌曲数", "已匹配歌曲数", '已发行歌曲数']);
$this->setColumns(['A:B' => 32, 'C:I' => 24]);
}
protected function setData($item): array
{
return [
$item->name,
$item->getRelation('master')?->getAttribute('nick_name'),
$item->manage_count ?? 0,
$item->member_count ?? 0,
$item->activity_up_count ?? 0,
$item->activity_match_count ?? 0,
$item->activity_send_count ?? 0,
];
}
}
<?php
namespace App\Excel;
use App\Enums\UserSexEnum;
use App\Support\ExcelExport;
use Illuminate\Support\Arr;
class SingerExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->addSelect(['company'])->with(['styleTags:id,name', 'authTags:id,name']);
$this->setHeader([
'用户艺名', '用户真名', '性别', '用户邮箱', '手机号码', '注册时间', '认证通过时间',
'队长艺名', '队长真名', '用户权限', '音乐人能力', '是否关注服务号', '公司', '常居地', '擅长的曲风', '状态'
]);
$this->setColumns(['A:P' => 20]);
}
protected function setData($item): array
{
return Arr::map([
$item->getAttribute('nick_name'),
$item->getAttribute('real_name'),
UserSexEnum::tryFrom($item->getAttribute('sex'))?->label(),
$item->getAttribute('email'),
sprintf('(%s)%s', $item->getFullPhone()->getPrefixedIDDCode('+'), $item->getFullPhone()->getNumber()),
(string)$item->getAttribute('created_at'),
(string)$item->getAttribute('audit_at'),
$item->getRelation('business')?->getAttribute('nick_name'),
$item->getRelation('business')?->getAttribute('real_name'),
$item->getAttribute('scope')?->label(),
$item->getRelation('authTags')?->implode('name', '|'),
$item->getAttribute('official_status')?->label(),
$item->getAttribute('company'),
$item->getFullAddress(),
$item->getRelation('styleTags')?->implode('name', ','),
$item->getAttribute('status')?->label(),
], static fn($value) => empty($value) ? '无' : $value);
}
}
<?php
namespace App\Excel;
use App\Enums\UserSexEnum;
use App\Support\ExcelExport;
use Illuminate\Support\Arr;
class UserRegisterExcel extends ExcelExport
{
protected function beforeHandle(): void
{
$this->builder->addSelect(['company'])->with(['styleTags:id,name']);
$this->setHeader(['用户艺名', '用户真名', '性别', '用户邮箱', '手机号码', '注册时间', '用户权限', '是否关注公众号', '公司', '常居地', '擅长的曲风', '状态']);
$this->setColumns(['A:L' => 20]);
}
protected function setData($item): array
{
return Arr::map([
$item->getAttribute('nick_name'),
$item->getAttribute('real_name'),
UserSexEnum::tryFrom($item->getAttribute('sex'))?->label(),
$item->getAttribute('email'),
sprintf('(%s)%s', $item->getFullPhone()->getPrefixedIDDCode('+'), $item->getFullPhone()->getNumber()),
(string)$item->getAttribute('created_at'),
$item->getAttribute('scope')?->label(),
$item->getAttribute('official_status')?->label(),
$item->getAttribute('company'),
$item->getFullAddress(),
$item->getRelation('styleTags')?->implode('name', ',') ?? '无',
$item->getAttribute('status')?->label(),
], static fn($value) => empty($value) ? '无' : $value);
}
}
<?php
namespace App\Exceptions;
use App\Helpers\JsonResource;
use App\Support\BusinessCode;
use Hikoon\LaravelApi\Exceptions\ValidationException;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiResponse;
use Hikoon\LaravelJwt\Exceptions\TokenExpiredException;
use Hikoon\LaravelJwt\Exceptions\TokenInvalidException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
ValidationException::class,
TokenExpiredException::class,
UserStatusException::class
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register(): void
{
$this->reportable(static function (Throwable $e) {
});
}
/**
* @param $request
* @param \Throwable $e
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
* @throws \Hikoon\LaravelApi\Exceptions\ValidationException
* @throws \Throwable
*/
public function render($request, Throwable $e): \Illuminate\Http\Response|JsonResponse|Response
{
if ($e instanceof \Illuminate\Validation\ValidationException) {
throw new ValidationException(ApiCode::VALIDATION_ERROR, $e->getMessage());
}
if ($e instanceof UserStatusException) {
throw new AuthenticationException('账号已被禁用/注销');
}
return parent::render($request, $e);
}
/**
* @param $request
* @param \Throwable $e
* @return \Illuminate\Http\JsonResponse
*/
protected function prepareJsonResponse($request, Throwable $e): JsonResponse
{
if ($request->is('manage/*') || $request->is('admin/*') || $request->is('user/*')) {
return match (true) {
$e instanceof \Illuminate\Validation\ValidationException => ApiResponse::fail(ApiCode::VALIDATION_ERROR, $e->getMessage()->errors()->first()),
$e instanceof TokenExpiredException, $e instanceof TokenInvalidException => ApiResponse::fail(BusinessCode::AUTHORIZED_EXPIRED_ERROR),
$e instanceof HttpException && $e->getStatusCode() === 503 => ApiResponse::fail(ApiCode::SERVICE_UNAVAILABLE_ERROR),
$e instanceof NotFoundHttpException => ApiResponse::fail(ApiCode::NOT_FOUND_ERROR),
$e instanceof MethodNotAllowedHttpException => ApiResponse::fail(ApiCode::METHOD_NOT_ALLOWED_ERROR),
$e instanceof ThrottleRequestsException => ApiResponse::fail(ApiCode::REQUEST_LIMIT_ERROR),
default => ApiResponse::make(Arr::except($this->convertExceptionToArray($e), 'message'))->toFail(ApiCode::SERVICE_ERROR)
};
}
if ($request->is('app/*')) {
return match (true) {
$e instanceof TokenExpiredException, $e instanceof TokenInvalidException => JsonResource::unauthenticated(JsonResource::AUTHORIZED_EXP),
$e instanceof MethodNotAllowedHttpException => JsonResource::fail('无效的请求方式'),
$e instanceof ThrottleRequestsException => JsonResource::fail('请求太频繁,请稍后再访问'),
$e instanceof NotFoundHttpException => JsonResource::fail('无法找到对应资源'),
default => JsonResource::error('服务器繁忙,请稍后再尝试...')
};
}
return parent::prepareJsonResponse($request, $e);
}
/**
* @inheritDoc
*/
protected function unauthenticated($request, AuthenticationException $exception): Response
{
if ($request->acceptsJson()) {
if ($request->is('manage/*') || $request->is('admin/*') || $request->is('user/*')) {
return ApiResponse::fail(ApiCode::UNAUTHORIZED_ERROR, $exception->getMessage())->setStatusCode(401);
}
if ($request->is('app/*')) {
return JsonResource::unauthenticated($exception->getMessage() ?? JsonResource::UNAUTHORIZED);
}
}
return parent::unauthenticated($request, $exception);
}
}
<?php
namespace App\Exceptions;
use App\Models\User;
use Exception;
use Throwable;
class SingerLimitException extends Exception
{
public User $user;
public int $masterId;
public int $maxSingerNum;
public function __construct(User $user, int $masterId, int $maxSingerNum, string $message = "", int $code = 0, ?Throwable $previous = NULL)
{
$this->user = $user;
$this->masterId = $masterId;
$this->maxSingerNum = $maxSingerNum;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace App\Exceptions;
use Exception;
class UserStatusException extends Exception
{
}
<?php
namespace App\Helpers;
use App\Models\Activity;
use App\Models\SystemConfig;
use Arr;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection as SupportCollection;
use Str;
class ConfigHelper
{
public const DEMO_TAG_KEY = 'CtGly-jsLjAu5yJvLrN7L';
public const DEMO_USER_KEY = 'n3HCbruqL_DemfX9MHj-K';
public const LISTEN_USER_KEY = '-BIGBAR9rPOMwCrw4hIuQ';
public const SING_USER_KEY = 'Jy-zgPbcSXSGnvHIx8moq';
public const SINGER_TAG_KEY = 'auth_singer_tag';
public const BUSINESS_SINGER_LIMIT = 'svMC9TNFz2Acx8ccPdAIH';
public const INFO_SCORE_LIMIT_KEY = 'iymo-wZcV2F0UWFUKJR5m';
public const INFO_SCORE_TEXT_KEY = 'YmSNBXwMB6cUo-zRT26s-';
public const INFO_SCORE_CONTENT_KEY = 'TeZnnaB-Pfwyz8IADs0SM';
/**
* @param string $identifier
* @param string $column
* @return ?string
*/
public static function getContent(string $identifier, string $column = 'content'): ?string
{
return SystemConfig::withoutGlobalScope('show')->where('status', 1)->where('identifier', $identifier)->value($column);
}
/**
* @param string|array $identifier
* @param string $column
* @return \Illuminate\Support\Collection
*/
public static function getList(string|array $identifier, string $column = 'content'): SupportCollection
{
return SystemConfig::withoutGlobalScope('show')->where('status', 1)->whereIn('identifier', Arr::wrap($identifier))->pluck($column, 'identifier');
}
/**
* @param string|array $identifier
* @param string $column
* @return \Illuminate\Support\Collection
*/
public static function getChildrenList(string|array $identifier, string $column = 'content'): SupportCollection
{
return SystemConfig::withoutGlobalScope('show')->parentKey($identifier)->where('status', 1)->pluck($column, 'identifier');
}
/**
* 获取歌手标签Ids
* @return array
*/
public static function getSingerTagIds(): array
{
return array_filter(explode(',', self::getContent(self::SINGER_TAG_KEY)));
}
/**
* 获取Demo标签Ids
* @return array
*/
public static function getDemoTagIds(): array
{
return array_filter(explode(',', self::getContent(self::DEMO_TAG_KEY)));
}
/**
* 获取Demo白名单用户Ids
* @return array
*/
public static function getDemoUserIds(): array
{
return array_filter(explode(',', self::getContent(self::DEMO_USER_KEY)));
}
/**
* @return \Illuminate\Support\Collection
*/
public static function getDemoList(): SupportCollection
{
return self::getList([self::DEMO_TAG_KEY, self::DEMO_USER_KEY])?->map(fn($item) => array_filter(explode(',', $item)));
}
/**
* 获取试听权限白名单用户Ids
* @return array
*/
public static function getListenUserIds(): array
{
return array_filter(explode(',', self::getContent(self::LISTEN_USER_KEY)));
}
/**
* 获取试听&试唱权限白名单用户Ids
* @return array
*/
public static function getSingUserIds(): array
{
return array_filter(explode(',', self::getContent(self::SING_USER_KEY)));
}
/**
* 获取试听与试唱白名单Ids
* @return \Illuminate\Support\Collection
*/
public static function getListenAndSingUserList(): SupportCollection
{
return self::getList([self::LISTEN_USER_KEY, self::SING_USER_KEY])?->map(fn($item) => array_filter(explode(',', $item)));
}
/**
* @return int
*/
public static function getProjectLimitDay(): int
{
return intval(self::getContent('i9V0wD_FitcGXR3HxUK5C')) ?? 7;
}
/**
* @return int
*/
public static function getBusinessSingerLimit(): int
{
return intval(self::getContent(self::BUSINESS_SINGER_LIMIT)) ?? 3;
}
/**
* @return array
*/
public static function getTelescopeIpList(): array
{
return array_filter(explode(',', self::getContent('telescope_ip')));
}
/**
* @param array|int $tagIds
* @param array|null $singerTagIds
* @return bool
*/
public static function hasSingerTag(array|int $tagIds, array $singerTagIds = NULL): bool
{
return filled(array_intersect(Arr::wrap($tagIds), $singerTagIds ?? self::getSingerTagIds()));
}
/**
* @param array|int $tagIds
* @param array|NULL $demoTagIds
* @return bool
*/
public static function hasDemoTag(array|int $tagIds, array $demoTagIds = NULL): bool
{
return filled(array_intersect(Arr::wrap($tagIds), $demoTagIds ?? self::getDemoTagIds()));
}
/**
* @return \Illuminate\Support\Collection
*/
public static function getInfoScore(): SupportCollection
{
return self::getList([self::INFO_SCORE_LIMIT_KEY, self::INFO_SCORE_TEXT_KEY])->map(fn($item, $key) => match ($key) {
self::INFO_SCORE_LIMIT_KEY => (int)$item,
self::INFO_SCORE_TEXT_KEY => Str::isJson($item) ? json_decode($item, false, 512, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : [],
default => $item,
})->put(self::INFO_SCORE_CONTENT_KEY, self::getChildrenList(self::INFO_SCORE_CONTENT_KEY));
}
/**
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getActivityDictionary(): EloquentCollection
{
return SystemConfig::parentKey(['activity_mark', 'activity_lang', 'activity_speed', 'activity_sex'])->where('status', 1)->get(['name', 'identifier', 'content']);
}
/**
* @param \App\Models\Activity $activity
* @param \Illuminate\Database\Eloquent\Collection|null $dictionary
* @return \App\Models\Activity
*/
public static function formatActivityDictionary(Activity $activity, EloquentCollection $dictionary = NULL): Activity
{
$dictionary = $dictionary ?? self::getActivityDictionary();
$activity->loadMissing('linkArranger:id,nick_name');
$activity->setAttribute('sex', $dictionary->where('identifier', $activity->getAttribute('sex'))->value('name', ''));
$activity->setAttribute('speed', $dictionary->where('identifier', $activity->getAttribute('speed'))->value('name', ''));
$activity->setAttribute('mark', $dictionary->where('identifier', $activity->getAttribute('mark'))->value('content', ''));
$activity->setAttribute('lang', $dictionary->whereIn('identifier', $activity->getAttribute('lang'))->pluck('name'));
$activity->setAttribute('arranger', ['in_side' => $activity->getAttribute('linkArranger'), 'out_side' => data_get($activity->getAttribute('expand'), 'arranger.supplement', [])]);
return $activity;
}
/**
* @param \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection $collection
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public static function formatManyActivityDictionary(EloquentCollection|SupportCollection $collection): EloquentCollection|SupportCollection
{
$dictionary = self::getActivityDictionary();
return $collection->each(fn($item) => self::formatActivityDictionary($item, $dictionary));
}
}
<?php
namespace App\Helpers;
use Arr;
use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Str;
use Tencent\TLSSigAPIv2;
class IMHelper
{
private string $host;
private string $key;
private string $secret;
private string $identifier;
public const SYSTEM_USER = 'system';
public function __construct()
{
$this->host = config('services.im.domain');
$this->key = config('services.im.key');
$this->secret = config('services.im.secret');
$this->identifier = config('services.im.identifier');
}
/**
* @throws \Exception
*/
private function createSignature(string $identifier): string
{
return (new TLSSigAPIv2($this->key, $this->secret))->genUserSig($identifier);
}
/**
* @throws \Exception
*/
private function getSystemSignature(): string
{
return $this->createSignature($this->identifier);
}
/**
* @throws \Exception
*/
public static function getUserSignature(int|string $userId): string
{
return (new static())->createSignature(self::userKeyToAccount($userId));
}
/**
* @throws \Exception
*/
public function getUrl(string $uri): string
{
return $this->host . '/' . $uri . '?' . Arr::query(['sdkappid' => $this->key, 'identifier' => $this->identifier, 'usersig' => $this->getSystemSignature(), 'random' => random_int(0, 4294967295), 'contenttype' => 'json']);
}
/**
* @param string $uri
* @param array $data
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
* @throws \Exception
*/
public static function send(string $uri, array $data): PromiseInterface|Response
{
$host = (new static())->getUrl($uri);
$result = Http::asJson()->post($host, $data);
Log::channel('jpush')->info('IM ' . $uri . ' status[' . $result->json('ActionStatus') . ']:', [
'uri' => $uri, 'data' => $data, 'result' => $result->json()
]);
return $result;
}
/**
* @param int|string $userId
* @return string
*/
public static function userKeyToAccount(int|string $userId): string
{
return (App::isProduction() ? '' : 'test') . $userId;
}
/**
* @param string $account
* @return string
*/
public static function accountToUserKey(string $account): string
{
return Str::replace('test', '', $account);
}
/**
* @param string $title
* @param string $desc
* @param string|array $ext
* @return array
* @throws \JsonException
*/
public static function createOfflineMessage(string $title, string $desc = '', string|array $ext = ''): array
{
return [
"PushFlag" => 0,
"Title" => $title,
"Desc" => $desc,
"Ext" => is_array($ext) ? json_encode($ext, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $ext,
"AndroidInfo" => ["Sound" => "android.mp3"],
"ApnsInfo" => ["Sound" => "apns.mp3", "BadgeMode" => 1]
];
}
/**
* @param string $type
* @param array $content
* @return array
*/
public static function createOnlineMessage(string $type, array $content): array
{
return [
'MsgType' => $type,
'MsgContent' => [
...Arr::map($content, static fn($item) => is_array($item) ? json_encode($item, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $item),
'Sound' => 'dingdong.aiff'
]
];
}
/**
* @param array $content
* @param array $ext
* @return array
*/
public static function createCustomMessage(array $content, array $ext = []): array
{
return self::createOnlineMessage('TIMCustomElem', [...$content, "Ext" => $ext,]);
}
/**
* @param string $form
* @param string $to
* @param array $payload
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
* @throws \Exception
*/
public static function sendSingleChatMessage(string $form, string $to, array $payload): PromiseInterface|Response
{
return self::send('v4/openim/sendmsg', [
"SyncOtherMachine" => 2,
'IsNeedReadReceipt' => 1,
"MsgRandom" => random_int(0, 4294967295),
"From_Account" => $form,
"To_Account" => self::userKeyToAccount($to)
] + $payload);
}
/**
* @param string $form
* @param array $to
* @param array $payload
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
* @throws \Exception
*/
public static function sendBatchChatMessage(string $form, array $to, array $payload): PromiseInterface|Response
{
return self::send('v4/openim/batchsendmsg', [
"SyncOtherMachine" => 2,
'IsNeedReadReceipt' => 1,
'MsgRandom' => random_int(0, 4294967295),
"From_Account" => is_numeric($form) ? self::userKeyToAccount($form) : $form,
"To_Account" => Arr::map($to, static fn($item) => self::userKeyToAccount($item)),
] + $payload);
}
/**
* @param string $form
* @param string $to
* @param string $msgKey
* @return \GuzzleHttp\Promise\PromiseInterface|\Illuminate\Http\Client\Response
* @throws \Exception
*/
public static function rollbackChatMessage(string $form, string $to, string $msgKey): PromiseInterface|Response
{
info('IM_ROLLBACK', [$form, $to, $msgKey]);
return self::send('v4/openim/admin_msgwithdraw', ["From_Account" => $form, "To_Account" => is_numeric($to) ? self::userKeyToAccount($to) : $to, "MsgKey" => $msgKey]);
}
/**
* @throws \Exception
*/
public static function checkAccount(array $account): PromiseInterface|Response
{
return self::send('/v4/im_open_login_svc/account_check ', [
'CheckItem' => Arr::map($account, static fn($item) => ['UserID' => self::userKeyToAccount($item)])
]);
}
/**
* @throws \JsonException
* @throws \Exception
*/
public static function importSingleAccount(string $userId, string|array $name, string $avatar = ''): PromiseInterface|Response
{
return self::send('/v4/im_open_login_svc/account_import', [
'UserID' => self::userKeyToAccount($userId),
'Nick' => is_array($name) ? json_encode($name, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) : $name,
'FaceUrl' => $avatar
]);
}
/**
* @throws \Exception
*/
public static function updateSingleAccountInfo(string $userId, array $profileItem): PromiseInterface|Response
{
return self::send('v4/profile/portrait_set', ['From_Account' => self::userKeyToAccount($userId), 'ProfileItem' => $profileItem]);
}
}
<?php
namespace App\Helpers;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class JsonResource
{
public const SUCCESS = '获取成功';
public const CREATE_SUCCESS = '创建成功';
public const UPDATE_SUCCESS = '更新成功';
public const DELETE_SUCCESS = '删除成功';
public const LOGIN_SUCCESS = '登录成功';
public const LOGOUT_SUCCESS = '登出成功';
public const UNAUTHORIZED = '未携带身份令牌';
public const AUTHORIZED_EXP = '身份令牌失效';
public const NO_PERMISSION = '无权限进行此操作';
public const USER_FOUND_ERROR = '用户不存在';
public const LOGIN_FOUND_ERROR = '账号不存在';
public const LOGIN_FORBID_ERROR = '账户已被封禁,请联系管理员';
public const LOGIN_PASSWORD_ERROR = '密码错误';
public const REPEAT_SUBMIT = '请勿重复提交';
public const IS_TOP_LIMIT = '最多允许置顶3条内容';
public const INVITE_SUCCESS = '邀请成功';
public const APPLY_SUCCESS = '申请成功';
public const INVITE_LIMIT = '1天内仅能向他发起一次,请不要频繁邀请';
public const APPLY_LIMIT = '1天内仅能向他发起一次,请不要频繁申请';
public const TEAM_INVITE_SINGER_LIMIT = '团队内歌手名额已满,暂无法邀请 ~';
public const TEAM_JOIN_SINGER_LIMIT = '团队内歌手名额已满';
public const JOINED_TEAM = '对方已在其他团队';
public const MEMBER_JOINED_TEAM = 'Ta已加入他人团队,您暂无法同意 ~';
public const SELF_MEMBER_JOINED_TEAM = '你已加入他人团队,您暂无法同意 ~';
public const INVITE_JOINED_TEAM = 'Ta已加入他人团队,您暂无法加入 ~';
public const SELF_INVITE_JOINED_TEAM = '你已加入他人团队,您暂无法同意 ~';
public const JOIN_TEAM_INVALID = '超时,已失效';
public const INVALIDED = '已失效';
public const HANDLE_SUCCESS = '处理成功';
public const OPERATION_SUCCESS = '操作成功';
public const MEMBER_TOP_LIMIT = '最多可置顶5人';
public const MEMBER_OUT_INVALID = '退出申请已被处理';
public const PROJECT_INVALIDED = '厂牌已被禁用或删除';
public const HANDEL_FAIL = '操作失败,请刷新后再试';
public const IS_PROJECT_MEMBER = '操作失败,您已经是厂牌成员了';
public const IS_NOT_MASTER = '操作失败,对方已经不是该厂牌管理员';
public const YOU_NOT_MASTER = '操作失败,您已经不是该厂牌管理员';
public const OUT_IS_HANDLE = '操作失败,该申请已被其他管理员处理';
/**
* @param string|null $message
* @param int $code
* @return JsonResponse
*/
public static function empty(string $message = NULL, int $code = Response::HTTP_OK): JsonResponse
{
return self::send('success', $code, $message ?? Response::$statusTexts[$code]);
}
/**
* @param string|null $message
* @param mixed $data
* @param int $code
* @param array $extend
* @return JsonResponse
*/
public static function success(string $message = NULL, $data = NULL, int $code = Response::HTTP_OK, array $extend = []): JsonResponse
{
return self::send('success', $code, $message ?? Response::$statusTexts[$code], $data, $extend);
}
/**
* @param mixed $data
* @param string|null $message
* @param int $code
* @return JsonResponse
*/
public static function fail(string $message = NULL, $data = NULL, int $code = Response::HTTP_UNPROCESSABLE_ENTITY): JsonResponse
{
return self::send('fail', $code, $message ?? Response::$statusTexts[$code], $data);
}
public static function unauthenticated(string $message = NULL, $data = NULL, int $code = 401): JsonResponse
{
return self::send('fail', $code, $message ?? Response::$statusTexts[$code], $data);
}
/**
* @param mixed $data
* @param string|null $message
* @param int $code
* @return JsonResponse
*/
public static function error(string $message = NULL, $data = NULL, int $code = Response::HTTP_INTERNAL_SERVER_ERROR): JsonResponse
{
return self::send('error', $code, $message ?? Response::$statusTexts[$code], config('app.debug') && $code >= 500 ? $data : []);
}
/**
* @param LengthAwarePaginator|\Illuminate\Contracts\Pagination\LengthAwarePaginator $data
* @return array
*/
public static function paginator(LengthAwarePaginator $data): array
{
return [
'data' => $data->items(), 'meta' => ['total' => (int)$data->total(), 'page' => (int)$data->currentPage(), 'size' => (int)$data->perPage()]
];
}
/**
* @param string $status
* @param int $code
* @param string $message
* @param $data
* @param array $extend
* @return JsonResponse
*/
public static function send(string $status, int $code, string $message, $data = NULL, array $extend = []): JsonResponse
{
$resource = array_merge(
['status' => $status, 'code' => $code, 'message' => $message],
$data instanceof LengthAwarePaginator ? self::paginator($data) : compact('data'),
);
if (is_null($resource['data']) && Str::startsWith(request()?->getRequestUri(), '/mapi')) {
unset($resource['data']);
}
if (count($extend) !== 0) {
Arr::set($resource, 'meta', array_merge(Arr::get($resource, 'meta', []), $extend));
}
return response()->json($resource);
}
}
<?php
namespace App\Helpers;
use App\Models\SystemOperateLog;
use App\Models\User;
use Auth;
use Illuminate\Database\Eloquent\Model;
use Str;
class OperationLog
{
protected string $guard;
protected string $action = '';
protected string $content = '';
protected Model $subject;
protected int $causer;
protected array $append = [];
private function __construct(string $guard)
{
$this->guard = $guard;
$this->causer = request()?->user()?->getKey() ?? 0;
}
/**
* @return self
*/
public static function admin(): self
{
return new self('Admin');
}
/**
* @return self
*/
public static function manage(): self
{
return new self('Manage');
}
/**
* @return self
*/
public static function guard(): self
{
return new self(Str::ucfirst(Auth::getDefaultDriver()));
}
/**
* @return $this
*/
public function createAction(): OperationLog
{
$this->action = '新增操作';
return $this;
}
/**
* @return $this
*/
public function updateAction(): OperationLog
{
$this->action = '内容调整';
return $this;
}
/**
* @return $this
*/
public function statusAction(): OperationLog
{
$this->action = '状态变更';
return $this;
}
/**
* @return $this
*/
public function deleteAction(): OperationLog
{
$this->action = '删除操作';
return $this;
}
/**
* @param string $action
* @return $this
*/
public function action(string $action): OperationLog
{
$this->action = $action;
return $this;
}
/**
* @param \Illuminate\Database\Eloquent\Model $subject
* @return $this
*/
public function subject(Model $subject): OperationLog
{
$this->subject = $subject;
return $this;
}
/**
* @param int|\App\Models\User $causer
* @return $this
*/
public function causer(User|int $causer): OperationLog
{
$this->causer = $causer instanceof User ? $causer->getKey() : (int)$causer;
return $this;
}
/**
* @param array $append
* @return $this
*/
public function append(array $append): OperationLog
{
$this->append = $append;
return $this;
}
/**
* @param string $format
* @param mixed ...$values
* @return SystemOperateLog
*/
public function content(string $format, mixed ...$values): SystemOperateLog
{
$this->content = sprintf($format, ...$values);
return $this->save();
}
/**
* @return SystemOperateLog
*/
private function save(): SystemOperateLog
{
if (!in_array($this->guard, ['Admin', 'Manage'], true)) {
return new SystemOperateLog([
'guard' => $this->guard,
'user_id' => $this->causer,
'action' => $this->action,
'content' => $this->content,
'subject_type' => $this->subject->getMorphClass(),
'subject_id' => $this->subject->getKey(),
] + $this->append);
}
return SystemOperateLog::query()->create([
'guard' => $this->guard,
'user_id' => $this->causer,
'path' => explode(',', request()?->header('Route', '')),
'action' => $this->action,
'content' => $this->content,
'subject_type' => $this->subject->getMorphClass(),
'subject_id' => $this->subject->getKey(),
] + $this->append);
}
}
<?php
namespace App\Helpers;
use Illuminate\Support\Str;
class PasswordHelper
{
/**
* 密码Hash生成
* @param string $password
* @return string
*/
public static function make(string $password): string
{
$header = "pbkdf2_sha256";
$iterations = 260000;
$salt = Str::random(22);
$hash = base64_encode(hash_pbkdf2("SHA256", $password, $salt, $iterations, 0, true));
return implode('$', [$header, $iterations, $salt, $hash]);
}
/**
* 密码Hash验证
* @param string $password
* @param string $hash
* @return bool
*/
public static function verify(string $password, string $hash): bool
{
if (empty($hash) || !Str::contains($hash, '$')) {
return false;
}
[$header, $iterations, $salt, $hash] = explode("$", $hash);
$verifyHash = base64_encode(hash_pbkdf2("SHA256", $password, $salt, (int)$iterations, 0, true));
return hash_equals($hash, $verifyHash);
}
}
<?php
namespace App\Helpers;
use Carbon\Carbon;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
class ServiceHelper
{
/**
* @return string
*/
public static function getServiceHost(): string
{
return config('services.provider.service_host', '');
}
/**
* @return string
*/
public static function getServiceSecret(): string
{
return config('services.provider.service_secret', '');
}
/**
* 推送微信服务号
* @param array<string,mixed> $data
* @return \Illuminate\Http\Client\Response
* @throws \JsonException
*/
public static function sendWechatOfficial(array $data): Response
{
return self::send('WechatOfficialMessage', $data);
}
/**
* 推送微信小程序
* @param array<string,mixed> $data
* @return \Illuminate\Http\Client\Response
* @throws \JsonException
*/
public static function sendWechatApp(array $data): Response
{
return self::send('WechatAppMessage', $data);
}
/**
* 推送极光消息
* @param array<string,mixed> $data
* @return \Illuminate\Http\Client\Response
* @throws \JsonException
*/
public static function sendJPush(array $data): Response
{
return self::send('JPushMessage', $data);
}
/**
* @param string $channel
* @param array<string,mixed> $data
* @return \Illuminate\Http\Client\Response
* @throws \JsonException
*/
public static function send(string $channel, array $data): Response
{
$payload = ['time' => Carbon::now()->timestamp, 'channel' => $channel, 'data' => $data];
$payload['sign'] = self::getSign($payload);
return Http::asJson()->post(self::getServiceHost() . '/api/provider', $payload);
}
/**
* @param array<string,mixed> $payload
* @return string
* @throws \JsonException
*/
private static function getSign(array $payload): string
{
$payload['data'] = json_encode($payload['data'], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
$payload['secret'] = self::getServiceSecret();
ksort($payload);
return md5(base64_encode(implode('&', $payload)));
}
}
<?php
namespace App\Helpers;
use App\Enums\UserStatusEnum;
use App\Models\SystemSmsLog;
use App\Models\User;
use Arr;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
use Overtrue\EasySms\PhoneNumber;
class SmsHelper
{
/**
* @param \Overtrue\EasySms\PhoneNumber $phoneNumber
* @param int|array|NULL $scope
* @return bool
*/
public static function checkPhoneExist(PhoneNumber $phoneNumber, int|array $scope = NULL): bool
{
return User::phoneNumber($phoneNumber)
->when($scope, fn(Builder $query) => $query->whereIn('scope', Arr::wrap($scope)))
->exists();
}
/**
* @param \Overtrue\EasySms\PhoneNumber $phoneNumber
* @param int|array|UserStatusEnum $status
* @param int|array|NULL $scope
* @return bool
*/
public static function checkPhoneStatus(PhoneNumber $phoneNumber, int|array|UserStatusEnum $status = 1, int|array $scope = NULL): bool
{
return User::phoneNumber($phoneNumber)
->when($scope, fn(Builder $query) => $query->whereIn('scope', Arr::wrap($scope)))
->whereIn('status', Arr::wrap($status))
->exists();
}
/**
* @param int $phone
* @param string $area
* @return \Overtrue\EasySms\PhoneNumber
*/
public static function getAreaPhoneNumber(int $phone, string $area = '86'): PhoneNumber
{
return new PhoneNumber($phone, $area);
}
/**
* @param int $length
* @return string
* @throws \Exception
*/
public static function createNumberCode(int $length = 6): string
{
return substr((string)(random_int(0, (10 ** $length) - 1) + 10 ** $length), 1, $length);
}
/**
* @param \Overtrue\EasySms\PhoneNumber $phone
* @return bool
*/
public static function checkPhoneLimit(PhoneNumber $phone): bool
{
return self::getPhoneHourCount($phone) <= 5 && self::getPhoneDayCount($phone) <= 32;
}
/**
* @param \Overtrue\EasySms\PhoneNumber $phone
* @return int
*/
public static function getPhoneHourCount(PhoneNumber $phone): int
{
return DB::table('system_sms_logs')
->where('phone', $phone->getUniversalNumber())
->where('status', 'success')
->whereBetween('created_at', [now()->subHour()->toDateTimeString(), now()->toDateTimeString()])
->count();
}
/**
* @param \Overtrue\EasySms\PhoneNumber $phone
* @return int
*/
public static function getPhoneDayCount(PhoneNumber $phone): int
{
return DB::table('system_sms_logs')
->where('phone', $phone->getUniversalNumber())
->where('status', 'success')
->whereBetween('created_at', [now()->subDay()->toDateTimeString(), now()->toDateTimeString()])
->count();
}
/**
* @param \Overtrue\EasySms\PhoneNumber $phone
* @param string $code
* @param string $type
* @return string
*/
public static function getSmsKey(string $type, PhoneNumber $phone, string $code, ?string $platform = ''): string
{
return collect(['sms', $phone->getUniversalNumber(), $platform, $type, $code])->filter()->implode(':');
}
/**
* @param string $smsKey
* @return bool
* @throws \RedisException
*/
public static function checkSmsKey(string $smsKey): bool
{
return Redis::client()->exists($smsKey);
}
/**
* @param string $smsKey
* @param int $second
* @param string $value
* @return bool
* @throws \RedisException
*/
public static function saveSmsKey(string $smsKey, int $second = 300, string $value = ''): bool
{
return Redis::client()->setex($smsKey, $second, $value);
}
}
<?php
namespace App\Helpers;
use AlibabaCloud\SDK\Mts\V20140618\Mts;
use AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleRequest;
use AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleResponseBody\credentials;
use AlibabaCloud\SDK\Sts\V20150401\Sts;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
use Darabonba\OpenApi\Models\Config;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
use OSS\OssClient;
class UploadHelper
{
/**
* @return bool
*/
public static function isInternalServer(): bool
{
return config('services.oss.internal_server', false);
}
/**
* @param array<string,string> $options
* @return \Darabonba\OpenApi\Models\Config
*/
protected static function getConfig(array $options = []): Config
{
return new Config(['accessKeyId' => config('services.oss.sts.key'), 'accessKeySecret' => config('services.oss.sts.secret'), ...$options]);
}
/**
* @return \AlibabaCloud\SDK\Sts\V20150401\Sts
*/
public static function getStsClient(): Sts
{
return new Sts(self::getConfig(['regionId' => config('services.oss.sts.endpoint')]));
}
/**
* @param int $second
* @return \AlibabaCloud\SDK\Sts\V20150401\Models\AssumeRoleResponseBody\credentials
*/
public static function getStsToken(int $second = 3000): credentials
{
$assumeRoleRequest = new AssumeRoleRequest([
"roleArn" => config('services.oss.sts.role'),
"roleSessionName" => Str::uuid()->toString(),
"durationSeconds" => $second,
]);
return self::getStsClient()->assumeRoleWithOptions($assumeRoleRequest, new RuntimeOptions([]))->body->credentials;
}
/**
* @return \AlibabaCloud\SDK\Mts\V20140618\Mts
*/
public static function getMtsClient(): Mts
{
return new Mts(self::getConfig(['regionId' => config('services.oss.mts.endpoint'), 'protocol' => 'HTTPS']));
}
/**
* @return \OSS\OssClient
*/
public static function getOssClient(bool $internal = false): OssClient
{
$token = self::getStsToken();
return new OssClient($token->accessKeyId, $token->accessKeySecret, $internal ? self::getOssInternal() : self::getOssEndpoint(), false, $token->securityToken);
}
/**
* @param string $object
* @param array<string,mixed> $options
* @param int $second
* @return string
* @throws \OSS\Core\OssException
*/
public static function getOssSignUrl(string $object, array $options = [], int $second = 3000): string
{
return self::getOssClient()->signUrl(self::getOssBucket(), $object, $second, "PUT", [
OssClient::OSS_OBJECT_ACL => OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE, ...$options
]);
}
/**
* @return string
*/
public static function getOssBucket(): string
{
return config('services.oss.bucket');
}
public static function getOssSSL(): string
{
return config('services.oss.ssl') ? 'https://' : 'http://';
}
/**
* @return string
*/
public static function getOssDomain(bool $host = true): string
{
return Str::of(config('services.oss.domain'))->when($host, fn(Stringable $str) => $str->prepend(self::getOssSSL()))->toString();
}
/**
* @param bool $useSuper
* @param bool $host
* @return string
*/
public static function getOssEndpoint(bool $useSuper = false, bool $host = false): string
{
return Str::of($useSuper ? config('services.oss.super_endpoint') : config('services.oss.endpoint'))
->when($host, fn(Stringable $str) => $str->prepend(self::getOssSSL() . self::getOssBucket() . '.'))->toString();
}
public static function getOssInternal(bool $host = false): string
{
return Str::of(config('services.oss.internal'))
->when($host, fn(Stringable $str) => $str->prepend(self::getOssSSL() . self::getOssBucket() . '.'))->toString();
}
/**
* @param string $prefix
* @return string
*/
public static function getFileName(string $prefix = ''): string
{
return implode('/', array_filter([$prefix, now()->format('Ymd'), Str::ulid()->toBase32()]));
}
/**
* @param string $url
* @return string
*/
public static function urlToPath(string $url): string
{
return ltrim(parse_url($url, PHP_URL_PATH), '/');
}
/**
* @param string $object
* @param string $file
* @param array<string,mixed> $options
* @return string
* @throws \OSS\Core\OssException
*/
public static function put(string $object, string $file, array $options = []): string
{
self::getOssClient(self::isInternalServer())->uploadFile(self::getOssBucket(), $object, $file, [
OssClient::OSS_OBJECT_ACL => OssClient::OSS_ACL_TYPE_PUBLIC_READ_WRITE, ...$options
]);
return self::getOssDomain() . '/' . $object;
}
/**
* CDN域名转 OSS域名
* @param string $url
* @param bool $internal
* @return string
*/
public static function toEndpointUrl(string $url, bool $internal = false): string
{
return Str::replace(self::getOssDomain(), $internal ? self::getOssInternal(true) : self::getOssEndpoint(false, true), $url);
}
}
<?php
namespace App\Helpers;
use App\Models\SystemTag;
use App\Models\User;
use App\Models\UserTagRelation;
use Arr;
class UserPermissionHelper
{
public User $user;
/**
* @param \App\Models\User $user
*/
private function __construct(User $user)
{
$this->user = $user;
}
/**
* @param int|\App\Models\User $user
* @return \App\Helpers\UserPermissionHelper
*/
public static function user(int|User $user): UserPermissionHelper
{
return new self(is_numeric($user) ? User::findOrFail($user, ['id', 'identity', 'demo_type', 'scope']) : $user);
}
/**
* @param int|array $tagIds
* @return bool
*/
public function checkTag(int|array $tagIds): bool
{
return UserTagRelation::query()->where('type', 4)->where('user_id', $this->user->getKey())->whereIn('tag_id', Arr::wrap($tagIds))->exists();
}
/**
* @return bool
*/
public function hasDemoPermission(): bool
{
if ($this->user->getAttribute('demo_type') === 0) {
return false;
}
$config = ConfigHelper::getDemoList();
if (in_array($this->user->getKey(), $config->get(ConfigHelper::DEMO_USER_KEY), false)) {
return true;
}
return $this->checkTag($config->get(ConfigHelper::DEMO_TAG_KEY));
}
/**
* @return bool
*/
public function hasSingPermission(): bool
{
if (in_array($this->user->getKey(), ConfigHelper::getSingUserIds(), false)) {
return true;
}
return $this->checkTag(SystemTag::query()->whereJsonContains('expand->permission', [2])->pluck('id')->merge(ConfigHelper::getSingerTagIds())->toArray());
}
/**
* @return bool
*/
public function hasListenPermission(): bool
{
if ($this->hasSingPermission() || in_array($this->user->getAttribute('identity'), [2, 3], false) || in_array($this->user->getKey(), ConfigHelper::getListenUserIds(), false)) {
return true;
}
return $this->checkTag(SystemTag::query()->whereJsonContains('expand->permission', [1])->pluck('id')->toArray());
}
/**
* @return bool
*/
public function isSinger(): bool
{
return $this->checkTag(ConfigHelper::getSingerTagIds());
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Audition;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\Audition\ActivityManagerCreateRequest;
use App\Http\Container\AdminSection\Requests\Audition\ActivityManagerUpdateRequest;
use App\Models\Activity;
use App\Models\Pivots\ActivityLinkPivot;
use App\Support\Controller;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
class ActivityManagerController extends Controller
{
/**
* @param \App\Http\Container\AdminSection\Requests\Audition\ActivityManagerCreateRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(ActivityManagerCreateRequest $request): Response
{
$data = $request->safe()->toArray();
$activity = Activity::query()->find($data['activity_id']);
$activity->links()->wherePivot('type', 'manager')->syncWithPivotValues($data['user_ids'], ['type' => 'manager', 'expand->permission' => $data['expand->permission']], false);
OperationLog::admin()->createAction()->subject($activity)->content('《%s》新增外部管理员', $activity->getAttribute('song_name'));
return $this->success(ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\Audition\ActivityManagerUpdateRequest $request
* @param \App\Models\Pivots\ActivityLinkPivot $activityManager
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(ActivityManagerUpdateRequest $request, ActivityLinkPivot $activityManager): Response
{
$activityManager->update($request->safe()->toArray());
$nickName = $activityManager->link()->value('nick_name');
$activityName = $activityManager->activity()->value('song_name');
OperationLog::admin()->subject($activityManager)->updateAction()->content('《%s》调整外部管理员:%s 的权限', $activityName, $nickName);
return $this->successWithData($activityManager, ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\Pivots\ActivityLinkPivot $activityManager
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(ActivityLinkPivot $activityManager): Response
{
$nickName = $activityManager->link()->value('nick_name');
$activityName = $activityManager->activity()->value('song_name');
$activityManager->delete();
OperationLog::admin()->subject($activityManager)->deleteAction()->content('《%s》移除外部管理员:%s', $activityName, $nickName);
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Audition;
use App\Excel\ActivityReListRecordExcel;
use App\Models\ActivityReListRecord;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ActivityReListRecordController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function index(Request $request): Response|BinaryFileResponse
{
$pageSize = $request->get('pageSize');
$filter = $request->except(['page', 'pageSize', 'fetchType']);
$query = ActivityReListRecord::filter($filter);
if ($request->get('fetchType') === 'excel') {
return ActivityReListRecordExcel::withBuilder($query)->download();
}
return $this->successWithData($query->paginate($pageSize));
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Audition;
use App\Enums\ActivityWorkStatusEnum;
use App\Excel\ActivityWorkExcel;
use App\Helpers\OperationLog;
use App\Http\Service\ActivityService;
use App\Models\Activity;
use App\Models\Views\ActivityWork;
use Auth;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ActivityWorkController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @param \App\Http\Service\ActivityService $service
* @return \Hikoon\LaravelApi\Facades\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function index(Request $request): Response|BinaryFileResponse
{
$pageSize = $request->get('pageSize');
$fetchType = $request->get('fetchType', 'page');
$filter = $request->except(['page', 'pageSize', 'fetchType']);
$query = ActivityWork::filter($filter);
return match ($fetchType) {
'excel' => ActivityWorkExcel::withBuilder($query)->download(),
default => $this->successWithData($query->paginate($pageSize))
};
}
/**
* @param \Illuminate\Http\Request $request
* @param \App\Models\Views\ActivityWork $work
* @param \App\Http\Service\ActivityService $service
* @return \Hikoon\LaravelApi\Facades\Response
* @throws \Hikoon\LaravelApi\Exceptions\ValidationException
*/
public function changeStatus(Request $request, ActivityWork $work, ActivityService $service): Response
{
$attribute = $request->validate(
['status' => 'bail|required|in:1,2'],
['status.required' => '请选择操作类型', 'status.in' => '操作类型不在指定范围内']
);
$service->auditWork($work, ActivityWorkStatusEnum::from($attribute['status']));
OperationLog::admin()->updateAction()->causer(Auth::user())->subject(new Activity(['id' => $work->getAttribute('activity_id')]))->content(
'歌曲《%s》,%s,作品《%s-V%s版本》',
$work->getAttribute('activity_name'),
$attribute['status'] === ActivityWorkStatusEnum::REJECT->value ? '标记不合适' : '完成匹配',
$work->getAttribute('user_nick_name'),
$work->getAttribute('version')
);
return $this->success(ApiCode::UPDATE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Audition;
use App\Enums\ActivityAuditStatusEnum;
use App\Enums\ActivitySongTypeEnum;
use App\Enums\ActivityStatusEnum;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\Audition\ApplyAuditRequest;
use App\Http\Container\ManageSection\Requests\Audition\ApplyCreateRequest;
use App\Http\Container\ManageSection\Requests\Audition\ApplyUpdateRequest;
use App\Http\Service\ActivityService;
use App\Jobs\ActivityMakeMediaJob;
use App\Jobs\ActivityPublishJob;
use App\Models\Activity;
use App\Models\ActivityApplyRecord;
use App\Notifications\ActivityApplyFailNotification;
use App\Notifications\ActivityApplySuccessNotification;
use App\Support\Controller;
use App\Support\Model;
use Arr;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
class ApplyController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$data = Activity::filter($request->mergeIfMissing(['audit_status' => [0, 2]])->except(['page', 'pageSize']))
->select(['id', 'song_name', 'cover', 'lang', 'speed', 'sub_title', 'sex', 'user_id', 'weight', 'project_id', 'lyric', 'clip_lyric', 'is_push', 'mark', 'is_official', 'expand', 'audit_status', 'created_at', 'estimate_release_at'])
->addSelect([
'record_count' => ActivityApplyRecord::query()->selectRaw('count(*)')->whereColumn('activity_apply_records.activity_id', 'activitys.id'),
'last_record' => ActivityApplyRecord::query()->select('current')->whereColumn('activity_apply_records.activity_id', 'activitys.id')->latest()->limit(1)
])
->latest('updated_at')->withCasts(['last_record' => 'json'])->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Http\Container\ManageSection\Requests\Audition\ApplyCreateRequest $request
* @param \App\Http\Service\ActivityService $service
* @return \Hikoon\LaravelApi\Facades\Response
* @throws \Hikoon\LaravelApi\Exceptions\ValidationException
*/
public function store(ApplyCreateRequest $request, ActivityService $service): Response
{
$attribute = array_merge($request->safe()->toArray(), ['user_id' => (int)Auth::id(), 'created_form' => 1]);
if (Cache::lock('user-' . Auth::id() . '-apply', 5)->get()) {
$activity = match ($request->integer('song_type')) {
ActivitySongTypeEnum::SONG->value => $service->createActivityApply($attribute + ['lang' => [], 'is_push' => 0], false),
ActivitySongTypeEnum::DEMO->value => $service->createDemoApply($attribute + ['is_push' => 1], false)
};
return $this->successWithData($activity, ApiCode::CREATE_SUCCESS);
}
return $this->fail(ApiCode::REQUEST_LIMIT_ERROR);
}
/**
* @param \App\Http\Container\ManageSection\Requests\Audition\ApplyUpdateRequest $request
* @param \App\Models\Activity $apply
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(ApplyUpdateRequest $request, Activity $apply): Response
{
$apply->update(Model::dot($request->safe()->merge(['audit_status' => 0, 'status' => 0])));
OperationLog::manage()->subject($apply)->updateAction()
->content('《%s》,重新申请%s', $apply->getAttribute('song_name'), $apply->getChangeColumn()->except(['audit_status', 'status'])->format(',修改了:'));
return $this->successWithData($apply, ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\Audition\ApplyAuditRequest $request
* @param \App\Models\Activity $apply
* @param \App\Http\Service\ActivityService $service
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function audit(ApplyAuditRequest $request, Activity $apply, ActivityService $service): Response
{
$data = $request->safe()->toArray();
if ($data['audit_status'] === 1) {
$data['publish_at'] = now()->toDateTimeString();
$data['status'] = ActivityStatusEnum::PROCESS->value;
$data['audit_at'] = now()->toDateTimeString();
DB::transaction(static function () use ($apply, $data, $service) {
$apply->update(Model::dot(Arr::except($data, 'out_side_manages')));
$change = $apply->getChangeColumn();
if (isset($data['out_side_manages'])) {
$syncResult = $service->syncManageLink($apply, $data['out_side_manages']);
if (count($syncResult) !== 0) {
$change->add('manage_link');
}
}
OperationLog::admin()->statusAction()->subject($apply)->content('歌曲《%s》,审核通过%s', $apply->getAttribute('song_name'), $change->format(',修改了:'));
Bus::chain([
new ActivityMakeMediaJob($apply, ActivityStatusEnum::UP, true),
new ActivityPublishJob($apply, data_get($apply, 'expand.push_type', []), data_get($apply, 'expand.push_user', [])),
])->dispatch();
//推送申请人通知
Notification::send($apply->getAttribute('user'), new ActivityApplySuccessNotification($apply));
});
}
if ($data['audit_status'] === 2) {
DB::transaction(static function () use ($apply, $data) {
$apply->applyRecords()->create(['audit_user_id' => Auth::id(), 'audit_msg' => $data['msg'], 'current' => $apply->attributesToArray()]);
OperationLog::admin()->statusAction()->subject($apply)->content('歌曲《%s》,审核不通过,理由:%s', $apply->getAttribute('song_name'), $data['msg']);
$apply->update(['audit_status' => 2]);
//推送申请人通知
Notification::send($apply->getAttribute('user'), new ActivityApplyFailNotification($apply, $data['msg']));
});
}
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \Illuminate\Http\Request $request
* @param \App\Models\Activity $apply
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function record(Request $request, Activity $apply): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except('page', 'pageSize');
$data = ActivityApplyRecord::filter($filter)->where('activity_id', $apply->getKey())->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Models\Activity $apply
* @param \App\Http\Service\ActivityService $service
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(Activity $apply, ActivityService $service): Response
{
if ($apply->getAttribute('audit_status') !== ActivityAuditStatusEnum::FAIL->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '只允许删除审核未通过歌曲');
}
$service->delete($apply);
OperationLog::admin()->subject($apply)->deleteAction()->content('歌曲《%s》', $apply->getAttribute('song_name'));
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Audition;
use App\Enums\UserScopeEnum;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\Audition\ProjectManagerRequest;
use App\Models\Pivots\UserProjectPivot;
use App\Models\Project;
use App\Models\User;
use App\Support\Controller;
use DB;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
class ProjectManagerController extends Controller
{
/**
* @param \App\Http\Container\AdminSection\Requests\Audition\ProjectManagerRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(ProjectManagerRequest $request): Response
{
$attribute = $request->validated();
$user = User::query()->find($attribute['user_id'], ['id', 'nick_name']);
$projectName = Project::query()->whereKey($attribute['project_id'])->value('name');
if (!$user) {
return $this->fail(ApiCode::VALIDATION_ERROR, '用户不存在');
}
if ($user->getAttribute('scope') === UserScopeEnum::SYSTEM) {
return $this->fail(ApiCode::VALIDATION_ERROR, '该用户为系统管理员');
}
User::query()->whereKey($attribute['user_id'])->update(['scope' => UserScopeEnum::PROJECT]);
$projectManager = UserProjectPivot::query()->updateOrCreate($attribute);
OperationLog::admin()->subject($projectManager)->createAction()->content('《%s》新增管理员:%s', $projectName, $user->getAttribute('nick_name'));
return $this->success(ApiCode::CREATE_SUCCESS);
}
public function destroy(UserProjectPivot $projectManager): Response
{
DB::transaction(static function () use ($projectManager) {
$nickName = $projectManager->user()->value('nick_name');
$projectName = $projectManager->project()->value('name');
$userId = $projectManager->getAttribute('user_id');
$projectManager->delete();
if (UserProjectPivot::query()->where('user_id', $userId)->doesntExist()) {
User::query()->whereKey($userId)->update(['scope' => UserScopeEnum::UNSET]);
}
OperationLog::admin()->subject($projectManager)->deleteAction()->content('《%s》移除管理员:%s', $projectName, $nickName);
});
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers;
use App\Http\Request\UserChangePwdRequest;
use App\Models\SystemPermission;
use App\Models\SystemRole;
use App\Support\Controller;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function show(Request $request): Response
{
if (Auth::user()?->getAttribute('is_super') === 1) {
$permissions = SystemPermission::query()->pluck('system_permissions.name');
} else {
$roleIds = SystemRole::query()
->join('user_has_roles', 'user_has_roles.role_id', 'system_roles.id')
->where('user_has_roles.user_id', Auth::id())
->where('system_roles.status', 1)
->pluck('system_roles.id')->toArray();
$permissions = SystemPermission::query()
->join('system_role_has_permissions', 'system_role_has_permissions.permission_id', 'system_permissions.id')
->where('system_permissions.guard', 'Admin')
->whereIn('system_role_has_permissions.role_id', $roleIds)
->pluck('system_permissions.name');
}
return $this->successWithData([
'user' => $request->user()->only(['id', 'nick_name', 'avatar', 'role', 'last_login']),
'permissions' => $permissions
]);
}
/**
* @param \App\Http\Request\UserChangePwdRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function changePwd(UserChangePwdRequest $request): Response
{
$request->user()->update($request->safe()->toArray());
return $this->success(ApiCode::UPDATE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Dashboard;
use App\Enums\ActivityStatusEnum;
use App\Models\Activity;
use App\Models\Pivots\ActivityTagPivot;
use App\Models\SystemTag;
use App\Models\UserAction;
use App\Support\Controller;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use DB;
use Hikoon\LaravelApi\Facades\Response;
class ActivityController extends Controller
{
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function total(): Response
{
$calendar = CarbonPeriod::create(now()->startOfDay()->subDays(7), '1 day', 'now');
$trend = DB::table('activity_status_records')
->whereBetween('time', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->pluck('up', 'time');
$today = [today()->toDateString() => Activity::query()->where('activitys.audit_status', 1)->where('activitys.status', 1)->count()];
$time = collect();
$calendar->forEach(fn(Carbon $item) => $time->put($item->toDateString(), 0));
return $this->successWithData([
'time' => $time->keys()->toArray(),
'data' => $time->merge($trend)->merge($today)->values()->toArray()
]);
}
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function listen(): Response
{
$calendar = CarbonPeriod::create(now()->startOfDay()->subDays(7), '1 day', 'now');
$trend = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(distinct user_id) as num")
->whereIn('event_name', ['listen_clip', 'listen_entire'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$time = collect();
$calendar->forEach(fn(Carbon $item) => $time->put($item->toDateString(), 0));
return $this->successWithData([
'time' => $time->keys()->toArray(),
'data' => $time->merge($trend)->values()->toArray()
]);
}
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function like(): Response
{
$calendar = CarbonPeriod::create(now()->startOfDay()->subDays(7), '1 day', 'now');
$trend = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(distinct user_id) as num")
->whereIn('event_name', ['bottom_like', 'song_like', 'sing_like'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$time = collect();
$calendar->forEach(fn(Carbon $item) => $time->put($item->toDateString(), 0));
return $this->successWithData([
'time' => $time->keys()->toArray(),
'data' => $time->merge($trend)->values()->toArray()
]);
}
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function style(): Response
{
SystemTag::resolveRelationUsing('activities', static function (SystemTag $tag) {
return $tag->belongsToMany(Activity::class, ActivityTagPivot::class, 'tag_id', 'activity_id')
->where('activitys.status', ActivityStatusEnum::UP);
});
$result = collect();
SystemTag::query()->select(['id', 'name'])->withCount('activities')->where('type', 1)->get()
->each(fn(SystemTag $tag) => $result->push(['value' => $tag->getAttribute('activities_count'), 'name' => $tag->getAttribute('name')]));
return $this->successWithData($result);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Dashboard;
use App\Enums\ActivityWorkModeEnum;
use App\Enums\ActivityWorkTypeEnum;
use App\Models\UserAction;
use App\Models\Views\ActivityWork;
use App\Support\Controller;
use Arr;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Hikoon\LaravelApi\Facades\Response;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Vtiful\Kernel\Excel;
use Vtiful\Kernel\Format;
class IndexController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function submitWork(Request $request): Response|BinaryFileResponse
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except('page', 'pageSize');
$build = ActivityWork::filter($filter)
->select([
'id', 'activity_id', 'activity_name', 'activity_type', 'activity_title', 'activity_status', 'project_id',
'user_id', 'user_nick_name', 'user_real_name', 'user_identity', 'business_id',
'price_id', 'mode', 'sing_type', 'demo_url', 'submit_at', 'status'
])
->with([
'business' => fn($query) => $query->select(['id', 'nick_name', 'real_name', 'identity'])->where('identity', 3),
'project:id,name',
'tags:id,name',
'price:id,value,is_deduct,is_talk,is_accept_address,address_id',
])
->where('type', ActivityWorkTypeEnum::SUBMIT);
if ($request->get('fetchType') === 'excel') {
$fileName = Str::random(32) . '.xlsx';
$excel = new Excel(['path' => storage_path('excels')]);
$fileObject = $excel->constMemory($fileName, NULL, false);
$format = new Format($fileObject->getHandle());
$headStyle = $format->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)->background(0XF2F2F2)->bold()->toResource();
$row = $fileObject
->header(['名称', '推荐语', '歌曲风格', '厂牌', '用户艺名', '用户真名', '身份', '经纪人艺名', '经纪人真名', '试唱方式', '唱酬', '分成', '价格是否可谈', '录音地点', '是否接受其他录音地点', '提交时间', '试唱音频', '试唱结果'])
->setRow('A1', 22, $headStyle)
->setColumn('A:J', 16)
->setColumn('K:U', 14);
$build->chunk(1000, static function (Collection $collection) use ($fileObject) {
$fileObject->data($collection->map(static function (ActivityWork $work) {
$price = $work->getAttribute('price');
$userIdentity = data_get($work, 'user_identity', 0);
return [
data_get($work, 'activity_name', ''),
// ActivitySongTypeEnum::tryFrom(data_get($work, 'activity_type', 1))?->label(),
data_get($work, 'activity_title', ''),
$work->getRelation('tags')->implode('name', '、'),
data_get($work, 'project.name', ''),
data_get($work, 'user_nick_name', ''),
data_get($work, 'user_real_name', ''),
match ($userIdentity) {
0 => '未认证',
1 => '音乐人',
2, 3 => "经纪人",
default => ''
},
data_get($work, 'business.nick_name', ''),
data_get($work, 'business.real_name', ''),
$work->getAttribute('mode') === ActivityWorkModeEnum::OFFLINE ? $work->getAttribute('mode')?->label() : $work->getAttribute('sing_type')?->label(),
data_get($price, 'value.is_reward', 0) === 1 ? data_get($price, 'value.amounts') : '无',
data_get($price, 'value.is_dividend', 0) === 1 ? $price->getDividendFormat() : '无',
data_get($price, 'is_talk', 0) === 1 ? '是' : '否',
$price ? $price->getAddressFormat() : '',
data_get($price, 'is_accept_address', 0) === 1 ? '是' : '否',
data_get($work, 'submit_at', ''),
data_get($work, 'demo_url', ''),
$work->getFormatStatus()
];
})->toArray());
});
return response()->download($row->output(), $fileName)->deleteFileAfterSend();
}
return $this->successWithData($build->paginate($pageSize));
}
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function overview(Request $request): Response|BinaryFileResponse
{
$request->validate([
'createBetween' => 'required|array|size:2',
'createBetween.*' => 'date'
], [
'createBetween.required' => '请选择查询区间',
'createBetween.array' => '查询参数异常',
'createBetween.size' => '查询参数异常',
'createBetween.*.date' => '查询参数异常'
]);
$searchTime = $request->offsetGet('createBetween');
$startTime = Carbon::parse(Arr::first($searchTime))->startOfDay();
$endTime = Carbon::parse(Arr::last($searchTime))->startOfDay();
$calendar = CarbonPeriod::create($startTime, '1 day', $endTime);
$listerUserCount = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(distinct user_id) as num")
->whereIn('event_name', ['listen_clip', 'listen_entire'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$listerCount = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(*) as num")
->whereIn('event_name', ['listen_clip', 'listen_entire'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$likeCount = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(*) as num")
->whereIn('event_name', ['bottom_like', 'song_like', 'sing_like'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$submitCount = UserAction::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(*) as num")
->whereIn('event_name', ['submit_piece_online', 'submit_sing_offline', 'submit_sing_online'])
->whereBetween('created_at', [$calendar->first()?->startOfDay()->toDateTimeString(), $calendar->last()?->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
$time = collect();
$calendar->forEach(fn(Carbon $item) => $time->put($item->toDateString(), 0));
$result = [
'time' => $time->keys()->toArray(),
'listen_user_count' => $time->merge($listerUserCount)->values()->toArray(),
'listen_count' => $time->merge($listerCount)->values()->toArray(),
'like_count' => $time->merge($likeCount)->values()->toArray(),
'submit_count' => $time->merge($submitCount)->values()->toArray(),
];
if ($request->get('fetchType') === 'excel') {
$fileName = Str::random(32) . '.xlsx';
$excel = new Excel(['path' => storage_path('excels')]);
$fileObject = $excel->constMemory($fileName, NULL, false);
$format = new Format($fileObject->getHandle());
$headStyle = $format->align(Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER)->background(0XF2F2F2)->bold()->toResource();
$row = $fileObject
->header(['日期', '试听用户', '试听次数', '收藏次数', '提交作品数'])
->setRow('A1', 22, $headStyle)
->setColumn('A:E', 24);
$fileObject->data([
[
sprintf('%s~%s', Arr::first($result['time']), Arr::last($result['time'])),
$listerUserCount->sum(),
$listerCount->sum(),
$likeCount->sum(),
$submitCount->sum(),
],
...Arr::map($result['time'], static fn($val, $key) => [
$val,
Arr::get($result['listen_user_count'], $key, 0),
Arr::get($result['listen_count'], $key, 0),
Arr::get($result['like_count'], $key, 0),
Arr::get($result['submit_count'], $key, 0),
])
]);
return response()->download($row->output(), $fileName)->deleteFileAfterSend();
}
return $this->successWithData($result);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\Dashboard;
use App\Enums\UserStatusEnum;
use App\Models\SystemConfig;
use App\Models\SystemTag;
use App\Models\User;
use App\Models\UserCertify;
use App\Models\UserTagRelation;
use App\Support\Controller;
use Arr;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Hikoon\LaravelApi\Facades\Response;
use Illuminate\Database\Query\Builder;
class UserController extends Controller
{
/**
* 认证数统计
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function certify(): Response
{
$calendar = collect();
CarbonPeriod::create(now()->startOfDay()->subDays(7), '1 day', 'now')->forEach(fn(Carbon $item) => $calendar->put($item->toDateString(), 0));
$keys = $calendar->keys()->toArray();
$trend = UserCertify::query()
->selectRaw("DATE_FORMAT(created_at,'%Y-%m-%d') as days,count(distinct user_id) as num")
->whereBetween('created_at', [Carbon::parse(Arr::first($keys))->startOfDay()->toDateTimeString(), Carbon::parse(Arr::last($keys))->endOfDay()->toDateTimeString()])
->groupByRaw('days')->pluck('num', 'days');
return $this->successWithData([
'time' => $keys,
'data' => $calendar->merge($trend)->values()
]);
}
/**
* 用户数统计
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function total(): Response
{
return $this->successWithData([
'type' => ['未认证', '音乐人', '经纪人', '厂牌管理员', '系统管理员'],
'data' => [
User::query()->where('identity', 0)->where('status', UserStatusEnum::ACTIVATE)->count(),
User::query()->whereIn('identity', [1, 3])->where('status', UserStatusEnum::ACTIVATE)->count(),
User::query()->whereIn('identity', [2, 3])->where('status', UserStatusEnum::ACTIVATE)->count(),
User::query()->where('scope', 2)->where('status', UserStatusEnum::ACTIVATE)->count(),
User::query()->where('scope', 1)->where('status', UserStatusEnum::ACTIVATE)->count(),
]
]);
}
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function style(): Response
{
$singerTagIds = explode(',', SystemConfig::query()->where('identifier', 'auth_singer_tag')->value('content') ?? '');
SystemTag::resolveRelationUsing('users', static function (SystemTag $tag) use ($singerTagIds) {
return $tag->belongsToMany(User::class, UserTagRelation::class, 'tag_id', 'user_id')
->whereExists(fn(Builder $query) => $query->from('user_tag_relations', 'r')->selectRaw('1')->whereIn('r.tag_id', $singerTagIds)->where('r.type', 4)->whereColumn('r.user_id', 'users.id'))
->wherePivot('type', 1)->where('users.status', UserStatusEnum::ACTIVATE);
});
$result = collect();
SystemTag::query()->select(['id', 'name'])->withCount('users')->where('type', 1)->get()
->each(fn(SystemTag $tag) => $result->push(['value' => $tag->getAttribute('users_count'), 'name' => $tag->getAttribute('name')]));
return $this->successWithData($result);
}
/**
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function skill(): Response
{
SystemTag::resolveRelationUsing('users', static function (SystemTag $tag) {
return $tag->belongsToMany(User::class, UserTagRelation::class, 'tag_id', 'user_id')
->wherePivot('type', 4)->whereIn('users.identity', [1, 3])->where('users.status', UserStatusEnum::ACTIVATE);
});
$result = collect();
SystemTag::query()->select(['id', 'name'])->withCount('users')->where('type', 4)->get()
->each(fn(SystemTag $tag) => $result->push(['value' => $tag->getAttribute('users_count'), 'name' => $tag->getAttribute('name')]));
return $this->successWithData($result);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System;
use App\Helpers\OperationLog;
use App\Http\Request\BooleanStatusRequest;
use App\Http\Container\AdminSection\Requests\System\BannerRequest;
use App\Models\Banner;
use App\Support\Controller;
use Auth;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
class BannerController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except('page', 'pageSize');
$data = Banner::filter($filter)->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BannerRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(BannerRequest $request): Response
{
$attribute = $request->safe()->merge(['user_id' => Auth::id()])->toArray();
Banner::created(static fn(Banner $banner) => $banner->getAttribute('type') === 4 && $banner->update(['content_picture' => URL::asset('/app/banners/' . $banner->getKey())]));
$banner = Banner::query()->create($attribute);
OperationLog::admin()->createAction()->subject($banner)->content('轮播图《%s》', $banner->getAttribute('name'));
return $this->successWithData($banner, ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BannerRequest $request
* @param \App\Models\Banner $banner
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(BannerRequest $request, Banner $banner): Response
{
$attribute = $request->safe()->toArray();
if ($banner->getAttribute('type') === 4) {
$attribute['content_picture'] = URL::asset('/app/banners/' . $banner->getKey());
}
$banner->update($attribute);
OperationLog::admin()->updateAction()->subject($banner)->content('轮播图《%s》', $banner->getAttribute('name'));
return $this->successWithData($banner, ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Http\Request\BooleanStatusRequest $request
* @param \App\Models\Banner $banner
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function changeStatus(BooleanStatusRequest $request, Banner $banner): Response
{
$status = $request->safe()->offsetGet('status');
$banner->update(['status' => $status]);
OperationLog::admin()->statusAction()->subject($banner)->content('轮播图《%s》%s', $banner->getAttribute('name'), $banner->getAttribute('status')?->label());
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\Banner $banner
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(Banner $banner): Response
{
Banner::query()->whereKey($banner->getKey())->limit(1)->delete();
OperationLog::admin()->deleteAction()->subject($banner)->content('轮播图《%s》', $banner->getAttribute('name'));
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System\Broker;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\BrokerPushConfigRequest;
use App\Models\BrokerPushConfig;
use Auth;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
use Str;
class PushConfigController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$fetchType = $request->get('fetchType', 'page');
$filter = $request->except(['page', 'pageSize', 'fetchType']);
$build = BrokerPushConfig::query()->filter($filter);
if ($fetchType === 'all') {
return $this->successWithData($build->get());
}
return $this->successWithData($build->paginate($pageSize));
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerPushConfigRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(BrokerPushConfigRequest $request): Response
{
$config = BrokerPushConfig::query()->create($request->safe()->merge(['user_id' => Auth::id()])->toArray());
OperationLog::admin()->subject($config)->createAction()->content('推送配置《%s》', Str::limit($config->getAttribute('identifier')));
return $this->successWithData($config, ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerPushConfigRequest $request
* @param \App\Models\BrokerPushConfig $pushConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(BrokerPushConfigRequest $request, BrokerPushConfig $pushConfig): Response
{
$pushConfig->update($request->safe()->toArray());
OperationLog::admin()->subject($pushConfig)->updateAction()->content('推送配置《%s》', Str::limit($pushConfig->getAttribute('identifier')));
return $this->successWithData($pushConfig, ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\BrokerPushConfig $pushConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(BrokerPushConfig $pushConfig): Response
{
$pushConfig->delete();
OperationLog::admin()->subject($pushConfig)->deleteAction()->content('推送配置《%s》', Str::limit($pushConfig->getAttribute('identifier')));
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System\Broker;
use App\Enums\BrokerPushLevelRecordStatusEnum;
use App\Enums\BrokerPushMatchRecordStatusEnum;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\BrokerPushLevelRecordRequest;
use App\Jobs\BrokerPushLevelRecordRollBackJob;
use App\Jobs\BrokerPushLevelRecordSendJob;
use App\Models\BrokerPushLevelRecord;
use App\Models\BrokerPushLevelRecordItem;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
class PushLevelRecordController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except(['page', 'pageSize']);
$data = BrokerPushLevelRecord::filter($filter)->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerPushLevelRecordRequest $request
* @param \App\Models\BrokerPushLevelRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(BrokerPushLevelRecordRequest $request, BrokerPushLevelRecord $record): Response
{
if (!in_array($record->getAttribute('status'), [BrokerPushLevelRecordStatusEnum::WAITING->value, BrokerPushMatchRecordStatusEnum::FAIL->value], false)) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息已发送或发送中,无法编辑');
}
$record->update($request->safe()->toArray());
OperationLog::admin()->subject($record)->updateAction()->content('等级推送《%s》', $record->getAttribute('title'));
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \Illuminate\Http\Request $request
* @param \App\Models\BrokerPushLevelRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function children(Request $request, BrokerPushLevelRecord $record): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except(['page', 'pageSize']);
$data = BrokerPushLevelRecordItem::filter($filter)->where('record_id', $record->getKey())->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Models\BrokerPushLevelRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function send(BrokerPushLevelRecord $record): Response
{
if ($record->getAttribute('status') === BrokerPushLevelRecordStatusEnum::WAITING->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息尚未到发送时间,无法发送');
}
if (!in_array($record->getAttribute('status'), [BrokerPushLevelRecordStatusEnum::FAIL->value, BrokerPushLevelRecordStatusEnum::ROLLBACK->value], false)) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息已发送或发送中,无法再次发送');
}
$record->update(['status' => BrokerPushLevelRecordStatusEnum::PROCESSING, 'publish_at' => now()->toDateTimeString()]);
OperationLog::admin()->subject($record)->updateAction()->content('发送经纪人等级通知《%s》', $record->getAttribute('title'));
BrokerPushLevelRecordSendJob::dispatch($record);
return $this->success(ApiCode::UPDATE_SUCCESS, '操作成功');
}
/**
* @param \App\Models\BrokerPushLevelRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function rollback(BrokerPushLevelRecord $record): Response
{
if ($record->getAttribute('status') !== BrokerPushLevelRecordStatusEnum::SUCCESS->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息尚未发送成功,无法撤回');
}
$record->update(['status' => BrokerPushLevelRecordStatusEnum::PROCESSING]);
BrokerPushLevelRecordRollBackJob::dispatch($record);
OperationLog::admin()->subject($record)->updateAction()->content('撤销经纪人等级通知《%s》', $record->getAttribute('title'));
return $this->success(ApiCode::UPDATE_SUCCESS, '操作成功');
}
public function destroy(BrokerPushLevelRecord $record): Response
{
BrokerPushLevelRecord::destroy($record->getKey());
BrokerPushLevelRecordItem::query()->where('record_id', $record->getKey())->update(['deleted_at' => now()->toDateTimeString()]);
OperationLog::admin()->subject($record)->deleteAction()->content('等级推送《%s》', $record->getAttribute('title'));
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System\Broker;
use App\Enums\BrokerPushMatchRecordStatusEnum;
use App\Excel\BrokerPushMatchRecordExcel;
use App\Helpers\IMHelper;
use App\Helpers\OperationLog;
use App\Jobs\BrokerPushMatchRecordJob;
use App\Models\BrokerPushMatchRecord;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class PushMatchRecordController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function index(Request $request): Response|BinaryFileResponse
{
$pageSize = $request->get('pageSize', 20);
$fetchType = $request->get('fetchType', 'page');
$filter = $request->except(['page', 'pageSize', 'fetchType']);
$build = BrokerPushMatchRecord::query()->filter($filter);
if ($fetchType === 'excel') {
return BrokerPushMatchRecordExcel::withBuilder($build)->download('运营经纪人-合作通知日志.xlsx');
}
return $this->successWithData($build->paginate($pageSize));
}
/**
* @param \App\Models\BrokerPushMatchRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function send(BrokerPushMatchRecord $record): Response
{
if (!in_array($record->getAttribute('status'), [BrokerPushMatchRecordStatusEnum::FAIL->value, BrokerPushMatchRecordStatusEnum::ROLLBACK->value], false)) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息已发送或发送中,无法再次发送');
}
DB::transaction(static function () use ($record) {
$record->update(['status' => 0, 'message_id' => '']);
OperationLog::admin()->subject($record)->updateAction()->content('发送合作通知日志:' . $record->getAttribute('content'));
BrokerPushMatchRecordJob::dispatch($record);
});
return $this->success(ApiCode::UPDATE_SUCCESS, '操作成功');
}
/**
* @param \App\Models\BrokerPushMatchRecord $record
* @return \Hikoon\LaravelApi\Facades\Response
* @throws \Exception
*/
public function rollback(BrokerPushMatchRecord $record): Response
{
if ($record->getAttribute('status') !== BrokerPushMatchRecordStatusEnum::SUCCESS->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '消息尚未发送成功,无法撤回');
}
if (IMHelper::rollbackChatMessage(IMHelper::SYSTEM_USER, $record->getAttribute('broker_id'), $record->getAttribute('message_id'))->json('ActionStatus') === 'OK') {
$record->update(['status' => BrokerPushMatchRecordStatusEnum::ROLLBACK]);
OperationLog::admin()->subject($record)->updateAction()->content('撤销合作通知日志:' . $record->getAttribute('content'));
}
return $this->success(ApiCode::UPDATE_SUCCESS, '操作成功');
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System\Broker;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\BrokerPushLevelRecordRequest;
use App\Http\Container\AdminSection\Requests\System\BrokerUserConfigRequest;
use App\Jobs\BrokerPushLevelRecordCreateJob;
use App\Models\BrokerUserConfig;
use App\Models\BrokerUserConfigItem;
use App\Models\User;
use Arr;
use Auth;
use Carbon\Carbon;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Hikoon\LaravelApi\Support\ApiController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\LazyCollection;
use Str;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Vtiful\Kernel\Excel;
class UserConfigController extends ApiController
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$fetchType = $request->get('fetchType', 'page');
$filter = $request->except(['page', 'pageSize', 'fetchType']);
$build = BrokerUserConfig::query()->filter($filter);
if ($fetchType === 'all') {
return $this->successWithData($build->get());
}
return $this->successWithData($build->paginate($pageSize));
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerUserConfigRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(BrokerUserConfigRequest $request): Response
{
if (!RateLimiter::attempt('user-' . Auth::id() . '-create-broker', $perMinute = 1, static fn() => NULL, 3)) {
return $this->fail(ApiCode::REQUEST_LIMIT_ERROR);
}
$attribute = $request->safe(['title', 'begin_at', 'end_at', 'push_type', 'push_at']) + ['user_id' => Auth::id()];
$items = $request->input('items', []);
$userConfig = DB::transaction(static function () use ($attribute, $items) {
$config = BrokerUserConfig::query()->create(Arr::except($attribute, ['push_type', 'push_at']));
BrokerUserConfigItem::query()->upsert(
Arr::map($items, static fn($item) => Arr::only($item + ['config_id' => $config->getKey()], ['user_id', 'identifier', 'singer_num', 'config_id'])),
['config_id', 'user_id'], ['singer_num', 'identifier']
);
BrokerUserConfigItem::query()
->leftJoin('users', 'users.id', 'broker_user_config_items.user_id')
->where('broker_user_config_items.config_id', $config->getKey())
->whereIn('users.identity', [2, 3])
->update(['broker_user_config_items.status' => DB::raw('CASE WHEN users.`status`= 1 THEN 1 ELSE 0 END')]);
BrokerPushLevelRecordCreateJob::dispatchIf($attribute['push_type'] !== 0, $config, Auth::id(), Carbon::parse($attribute['push_at']), $attribute['push_type'] === 2);
OperationLog::admin()->subject($config)->createAction()->content('经纪人配置《%s》', Str::limit($config->getAttribute('title')));
return $config;
});
return $this->successWithData($userConfig, ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Models\BrokerUserConfig $userConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function show(BrokerUserConfig $userConfig): Response
{
$userConfig->load(['user:id,nick_name,real_name,identity', 'items:id,user_id,config_id,identifier,singer_num,status', 'items.user:id,nick_name,real_name,identity']);
return $this->successWithData($userConfig);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerUserConfigRequest $request
* @param \App\Models\BrokerUserConfig $userConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(BrokerUserConfigRequest $request, BrokerUserConfig $userConfig): Response
{
$attribute = $request->safe(['title', 'begin_at', 'end_at']) + ['user_id' => Auth::id()];
$items = $request->input('items', []);
DB::transaction(static function () use ($userConfig, $attribute, $items) {
$userConfig->update($attribute);
$itemIds = Arr::pluck($items, 'user_id');
//删除不在此次用户数据
BrokerUserConfigItem::query()->where('config_id', $userConfig->getKey())->whereNotIn('user_id', $itemIds)->delete();
//新增与更新数据
BrokerUserConfigItem::query()->upsert(
Arr::map($items, static fn($item) => Arr::only($item + ['config_id' => $userConfig->getKey()], ['user_id', 'identifier', 'singer_num', 'config_id'])),
['config_id', 'user_id'], ['singer_num', 'identifier']
);
//同步经纪人匹配状态
BrokerUserConfigItem::query()
->leftJoin('users', 'users.id', 'broker_user_config_items.user_id')
->where('broker_user_config_items.config_id', $userConfig->getKey())
->whereIn('users.identity', [2, 3])
->update(['broker_user_config_items.status' => DB::raw('CASE WHEN users.`status`= 1 AND users.`identity` IN (2,3) THEN 1 ELSE 0 END')]);
OperationLog::admin()->subject($userConfig)->updateAction()->content('经纪人配置《%s》', Str::limit($userConfig->getAttribute('title')));
});
return $this->successWithData($userConfig, ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Models\BrokerUserConfig $userConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(BrokerUserConfig $userConfig): Response
{
$userConfig->delete();
$userConfig->items()->delete();
OperationLog::admin()->subject($userConfig)->deleteAction()->content('经纪人配置《%s》', Str::limit($userConfig->getAttribute('title')));
return $this->success(ApiCode::DELETE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\BrokerPushLevelRecordRequest $request
* @param \App\Models\BrokerUserConfig $userConfig
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function storeLevelRecord(BrokerPushLevelRecordRequest $request, BrokerUserConfig $userConfig): Response
{
$attribute = $request->safe()->toArray();
BrokerPushLevelRecordCreateJob::dispatchSync($userConfig, Auth::id(), Carbon::parse($attribute['publish_at']), (bool)$attribute['is_alert']);
return $this->success(ApiCode::CREATE_SUCCESS);
}
/**
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function upload(Request $request): StreamedResponse
{
ignore_user_abort(true);
return \Response::stream(static function () use ($request) {
LazyCollection::make(static function () use ($request) {
$filePath = $request->file('file')?->path();
$excel = new Excel(['path' => dirname($filePath)]);
$excel->openFile(basename($filePath))->openSheet()->setSkipRows(0);
while (($row = $excel->nextRow()) !== NULL) {
yield $row;
}
})->chunk(1000)->each(function (LazyCollection $items) {
$users = User::withTrashed()->whereIn('id', Arr::pluck($items, 0))->get(['id', 'identity', 'nick_name', 'status'])->groupBy('id');
$items->reject(fn($item) => array_filter($item) === [])->each(function ($item) use ($users) {
if (isset($item[0], $item[1], $item[2]) && is_int($item[0]) && (is_int($item[2]) || empty($item[2]))) {
$user = $users->get($item[0])?->first();
echo "id: " . $item[0] . PHP_EOL . "event: item" . PHP_EOL . "data: " . json_encode([
'user' => ['id' => $item[0], 'nick_name' => $user?->getRawOriginal('nick_name', '')],
'identifier' => $item[1],
'singer_num' => $item[2],
'status' => ($user?->getRawOriginal('status') === 1 && in_array($user?->getRawOriginal('identity'), [2, 3], false)) ? 1 : 0
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE) . PHP_EOL . 'retry: 5000' . PHP_EOL . PHP_EOL;
ob_flush();
flush();
}
});
});
echo "id: 0" . PHP_EOL . "event: finish" . PHP_EOL . "data: {}" . PHP_EOL . 'retry: 0' . PHP_EOL . PHP_EOL;
ob_flush();
flush();
}, 200, ['Cache-Control' => 'no-cache', 'Content-Type' => 'text/event-stream', 'X-Accel-Buffering' => 'no']);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\ConfigCreateRequest;
use App\Http\Container\AdminSection\Requests\System\ConfigUpdateRequest;
use App\Http\Request\BooleanStatusRequest;
use App\Models\SystemConfig;
use App\Models\User;
use App\Support\Controller;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Http\Request;
class ConfigController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->mergeIfMissing(['parent_id' => 0])->except('page', 'pageSize', 'fetchType');
$fetchType = $request->get('fetchType', 'page');
$build = SystemConfig::filter($filter);
return match ($fetchType) {
'all' => $this->successWithData($build->get()),
default => $this->successWithData($build->paginate($pageSize))
};
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\ConfigCreateRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(ConfigCreateRequest $request): Response
{
$config = SystemConfig::query()->create($request->validated());
OperationLog::admin()->createAction()->subject($config)->content('配置字典《%s》', $config->getAttribute('name'));
return $this->successWithData($config, ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Models\SystemConfig $config
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function show(SystemConfig $config): Response
{
$config->load('children');
return $this->successWithData($config);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\ConfigUpdateRequest $request
* @param \App\Models\SystemConfig $config
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(ConfigUpdateRequest $request, SystemConfig $config): Response
{
//如果是修改Demo标签
if ($config->getAttribute('identifier') === 'CtGly-jsLjAu5yJvLrN7L') {
$currentIds = array_filter(explode(',', $config->getAttribute('content')));
$nextIds = array_filter(explode(',', $request->get('content')));
$diffIds = array_filter($currentIds, static fn($value) => !in_array($value, $nextIds, false));
if (count($diffIds) > 0) {
User::query()->where('demo_type', 0)
->whereHas('authTags', fn($query) => $query->whereIn('tag_id', $diffIds))
->whereDoesntHave('authTags', fn($query) => $query->whereIn('tag_id', $nextIds))
->update(['demo_type' => 1]);
}
}
$config->update($request->validated());
OperationLog::admin()->updateAction()->subject($config)->content('配置字典《%s》', $config->getAttribute('name'));
return $this->successWithData($config, ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Http\Request\BooleanStatusRequest $request
* @param \App\Models\SystemConfig $config
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function changeStatus(BooleanStatusRequest $request, SystemConfig $config): Response
{
$config->update($request->validated());
OperationLog::admin()->statusAction()->subject($config)->content('配置字典《%s》', $config->getAttribute('name'));
return $this->success(ApiCode::UPDATE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\MaterialRequest;
use App\Models\SystemMaterial;
use App\Support\Controller;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Http\Request;
class MaterialController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 30);
$filter = $request->except('page', 'pageSize');
$data = SystemMaterial::filter($filter)->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\MaterialRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(MaterialRequest $request): Response
{
$attributes = $request->safe()->offsetGet('data');
$ids = [];
foreach ($attributes as $attribute) {
$ids[] = SystemMaterial::query()->create($attribute)?->getKey();
}
OperationLog::admin()->createAction()->subject(new SystemMaterial(['id' => 0]))->content('素材:%s', implode(',', $ids));
return $this->success(ApiCode::CREATE_SUCCESS);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\MaterialRequest $request
* @param \App\Models\SystemMaterial $material
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(MaterialRequest $request, SystemMaterial $material): Response
{
$material->update($request->validated());
OperationLog::admin()->updateAction()->subject($material)->content('素材:%s', $material->getKey());
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\SystemMaterial $material
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function destroy(SystemMaterial $material): Response
{
$material->delete();
OperationLog::admin()->deleteAction()->subject($material)->content('素材:%s', $material->getKey());
return $this->success(ApiCode::DELETE_SUCCESS);
}
}
<?php
namespace App\Http\Container\AdminSection\Controllers\System;
use App\Enums\NotificationStatusEnum;
use App\Helpers\OperationLog;
use App\Http\Container\AdminSection\Requests\System\NotificationRequest;
use App\Jobs\SystemNotificationRollBackJob;
use App\Jobs\SystemNotificationSendJob;
use App\Models\Activity;
use App\Models\Notification;
use App\Models\NotificationUser;
use App\Models\Project;
use App\Models\User;
use App\Support\Controller;
use Auth;
use DB;
use Hikoon\LaravelApi\Facades\Response;
use Hikoon\LaravelApi\Support\ApiCode;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Str;
class NotificationController extends Controller
{
/**
* @param \Illuminate\Http\Request $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function index(Request $request): Response
{
$pageSize = $request->get('pageSize', 20);
$filter = $request->except(['page', 'pageSize']);
$data = Notification::filter($filter)->withCount([
'items',
'items as read_count' => fn($query) => $query->whereNotNull('read_at')
])->paginate($pageSize);
return $this->successWithData($data);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\NotificationRequest $request
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function store(NotificationRequest $request): Response
{
$attribute = $request->validated();
$notify = DB::transaction(static function () use ($attribute) {
$notify = Notification::query()->create($attribute + ['user_id' => Auth::id()]);
User::filter(['status' => [0, 1], 'identityRole' => $attribute['publish_to']])->select(['id'])
->chunkById(1000, function (Collection $collection) use ($notify) {
NotificationUser::query()->insert($collection->map(fn($item) => [
'user_id' => $item->getKey(),
'notification_id' => $notify->getKey(),
'created_at' => $notify->getAttribute('created_at'),
'updated_at' => $notify->getAttribute('updated_at'),
'status' => NotificationStatusEnum::WAITING
])->toArray());
});
OperationLog::admin()->subject($notify)->createAction()->content('系统通知《%s》', Str::limit($notify->getAttribute('title')));
return $notify;
});
if ($notify->getAttribute('publish_type') === 1) {
$this->send($notify);
}
return $this->successWithData($notify);
}
/**
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function show(Notification $notification): Response
{
$notification->loadCount('items');
$linkType = $notification->getAttribute('link_type');
$linkId = $notification->getAttribute('link_id');
$notification->setAttribute('link_name', match ($linkType) {
'user' => User::query()->whereKey($linkId)->value('nick_name'),
'project' => Project::query()->whereKey($linkId)->value('name'),
'activity' => Activity::query()->whereKey($linkId)->value('song_name'),
default => ''
});
return $this->successWithData($notification);
}
/**
* @param \App\Http\Container\AdminSection\Requests\System\NotificationRequest $request
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function update(NotificationRequest $request, Notification $notification): Response
{
if (!in_array($notification->getAttribute('status'), [NotificationStatusEnum::CANCEL->value, NotificationStatusEnum::ROLLBACK->value], false)) {
return $this->fail(ApiCode::VALIDATION_ERROR, '当前状态无法修改');
}
$attribute = $request->validated();
$data = DB::transaction(static function () use ($notification, $attribute) {
$notification->update($attribute + ['status' => NotificationStatusEnum::WAITING]);
NotificationUser::query()->where('notification_id', $notification->getKey())->delete();
User::filter(['status' => [0, 1], 'identityRole' => $attribute['publish_to']])->select(['id'])->chunkById(1000, function (Collection $collection) use ($notification) {
NotificationUser::query()->insert($collection->map(fn($item) => [
'user_id' => $item->getKey(),
'notification_id' => $notification->getKey(),
'created_at' => $notification->getAttribute('created_at'),
'updated_at' => $notification->getAttribute('updated_at')
])->toArray());
});
OperationLog::admin()->subject($notification)->updateAction()
->content('系统通知《%s》%s', Str::limit($notification->getAttribute('title')), $notification->getChangeColumn()->except(['status'])->format(',修改了:'));
return $notification;
});
return $this->successWithData($data);
}
/**
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function send(Notification $notification): Response
{
if (!in_array($notification->getAttribute('status'), [NotificationStatusEnum::WAITING->value, NotificationStatusEnum::FAIL->value], false)) {
return $this->fail(ApiCode::VALIDATION_ERROR, '当前状态无法发送');
}
DB::transaction(static function () use ($notification) {
$notification->update(['status' => NotificationStatusEnum::PROCESSING, 'publish_at' => now()->toDateTimeString()]);
SystemNotificationSendJob::dispatch($notification);
});
return $this->successWithData(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function cancel(Notification $notification): Response
{
if ($notification->getAttribute('status') !== NotificationStatusEnum::WAITING->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '当前状态无法取消发送');
}
$notification->update(['status' => NotificationStatusEnum::CANCEL]);
OperationLog::admin()->subject($notification)->updateAction()->content('系统通知《%s》取消发送', Str::limit($notification->getAttribute('title')));
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
* @throws \Throwable
*/
public function rollback(Notification $notification): Response
{
if ($notification->getAttribute('status') !== NotificationStatusEnum::SUCCESS->value) {
return $this->fail(ApiCode::VALIDATION_ERROR, '当前状态无法撤销发送');
}
DB::transaction(static function () use ($notification) {
$notification->update(['status' => NotificationStatusEnum::ROLLBACK]);
SystemNotificationRollBackJob::dispatch($notification);
});
return $this->success(ApiCode::UPDATE_SUCCESS);
}
/**
* @param \Illuminate\Http\Request $request
* @param \App\Models\Notification $notification
* @return \Hikoon\LaravelApi\Facades\Response
*/
public function user(Request $request, Notification $notification): Response
{
$pageSize = $request->get('pageSize', 20);
$userFilter = $request->only(['nick_name', 'authIds', 'setWith']);
$notifyFilter = $request->only(['status', 'read_status']);
$data = User::filter($userFilter)->withTrashed($userFilter)->withCasts(['status' => 'int'])
->joinSub(NotificationUser::filter($notifyFilter)->where('notification_id', $notification->getKey()), 'notify', 'notify.user_id', 'users.id')
->selectRaw('users.id,users.nick_name,users.sex,users.avatar,users.official_status,users.identity,notify.status,notify.send_at,notify.read_at')
->orderByRaw('users.created_at')
->paginate($pageSize);
return $this->successWithData($data);
}
}
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
No preview for this file type
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
No preview for this file type
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This file is too large to display.