ActivityRewardService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. <?php
  2. namespace App\Services;
  3. use App\Constants\HttpStatus;
  4. use App\Constants\Util;
  5. use App\Models\ActivityReward;
  6. use App\Models\Config;
  7. use App\Models\Role;
  8. use App\Models\UserSession;
  9. use App\Models\User;
  10. use Illuminate\Support\Facades\Validator;
  11. use Illuminate\Validation\Rule;
  12. use App\Support\ActivityCatalog;
  13. use App\Support\AnnouncementCatalog;
  14. use Illuminate\Validation\ValidationException;
  15. use Exception;
  16. class ActivityRewardService extends BaseService
  17. {
  18. public static string $MODEL = ActivityReward::class;
  19. public static function configurationDefaults(): array
  20. {
  21. return [
  22. 'publish_platforms' => [], 'budget' => null, 'reward_quota' => null, 'sort' => 0,
  23. 'register_time_limited' => false, 'register_start_time' => null, 'register_end_time' => null,
  24. 'register_platforms' => [], 'deposit_limited' => false, 'deposit_min' => null,
  25. 'deposit_max' => null, 'show_ineligible' => true,
  26. ];
  27. }
  28. private static function configuration(array $params, ?ActivityReward $existing): array
  29. {
  30. $defaults = static::configurationDefaults();
  31. if ((int)($params['type'] ?? $existing?->type) === 2) {
  32. return $defaults;
  33. }
  34. $quotaMode = !$existing || $existing->quota_enabled;
  35. Validator::make($params, [$quotaMode ? 'budget' : 'reward_quota' => ['prohibited']])->validate();
  36. $previous = $existing ? $existing->only(array_keys($defaults)) : [];
  37. foreach (['publish_platforms', 'register_platforms'] as $field) {
  38. if (($previous[$field] ?? null) === null) $previous[$field] = [];
  39. }
  40. $config = array_replace($defaults, $previous, array_intersect_key($params, $defaults));
  41. if ($quotaMode) $config['budget'] = null;
  42. else $config['reward_quota'] = null;
  43. if (empty($config['register_time_limited'])) {
  44. $config['register_start_time'] = $config['register_end_time'] = null;
  45. }
  46. if (empty($config['deposit_limited'])) {
  47. $config['deposit_min'] = $config['deposit_max'] = null;
  48. }
  49. $money = ['nullable', 'numeric', 'min:0', 'regex:/^\d{1,12}(\.\d{1,2})?$/'];
  50. Validator::make($config, [
  51. 'publish_platforms' => ['present', 'array'],
  52. 'publish_platforms.*' => ['required', 'string', 'distinct', Rule::in(['PC', 'H5', 'APP'])],
  53. 'reward_quota' => ['nullable', 'integer', 'min:0', 'max:2147483647'],
  54. 'budget' => $money,
  55. 'sort' => ['required', 'integer', 'min:0', 'max:4294967295'],
  56. 'register_time_limited' => ['required', 'boolean'],
  57. 'register_start_time' => ['nullable', 'required_if:register_time_limited,1', 'date'],
  58. 'register_end_time' => ['nullable', 'required_if:register_time_limited,1', 'date', 'after_or_equal:register_start_time'],
  59. 'register_platforms' => ['present', 'array'],
  60. 'register_platforms.*' => ['required', 'string', 'distinct', Rule::in(['PC', 'H5', 'APP'])],
  61. 'deposit_limited' => ['required', 'boolean'],
  62. 'deposit_min' => array_merge($money, ['required_if:deposit_limited,1']),
  63. 'deposit_max' => array_merge($money, ['required_if:deposit_limited,1', 'gte:deposit_min']),
  64. 'show_ineligible' => ['required', 'boolean'],
  65. ])->validate();
  66. foreach (['budget', 'deposit_min', 'deposit_max'] as $field) {
  67. if ($config[$field] !== null) {
  68. $config[$field] = bcadd((string)$config[$field], '0', 2);
  69. }
  70. }
  71. if ($config['reward_quota'] !== null) $config['reward_quota'] = (int)$config['reward_quota'];
  72. foreach (['register_start_time', 'register_end_time'] as $field) {
  73. if ($config[$field] !== null) {
  74. $config[$field] = date('Y-m-d H:i:s', strtotime($config[$field]));
  75. }
  76. }
  77. return $config;
  78. }
  79. /**
  80. * @description: 查询单条数据
  81. * @param array $search
  82. * @return ActivityReward|null
  83. */
  84. public static function findOne(array $search): ActivityReward|null
  85. {
  86. return static::$MODEL::where(static::getWhere($search))->first();
  87. }
  88. public static function activeForMember(int $type, $memberId): ?ActivityReward
  89. {
  90. $context = ActivityEligibilityService::memberContext($memberId);
  91. $activities = ActivityReward::where('lang', 'zh')->where('type', $type)->where('status', 1)
  92. ->where('start_time', '<=', time())->where('end_time', '>=', time())
  93. ->orderByDesc('sort')->orderByDesc('id')->get();
  94. foreach ($activities as $activity) {
  95. if (!ActivityEligibilityService::evaluate($activity, $context)['is_eligible']) continue;
  96. if (!ActivityQuotaService::canAward($activity, $memberId)) continue;
  97. return $activity;
  98. }
  99. return null;
  100. }
  101. public static function getWhere(array $search = []): array
  102. {
  103. $where = [];
  104. if (isset($search['id']) && !empty($search['id'])) {
  105. $where[] = ['id', '=', $search['id']];
  106. }
  107. if (isset($search['status']) && $search['status'] != '') {
  108. $where[] = ['status', '=', $search['status']];
  109. }
  110. if (isset($search['type']) && $search['type'] != '') {
  111. $where[] = ['type', '=', $search['type']];
  112. }
  113. if (isset($search['title']) && !empty($search['title'])) {
  114. $where[] = ['title', 'like', "%{$search['title']}%"];
  115. }
  116. if (isset($search['start_time']) && !empty($search['start_time'])) {
  117. if (is_array($search['start_time'])) {
  118. if (count($search['start_time']) == 2) {
  119. $where[] = ['start_time', $search['start_time'][0], $search['start_time'][1]];
  120. } else {
  121. $where[] = ['start_time', '=', $search['start_time'][0]];
  122. }
  123. } else {
  124. $where[] = ['start_time', '=', $search['start_time']];
  125. }
  126. }
  127. if (isset($search['end_time']) && !empty($search['end_time'])) {
  128. if (is_array($search['end_time'])) {
  129. if (count($search['end_time']) == 2) {
  130. $where[] = ['end_time', $search['end_time'][0], $search['end_time'][1]];
  131. } else {
  132. $where[] = ['end_time', '=', $search['end_time'][0]];
  133. }
  134. } else {
  135. $where[] = ['end_time', '=', $search['end_time']];
  136. }
  137. }
  138. return $where;
  139. }
  140. public static function deleteAll(array $search = []): bool
  141. {
  142. $count = static::$MODEL::where(static::getWhere($search))->delete();
  143. if ($count < 1) throw new Exception('删除失败', HttpStatus::CUSTOM_ERROR);
  144. return true;
  145. }
  146. /**
  147. * Update or create
  148. * @param array $params
  149. * @return bool
  150. * @throws Exception
  151. */
  152. public static function submit(array $params = []): bool
  153. {
  154. $info = !empty($params['id']) ? static::findOne(['id' => $params['id']]) : null;
  155. if (!empty($params['id']) && !$info) throw new Exception('操作失败', HttpStatus::CUSTOM_ERROR);
  156. if (array_key_exists('game_category', $params)) {
  157. Validator::make($params, [
  158. 'game_category' => ['nullable', 'string', Rule::in(array_keys(ActivityCatalog::gameTypes()))],
  159. ])->validate();
  160. if ($params['game_category'] === '') $params['game_category'] = null;
  161. }
  162. $params = array_merge($params, static::configuration($params, $info), static::clickBehavior($params, $info));
  163. unset($params['rewarded_count'], $params['quota_enabled'], $params['budget_used']); // Server-owned fields.
  164. if (isset($params['start_time']))
  165. $params['start_time'] = strtotime($params['start_time']);
  166. if (isset($params['end_time']))
  167. $params['end_time'] = strtotime($params['end_time']);
  168. if (isset($params['detail_image']))
  169. $params['detail_image'] = Util::replacePartInUrl($params['detail_image']);
  170. if (isset($params['pc_image']))
  171. $params['pc_image'] = Util::replacePartInUrl($params['pc_image']);
  172. if (isset($params['detail_banner']))
  173. $params['detail_banner'] = Util::replacePartInUrl($params['detail_banner']);
  174. if (isset($params['pc_banner']))
  175. $params['pc_banner'] = Util::replacePartInUrl($params['pc_banner']);
  176. if (!empty($params['id'])) {
  177. $info->update($params);
  178. } else {
  179. $info = new ActivityReward($params);
  180. $info->quota_enabled = true; // Existing rows stay false; clients cannot change the mode.
  181. $info->save();
  182. }
  183. return true;
  184. }
  185. private static function clickBehavior(array $params, ?ActivityReward $existing): array
  186. {
  187. if (!array_key_exists('click_type', $params) && !array_key_exists('link', $params) && !array_key_exists('click_position', $params)) return [];
  188. $type = array_key_exists('click_type', $params) ? $params['click_type'] : ($existing?->click_type ?? 'none');
  189. Validator::make(['click_type' => $type], ['click_type' => ['required', 'string', Rule::in(array_keys(AnnouncementCatalog::CLICKS))]],
  190. ['click_type.in' => '点击方式仅支持无链接、外站链接、站内路径'])->validate();
  191. if ($type === 'none') return ['click_type' => 'none', 'link' => null, 'click_position' => null];
  192. $position = array_key_exists('click_position', $params) ? $params['click_position'] : $existing?->click_position;
  193. Validator::make(['click_position' => $position], ['click_position' => ['required', 'string', Rule::in(['cover', 'detail'])]],
  194. ['click_position.required' => '请选择链接触发位置', 'click_position.in' => '链接触发位置仅支持活动列表封面或活动详情按钮'])->validate();
  195. $link = array_key_exists('link', $params) ? $params['link'] : $existing?->link;
  196. Validator::make(['link' => $link], ['link' => ['required', 'string', 'max:2048']], ['link.required' => '请填写跳转链接'])->validate();
  197. $valid = !preg_match('/[\x00-\x20\x7f\\\\]/', $link);
  198. if ($type === 'internal') $valid = $valid && str_starts_with($link, '/') && !str_starts_with($link, '//');
  199. else {
  200. $parts = parse_url($link);
  201. $valid = $valid && filter_var($link, FILTER_VALIDATE_URL) && $parts
  202. && in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true) && !isset($parts['user']) && !isset($parts['pass']);
  203. }
  204. if (!$valid) throw ValidationException::withMessages(['link' => '请填写有效的外站链接或站内相对路径']);
  205. return ['click_type' => $type, 'link' => $link, 'click_position' => $position];
  206. }
  207. public static function promotionalActivity($chatId): array
  208. {
  209. $keyboard = [];
  210. $url = Config::where('field','activity_home_url')->first()->val;
  211. $url.="?member_id=".$chatId;
  212. $keyboard[] = [
  213. ['text'=>'活动首页','url'=>$url]
  214. ];
  215. $msg = [];
  216. $msg['chat_id'] = $chatId;
  217. $msg['text'] = "点击下方按钮进入活动首页";
  218. $msg['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
  219. return $msg;
  220. }
  221. //F1bet娱乐更多玩法(足球首页)
  222. public static function footballUrl($chatId): array
  223. {
  224. $token = UserSession::setToken($chatId);
  225. $is_bind = User::where('member_id', $chatId)->value('account') ? 1 : 0;
  226. $keyboard = [];
  227. $url = Config::where('field','football_home_url ')->first()->val;
  228. $url.="?token=".$token."&is_bind=".$is_bind;
  229. $keyboard[] = [
  230. ['text'=>'F1bet娱乐首页','url'=>$url]
  231. ];
  232. $msg = [];
  233. $msg['chat_id'] = $chatId;
  234. $msg['text'] = "点击下方按钮进入F1bet娱乐首页";
  235. $msg['reply_markup'] = json_encode(['inline_keyboard' => $keyboard]);
  236. return $msg;
  237. }
  238. }