Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
050be992fd | ||
|
|
eac137bb9c | ||
|
|
8ec06cf1ce | ||
|
|
097b18c2c6 | ||
|
|
75cdcd384a | ||
|
|
08d8b7d1ba | ||
|
|
daab94d149 | ||
|
|
f1523ea03f |
@@ -6,20 +6,6 @@
|
||||
> 作为一款私域引流工具,凭借其强大的功能和灵活的应用场景,成为了推广营销领域的热门选择,它支持从抖音直接跳转到个人微信、微信群、小程序以及企业微信客服,有效打通了不同平台之间的壁垒,实现了流量的高效转化和利用,推广营销必备!
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 系统演示
|
||||
```
|
||||
演示地址:https://open.mayilink.cn/
|
||||
管理员账号:admin
|
||||
管理员密码:admin123
|
||||
用户账号:13999999999
|
||||
用户密码:123456
|
||||
```
|
||||
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
- admin 后台管理
|
||||
@@ -90,15 +76,7 @@ let baseUrl = "https://xxx.xxxxx.xxx"; // 后台请求域名改为你的域名
|
||||
|
||||
|
||||
## 重要信息
|
||||
|
||||
欢迎进群获取最新开发计划或交流技术
|
||||
|
||||
如需进群请添加微信号
|
||||
|
||||
微信号:tok4mi
|
||||
|
||||
添加请备注:交流群
|
||||
|
||||
项目合作洽谈,请联系客服微信(使用微信扫码添加好友,请注明来意)。
|
||||
|
||||
<img src="https://huoxing.tos-cn-shanghai.volces.com/2024/11/05/HFiFy0AaqSLxmlFb0RScBKgLtGVzC9xXb7M9HCJ5.jpg" width="400">
|
||||

