diff --git a/app/admin/controller/Config.php b/app/admin/controller/Config.php index 6e16967..b5c4a7b 100644 --- a/app/admin/controller/Config.php +++ b/app/admin/controller/Config.php @@ -56,22 +56,51 @@ class Config extends Controller */ public function index() { - $this->title = '系统参数配置'; - $this->files = Storage::types(); - $this->plugins = Plugin::get(null, true); - $this->issuper = AdminService::isSuper(); - $this->systemid = ModuleService::getRunVar('uni'); - $this->framework = ModuleService::getLibrarys('topthink/framework'); - $this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library'); - if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) { - $url = url('admin/index/pass', ['id' => AdminService::getUserId()]); - $this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即修改密码!", [$url]); + if ($this->request->isGet()) { + $this->title = '系统参数配置'; + $this->files = Storage::types(); + $this->plugins = Plugin::get(null, true); + $this->issuper = AdminService::isSuper(); + $this->systemid = ModuleService::getRunVar('uni'); + $this->framework = ModuleService::getLibrarys('topthink/framework'); + $this->thinkadmin = ModuleService::getLibrarys('zoujingli/think-library'); + if (AdminService::isSuper() && $this->app->session->get('user.password') === md5('admin')) { + $url = url('admin/index/pass', ['id' => AdminService::getUserId()]); + $this->showErrorMessage = lang("超级管理员账号的密码未修改,建议立即修改密码!", [$url]); + } + uasort($this->plugins, static function ($a, $b) { + if ($a['space'] === $b['space']) return 0; + return $a['space'] > $b['space'] ? 1 : -1; + }); + $this->fetch(); + } else { + $data = $this->request->post(); + foreach ($data as $k => $v) { + sysconf($k, $v); + } + $this->success('参数修改成功!'); } - uasort($this->plugins, static function ($a, $b) { - if ($a['space'] === $b['space']) return 0; - return $a['space'] > $b['space'] ? 1 : -1; - }); - $this->fetch(); + } + + /** + * 获取系统配置参数 + * @param string $name 配置名称 + * @return mixed + */ + public function get($name) + { + return sysconf($name); + } + + /** + * 修改系统配置参数 + * @param string $name 配置名称 + * @param mixed $value 配置值 + * @return mixed + */ + public function set($name, $value) + { + return sysconf($name, $value); } /** diff --git a/app/admin/controller/api/Account.php b/app/admin/controller/api/Account.php index d2ac1c5..f96bd77 100644 --- a/app/admin/controller/api/Account.php +++ b/app/admin/controller/api/Account.php @@ -82,18 +82,48 @@ class Account extends Controller ]); } - // 检查设备是否在冷却期 - $cooldownKey = "device_cooldown_{$machineId}"; - $isInCooldown = \think\facade\Cache::get($cooldownKey); + // 优先检查缓存中是否有该设备最近使用的账号 + $cacheKey = "device_account_{$machineId}"; + $cachedAccount = \think\facade\Cache::get($cacheKey); + + if ($cachedAccount) { + // 检查账号是否仍然可用 + $account = Db::name('cursor_accounts') + ->where('id', $cachedAccount['id']) + ->find(); + + if ($account) { + // 更新缓存时间 + \think\facade\Cache::set($cacheKey, $account, 600); // 10分钟缓存 + + // 查询设备激活状态用于计算过期时间 + $activations = Db::name('cursor_activation_codes') + ->where('used_by', '=', $machineId) + ->where('is_used', '=', 1) + ->order('used_at desc') + ->select() + ->toArray(); - if ($isInCooldown) { - return json([ - 'code' => 429, - 'msg' => '请求过于频繁,请稍后再试', - 'data' => [ - 'cooldown_expires' => date('Y-m-d H:i:s', $isInCooldown) - ] - ]); + if (!empty($activations)) { + $totalDays = array_sum(array_column($activations, 'days')); + $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); + $expireTime = $firstActivationTime + ($totalDays * 24 * 3600); + + // 返回账号信息 + return json([ + 'code' => 200, + 'msg' => '获取成功', + 'data' => [ + 'email' => $account['email'], + 'password' => $account['password'], + 'access_token' => $account['access_token'], + 'refresh_token' => $account['refresh_token'], + 'expire_time' => date('Y-m-d H:i:s', $expireTime), + 'days_left' => ceil(($expireTime - time()) / 86400) + ] + ]); + } + } } // 查询设备激活状态 @@ -124,39 +154,18 @@ class Account extends Controller ]); } - // 检查缓存中是否有该设备最近使用的账号 - $cacheKey = "device_account_{$machineId}"; - $cachedAccount = \think\facade\Cache::get($cacheKey); - - if ($cachedAccount) { - // 检查账号是否仍然可用 - $account = Db::name('cursor_accounts') - ->where('id', $cachedAccount['id']) - ->find(); - - if ($account) { - // 更新缓存时间 - \think\facade\Cache::set($cacheKey, $account, 600); // 10分钟缓存 - - // 返回账号信息 - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'email' => $account['email'], - 'password' => $account['password'], - 'access_token' => $account['access_token'], - 'refresh_token' => $account['refresh_token'], - 'expire_time' => date('Y-m-d H:i:s', $expireTime), - 'days_left' => ceil(($expireTime - time()) / 86400) - ] - ]); - } + // 在分配新账号前检查冷却期 + $cooldownKey = "device_cooldown_{$machineId}"; + $isInCooldown = \think\facade\Cache::get($cooldownKey); + if ($isInCooldown) { + return json([ + 'code' => 429, + 'msg' => '请求过于频繁,请稍后再试', + 'data' => [ + 'cooldown_expires' => date('Y-m-d H:i:s', intval($isInCooldown)) + ] + ]); } - - // 记录冷却期 - $cooldownExpires = time() + 1800; // 30分钟冷却期 - \think\facade\Cache::set($cooldownKey, $cooldownExpires, 1800); // 开启事务 Db::startTrans(); @@ -195,6 +204,10 @@ class Account extends Controller 'used_by' => $machineId ]); + // 设置冷却期(仅在成功分配新账号时) + $cooldownExpires = time() + 1800; // 30分钟冷却期 + \think\facade\Cache::set($cooldownKey, $cooldownExpires, 1800); + Db::commit(); // 缓存新账号信息 diff --git a/app/admin/controller/api/Mail.php b/app/admin/controller/api/Mail.php index a6e75db..6de2d8e 100644 --- a/app/admin/controller/api/Mail.php +++ b/app/admin/controller/api/Mail.php @@ -45,6 +45,17 @@ class Mail extends Controller 'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36', 'MAIL_SERVER' => 'https://tempmail.plus' ] + ] , + [ + 'name' => '备用配置3', + 'config' => [ + 'DOMAIN' => 'jxyweb.site', + 'TEMP_MAIL' => 'exvet', + 'TEMP_MAIL_EXT' => '@mailto.plus', + 'BROWSER_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36', + 'MAIL_SERVER' => 'https://tempmail.plus', + 'TEMP_MAIL_EPIN'=>'889944' + ] ] ]; diff --git a/app/admin/controller/api/Member.php b/app/admin/controller/api/Member.php index 3489061..6d9004e 100644 --- a/app/admin/controller/api/Member.php +++ b/app/admin/controller/api/Member.php @@ -205,16 +205,31 @@ class Member extends Controller return json(['code' => 400, 'msg' => '设备ID不能为空']); } + try { + // 获取Redis实例 + $redis = cache('redis'); + $cacheKey = "device_status:{$machineId}"; + + // 尝试获取缓存 + if ($redis && $statusInfo = $redis->get($cacheKey)) { + return json(json_decode($statusInfo, true)); + } + } catch (\Exception $e) { + // Redis异常时记录日志但继续执行 + trace("Redis异常: " . $e->getMessage(), 'error'); + } + // 获取该设备所有的激活记录 $activations = Db::name('cursor_activation_codes') ->where('used_by', '=', $machineId) ->where('is_used', '=', 1) + ->field(['days', 'used_at', 'code', 'device_info']) ->order('used_at desc') ->select() ->toArray(); if (empty($activations)) { - return json([ + $result = [ 'code' => 401, 'msg' => '未激活', 'data' => [ @@ -224,7 +239,16 @@ class Member extends Controller 'days_left' => 0, 'activation_records' => [] ] - ]); + ]; + // 未激活状态缓存1分钟 + try { + if ($redis) { + $redis->set($cacheKey, json_encode($result), 60); + } + } catch (\Exception $e) { + trace("Redis缓存写入异常: " . $e->getMessage(), 'error'); + } + return json($result); } // 计算总天数 @@ -232,13 +256,13 @@ class Member extends Controller // 计算最终到期时间 $now = time(); - $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); // 第一次激活时间 - $expireTime = $firstActivationTime + ($totalDays * 24 * 3600); // 总天数转换为秒数 + $firstActivationTime = strtotime($activations[count($activations)-1]['used_at']); + $expireTime = $firstActivationTime + ($totalDays * 24 * 3600); $daysLeft = ceil(($expireTime - $now) / 86400); // 判断是否过期 if ($daysLeft <= 0) { - return json([ + $result = [ 'code' => 401, 'msg' => '已过期', 'data' => [ @@ -256,11 +280,20 @@ class Member extends Controller ]; }, $activations) ] - ]); + ]; + // 过期状态缓存1分钟 + try { + if ($redis) { + $redis->set($cacheKey, json_encode($result), 60); + } + } catch (\Exception $e) { + trace("Redis缓存写入异常: " . $e->getMessage(), 'error'); + } + return json($result); } - // 返回正常状态 - return json([ + // 正常状态返回 + $result = [ 'code' => 200, 'msg' => '正常', 'data' => [ @@ -278,12 +311,21 @@ class Member extends Controller ]; }, $activations) ] - ]); - + ]; + + // 正常状态缓存5分钟 + try { + if ($redis) { + $redis->set($cacheKey, json_encode($result), 300); + } + } catch (\Exception $e) { + trace("Redis缓存写入异常: " . $e->getMessage(), 'error'); + } + return json($result); } catch (\Exception $e) { return json([ 'code' => 500, - 'msg' => $e->getMessage() + 'msg' => '系统异常,请稍后重试' ]); } } diff --git a/app/admin/controller/api/Version.php b/app/admin/controller/api/Version.php new file mode 100644 index 0000000..f296f6f --- /dev/null +++ b/app/admin/controller/api/Version.php @@ -0,0 +1,137 @@ +request->param('platform', 'all'); + $version = VersionModel::mk()->getLatestVersion($platform); + if (!$version) { + $this->error('暂无版本信息'); + } + + // 处理下载地址 + $version['download_url'] = $this->getFullUrl($version['download_url']); + $this->success('获取成功', $version); + } + + /** + * 检查更新 + * @return void + */ + public function check() + { + $currentVersion = $this->request->param('version'); + $platform = $this->request->param('platform', 'all'); + + if (empty($currentVersion)) { + $this->error('请提供当前版本号'); + } + + $result = VersionModel::mk()->checkUpdate($currentVersion, $platform); + if (isset($result['error'])) { + $this->error($result['error']); + } + + // 如果有更新,处理下载地址 + if ($result['has_update'] && isset($result['version_info'])) { + $result['version_info']['download_url'] = $this->getFullUrl($result['version_info']['download_url']); + } + + $this->success('检查完成', $result); + } + + /** + * 获取完整的下载地址 + * @param string $path 相对路径 + * @return string + */ + protected function getFullUrl($path) + { + return sysconf('site_domain') . $path; + } +} \ No newline at end of file diff --git a/app/admin/model/Version.php b/app/admin/model/Version.php new file mode 100644 index 0000000..73dce96 --- /dev/null +++ b/app/admin/model/Version.php @@ -0,0 +1,167 @@ +value('version_no'); + $currentVersion = $latest ?: '1.0.0'; + } + + // 拆分版本号 + $parts = explode('.', $currentVersion); + $major = intval($parts[0] ?? 1); + $minor = intval($parts[1] ?? 0); + $patch = intval($parts[2] ?? 0); + + // 增加修订号 + $patch++; + // 如果修订号超过99,增加次版本号 + if ($patch > 99) { + $minor++; + $patch = 0; + } + // 如果次版本号超过99,增加主版本号 + if ($minor > 99) { + $major++; + $minor = 0; + } + + return sprintf('%d.%d.%d', $major, $minor, $patch); + } + + /** + * 生成版本名称 + * @param string $version_no 版本号 + * @param string $filename 文件名 + * @return string + */ + public static function generateVersionName($version_no, $filename = '') + { + // 如果有文件名,尝试从文件名中提取有意义的部分 + if ($filename) { + // 移除扩展名 + $name = pathinfo($filename, PATHINFO_FILENAME); + // 移除版本号部分 (匹配v后面的所有数字和点) + $name = preg_replace('/v\d+[\d\.]+/', '', $name); + $name = trim($name); + if (!empty($name)) { + return $name; + } + } + + // 默认使用版本号作为名称 + return sprintf('版本 %s', $version_no); + } + + /** + * 从文件名解析版本号 + * @param string $filename + * @return string + */ + public static function parseVersionFromFilename($filename) + { + // 移除扩展名 + $name = pathinfo($filename, PATHINFO_FILENAME); + + // 匹配版本号 (匹配v后面的所有数字和点) + if (preg_match('/v(\d+[\d\.]+)/', $name, $matches)) { + return $matches[1]; + } + + // 如果没有找到版本号,使用当前最新版本号加1 + return self::getNextVersion(); + } + + /** + * 获取最新版本信息 + * @param string $platform 平台 + * @return array|null + */ + public function getLatestVersion($platform = 'all') + { + return $this->where([ + ['status', '=', self::STATUS_ENABLED], + ['platform', 'in', [$platform, 'all']] + ])->order('version_no DESC')->find(); + } + + /** + * 检查版本更新 + * @param string $currentVersion 当前版本 + * @param string $platform 平台 + * @return array + */ + public function checkUpdate($currentVersion, $platform = 'all') + { + $latest = $this->getLatestVersion($platform); + if (!$latest) { + return ['has_update' => false]; + } + + // 版本号比较 + if (version_compare($latest['version_no'], $currentVersion) > 0) { + // 检查是否满足最低版本要求 + if ($latest['min_version'] && version_compare($currentVersion, $latest['min_version']) < 0) { + return [ + 'has_update' => false, + 'error' => '当前版本过低,请先更新到' . $latest['min_version'] . '版本' + ]; + } + + return [ + 'has_update' => true, + 'is_force' => $latest['is_force'], + 'version_info' => $latest + ]; + } + + return ['has_update' => false]; + } + + /** + * 检查版本权限 + * @param int $versionId 版本ID + * @param int $targetId 目标ID + * @param string $targetType 目标类型 + * @return bool + */ + public function checkVersionPermission($versionId, $targetId, $targetType) + { + $control = \think\facade\Db::name('version_control') + ->where([ + ['version_id', '=', $versionId], + ['target_id', '=', $targetId], + ['target_type', '=', $targetType], + ['expire_time', '>', date('Y-m-d H:i:s')] + ]) + ->find(); + + if (!$control) { + return true; + } + + return $control['control_type'] === 'white_list'; + } +} \ No newline at end of file diff --git a/app/admin/view/config/index.html b/app/admin/view/config/index.html index aaa6f72..b896fc0 100644 --- a/app/admin/view/config/index.html +++ b/app/admin/view/config/index.html @@ -11,224 +11,234 @@ {/block} {block name="content"} - -
-
- - {:lang('运行模式')}( {:lang('仅超级管理员可配置')} ) - -
+
- -
-

{:lang('开发模式')}:{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}

-

{:lang('生产模式')}:{:lang('项目正式部署上线后使用,系统异常时统一显示 “%s”,只记录重要的异常日志信息,强烈推荐上线后使用此模式。',[config('app.error_message')])}

-
-
-
- -
-
- - {:lang('富编辑器')}( {:lang('仅超级管理员可配置')} ) - -
-
-
- {if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if} - {foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k} - {if auth('storage')}{$v}{else}{$v}{/if} - {else} - {if auth('storage')}{$v}{else}{$v}{/if} - {/if}{/foreach} -
-
-

CKEditor4:{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}

-

CKEditor5:{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}

-

wangEditor:{:lang('国产优质富文本编辑器,对于小程序及App内容支持会更友好,推荐使用。')}

-

{:lang('自适应模式')}:{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}

-
-
-
- - -
-
- - {:lang('存储引擎')}( {:lang('文件默认存储方式')} ) - -
- - {if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if} - {if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if} - {if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if} - {if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if} - {if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if} -
-
- {foreach $files as $k => $v}{if sysconf('storage.type') eq $k} - {if auth('storage')}{$v}{else}{$v}{/if} - {else} - {if auth('storage')}{$v}{else}{$v}{/if} - {/if}{/foreach} -
-
-

{:lang('本地服务器存储')}:{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}

-

{:lang('自建Alist存储')}:{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}

-

{:lang('七牛云对象存储')}:{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

-

{:lang('又拍云USS存储')}:{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

-

{:lang('阿里云OSS存储')}:{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

-

{:lang('腾讯云COS存储')}:{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

-
-
-
- -
-
- - {:lang('系统参数')}( {:lang('当前系统配置参数')} ) - -
-
-
-
{:lang('网站名称')}Website
- -
{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}
-
-
-
{:lang('管理程序名称')}Name
- -
{:lang('管理程序名称,将显示在后台左上角标题。')}
-
-
-
{:lang('管理程序版本')}Version
- -
{:lang('管理程序版本,将显示在后台左上角标题。')}
-
-
-
{:lang('公安备案号')}Beian
- -

- {:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['www.beian.gov.cn'])} -

-
-
-
{:lang('网站备案号')}Miitbeian
- -
- {:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['beian.miit.gov.cn'])} + + + +
+
+ + {:lang('运行模式')}( {:lang('仅超级管理员可配置')} ) + +
+
+ +
+

{:lang('开发模式')}:{:lang('开发人员或在功能调试时使用,系统异常时会显示详细的错误信息,同时还会记录操作日志及数据库 SQL 语句信息。')}

+

{:lang('生产模式')}:{:lang('项目正式部署上线后使用,系统异常时统一显示 "%s",只记录重要的异常日志信息,强烈推荐上线后使用此模式。',[config('app.error_message')])}

+
-
-
{:lang('网站版权信息')}Copyright
- -
{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}
+ +
+
+ + {:lang('富编辑器')}( {:lang('仅超级管理员可配置')} ) + +
+
+
+ {if !in_array(sysconf('base.editor'),['ckeditor4','ckeditor5','wangEditor','auto'])}{php}sysconf('base.editor','ckeditor4');{/php}{/if} + {foreach ['ckeditor4'=>'CKEditor4','ckeditor5'=>'CKEditor5','wangEditor'=>'wangEditor','auto'=>lang('自适应模式')] as $k => $v}{if sysconf('base.editor') eq $k} + {if auth('storage')}{$v}{else}{$v}{/if} + {else} + {if auth('storage')}{$v}{else}{$v}{/if} + {/if}{/foreach} +
+
+

CKEditor4:{:lang('旧版本编辑器,对浏览器兼容较好,但内容编辑体验稍有不足。')}

+

CKEditor5:{:lang('新版本编辑器,只支持新特性浏览器,对内容编辑体验较好,推荐使用。')}

+

wangEditor:{:lang('国产优质富文本编辑器,对于小程序及App内容支持会更友好,推荐使用。')}

+

{:lang('自适应模式')}:{:lang('优先使用新版本编辑器,若浏览器不支持新版本时自动降级为旧版本编辑器。')}

+
+
+
+ + +
+
+ + {:lang('存储引擎')}( {:lang('文件默认存储方式')} ) + +
+ + {if !sysconf('storage.type')}{php}sysconf('storage.type','local');{/php}{/if} + {if !sysconf('storage.link_type')}{php}sysconf('storage.link_type','none');{/php}{/if} + {if !sysconf('storage.name_type')}{php}sysconf('storage.name_type','xmd5');{/php}{/if} + {if !sysconf('storage.allow_exts')}{php}sysconf('storage.allow_exts','doc,gif,ico,jpg,mp3,mp4,p12,pem,png,rar,xls,xlsx');{/php}{/if} + {if !sysconf('storage.local_http_protocol')}{php}sysconf('storage.local_http_protocol','follow');{/php}{/if} +
+
+ {foreach $files as $k => $v}{if sysconf('storage.type') eq $k} + {if auth('storage')}{$v}{else}{$v}{/if} + {else} + {if auth('storage')}{$v}{else}{$v}{/if} + {/if}{/foreach} +
+
+

{:lang('本地服务器存储')}:{:lang('文件上传到本地服务器的 `static/upload` 目录,不支持大文件上传,占用服务器磁盘空间,访问时消耗服务器带宽流量。')}

+

{:lang('自建Alist存储')}:{:lang('文件上传到 Alist 存储的服务器或云存储空间,根据服务配置可支持大文件上传,不占用本身服务器空间及服务器带宽流量。')}

+

{:lang('七牛云对象存储')}:{:lang('文件上传到七牛云存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

+

{:lang('又拍云USS存储')}:{:lang('文件上传到又拍云 USS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

+

{:lang('阿里云OSS存储')}:{:lang('文件上传到阿里云 OSS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

+

{:lang('腾讯云COS存储')}:{:lang('文件上传到腾讯云 COS 存储空间,支持大文件上传,不占用服务器空间及服务器带宽流量,支持 CDN 加速访问,访问量大时推荐使用。')}

+
+
+
+ +
+
+ + {:lang('系统参数')}( {:lang('当前系统配置参数')} ) + +
+
+
+
{:lang('网站名称')}Website
+ +
{:lang('网站名称及网站图标,将显示在浏览器的标签上。')}
+
+
+
{:lang('管理程序名称')}Name
+ +
{:lang('管理程序名称,将显示在后台左上角标题。')}
+
+
+
{:lang('管理程序版本')}Version
+ +
{:lang('管理程序版本,将显示在后台左上角标题。')}
+
+
+
{:lang('公安备案号')}Beian
+ +

+ {:lang('公安备案号,可以在 %s 查询获取,将在登录页面下面显示。',['www.beian.gov.cn'])} +

+
+
+
{:lang('网站备案号')}Miitbeian
+ +
+ {:lang('网站备案号,可以在 %s 查询获取,将显示在登录页面下面。',['beian.miit.gov.cn'])} +
+
+
+
{:lang('网站版权信息')}Copyright
+ +
{:lang('网站版权信息,在后台登录页面显示版本信息并链接到备案到信息备案管理系统。')}
+
+
+
+ + +
+
+ + {:lang('系统信息')}( {:lang('仅开发模式可见')} ) + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{:lang('核心框架')}ThinkPHP Version {$framework.version|default='None'}
{:lang('平台框架')}ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}
{:lang('操作系统')}{:php_uname()}
{:lang('运行环境')}{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}
{:lang('系统序号')}{$systemid|default=''}
+
+
+ + {notempty name='plugins'} +
+
+ + {:lang('应用插件')}( {:lang('仅开发模式可见')} ) + +
+
+ + + + + + + + + + + + {foreach $plugins as $key=>$plugin} + + + + + + + + {/foreach} + +
{:lang('应用名称')}{:lang('插件名称')}{:lang('插件包名')}{:lang('插件版本')}{:lang('授权协议')}
{$key}{$plugin.name|lang} + {if empty($plugin.install.document)}{$plugin.package} + {else}{$plugin.package}{/if} + {$plugin.install.version|default='unknow'} + {if empty($plugin.install.license)} - + {elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###} + {else}{$plugin.install.license|default='-'}{/if} +
+
+
+ + +
+
+
-
- - -
-
- - {:lang('系统信息')}( {:lang('仅开发模式可见')} ) - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - -
{:lang('核心框架')}ThinkPHP Version {$framework.version|default='None'}
{:lang('平台框架')}ThinkAdmin Version {$thinkadmin.version|default='6.0.0'}
{:lang('操作系统')}{:php_uname()}
{:lang('运行环境')}{:ucfirst($request->server('SERVER_SOFTWARE',php_sapi_name()))} & PHP {$Think.const.PHP_VERSION} & {:ucfirst(app()->db->connect()->getConfig('type'))}
{:lang('系统序号')}{$systemid|default=''}
-
-
- -{notempty name='plugins'} -
-
- - {:lang('应用插件')}( {:lang('仅开发模式可见')} ) - -
-
- - - - - - - - - - - - {foreach $plugins as $key=>$plugin} - - - - - - - - {/foreach} - -
{:lang('应用名称')}{:lang('插件名称')}{:lang('插件包名')}{:lang('插件版本')}{:lang('授权协议')}
{$key}{$plugin.name|lang} - {if empty($plugin.install.document)}{$plugin.package} - {else}{$plugin.package}{/if} - {$plugin.install.version|default='unknow'} - {if empty($plugin.install.license)} - - {elseif is_array($plugin.install.license)}{$plugin.install.license|join='、',###} - {else}{$plugin.install.license|default='-'}{/if} -
-
-
-{/notempty} - + {/block} \ No newline at end of file diff --git a/app/admin/view/config/system.html b/app/admin/view/config/system.html index 1b7c7c5..b48e230 100644 --- a/app/admin/view/config/system.html +++ b/app/admin/view/config/system.html @@ -99,6 +99,20 @@
+ + + +
+
站点域名
+ +
+ 请设置站点访问域名,例如:https://www.example.com +
+
+
网站备案号和公安备案号可以在备案管理中心查询并获取,网站上线时必需配置备案号,备案号会链接到信息备案管理系统 ~
diff --git a/app/agent/controller/Index.php b/app/agent/controller/Index.php deleted file mode 100644 index 8091e65..0000000 --- a/app/agent/controller/Index.php +++ /dev/null @@ -1,108 +0,0 @@ -request->isPost()) { - $count = intval(input('count', 1)); - $count = max(1, min($count, 100)); // 限制一次最多生成100个 - - // 获取当前代理商信息 - $agentId = session('agent.id'); - $agent = Db::name('cursor_agents')->where('id', $agentId)->find(); - if (empty($agent)) { - $this->error('代理商信息不存在!'); - } - - // 开启事务 - Db::startTrans(); - try { - $codes = []; - $agentCodes = []; - $now = date('Y-m-d H:i:s'); - - for ($i = 0; $i < $count; $i++) { - // 生成激活码 - $codes[] = [ - 'code' => strtoupper(substr(md5(uniqid() . mt_rand()), 0, 16)), - 'days' => 30, // 固定30天 - 'created_at' => $now, - 'is_used' => 0 - ]; - } - - // 批量插入激活码 - $codeIds = Db::name('cursor_activation_codes')->insertAll($codes); - - // 计算佣金 - $price = 100; // 固定价格100元 - $commission = $price * ($agent['commission_rate'] / 100); - - // 如果是二级代理,计算上级佣金 - $parentCommission = 0; - if ($agent['level'] == 2 && $agent['parent_id'] > 0) { - $parent = Db::name('cursor_agents')->where('id', $agent['parent_id'])->find(); - if ($parent) { - $parentCommission = $price * ($parent['commission_rate'] / 100); - } - } - - // 准备代理商激活码数据 - foreach ($codes as $index => $code) { - $agentCodes[] = [ - 'agent_id' => $agentId, - 'code_id' => $codeIds[$index], - 'price' => $price, - 'commission' => $commission, - 'parent_commission' => $parentCommission, - 'status' => 0, - 'created_at' => $now, - 'updated_at' => $now - ]; - } - - // 批量插入代理商激活码关联 - Db::name('cursor_agent_codes')->insertAll($agentCodes); - - Db::commit(); - $this->success('生成成功!'); - } catch (\Exception $e) { - Db::rollback(); - $this->error('生成失败:' . $e->getMessage()); - } - } else { - $this->fetch(); - } - } - - /** - * 我的激活码列表 - */ - public function codes() - { - $this->title = '我的激活码'; - $agentId = session('agent.id'); - - $query = $this->_query('cursor_agent_codes')->alias('ac') - ->join('cursor_activation_codes c', 'c.id = ac.code_id') - ->where('ac.agent_id', $agentId) - ->field('ac.*, c.code, c.days, c.is_used, c.used_at, c.used_by'); - - // 数据列表处理 - $query->equal('c.is_used')->equal('ac.status'); - // 列表排序并显示 - $query->order('ac.id desc')->page(); - } -} \ No newline at end of file diff --git a/app/agent/view/index/codes.html b/app/agent/view/index/codes.html deleted file mode 100644 index d8ce2b3..0000000 --- a/app/agent/view/index/codes.html +++ /dev/null @@ -1,83 +0,0 @@ -{extend name="../../admin/view/main"} - -{block name="button"} - -{/block} - -{block name="content"} -
- - - - {notempty name='list'} - - - - - - - - - - - - - - {/notempty} - - {foreach $list as $key=>$vo} - - - - - - - - - - - - {/foreach} - -
激活码有效天数销售价格佣金使用状态使用时间使用者结算状态生成时间
{$vo.code}{$vo.days} 天{$vo.price}{$vo.commission} - {if $vo.is_used eq 1} - 已使用 - {else} - 未使用 - {/if} - {$vo.used_at|format_datetime|default='--'}{$vo.used_by|default='--'} - {if $vo.status eq 1} - 已结算 - {else} - 未结算 - {/if} - {$vo.created_at|format_datetime}
- {empty name='list'}没有记录哦{else}{$pagehtml|raw|default=''}{/empty} -
-{/block} \ No newline at end of file diff --git a/app/agent/view/index/generate.html b/app/agent/view/index/generate.html deleted file mode 100644 index c38eeec..0000000 --- a/app/agent/view/index/generate.html +++ /dev/null @@ -1,20 +0,0 @@ -{extend name="../../admin/view/main"} - -{block name="content"} -
-
-
- -
- - 一次最多可生成100个激活码,每个激活码售价100元 -
-
-
-
-
- - -
-
-{/block} \ No newline at end of file diff --git a/app/manager/controller/Account.php b/app/manager/controller/Account.php index 3ae0c3b..67e7672 100644 --- a/app/manager/controller/Account.php +++ b/app/manager/controller/Account.php @@ -3,6 +3,7 @@ namespace app\manager\controller; use think\admin\Controller; +use think\admin\service\AdminService; /** * Cursor账号管理 @@ -41,7 +42,7 @@ class Account extends Controller $price = floatval(input('price', 0)); // 参数验证 - if ($count < 1 || $count > 100) { + if ($count < 1 || $count > 500) { $this->error('生成数量必须在1-100之间'); } if ($days < 1) { @@ -60,7 +61,8 @@ class Account extends Controller 'code' => strtoupper(substr(md5(uniqid() . mt_rand()), 0, 16)), 'days' => $days, 'created_at' => $now, - 'is_used' => 0 + 'is_used' => 0, + 'status' => 1 // 设置默认状态为正常 ]; } @@ -153,7 +155,7 @@ class Account extends Controller ->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id') ->leftJoin('cursor_agents a', 'ac.agent_id = a.id') ->field([ - 'c.*', + 'c.*', // 包含所有激活码字段,包括 status 'a.id as agent_id', 'a.username as agent_name', 'a.level as agent_level', @@ -184,6 +186,18 @@ class Account extends Controller if ($usedBy) { $query->whereLike('c.used_by', "%{$usedBy}%"); } + + // 添加天数筛选 + $days = input('days', ''); + if ($days !== '') { + $query->where('c.days', '=', intval($days)); + } + + // 添加状态筛选 + $status = input('status', ''); + if ($status !== '') { + $query->where('c.status', '=', $status); + } }); // 列表排序并显示 @@ -220,6 +234,36 @@ class Account extends Controller $this->_delete('cursor_activation_codes'); } + /** + * 批量删除激活码 + * @auth true + */ + public function batchRemoveCodes() + { + $this->_applyFormToken(); + $ids = $this->request->post('ids', ''); + // 如果是字符串,转换为数组 + if (is_string($ids)) { + $ids = explode(',', $ids); + } + // 确保ids是数组且不为空 + if (empty($ids) || !is_array($ids)) { + $this->error('请选择要删除的激活码'); + } + + // 删除激活码 + $deleteResult1 = \think\facade\Db::name('cursor_activation_codes')->whereIn('id', $ids)->delete(); + // 删除关联的代理商激活码记录 + $deleteResult2 = \think\facade\Db::name('cursor_agent_codes')->whereIn('code_id', $ids)->delete(); + + // 只要有一个删除操作成功就认为是成功的 + if ($deleteResult1 || $deleteResult2) { + $this->success('批量删除成功!'); + } else { + $this->success('没有需要删除的数据!'); + } + } + /** * 设备账号管理 * @auth true @@ -228,42 +272,77 @@ class Account extends Controller public function devices() { $this->title = '设备账号管理'; + // 创建查询对象 - $query = $this->_query('cursor_accounts') - ->where('used_by', '<>', '') - ->where('is_used', 1); + $query = $this->_query('cursor_activation_codes')->alias('c') + ->field([ + 'c.used_by', + 'MIN(c.used_at) as first_activation', + 'COUNT(c.id) as total_codes', + 'SUM(c.days) as total_days', + 'GROUP_CONCAT(DISTINCT a.username) as agent_names' + ]) + ->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id') + ->leftJoin('cursor_agents a', 'ac.agent_id = a.id') + ->where('c.used_by', '<>', '') + ->where('c.is_used', 1) + ->group('c.used_by'); // 数据列表处理 - $query->like('email,used_by'); - // 列表排序并显示 - $query->order('used_at desc')->page(); - - // 获取列表数据 - $list = $this->app->db->getList(); - - // 检查每个设备的缓存状态 - foreach ($list as &$item) { - if (!empty($item['used_by'])) { - // 检查账号缓存 - $cacheKey = "device_account_{$item['used_by']}"; - $cachedAccount = \think\facade\Cache::get($cacheKey); - $item['has_cache'] = !empty($cachedAccount); - - // 检查冷却期 - $cooldownKey = "device_cooldown_{$item['used_by']}"; - $cooldownTime = \think\facade\Cache::get($cooldownKey); - $item['in_cooldown'] = $cooldownTime > time(); - $item['cooldown_time'] = $cooldownTime ? date('Y-m-d H:i:s', $cooldownTime) : ''; - - // 检查缓存账号是否与数据库一致 - $item['cache_match'] = false; - if ($cachedAccount) { - $item['cache_match'] = ($cachedAccount['id'] == $item['id']); - } + $query->where(function($query) { + $usedBy = input('used_by', ''); + if ($usedBy) { + $query->whereLike('c.used_by', "%{$usedBy}%"); } + }); + + // 获取数据 + $result = $query->order('first_activation desc')->page(); + + // 处理列表数据 + $list = []; + foreach ($result['list'] as $item) { + // 获取当前使用的账号 + $account = \think\facade\Db::name('cursor_accounts') + ->where('used_by', $item['used_by']) + ->where('is_used', 1) + ->find(); + + // 计算到期时间 + $firstActivationTime = strtotime($item['first_activation']); + $expireTime = $firstActivationTime + ($item['total_days'] * 24 * 3600); + + // 检查缓存状态 + $cacheKey = "device_account_{$item['used_by']}"; + $cooldownKey = "device_cooldown_{$item['used_by']}"; + + $cachedAccount = \think\facade\Cache::get($cacheKey); + $cooldownTime = \think\facade\Cache::get($cooldownKey); + + // 构建数据 + $list[] = [ + 'used_by' => $item['used_by'], + 'email' => $account ? $account['email'] : '--', + 'agent_names' => $item['agent_names'] ?: '--', + 'total_codes' => $item['total_codes'], + 'total_days' => $item['total_days'], + 'first_activation' => $item['first_activation'], + 'expire_time' => date('Y-m-d H:i:s', $expireTime), + 'days_left' => ceil(($expireTime - time()) / 86400), + 'has_cache' => !empty($cachedAccount), + 'in_cooldown' => $cooldownTime > time(), + 'cooldown_time' => $cooldownTime ? date('Y-m-d H:i:s', $cooldownTime) : '' + ]; } - $this->list = $list; + // 传递数据到模板 + $this->assign([ + 'list' => $list, + 'page' => $result['page'] + ]); + + // 渲染模板 + $this->fetch(); } /** @@ -301,4 +380,355 @@ class Account extends Controller $this->error("重置失败:{$e->getMessage()}"); } } + + /** + * 批量切换代理商 + * @auth true + */ + public function batchChangeAgent() + { + if ($this->request->isPost()) { + $data = $this->_vali([ + 'agent_id.require' => '代理商不能为空', + 'code_ids.require' => '请选择要切换的激活码', + 'price.require' => '销售价格不能为空', + 'price.float' => '销售价格必须是数字' + ]); + + // 检查代理商是否存在 + $agent = \think\facade\Db::name('cursor_agents') + ->where('id', $data['agent_id']) + ->where('status', 1) + ->find(); + if (empty($agent)) { + $this->error('代理商不存在或已被禁用!'); + } + + // 解析激活码ID + $codeIds = explode(',', $data['code_ids']); + if (empty($codeIds)) { + $this->error('请选择要切换的激活码!'); + } + + // 开启事务 + \think\facade\Db::startTrans(); + + $now = date('Y-m-d H:i:s'); + + // 计算佣金 + $commission = $data['price'] * ($agent['commission_rate'] / 100); + + // 如果是二级代理,计算上级佣金 + $parentCommission = 0; + if ($agent['level'] == 2 && $agent['parent_id'] > 0) { + $parent = \think\facade\Db::name('cursor_agents') + ->where('id', $agent['parent_id']) + ->find(); + if ($parent) { + $parentCommission = $data['price'] * ($parent['commission_rate'] / 100); + } + } + + // 删除旧的代理商关联 + $deleteResult = \think\facade\Db::name('cursor_agent_codes') + ->whereIn('code_id', $codeIds) + ->delete(); + + // 批量插入新的关联 + $insertData = []; + foreach ($codeIds as $codeId) { + $insertData[] = [ + 'agent_id' => $agent['id'], + 'code_id' => $codeId, + 'price' => $data['price'], + 'commission' => $commission, + 'parent_commission' => $parentCommission, + 'status' => 0, + 'created_at' => $now, + 'updated_at' => $now + ]; + } + + // 插入新数据 + if (!empty($insertData)) { + $insertResult = \think\facade\Db::name('cursor_agent_codes')->insertAll($insertData); + if ($insertResult === false) { + \think\facade\Db::rollback(); + $this->error('切换失败:插入新数据失败'); + } + } + + \think\facade\Db::commit(); + $this->success('切换成功!'); + } else { + // 获取代理商列表 + $this->agents = \think\facade\Db::name('cursor_agents') + ->where('status', 1) + ->select() + ->toArray(); + $this->fetch(); + } + } + + /** + * 代理商激活码管理 + * @auth true + * @menu true + */ + public function agentCodes() + { + $this->title = '代理商激活码'; + + // 获取当前管理员ID和角色 + $adminId = session('user.id'); + $isSuper = AdminService::isSuper(); + + if (!$isSuper) { + // 普通管理员,获取关联的代理商信息 + $agent = \think\facade\Db::name('cursor_agents') + ->where('admin_id', $adminId) + ->where('status', 1) + ->find(); + + if (empty($agent)) { + $this->error('未找到关联的代理商账号'); + } + } + + // 创建查询对象 + $query = $this->_query('cursor_activation_codes')->alias('c') + ->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id') + ->leftJoin('cursor_agents a', 'ac.agent_id = a.id') + ->field([ + 'c.*', // 包含所有激活码字段,包括 status + 'ac.price', + 'ac.commission', + 'ac.status as settle_status', // 重命名代理商激活码的状态字段 + 'a.username as agent_name', + 'a.level as agent_level' + ]); + + // 如果不是超级管理员,只显示自己的激活码 + if (!$isSuper) { + $query->where('ac.agent_id', $agent['id']); + } + + // 数据列表处理 + $query->where(function($query) use ($isSuper) { + $code = input('code', ''); + if ($code) { + $query->whereLike('c.code', "%{$code}%"); + } + + $isUsed = input('is_used', ''); + if ($isUsed !== '') { + $query->where('c.is_used', '=', $isUsed); + } + + $status = input('status', ''); + if ($status !== '') { + $query->where('c.status', '=', $status); + } + + // 添加天数筛选 + $days = input('days', ''); + if ($days !== '') { + $query->where('c.days', '=', intval($days)); + } + + // 超级管理员可以按代理商筛选 + if ($isSuper) { + $agentId = input('agent_id', ''); + if ($agentId !== '') { + $query->where('ac.agent_id', '=', $agentId); + } + } + + // 添加时间范围筛选 + $createTime = input('create_time', ''); + if ($createTime) { + $createTimeArr = explode(' - ', $createTime); + if (count($createTimeArr) == 2) { + $query->whereBetween('c.created_at', [ + "{$createTimeArr[0]} 00:00:00", + "{$createTimeArr[1]} 23:59:59" + ]); + } + } + + // 使用时间范围筛选 + $useTime = input('use_time', ''); + if ($useTime) { + $useTimeArr = explode(' - ', $useTime); + if (count($useTimeArr) == 2) { + $query->whereBetween('c.used_at', [ + "{$useTimeArr[0]} 00:00:00", + "{$useTimeArr[1]} 23:59:59" + ]); + } + } + }); + + // 获取代理商列表(仅超级管理员可见) + if ($isSuper) { + $this->agents = \think\facade\Db::name('cursor_agents') + ->where('status', 1) + ->select() + ->toArray(); + } + + // 列表排序并显示 + $query->order('c.id desc')->page(); + } + + /** + * 禁用激活码 + * @auth true + */ + public function disableCode() + { + $this->_applyFormToken(); + $id = input('id'); + + // 更新激活码状态为禁用 (0) + $result = \think\facade\Db::name('cursor_activation_codes') + ->where('id', $id) + ->update(['status' => 0]); + + if ($result !== false) { + $this->success('禁用成功!'); + } else { + $this->error('禁用失败!'); + } + } + + /** + * 批量禁用激活码 + * @auth true + */ + public function batchDisableCodes() + { + $this->_applyFormToken(); + $ids = $this->request->post('ids', ''); + + if (empty($ids)) { + $this->error('请选择要禁用的激活码'); + } + + // 如果是字符串,转换为数组 + if (is_string($ids)) { + $ids = explode(',', $ids); + } + + // 批量更新状态为禁用 (0) + $result = \think\facade\Db::name('cursor_activation_codes') + ->whereIn('id', $ids) + ->update(['status' => 0]); + + if ($result !== false) { + $this->success('批量禁用成功!'); + } else { + $this->error('批量禁用失败!'); + } + } + + /** + * 导出激活码到txt文件 + * @auth true + */ + public function exportCodes() + { + try { + // 获取当前管理员ID和角色 + $adminId = session('user.id'); + $isSuper = AdminService::isSuper(); + + if (!$isSuper) { + // 普通管理员,获取关联的代理商信息 + $agent = \think\facade\Db::name('cursor_agents') + ->where('admin_id', $adminId) + ->where('status', 1) + ->find(); + + if (empty($agent)) { + $this->error('未找到关联的代理商账号'); + } + } + + // 创建查询对象 + $query = \think\facade\Db::name('cursor_activation_codes')->alias('c') + ->leftJoin('cursor_agent_codes ac', 'c.id = ac.code_id') + ->leftJoin('cursor_agents a', 'ac.agent_id = a.id') + ->field([ + 'c.code', + 'c.days', + 'c.is_used', + 'c.status' + ]); + + // 如果不是超级管理员,只显示自己的激活码 + if (!$isSuper) { + $query->where('ac.agent_id', $agent['id']); + } + + // 应用筛选条件 + $code = input('code', ''); + if ($code) { + $query->whereLike('c.code', "%{$code}%"); + } + + $isUsed = input('is_used', ''); + if ($isUsed !== '') { + $query->where('c.is_used', '=', $isUsed); + } + + $status = input('status', ''); + if ($status !== '') { + $query->where('c.status', '=', $status); + } + + $days = input('days', ''); + if ($days !== '') { + $query->where('c.days', '=', intval($days)); + } + + // 超级管理员可以按代理商筛选 + if ($isSuper) { + $agentId = input('agent_id', ''); + if ($agentId !== '') { + $query->where('ac.agent_id', '=', $agentId); + } + } + + // 获取数据 + $list = $query->order('c.id desc')->select()->toArray(); + + // 生成文件内容 + $content = ''; + foreach ($list as $item) { + // 获取状态文本 + $statusText = $item['status'] == 1 ? '正常' : '禁用'; + $usedText = $item['is_used'] == 1 ? '已使用' : '未使用'; + + // 格式化每一行: 激活码 天数 状态 + $content .= sprintf("%s\t%d\t%s/%s\n", + $item['code'], + $item['days'], + $usedText, + $statusText + ); + } + + // 设置响应头 + header('Content-Type: text/plain'); + header('Content-Disposition: attachment; filename="activation_codes_' . date('YmdHis') . '.txt"'); + + // 输出内容 + echo $content; + exit; + + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + } } \ No newline at end of file diff --git a/app/manager/controller/Agent.php b/app/manager/controller/Agent.php index 574fd32..8db62ae 100644 --- a/app/manager/controller/Agent.php +++ b/app/manager/controller/Agent.php @@ -43,6 +43,7 @@ class Agent extends Controller 'parent_id.default' => 0, 'commission_rate.require' => '佣金比例不能为空', 'status.default' => 1, + 'admin_id.default' => 0, // 绑定的管理员ID 'remark.default' => '' ]); @@ -62,6 +63,19 @@ class Agent extends Controller } } + // 如果绑定了管理员账号,检查是否存在 + if (!empty($data['admin_id'])) { + $admin = Db::name('system_user')->where('id', $data['admin_id'])->find(); + if (empty($admin)) { + $this->error('管理员账号不存在!'); + } + // 检查管理员账号是否已被绑定 + $exists = Db::name('cursor_agents')->where('admin_id', $data['admin_id'])->find(); + if ($exists) { + $this->error('该管理员账号已被其他代理商绑定!'); + } + } + // 处理密码 $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); $data['created_at'] = $data['updated_at'] = date('Y-m-d H:i:s'); @@ -77,6 +91,13 @@ class Agent extends Controller ->where('level', 1) ->where('status', 1) ->select(); + + // 获取管理员列表 + $this->admins = Db::name('system_user') + ->where('status', 1) + ->field('id, username, nickname') + ->select(); + $this->fetch(); } } @@ -87,8 +108,85 @@ class Agent extends Controller */ public function edit() { - $this->_applyFormToken(); - $this->_form('cursor_agents', 'form'); + if ($this->request->isPost()) { + $data = $this->_vali([ + 'id.require' => '代理商ID不能为空', + 'username.require' => '用户名不能为空', + 'password.default' => '', + 'nickname.default' => '', + 'level.require' => '代理级别不能为空', + 'parent_id.default' => 0, + 'commission_rate.require' => '佣金比例不能为空', + 'status.default' => 1, + 'admin_id.default' => 0, // 绑定的管理员ID + 'remark.default' => '' + ]); + + // 检查用户名是否存在 + $exists = Db::name('cursor_agents') + ->where('username', $data['username']) + ->where('id', '<>', $data['id']) + ->find(); + if ($exists) { + $this->error('用户名已存在!'); + } + + // 如果是二级代理,检查上级代理 + if ($data['level'] == 2) { + if (empty($data['parent_id'])) { + $this->error('请选择上级代理!'); + } + $parent = Db::name('cursor_agents')->where('id', $data['parent_id'])->find(); + if (empty($parent) || $parent['level'] != 1) { + $this->error('上级代理不存在或不是一级代理!'); + } + } + + // 如果绑定了管理员账号,检查是否存在 + if (!empty($data['admin_id'])) { + $admin = Db::name('system_user')->where('id', $data['admin_id'])->find(); + if (empty($admin)) { + $this->error('管理员账号不存在!'); + } + // 检查管理员账号是否已被其他代理商绑定 + $exists = Db::name('cursor_agents') + ->where('admin_id', $data['admin_id']) + ->where('id', '<>', $data['id']) + ->find(); + if ($exists) { + $this->error('该管理员账号已被其他代理商绑定!'); + } + } + + // 处理密码 + if (!empty($data['password'])) { + $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT); + } else { + unset($data['password']); + } + + $data['updated_at'] = date('Y-m-d H:i:s'); + + if (Db::name('cursor_agents')->update($data) !== false) { + $this->success('更新成功!'); + } else { + $this->error('更新失败!'); + } + } else { + // 获取一级代理列表(用于选择上级) + $this->agents = Db::name('cursor_agents') + ->where('level', 1) + ->where('status', 1) + ->select(); + + // 获取管理员列表 + $this->admins = Db::name('system_user') + ->where('status', 1) + ->field('id, username, nickname') + ->select(); + + $this->_form('cursor_agents', 'form'); + } } /** diff --git a/app/manager/controller/Version.php b/app/manager/controller/Version.php new file mode 100644 index 0000000..894e86b --- /dev/null +++ b/app/manager/controller/Version.php @@ -0,0 +1,228 @@ +title = '版本管理'; + // 创建查询对象 + $query = VersionModel::mQuery(); + // 数据列表处理 + $query->like('version_no,version_name')->equal('platform,status'); + // 列表排序处理 + $query->order('version_no desc')->page(); + } + + /** + * 添加版本 + * @auth true + */ + public function add() + { + $this->title = '添加版本'; + VersionModel::mForm('form'); + } + + /** + * 编辑版本 + * @auth true + */ + public function edit() + { + $this->title = '编辑版本'; + VersionModel::mForm('form'); + } + + /** + * 表单数据处理 + */ + protected function _form_filter(&$data) + { + if ($this->request->isGet()) { + // 获取版本日志 + if (isset($data['id'])) { + $data['logs'] = \think\facade\Db::name('version_log') + ->where(['version_id' => $data['id']]) + ->order('id asc') + ->select() + ->toArray(); + } + } else { + // 检查版本号是否已存在 + if (empty($data['id'])) { + $exists = VersionModel::mk()->where('version_no', $data['version_no'])->find(); + if ($exists) { + $this->error('该版本号已存在!'); + } + } + + // 保存版本日志 + if (isset($data['logs'])) { + $logs = []; + foreach ($data['logs']['type'] as $key => $type) { + if (!empty($data['logs']['content'][$key])) { + $logs[] = [ + 'version_id' => $data['id'] ?? 0, + 'type' => $type, + 'content' => $data['logs']['content'][$key], + 'create_time' => date('Y-m-d H:i:s') + ]; + } + } + unset($data['logs']); + if (!empty($logs)) { + if (isset($data['id'])) { + \think\facade\Db::name('version_log')->where(['version_id' => $data['id']])->delete(); + } + \think\facade\Db::name('version_log')->insertAll($logs); + } + } + } + } + + /** + * 删除版本 + * @auth true + */ + public function remove() + { + VersionModel::mDelete(); + } + + /** + * 版本状态 + * @auth true + */ + public function state() + { + VersionModel::mSave($this->_vali([ + 'status.in:0,1' => '状态值范围异常!', + 'status.require' => '状态值不能为空!', + ])); + } + + /** + * 上传更新包 + * @auth true + */ + public function upload() + { + if ($this->request->isPost()) { + try { + // 获取上传的文件 + $file = $this->request->file('file'); + if (empty($file)) { + $this->error('文件上传异常,文件可能过大!'); + } + + // 检查文件类型 + $ext = strtolower($file->getOriginalExtension()); + if (!in_array($ext, $this->allowExts)) { + $this->error('不支持的文件类型!'); + } + + // 获取原始文件名 + $originalName = $file->getOriginalName(); + + // 从文件名解析版本号 + $version_no = VersionModel::parseVersionFromFilename($originalName); + + // 生成版本名称 + $version_name = VersionModel::generateVersionName($version_no, $originalName); + + // 执行文件上传 + $type = sysconf('storage.type|raw') ?: 'local'; + if ($type === 'local') { + $local = \think\admin\storage\LocalStorage::instance(); + $distName = dirname($local->path($originalName, false)) . '/' . $originalName; + $file->move(dirname($distName), basename($distName)); + $info = [ + 'url' => '/upload/' . $originalName, + 'key' => $originalName + ]; + + // 设置上传文件的权限为755 + @chmod($distName, 0755); + } else { + $bina = file_get_contents($file->getPathname()); + $info = \think\admin\Storage::instance($type)->set($originalName, $bina, false, $originalName); + } + + + if (isset($info['url'])) { + $data = [ + 'code' => 0, + 'msg' => '文件上传成功!', + 'data' => [ + 'url' => $info['url'], + 'name' => $originalName, + 'version_no' => $version_no, + 'version_name' => $version_name + ] + ]; + return json($data); + } else { + $this->error('文件上传失败!'); + } + } catch (\Exception $exception) { + $this->error($exception->getMessage()); + } + } + } + + /** + * 从文件名解析版本信息 + * @param string $filename + * @return array + */ + protected function parseVersionFromFilename($filename) + { + // 移除扩展名 + $name = pathinfo($filename, PATHINFO_FILENAME); + + // 匹配版本号 (支持 v3.4.0 或 3.4.0 格式) + if (preg_match('/[v]?(\d+\.\d+\.\d+)/', $name, $matches)) { + $version_no = $matches[1]; + // 提取版本名称 (去掉版本号部分) + $version_name = trim(str_replace($matches[0], '', $name)); + if (empty($version_name)) { + $version_name = "版本 {$version_no}"; + } + } else { + // 如果没有找到版本号,使用时间戳作为版本号 + $version_no = date('y.m.d'); + $version_name = $name ?: "版本 {$version_no}"; + } + + return [ + 'version_no' => $version_no, + 'version_name' => $version_name + ]; + } +} \ No newline at end of file diff --git a/app/manager/model/AgentCode.php b/app/manager/model/AgentCode.php index cc27bce..ec6c68b 100644 --- a/app/manager/model/AgentCode.php +++ b/app/manager/model/AgentCode.php @@ -23,6 +23,28 @@ class AgentCode extends Model 'updated_at' => 'datetime', ]; + // 定义卡类型配置 + public static $cardTypes = [ + 1 => ['days' => 1, 'name' => '1天卡'], + 5 => ['days' => 5, 'name' => '5天卡'], + 30 => ['days' => 30, 'name' => '30天卡'], + 90 => ['days' => 90, 'name' => '三个月卡'], + 180 => ['days' => 180, 'name' => '六个月卡'], + 365 => ['days' => 365, 'name' => '一年卡'], + ]; + + // 获取所有卡类型 + public static function getCardTypes() + { + return self::$cardTypes; + } + + // 获取卡类型名称 + public static function getCardTypeName($days) + { + return isset(self::$cardTypes[$days]) ? self::$cardTypes[$days]['name'] : "{$days}天卡"; + } + // 获取所属代理商 public function agent() { diff --git a/app/manager/view/account/agent_codes.html b/app/manager/view/account/agent_codes.html new file mode 100644 index 0000000..5b777c1 --- /dev/null +++ b/app/manager/view/account/agent_codes.html @@ -0,0 +1,278 @@ +{extend name="../../admin/view/main"} + +{block name="button"} +{if auth("batchDisableCodes")} + +{/if} + +导出激活码 +{/block} + +{block name="content"} +
+ + + + {notempty name='list'} + + + + + + {if $Think.admin.is_super} + + + {/if} + + + + + + + + + + + {/notempty} + + {foreach $list as $key=>$vo} + + + + + {if $Think.admin.is_super} + + + {/if} + + + + + + + + + + {/foreach} + +
+ + 激活码有效天数代理商代理级别销售价格佣金使用状态使用时间使用者生成时间状态
+ + {$vo.code}{$vo.days} 天{$vo.agent_name|default='--'} + {if $vo.agent_level eq 1} + 一级代理 + {elseif $vo.agent_level eq 2} + 二级代理 + {else} + -- + {/if} + {$vo.price|default='--'}{$vo.commission|default='--'} + {if $vo.is_used eq 1} + 已使用 + {else} + 未使用 + {/if} + {$vo.used_at|format_datetime|default='--'}{$vo.used_by|default='--'}{$vo.created_at|format_datetime} + {if $vo.status eq 1} + 正常 + {else} + 已禁用 + {/if} + + {if auth("disableCode") && $vo.status eq 1} + 禁用 + {/if} +
+ {empty name='list'}没有记录哦{else}{$pagehtml|raw|default=''}{/empty} +
+ + + + + + + +{/block} \ No newline at end of file diff --git a/app/manager/view/account/batch_change_agent.html b/app/manager/view/account/batch_change_agent.html new file mode 100644 index 0000000..c025a7d --- /dev/null +++ b/app/manager/view/account/batch_change_agent.html @@ -0,0 +1,44 @@ +{extend name="../../admin/view/main"} + +{block name="content"} +
+
+
+ +
+ +
+
+ +
+ +
+ + 设置激活码的销售价格,用于计算代理商佣金 +
+
+ + +
+
+
+ + +
+
+ + +{/block} \ No newline at end of file diff --git a/app/manager/view/account/codes.html b/app/manager/view/account/codes.html index 0f1f5b9..8b7ff13 100644 --- a/app/manager/view/account/codes.html +++ b/app/manager/view/account/codes.html @@ -2,6 +2,13 @@ {block name="button"} +{if auth("batchRemoveCodes")} + +{/if} +{if auth("batchChangeAgent")} + +{/if} + {/block} {block name="content"} @@ -39,6 +46,29 @@
+
+ +
+ +
+
+ +
+ +
+ +
+
+
@@ -63,6 +93,7 @@ 使用时间 使用者 生成时间 + 状态 @@ -109,9 +140,19 @@ {$vo.used_at|format_datetime|default='--'} {$vo.used_by|default='--'} {$vo.created_at|format_datetime} + + {if $vo.status eq 1} + 正常 + {else} + 已禁用 + {/if} + {if auth("removeCode")} - 删 除 + 删除 + {/if} + {if auth("disableCode") && $vo.status eq 1} + 禁用 {/if} @@ -120,4 +161,91 @@ {empty name='list'}没有记录哦{else}{$pagehtml|raw|default=''}{/empty} + + + + + {/block} \ No newline at end of file diff --git a/app/manager/view/account/devices.html b/app/manager/view/account/devices.html index 425dba4..1639020 100644 --- a/app/manager/view/account/devices.html +++ b/app/manager/view/account/devices.html @@ -26,10 +26,14 @@ {notempty name='list'} - 账号邮箱 设备ID - 使用时间 - 注册时间 + 账号邮箱 + 代理商 + 激活码数量 + 总天数 + 首次激活 + 到期时间 + 剩余天数 缓存状态 冷却状态 操作 @@ -39,24 +43,30 @@ {foreach $list as $key=>$vo} - {$vo.email} - {$vo.used_by} - {$vo.used_at|format_datetime} - {$vo.registration_time|format_datetime} + {$vo.used_by|default='--'} + {if isset($vo.email)}{$vo.email}{else}--{/if} + {if isset($vo.agent_names)}{$vo.agent_names}{else}--{/if} + {if isset($vo.total_codes)}{$vo.total_codes}{else}0{/if} + {if isset($vo.total_days)}{$vo.total_days} 天{else}0 天{/if} + {if isset($vo.first_activation)}{$vo.first_activation|format_datetime}{else}--{/if} + {if isset($vo.expire_time)}{$vo.expire_time}{else}--{/if} - {if $vo.has_cache} - {if $vo.cache_match} - 正常 - {else} - 异常(缓存不匹配) - {/if} + {if isset($vo.days_left) && $vo.days_left > 0} + {$vo.days_left} 天 + {else} + 已过期 + {/if} + + + {if isset($vo.has_cache) && $vo.has_cache} + 已缓存 {else} 无缓存 {/if} - {if $vo.in_cooldown} - 冷却中 (至 {$vo.cooldown_time}) + {if isset($vo.in_cooldown) && $vo.in_cooldown} + 冷却中 {if isset($vo.cooldown_time)}(至 {$vo.cooldown_time}){/if} {else} 可用 {/if} diff --git a/app/manager/view/account/generate.html b/app/manager/view/account/generate.html index c5e18f0..bb5126c 100644 --- a/app/manager/view/account/generate.html +++ b/app/manager/view/account/generate.html @@ -6,52 +6,79 @@
- - 一次最多可生成100个激活码 + + 单次可生成1-500个激活码
+
- +
+ {foreach :app\manager\model\AgentCode::getCardTypes() as $days => $info} + + {/foreach} +
+ + 激活码的有效使用天数
+
- {foreach $agents as $agent} - + {/foreach} - 如果选择代理商,需要设置销售价格 + 可以选择直接分配给代理商
-
- - + +
+ + {/block} \ No newline at end of file diff --git a/app/manager/view/account/index_search.html b/app/manager/view/account/index_search.html new file mode 100644 index 0000000..d828351 --- /dev/null +++ b/app/manager/view/account/index_search.html @@ -0,0 +1,70 @@ +{include file='../../admin/view/search'} + +
+ 条件搜索 + +
\ No newline at end of file diff --git a/app/manager/view/agent/add.html b/app/manager/view/agent/add.html index 470d744..9ef814c 100644 --- a/app/manager/view/agent/add.html +++ b/app/manager/view/agent/add.html @@ -47,6 +47,19 @@ +
+ +
+ + 选择要绑定的管理员账号,绑定后该管理员可以作为二级代理 +
+
+
diff --git a/app/manager/view/agent/form.html b/app/manager/view/agent/form.html index ee94aec..eb1b661 100644 --- a/app/manager/view/agent/form.html +++ b/app/manager/view/agent/form.html @@ -6,6 +6,7 @@
+
@@ -13,8 +14,7 @@
- - 编辑时不填写表示不修改密码 +
@@ -28,25 +28,39 @@
- + + +
-