|
||||
|
||||
|
||||
@@ -52,6 +52,26 @@ class SystemInit extends Command
|
||||
private function config_data(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'slug' => 'web_site_title',
|
||||
'value' => '蚂蚁快链-智慧引流工具',
|
||||
'desc' => '网站名称',
|
||||
],
|
||||
[
|
||||
'slug' => 'web_site_logo',
|
||||
'value' => '/image/toplogo.png',
|
||||
'desc' => '网站logo浅色',
|
||||
],
|
||||
[
|
||||
'slug' => 'web_site_bottom_logo',
|
||||
'value' => '/image/toplogo.png',
|
||||
'desc' => '网站logo深色',
|
||||
],
|
||||
[
|
||||
'slug' => 'web_site_customer_service',
|
||||
'value' => '/image/web-qr.png',
|
||||
'desc' => '客服二维码',
|
||||
],
|
||||
[
|
||||
'slug' => 'is_give_vip',
|
||||
'value' => 1,
|
||||
|
||||
@@ -10,6 +10,7 @@ enum LinkType: int
|
||||
case CLI_QR = 3;
|
||||
case WORK_WECHAT = 4;
|
||||
case LANDING_MINI = 5;
|
||||
case QR_QQ = 11; // 腾讯优码
|
||||
|
||||
// 获取所有的类型
|
||||
public static function getAllType(): array
|
||||
|
||||
@@ -19,7 +19,6 @@ class BaseConfig implements SimpleForm
|
||||
'recommend_commission_2' => 'nullable|integer',
|
||||
'member_pay_commission_1' => 'nullable|integer', // 一级邀请人消费提成 1%
|
||||
'member_pay_commission_2' => 'nullable|integer', // 二级邀请人消费提成 1%
|
||||
'send_code_mode' => 'required|integer|in:1,2', // 1短信2邮箱
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Forms;
|
||||
|
||||
use App\Services\SystemConfig;
|
||||
use Illuminate\Http\Request;
|
||||
use Ugly\Base\Contracts\SimpleForm;
|
||||
|
||||
class WebSite implements SimpleForm
|
||||
{
|
||||
public function policy(Request $request): array
|
||||
{
|
||||
return $request->validate([
|
||||
'web_site_customer_service' => 'nullable',
|
||||
'web_site_title' => 'nullable',
|
||||
'web_site_logo' => 'nullable',
|
||||
'web_site_bottom_logo' => 'nullable',
|
||||
|
||||
'verify_code_is_open' => 'required|boolean',
|
||||
'send_code_mode' => 'nullable|integer|in:1,2', // 1短信2邮箱
|
||||
'ali_sms_key' => 'nullable',
|
||||
'ali_sms_secret' => 'nullable',
|
||||
'ali_sms_sign_name' => 'nullable',
|
||||
|
||||
'mail_from_name' => 'nullable',
|
||||
'mail_host' => 'nullable',
|
||||
'mail_port' => 'nullable',
|
||||
'mail_from_address' => 'nullable',
|
||||
'mail_username' => 'nullable',
|
||||
'mail_password' => 'nullable',
|
||||
|
||||
'wechat_pay_app_id' => 'nullable',
|
||||
'wechat_pay_mch_id' => 'nullable',
|
||||
'wechat_pay_secret_key' => 'nullable',
|
||||
'wechat_pay_private_cert' => 'nullable',
|
||||
'wechat_pay_certificate' => 'nullable',
|
||||
]);
|
||||
}
|
||||
|
||||
// 保存
|
||||
public function handle(array $input): void
|
||||
{
|
||||
SystemConfig::set($input);
|
||||
}
|
||||
|
||||
public function default(): array
|
||||
{
|
||||
return [
|
||||
'web_site_customer_service' => SystemConfig::get('web_site_customer_service'),
|
||||
'web_site_title' => SystemConfig::get('web_site_title'),
|
||||
'web_site_logo' => SystemConfig::get('web_site_logo'),
|
||||
'web_site_bottom_logo' => SystemConfig::get('web_site_bottom_logo'),
|
||||
|
||||
'verify_code_is_open' => SystemConfig::get('verify_code_is_open'), // 开启
|
||||
'send_code_mode' => SystemConfig::get('send_code_mode'), //
|
||||
|
||||
'ali_sms_key' => SystemConfig::get('ali_sms_key'),
|
||||
'ali_sms_secret' => SystemConfig::get('ali_sms_secret'),
|
||||
'ali_sms_sign_name' => SystemConfig::get('ali_sms_sign_name'),
|
||||
|
||||
'mail_from_name' => SystemConfig::get('mail_from_name'),
|
||||
'mail_host' => SystemConfig::get('mail_host'),
|
||||
'mail_port' => SystemConfig::get('mail_port'),
|
||||
'mail_from_address' => SystemConfig::get('mail_from_address'),
|
||||
'mail_username' => SystemConfig::get('mail_username'),
|
||||
'mail_password' => SystemConfig::get('mail_password'),
|
||||
|
||||
'wechat_pay_app_id' => SystemConfig::get('wechat_pay_app_id'),
|
||||
'wechat_pay_mch_id' => SystemConfig::get('wechat_pay_mch_id'),
|
||||
'wechat_pay_secret_key' => SystemConfig::get('wechat_pay_secret_key'),
|
||||
'wechat_pay_private_cert' => SystemConfig::get('wechat_pay_secret_key'), // 私钥证书
|
||||
'wechat_pay_certificate' => SystemConfig::get('wechat_pay_certificate'), // 公钥证书
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Forms\BaseConfig;
|
||||
use App\Forms\WebSite;
|
||||
use Ugly\Base\Http\Controllers\SimpleFormController;
|
||||
|
||||
class ConfigController extends SimpleFormController
|
||||
{
|
||||
protected array $form = [
|
||||
'base' => BaseConfig::class,
|
||||
'web_site' => WebSite::class,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -114,19 +114,22 @@ class IndexController extends Controller
|
||||
$configs = [
|
||||
'code_mode' => SystemConfig::get('send_code_mode'),
|
||||
'package' => VipPackage::query()->orderBy('level')->get(['id', 'name', 'price', 'config']),
|
||||
'web_site_customer_service' => SystemConfig::get('web_site_customer_service'),
|
||||
'web_site_title' => SystemConfig::get('web_site_title'),
|
||||
'web_site_logo' => SystemConfig::get('web_site_logo'),
|
||||
'web_site_bottom_logo' => SystemConfig::get('web_site_bottom_logo'),
|
||||
'asset_url' => Storage::url(''), // 图片路径
|
||||
];
|
||||
|
||||
$user = auth('api')->user();
|
||||
if ($user) {
|
||||
// 图片路径
|
||||
$configs['asset_url'] = Storage::url('');
|
||||
// 安全域名
|
||||
// $configs['domains'] = Domain::query()->where('enable', true)->get(['id', 'title']);
|
||||
// 小程序 (区分是否有权限使用平台小程序池)
|
||||
$configs['mini_programs'] = MiniProgram::query()
|
||||
->where(
|
||||
fn($query) => $query->where('user_id', $user->id)
|
||||
->when($user->getPackageConfig('pre_min'), fn($query) => $query->orWhere('is_pre_min', true))
|
||||
fn ($query) => $query->where('user_id', $user->id)
|
||||
->when($user->getPackageConfig('pre_min'), fn ($query) => $query->orWhere('is_pre_min', true))
|
||||
)
|
||||
->get(['id', 'name']);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enums\LinkType;
|
||||
use App\Enums\SwitchType;
|
||||
use App\Enums\UserType;
|
||||
use App\Enums\UVLimitType;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkVisitLog;
|
||||
use App\Models\MiniProgram;
|
||||
use App\Models\User;
|
||||
use App\Models\VipPackage;
|
||||
use EasyWeChat\MiniApp\Application as WechatApp;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Cache\Psr16Cache;
|
||||
use Ugly\Base\Traits\ApiResource;
|
||||
|
||||
class JumpController extends Controller
|
||||
{
|
||||
use ApiResource;
|
||||
|
||||
// 获取链接跳转目标
|
||||
public function target(Request $request, $code): JsonResponse
|
||||
{
|
||||
$link = Link::query()->where('code', $code)->first();
|
||||
if (empty($link)) {
|
||||
return $this->failed('此链接已被删除!');
|
||||
}
|
||||
if ($link->expired_at < now()) {
|
||||
return $this->failed('链接已过期!');
|
||||
}
|
||||
$user = User::query()->findOrFail($link->user_id);
|
||||
if (empty($user->status)) {
|
||||
return $this->failed('账号被禁用!');
|
||||
}
|
||||
$isAdmin = $user->type === UserType::Admin;
|
||||
if (! $isAdmin && ! $user->vip_id) {
|
||||
return $this->failed('会员已到期,禁止访问!');
|
||||
}
|
||||
|
||||
$device_uid = $request->input('device_uid') ?: Str::uuid();
|
||||
$cache = data_get(LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->where('device_uid', $device_uid)
|
||||
->orderByDesc('id')
|
||||
->first(), 'cache', []);
|
||||
if (empty($cache)) {
|
||||
if (! $isAdmin) {
|
||||
// 会员UV限制
|
||||
$totalUV = LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->groupBy('device_uid')
|
||||
->whereBetween('created_at', [$user->start_at, $user->end_at])
|
||||
->count();
|
||||
$limit = (int) data_get(VipPackage::query()->find($user->vip_id), 'config.uv_limit');
|
||||
|
||||
if ($limit > 0 && $totalUV >= $limit) {
|
||||
return $this->failed('当前访问已达上限!');
|
||||
}
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'title' => $link->title,
|
||||
'description' => $link->description,
|
||||
'icon' => Storage::url($link->icon),
|
||||
];
|
||||
if (
|
||||
$link->type === LinkType::MINI_PROGRAM ||
|
||||
$link->type === LinkType::LANDING_MINI
|
||||
) {
|
||||
// 跳转到小程序 参数
|
||||
$mp = MiniProgram::query()->findOrFail(data_get($link, 'config.min_id'));
|
||||
if (empty($mp) || ! $mp->is_enable) {
|
||||
return $this->failed('当前小程序不能使用');
|
||||
}
|
||||
if ($link->type === LinkType::LANDING_MINI) {
|
||||
$qr = $this->getWechatNextQr($link);
|
||||
if (empty($qr)) {
|
||||
return $this->failed('当前小程序已无可用二维码!');
|
||||
}
|
||||
}
|
||||
$cache['params'] = [
|
||||
'appid' => $mp->app_id,
|
||||
'secret' => $mp->secret,
|
||||
'qr' => $qr ?? null,
|
||||
'path' => data_get($link, 'config.url') ?: $mp->url,
|
||||
];
|
||||
} elseif ($link->type === LinkType::KING_DOC) {
|
||||
$cache['params']['sid'] = str_replace('https://kdocs.cn/l/', '', data_get($link, 'config.url'));
|
||||
} elseif ($link->type === LinkType::CLI_QR) {
|
||||
/**
|
||||
* 1、电脑端访问草料二维码官网:https://cli.im/img
|
||||
* 2、上传微信二维码、公众号二维码、企业微信二维码、视频号二维码等等,然后点生成活码
|
||||
* 3、后台创建草料
|
||||
*/
|
||||
$configUrl = str_replace('https://qr61.cn/', '', data_get($link, 'config.url'));
|
||||
[$userID, $cliID] = explode('/', $configUrl);
|
||||
$cache['params'] = [
|
||||
'userID' => $userID,
|
||||
'cliID' => $cliID,
|
||||
];
|
||||
} elseif ($link->type === LinkType::WORK_WECHAT) {
|
||||
$cache['params'] = [
|
||||
'url' => data_get($link, 'config.url'),
|
||||
];
|
||||
} elseif ($link->type === LinkType::QR_QQ) {
|
||||
$cache['params'] = [
|
||||
'url' => data_get($link, 'config.url'),
|
||||
];
|
||||
} else {
|
||||
return $this->failed('暂不支持此链接跳转!');
|
||||
}
|
||||
} else {
|
||||
unset($cache['target']);
|
||||
}
|
||||
|
||||
// 生成链接
|
||||
if ($link->type === LinkType::WORK_WECHAT) {
|
||||
$cache['target'] = data_get($cache, 'params.url');
|
||||
} elseif ($link->type === LinkType::CLI_QR) {
|
||||
$userID = data_get($cache, 'params.userID');
|
||||
$cliID = data_get($cache, 'params.cliID');
|
||||
$getTicketUrl = file_get_contents('https://nc.cli.im/api/weixin/getWxUrlScheme/?query=q=qr61.cn/'.$userID.'/'.$cliID.'&path=pages/code/code&appid=wx5db79bd23a923e8e&org_coding='.$userID);
|
||||
$fetchUrlData = file_get_contents(data_get(json_decode($getTicketUrl, true), 'data.wx_url_scheme.fetchUrl'));
|
||||
$arr = json_decode($fetchUrlData, true);
|
||||
$cache['target'] = data_get($arr, 'data.urlScheme');
|
||||
} elseif ($link->type === LinkType::QR_QQ) {
|
||||
$configUrl = data_get($cache, 'params.url');
|
||||
$getContent = file_get_contents($configUrl);
|
||||
$pattern = '/weixin:\/\/dl\/business\/\?t=[^\\\']+/';
|
||||
preg_match($pattern, $getContent, $matches);
|
||||
$cache['target'] = $matches[0];
|
||||
} elseif ($link->type === LinkType::KING_DOC) {
|
||||
$sidUrl = data_get($cache, 'params.sid');
|
||||
preg_match('/https:\/\/kdocs\.cn\/l\/([a-zA-Z0-9]+)/', $sidUrl, $matches);
|
||||
$url_link = Http::withUserAgent('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36')
|
||||
->get('https://account.kdocs.cn/api/v3/miniprogram/urllink', [
|
||||
'appid' => 'wx5b97b0686831c076',
|
||||
'path' => 'pages/navigate/navigate',
|
||||
'query' => http_build_query([
|
||||
'url' => 'pages/preview/preview?from=wxminiprogram&fid=256465035925&sid='.$matches[1].'&fname='.urlencode('扫码加微信.docx'),
|
||||
'scene' => '102',
|
||||
'jump_from' => 'wechatlogin_guide_passive',
|
||||
'comp' => 'docx',
|
||||
'dw' => '1',
|
||||
]),
|
||||
'env_version' => 'release',
|
||||
'is_expire' => 'true',
|
||||
'expire_time' => time() + 7200,
|
||||
])->json('url_link');
|
||||
$jinShanResponse = Http::get($url_link)->throw();
|
||||
preg_match("/url_scheme:\s*'([^']+)'/", $jinShanResponse, $matches);
|
||||
|
||||
$cache['target'] = $matches[1];
|
||||
} else {
|
||||
// 小程序
|
||||
$miniApp = new WechatApp([
|
||||
'app_id' => data_get($cache, 'params.appid'),
|
||||
'secret' => data_get($cache, 'params.secret'),
|
||||
]);
|
||||
try {
|
||||
$res = $miniApp->getClient()->postJson('wxa/generatescheme', [
|
||||
'jump_wxa' => [
|
||||
'path' => data_get($cache, 'params.path'),
|
||||
'query' => 'code='.data_get($link, 'code').'&device_uid='.$device_uid,
|
||||
],
|
||||
])
|
||||
->toArray();
|
||||
if (isset($res['errcode']) && $res['errcode'] === 40001) {
|
||||
$cache = new Psr16Cache(new FilesystemAdapter('easywechat', 1500));
|
||||
$cache->clear();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$res = [
|
||||
'errcode' => 1,
|
||||
'errmsg' => '小程序配置错误',
|
||||
];
|
||||
}
|
||||
if (! empty($res['errcode'])) {
|
||||
return $this->failed($res['errmsg']);
|
||||
}
|
||||
|
||||
$cache['target'] = data_get($res, 'openlink');
|
||||
}
|
||||
|
||||
if (isset($cache['target']) && $cache['target']) {
|
||||
LinkVisitLog::query()->create([
|
||||
'link_id' => $link->id,
|
||||
'user_id' => $link->user_id,
|
||||
'ip' => $request->getClientIp(),
|
||||
'device_uid' => $device_uid,
|
||||
'cache' => $cache,
|
||||
]);
|
||||
unset($cache['params']);
|
||||
|
||||
return $this->success($cache);
|
||||
} else {
|
||||
return $this->failed('访问错误!');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取需要展示的二维码
|
||||
public function getShowQr($code): JsonResponse
|
||||
{
|
||||
$link = Link::query()->where('code', $code)->first();
|
||||
if (empty($link) || ! $link->status) {
|
||||
return $this->success([
|
||||
'id' => '',
|
||||
'avatar' => '',
|
||||
'title' => '数据不存在~',
|
||||
'sub_title' => '',
|
||||
'qr' => '',
|
||||
]);
|
||||
}
|
||||
$config = data_get($link, 'config.wx');
|
||||
$device_uid = request()->input('device_uid') ?: Str::uuid();
|
||||
$cache_qr = data_get(LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->where('device_uid', $device_uid)
|
||||
->orderByDesc('id')
|
||||
->first(), 'cache.params.qr', []);
|
||||
if (empty($cache_qr)) {
|
||||
return $this->failed('参数错误!');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'id' => $link->id,
|
||||
'avatar' => Storage::url(data_get($config, 'avatar')),
|
||||
'title' => data_get($config, 'title'),
|
||||
'sub_title' => data_get($config, 'sub_title'),
|
||||
'qr' => Storage::url(data_get($cache_qr, 'path')),
|
||||
]);
|
||||
}
|
||||
|
||||
// 获取下一次访问时展示的二维码
|
||||
private function getWechatNextQr($link)
|
||||
{
|
||||
$qrCodes = data_get($link, 'config.wx.qr', []);
|
||||
// 对链接中的二维码们按照 sort 从小到大排序
|
||||
usort($qrCodes, function ($a, $b) {
|
||||
return $a['sort'] <=> $b['sort'];
|
||||
});
|
||||
|
||||
$UVLimitType = (int) data_get($link, 'config.wx.uv_limit_type');
|
||||
$keyPrefix = "link-wx-type{$UVLimitType}-{$link->id}";
|
||||
|
||||
$singleUVViews = [];
|
||||
if ($UVLimitType === UVLimitType::ACCUMULATE->value) {
|
||||
$key = "{$keyPrefix}-accumulate";
|
||||
$singleUVViews = Redis::hgetall($key);
|
||||
} elseif ($UVLimitType === UVLimitType::DAILY->value) {
|
||||
$ymdDate = date('Ymd');
|
||||
$key = "{$keyPrefix}-daily-{$ymdDate}";
|
||||
$singleUVViews = Redis::hgetall($key);
|
||||
}
|
||||
|
||||
// 排除掉已过期的二维码和超出访问上限的二维码
|
||||
$filteredQrCodes = array_filter($qrCodes, function ($qrCode) use ($singleUVViews) {
|
||||
$expiredAt = ! empty($expiredAt) ? Carbon::parse($qrCode['expired_at']) : now()->addHour();
|
||||
$sort = $qrCode['sort'];
|
||||
$visitUV = $singleUVViews[$sort] ?? 0;
|
||||
$uvLimitNum = $qrCode['uv_limit_num'] ?? 99999999;
|
||||
|
||||
return $expiredAt->greaterThan(now()) && (is_null($uvLimitNum) || $visitUV < $uvLimitNum);
|
||||
});
|
||||
if (empty($filteredQrCodes)) {
|
||||
return [];
|
||||
}
|
||||
$filteredQrCodes = array_values($filteredQrCodes);
|
||||
|
||||
// 切换方式.
|
||||
$nextIndex = 0; // 默认返回第一个二维码
|
||||
$switch_type = data_get($link, 'config.wx.switch_type');
|
||||
if ($switch_type === SwitchType::SEQUENCE->value) {
|
||||
// 顺序切换
|
||||
$lockKey = "link-sequence-{$link->id}";
|
||||
$indexKey = "link-sequence-index-{$link->id}";
|
||||
$lock = Cache::lock($lockKey, 1);
|
||||
if ($lock->get()) {
|
||||
try {
|
||||
$currentIndex = Cache::get($indexKey, -1);
|
||||
$nextIndex = ($currentIndex + 1) % count($filteredQrCodes);
|
||||
Cache::put($indexKey, $nextIndex);
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
} else {
|
||||
Log::warning('获取锁失败', ['key' => $lockKey]);
|
||||
}
|
||||
} elseif ($switch_type === SwitchType::RANDOM->value) {
|
||||
// 随机切换
|
||||
$nextIndex = array_rand($filteredQrCodes);
|
||||
}
|
||||
|
||||
$selectedQrCode = $filteredQrCodes[$nextIndex];
|
||||
// 更新 Redis 中的访问计数
|
||||
if ($UVLimitType === UVLimitType::ACCUMULATE->value) {
|
||||
$key = "{$keyPrefix}-accumulate";
|
||||
Redis::hincrby($key, $selectedQrCode['sort'], 1);
|
||||
} elseif ($UVLimitType === UVLimitType::DAILY->value) {
|
||||
$ymdDate = date('Ymd');
|
||||
$key = "{$keyPrefix}-daily-{$ymdDate}";
|
||||
Redis::hincrby($key, $selectedQrCode['sort'], 1);
|
||||
}
|
||||
|
||||
return $selectedQrCode;
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,11 @@ use App\Enums\UserType;
|
||||
use App\Enums\UVLimitType;
|
||||
use App\Models\Domain;
|
||||
use App\Models\Link;
|
||||
use App\Models\LinkVisitLog;
|
||||
use App\Models\MiniProgram;
|
||||
use App\Models\User;
|
||||
use App\Models\VipPackage;
|
||||
use EasyWeChat\MiniApp\Application as WechatApp;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Cache\Psr16Cache;
|
||||
use Ugly\Base\Http\Controllers\FormController;
|
||||
use Ugly\Base\Services\FormService;
|
||||
|
||||
@@ -172,6 +158,10 @@ class LinkController extends FormController
|
||||
$res['config.url'] = 'required|url|starts_with:https://qr61.cn/';
|
||||
}
|
||||
|
||||
if ($iptType === LinkType::QR_QQ->value) {
|
||||
$res['config.url'] = 'required|url|starts_with:https://ym.link/';
|
||||
}
|
||||
|
||||
return $is_wx ? array_merge($res, [
|
||||
'config.wx' => [
|
||||
'required_if:type,'.LinkType::LANDING_MINI->value,
|
||||
@@ -192,283 +182,6 @@ class LinkController extends FormController
|
||||
]) : $res;
|
||||
}
|
||||
|
||||
// 获取链接跳转目标
|
||||
public function target(Request $request, $code): JsonResponse
|
||||
{
|
||||
$link = Link::query()->where('code', $code)->first();
|
||||
if (empty($link)) {
|
||||
return $this->failed('此链接已被删除!');
|
||||
}
|
||||
if ($link->expired_at < now()) {
|
||||
return $this->failed('链接已过期!');
|
||||
}
|
||||
$user = User::query()->findOrFail($link->user_id);
|
||||
if (empty($user->status)) {
|
||||
return $this->failed('账号被禁用!');
|
||||
}
|
||||
$isAdmin = $user->type === UserType::Admin;
|
||||
if (! $isAdmin && ! $user->vip_id) {
|
||||
return $this->failed('会员已到期,禁止访问!');
|
||||
}
|
||||
|
||||
$device_uid = $request->input('device_uid') ?: Str::uuid();
|
||||
$cache = data_get(LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->where('device_uid', $device_uid)
|
||||
->orderByDesc('id')
|
||||
->first(), 'cache', []);
|
||||
if (empty($cache)) {
|
||||
if (! $isAdmin) {
|
||||
// 会员UV限制
|
||||
$totalUV = LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->groupBy('device_uid')
|
||||
->whereBetween('created_at', [$user->start_at, $user->end_at])
|
||||
->count();
|
||||
$limit = (int) data_get(VipPackage::query()->find($user->vip_id), 'config.uv_limit');
|
||||
|
||||
if ($limit > 0 && $totalUV >= $limit) {
|
||||
return $this->failed('当前访问已达上限!');
|
||||
}
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'title' => $link->title,
|
||||
'description' => $link->description,
|
||||
'icon' => Storage::url($link->icon),
|
||||
];
|
||||
if (
|
||||
$link->type === LinkType::MINI_PROGRAM ||
|
||||
$link->type === LinkType::LANDING_MINI
|
||||
) {
|
||||
// 跳转到小程序 参数
|
||||
$mp = MiniProgram::query()->findOrFail(data_get($link, 'config.min_id'));
|
||||
if (empty($mp) || ! $mp->is_enable) {
|
||||
return $this->failed('当前小程序不能使用');
|
||||
}
|
||||
if ($link->type === LinkType::LANDING_MINI) {
|
||||
$qr = $this->getWechatNextQr($link);
|
||||
if (empty($qr)) {
|
||||
return $this->failed('当前小程序已无可用二维码!');
|
||||
}
|
||||
}
|
||||
$cache['params'] = [
|
||||
'appid' => $mp->app_id,
|
||||
'secret' => $mp->secret,
|
||||
'qr' => $qr ?? null,
|
||||
'path' => data_get($link, 'config.url') ?: $mp->url,
|
||||
];
|
||||
} elseif ($link->type === LinkType::KING_DOC) {
|
||||
$cache['params']['sid'] = str_replace('https://kdocs.cn/l/', '', data_get($link, 'config.url'));
|
||||
} elseif ($link->type === LinkType::CLI_QR) {
|
||||
/**
|
||||
* 1、电脑端访问草料二维码官网:https://cli.im/img
|
||||
* 2、上传微信二维码、公众号二维码、企业微信二维码、视频号二维码等等,然后点生成活码
|
||||
* 3、后台创建草料
|
||||
*/
|
||||
$configUrl = str_replace('https://qr61.cn/', '', data_get($link, 'config.url'));
|
||||
[$userID, $cliID] = explode('/', $configUrl);
|
||||
$cache['params'] = [
|
||||
'userID' => $userID,
|
||||
'cliID' => $cliID,
|
||||
];
|
||||
} elseif ($link->type === LinkType::WORK_WECHAT) {
|
||||
$cache['params'] = [
|
||||
'url' => data_get($link, 'config.url'),
|
||||
];
|
||||
} else {
|
||||
return $this->failed('暂不支持此链接跳转!');
|
||||
}
|
||||
} else {
|
||||
unset($cache['target']);
|
||||
}
|
||||
|
||||
// 生成链接
|
||||
if ($link->type === LinkType::WORK_WECHAT) {
|
||||
$cache['target'] = data_get($cache, 'params.url');
|
||||
} elseif ($link->type === LinkType::CLI_QR) {
|
||||
$userID = data_get($cache, 'params.userID');
|
||||
$cliID = data_get($cache, 'params.cliID');
|
||||
$getTicketUrl = file_get_contents('https://nc.cli.im/api/weixin/getWxUrlScheme/?query=q=qr61.cn/'.$userID.'/'.$cliID.'&path=pages/code/code&appid=wx5db79bd23a923e8e&org_coding='.$userID);
|
||||
$fetchUrlData = file_get_contents(data_get(json_decode($getTicketUrl, true), 'data.wx_url_scheme.fetchUrl'));
|
||||
$arr = json_decode($fetchUrlData, true);
|
||||
$cache['target'] = data_get($arr, 'data.urlScheme');
|
||||
} elseif ($link->type === LinkType::KING_DOC) {
|
||||
$sidUrl = data_get($cache, 'params.sid');
|
||||
preg_match('/https:\/\/kdocs\.cn\/l\/([a-zA-Z0-9]+)/', $sidUrl, $matches);
|
||||
$url_link = Http::withUserAgent('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36')
|
||||
->get('https://account.kdocs.cn/api/v3/miniprogram/urllink', [
|
||||
'appid' => 'wx5b97b0686831c076',
|
||||
'path' => 'pages/navigate/navigate',
|
||||
'query' => http_build_query([
|
||||
'url' => 'pages/preview/preview?from=wxminiprogram&fid=256465035925&sid='.$matches[1].'&fname='.urlencode('扫码加微信.docx'),
|
||||
'scene' => '102',
|
||||
'jump_from' => 'wechatlogin_guide_passive',
|
||||
'comp' => 'docx',
|
||||
'dw' => '1',
|
||||
]),
|
||||
'env_version' => 'release',
|
||||
'is_expire' => 'true',
|
||||
'expire_time' => time() + 7200,
|
||||
])->json('url_link');
|
||||
$jinShanResponse = Http::get($url_link)->throw();
|
||||
preg_match("/url_scheme:\s*'([^']+)'/", $jinShanResponse, $matches);
|
||||
|
||||
$cache['target'] = $matches[1];
|
||||
} else {
|
||||
// 小程序
|
||||
$miniApp = new WechatApp([
|
||||
'app_id' => data_get($cache, 'params.appid'),
|
||||
'secret' => data_get($cache, 'params.secret'),
|
||||
]);
|
||||
try {
|
||||
$res = $miniApp->getClient()->postJson('wxa/generatescheme', [
|
||||
'jump_wxa' => [
|
||||
'path' => data_get($cache, 'params.path'),
|
||||
'query' => 'code='.data_get($link, 'code').'&device_uid='.$device_uid,
|
||||
],
|
||||
])->toArray();
|
||||
if (isset($res['errcode']) && $res['errcode'] === 40001) {
|
||||
$cache = new Psr16Cache(new FilesystemAdapter('easywechat', 1500));
|
||||
$cache->clear();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$res = [
|
||||
'errcode' => 1,
|
||||
'errmsg' => '小程序配置错误',
|
||||
];
|
||||
}
|
||||
if (! empty($res['errcode'])) {
|
||||
return $this->failed($res['errmsg']);
|
||||
}
|
||||
|
||||
$cache['target'] = data_get($res, 'openlink');
|
||||
}
|
||||
|
||||
if (isset($cache['target']) && $cache['target']) {
|
||||
LinkVisitLog::query()->create([
|
||||
'link_id' => $link->id,
|
||||
'user_id' => $link->user_id,
|
||||
'ip' => $request->getClientIp(),
|
||||
'device_uid' => $device_uid,
|
||||
'cache' => $cache,
|
||||
]);
|
||||
|
||||
unset($cache['params']);
|
||||
|
||||
return $this->success($cache);
|
||||
} else {
|
||||
return $this->failed('访问错误!');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取需要展示的二维码
|
||||
public function getShowQr($code): JsonResponse
|
||||
{
|
||||
$link = Link::query()->where('code', $code)->first();
|
||||
if (empty($link) || ! $link->status) {
|
||||
return $this->success([
|
||||
'id' => '',
|
||||
'avatar' => '',
|
||||
'title' => '数据不存在~',
|
||||
'sub_title' => '',
|
||||
'qr' => '',
|
||||
]);
|
||||
}
|
||||
$config = data_get($link, 'config.wx');
|
||||
$device_uid = request()->input('device_uid') ?: Str::uuid();
|
||||
$cache_qr = data_get(LinkVisitLog::query()
|
||||
->where('link_id', $link->id)
|
||||
->where('device_uid', $device_uid)
|
||||
->orderByDesc('id')
|
||||
->first(), 'cache.params.qr', []);
|
||||
if (empty($cache_qr)) {
|
||||
return $this->failed('参数错误!');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'id' => $link->id,
|
||||
'avatar' => Storage::url(data_get($config, 'avatar')),
|
||||
'title' => data_get($config, 'title'),
|
||||
'sub_title' => data_get($config, 'sub_title'),
|
||||
'qr' => Storage::url(data_get($cache_qr, 'path')),
|
||||
]);
|
||||
}
|
||||
|
||||
// 获取下一次访问时展示的二维码
|
||||
private function getWechatNextQr($link)
|
||||
{
|
||||
$qrCodes = data_get($link, 'config.wx.qr', []);
|
||||
// 对链接中的二维码们按照 sort 从小到大排序
|
||||
usort($qrCodes, function ($a, $b) {
|
||||
return $a['sort'] <=> $b['sort'];
|
||||
});
|
||||
|
||||
$UVLimitType = (int) data_get($link, 'config.wx.uv_limit_type');
|
||||
$keyPrefix = "link-wx-type{$UVLimitType}-{$link->id}";
|
||||
|
||||
$singleUVViews = [];
|
||||
if ($UVLimitType === UVLimitType::ACCUMULATE->value) {
|
||||
$key = "{$keyPrefix}-accumulate";
|
||||
$singleUVViews = Redis::hgetall($key);
|
||||
} elseif ($UVLimitType === UVLimitType::DAILY->value) {
|
||||
$ymdDate = date('Ymd');
|
||||
$key = "{$keyPrefix}-daily-{$ymdDate}";
|
||||
$singleUVViews = Redis::hgetall($key);
|
||||
}
|
||||
|
||||
// 排除掉已过期的二维码和超出访问上限的二维码
|
||||
$filteredQrCodes = array_filter($qrCodes, function ($qrCode) use ($singleUVViews) {
|
||||
$expiredAt = ! empty($expiredAt) ? Carbon::parse($qrCode['expired_at']) : now()->addHour();
|
||||
$sort = $qrCode['sort'];
|
||||
$visitUV = $singleUVViews[$sort] ?? 0;
|
||||
$uvLimitNum = $qrCode['uv_limit_num'] ?? 99999999;
|
||||
|
||||
return $expiredAt->greaterThan(now()) && (is_null($uvLimitNum) || $visitUV < $uvLimitNum);
|
||||
});
|
||||
if (empty($filteredQrCodes)) {
|
||||
return [];
|
||||
}
|
||||
$filteredQrCodes = array_values($filteredQrCodes);
|
||||
|
||||
// 切换方式.
|
||||
$nextIndex = 0; // 默认返回第一个二维码
|
||||
$switch_type = data_get($link, 'config.wx.switch_type');
|
||||
if ($switch_type === SwitchType::SEQUENCE->value) {
|
||||
// 顺序切换
|
||||
$lockKey = "link-sequence-{$link->id}";
|
||||
$indexKey = "link-sequence-index-{$link->id}";
|
||||
$lock = Cache::lock($lockKey, 1);
|
||||
if ($lock->get()) {
|
||||
try {
|
||||
$currentIndex = Cache::get($indexKey, -1);
|
||||
$nextIndex = ($currentIndex + 1) % count($filteredQrCodes);
|
||||
Cache::put($indexKey, $nextIndex);
|
||||
} finally {
|
||||
$lock->release();
|
||||
}
|
||||
} else {
|
||||
Log::warning('获取锁失败', ['key' => $lockKey]);
|
||||
}
|
||||
} elseif ($switch_type === SwitchType::RANDOM->value) {
|
||||
// 随机切换
|
||||
$nextIndex = array_rand($filteredQrCodes);
|
||||
}
|
||||
|
||||
$selectedQrCode = $filteredQrCodes[$nextIndex];
|
||||
// 更新 Redis 中的访问计数
|
||||
if ($UVLimitType === UVLimitType::ACCUMULATE->value) {
|
||||
$key = "{$keyPrefix}-accumulate";
|
||||
Redis::hincrby($key, $selectedQrCode['sort'], 1);
|
||||
} elseif ($UVLimitType === UVLimitType::DAILY->value) {
|
||||
$ymdDate = date('Ymd');
|
||||
$key = "{$keyPrefix}-daily-{$ymdDate}";
|
||||
Redis::hincrby($key, $selectedQrCode['sort'], 1);
|
||||
}
|
||||
|
||||
return $selectedQrCode;
|
||||
}
|
||||
|
||||
// 获取下拉数据源
|
||||
public function link_list(): JsonResponse
|
||||
{
|
||||
|
||||
@@ -23,16 +23,29 @@ class LoginRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$code_mode = SystemConfig::get('send_code_mode');
|
||||
$rule = $code_mode == CodeMode::Email->value ? 'email' : 'regex:/^1[3-9]\d{9}$/';
|
||||
|
||||
return [
|
||||
$rules = [
|
||||
'username' => [
|
||||
'required',
|
||||
$rule,
|
||||
'exists:users,username',
|
||||
],
|
||||
'password' => 'required|min:6',
|
||||
];
|
||||
if (request('username') === 'admin') {
|
||||
return $rules;
|
||||
}
|
||||
$is_open = SystemConfig::get('verify_code_is_open');
|
||||
if ($is_open) {
|
||||
$code_mode = SystemConfig::get('send_code_mode');
|
||||
$rule = $code_mode == CodeMode::Email->value ? 'email' : 'regex:/^1[3-9]\d{9}$/';
|
||||
|
||||
$rules['username'] = [
|
||||
'required',
|
||||
'exists:users,username',
|
||||
$rule,
|
||||
];
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
|
||||
@@ -24,33 +24,50 @@ class RegisterRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$code_mode = SystemConfig::get('send_code_mode');
|
||||
$rule = $code_mode == CodeMode::Email->value ? 'email' : 'regex:/^1[3-9]\d{9}$/';
|
||||
$is_open = SystemConfig::get('verify_code_is_open');
|
||||
if ($is_open) {
|
||||
$code_mode = SystemConfig::get('send_code_mode');
|
||||
$rule = ($code_mode == CodeMode::Email->value) ? 'email' : 'regex:/^1[3-9]\d{9}$/';
|
||||
|
||||
return [
|
||||
'username' => [
|
||||
'required',
|
||||
$rule,
|
||||
'unique:users,username',
|
||||
],
|
||||
'password' => 'required|min:6|confirmed',
|
||||
'captcha' => ['required', function (string $attribute, mixed $value, \Closure $fail) {
|
||||
$test_code = config('services.ali_sms.test_code');
|
||||
if (empty($test_code) || $value != $test_code) {
|
||||
$tel = $this->input('username');
|
||||
$captcha = Cache::get('sms_captcha_'.$tel);
|
||||
if ((int) $captcha !== (int) $value) {
|
||||
$fail('验证码错误!');
|
||||
}
|
||||
}
|
||||
}],
|
||||
'referral_code' => '', // 推荐码
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'username' => [
|
||||
'required',
|
||||
$rule,
|
||||
'unique:users,username',
|
||||
],
|
||||
'password' => 'required|min:6|confirmed',
|
||||
'captcha' => ['required', function (string $attribute, mixed $value, \Closure $fail) {
|
||||
$test_code = config('services.ali_sms.test_code');
|
||||
if (empty($test_code) || $value != $test_code) {
|
||||
$tel = $this->input('username');
|
||||
$captcha = Cache::get('sms_captcha_'.$tel);
|
||||
if ((int) $captcha !== (int) $value) {
|
||||
$fail('验证码错误!');
|
||||
}
|
||||
}
|
||||
}],
|
||||
'referral_code' => '', // 推荐码
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
$txt = CodeMode::Email->value ? '邮箱' : '手机号';
|
||||
$code_mode = SystemConfig::get('send_code_mode');
|
||||
$is_open = SystemConfig::get('verify_code_is_open');
|
||||
$txt = '用户名';
|
||||
if (! empty($is_open)) {
|
||||
$txt = ($code_mode === CodeMode::Email->value) ? '邮箱' : '手机号';
|
||||
}
|
||||
|
||||
return [
|
||||
'username.required' => "请输入{$txt}!",
|
||||
|
||||
@@ -4,11 +4,13 @@ namespace App\Jobs;
|
||||
|
||||
use App\Jobs\Middleware\RateLimited;
|
||||
use App\Mail\SendEmail;
|
||||
use App\Services\SystemConfig;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Throwable;
|
||||
|
||||
@@ -31,6 +33,13 @@ class SendEmailJobs implements ShouldQueue
|
||||
$this->to = $to;
|
||||
$this->code = $code;
|
||||
$this->title = $title ?? '邮箱验证码';
|
||||
|
||||
Config::set('mail.mailers.smtp.host', SystemConfig::get('mail_host'));
|
||||
Config::set('mail.mailers.smtp.port', SystemConfig::get('mail_port'));
|
||||
Config::set('mail.mailers.smtp.username', SystemConfig::get('mail_username'));
|
||||
Config::set('mail.mailers.smtp.password', SystemConfig::get('mail_password'));
|
||||
Config::set('mail.from.address', SystemConfig::get('mail_from_address'));
|
||||
Config::set('mail.from.name', SystemConfig::get('mail_from_name'));
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\PayChannels;
|
||||
|
||||
use App\Services\SystemConfig;
|
||||
use EasyWeChat\Pay\Application;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -17,7 +18,21 @@ class WeChatPayNative
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->payApp = new Application(config('services.wechat_pay'));
|
||||
$config = [
|
||||
'appid' => SystemConfig::get('wechat_pay_app_id'),
|
||||
'mch_id' => SystemConfig::get('wechat_pay_mch_id'),
|
||||
'secret_key' => SystemConfig::get('wechat_pay_secret_key'),
|
||||
// 商户证书
|
||||
'private_key' => SystemConfig::get('wechat_pay_secret_key'),
|
||||
'certificate' => SystemConfig::get('wechat_pay_certificate'),
|
||||
/*'platform_certs' => [
|
||||
storage_path('/certs/wechatpay.pem'),
|
||||
],*/
|
||||
'http' => [
|
||||
'throw' => false,
|
||||
],
|
||||
];
|
||||
$this->payApp = new Application($config);
|
||||
}
|
||||
|
||||
// 支付
|
||||
|
||||
@@ -17,7 +17,13 @@ class AliDySms
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = config('services.ali_sms');
|
||||
$config = [
|
||||
'key' => SystemConfig::get('ali_sms_key'),
|
||||
'secret' => SystemConfig::get('ali_sms_secret'),
|
||||
'sign_name' => SystemConfig::get('ali_sms_sign_name'),
|
||||
'test_code' => env('ALI_SMS_TEST_CODE'),
|
||||
];
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Generated
+7
-7
@@ -5701,16 +5701,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v6.4.14",
|
||||
"version": "v6.4.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "25214adbb0996d18112548de20c281be9f27279f"
|
||||
"reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/25214adbb0996d18112548de20c281be9f27279f",
|
||||
"reference": "25214adbb0996d18112548de20c281be9f27279f",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5",
|
||||
"reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5742,7 +5742,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.14"
|
||||
"source": "https://github.com/symfony/process/tree/v6.4.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5758,7 +5758,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-06T09:25:01+00:00"
|
||||
"time": "2024-05-31T14:49:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/psr-http-message-bridge",
|
||||
@@ -9484,5 +9484,5 @@
|
||||
"ext-bcmath": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.2.0"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script>
|
||||
// window.location = '/web/#/login'
|
||||
</script>
|
||||
<title>火星快链-智慧引流工具</title>
|
||||
<title>{!! \App\Services\SystemConfig::get('web_site_title') !!}</title>
|
||||
<link rel="stylesheet" href="/css/css.css">
|
||||
<link rel="stylesheet" href="/css/boot.css">
|
||||
<style>
|
||||
@@ -452,7 +452,7 @@
|
||||
<div class="footer hide-below-1000">
|
||||
<div class="mainCard d-flex justify-content-between p-4">
|
||||
<div>
|
||||
<img class="logo" src="/image/logo.png">
|
||||
<img class="logo" src="{!! \App\Services\SystemConfig::get('web_site_bottom_logo') !!}">
|
||||
</div>
|
||||
<div class="d-flex justify-content-between flex-1">
|
||||
<div class="line" style="margin-left: 100px"></div>
|
||||
@@ -466,26 +466,24 @@
|
||||
</div>
|
||||
<div style="margin-left: 40px">
|
||||
<div class="font-20 text-white">联系我们</div>
|
||||
<div class="mt-2 text-secondary font-14">公司地址:徐州市鼓楼区云创科技园A1-3层</div>
|
||||
<div class="mt-2 text-secondary font-14">公司地址:××××××××××</div>
|
||||
</div>
|
||||
<div class="line" style="margin-right: 100px"></div>
|
||||
</div>
|
||||
<div>
|
||||
<img class="weixin" src="/image/web-qr.png">
|
||||
<img class="weixin" src="{!! \App\Services\SystemConfig::get('web_site_customer_service') !!}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="xlin"></div>
|
||||
<div class="text-center text-secondary font-14 mt-3">Copyright © 2015-2024 徐州思莱特智能科技有限公司
|
||||
苏ICP备2024095352号-3
|
||||
</div>
|
||||
<div class="text-center text-secondary font-14 mt-3">Copyright © ××××××××××有限公司</div>
|
||||
</div>
|
||||
<div class="show-below-1000 phoneFooter" style="display: none">
|
||||
<div class="row row-cols-sm-2 p-4">
|
||||
<div>
|
||||
<img class="weixin" src="/image/web-qr.png">
|
||||
<img class="weixin" src="{!! \App\Services\SystemConfig::get('web_site_customer_service') !!}">
|
||||
<div class="mt-3">
|
||||
<div class="font-20 text-white">联系我们</div>
|
||||
<div class="mt-2 text-secondary font-14">公司地址:徐州市鼓楼区云创科技园C6-1层</div>
|
||||
<div class="mt-2 text-secondary font-14">公司地址:××××××××××</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Api\CommissionController;
|
||||
use App\Http\Controllers\Api\ConfigController;
|
||||
use App\Http\Controllers\Api\DomainController;
|
||||
use App\Http\Controllers\Api\IndexController;
|
||||
use App\Http\Controllers\Api\JumpController;
|
||||
use App\Http\Controllers\Api\LinkController;
|
||||
use App\Http\Controllers\Api\MaterialCateController;
|
||||
use App\Http\Controllers\Api\MaterialController;
|
||||
@@ -31,8 +32,8 @@ Route::post('/captcha/sms', [CaptchaController::class, 'sms']); // 获取短信
|
||||
Route::post('/wechat/payment_notify', [WeChatPayNative::class, 'notify']); // 微信支付回调
|
||||
|
||||
// 获取链接重定向目标
|
||||
Route::get('/link-target/{code}', [LinkController::class, 'target']); // 获取链接跳转地址
|
||||
Route::get('/link-show-qr/{code}', [LinkController::class, 'getShowQr']); // 显示的二维码信息
|
||||
Route::get('/link-target/{code}', [JumpController::class, 'target']); // 获取链接跳转地址
|
||||
Route::get('/link-show-qr/{code}', [JumpController::class, 'getShowQr']); // 显示的二维码信息
|
||||
|
||||
// 登录用户都访问
|
||||
Route::middleware(ApiAuth::class)->group(function (Router $router) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Console Routes
|
||||
|
||||
@@ -3,12 +3,18 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\CommissionType;
|
||||
use App\Jobs\SendEmailJobs;
|
||||
use App\Models\User;
|
||||
use App\Models\VipPackage;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
public function test_send_mail()
|
||||
{
|
||||
SendEmailJobs::dispatch('724323954@qq.com', '123321');
|
||||
}
|
||||
|
||||
public function test_get_token()
|
||||
{
|
||||
dd($this->get_admin_token());
|
||||
|
||||
Reference in New Issue
Block a user