Browse Source

代码提交2026年7月20日16H

master
xiaodong 2 weeks ago
parent
commit
b771b70cf1
  1. 201
      LICENSE
  2. 32
      LICENSE.txt
  3. 22
      app/AppService.php
  4. 118
      app/BaseController.php
  5. 82
      app/ExceptionHandle.php
  6. 309
      app/Plugin.php
  7. 8
      app/Request.php
  8. 101
      app/command/BaiduTongji.php
  9. 61
      app/command/CreateController.php
  10. 53
      app/command/CreateLink.php
  11. 28
      app/command/CreateModel.php
  12. 4717
      app/command/Link.php
  13. 71
      app/command/ReportCreateJob.php
  14. 59
      app/command/WebDiagnosis.php
  15. 55
      app/command/WebDiagnosis2.php
  16. 55
      app/command/WebDiagnosis3.php
  17. 54
      app/command/gather/NoticeGatherJob.php
  18. 55
      app/command/gather/service/BaseGather.php
  19. 110
      app/command/gather/service/CommodityGather.php
  20. 104
      app/command/gather/service/EnergyGather.php
  21. 103
      app/command/gather/service/FinanceGather.php
  22. 107
      app/command/gather/service/FuturesGather.php
  23. 30
      app/command/service/Server.php
  24. 149
      app/command/service/TaskServer.php
  25. 171
      app/command/service/TongJiServer.php
  26. 55
      app/command/service/step/CheckIncluded.php
  27. 124
      app/command/service/step/CheckKeywords.php
  28. 78
      app/command/service/step/CheckLinks.php
  29. 156
      app/command/service/step/CheckSSL301.php
  30. 136
      app/command/service/step/CheckTplCode.php
  31. 138
      app/command/stubs/controller.stub
  32. 113
      app/command/stubs/model.stub
  33. 1337
      app/common.php
  34. 1
      app/controller/api/.gitignore
  35. 210
      app/controller/backend/AccountController.php
  36. 323
      app/controller/backend/AdminController.php
  37. 40
      app/controller/backend/AdminLoginLogController.php
  38. 182
      app/controller/backend/AdminMenuController.php
  39. 41
      app/controller/backend/AdminOptLogController.php
  40. 140
      app/controller/backend/AdvertisementController.php
  41. 158
      app/controller/backend/AttachmentCateController.php
  42. 572
      app/controller/backend/AttachmentController.php
  43. 90
      app/controller/backend/BaseController.php
  44. 537
      app/controller/backend/CategoryController.php
  45. 1287
      app/controller/backend/ContentController.php
  46. 146
      app/controller/backend/ContentTagController.php
  47. 68
      app/controller/backend/DashboardController.php
  48. 146
      app/controller/backend/DatabaseController.php
  49. 667
      app/controller/backend/DesignController.php
  50. 27
      app/controller/backend/ExcelController.php
  51. 585
      app/controller/backend/FormController.php
  52. 241
      app/controller/backend/GatherController.php
  53. 14
      app/controller/backend/GoogleStatisticsController.php
  54. 154
      app/controller/backend/InnerChartController.php
  55. 130
      app/controller/backend/InquiryCategoryController.php
  56. 231
      app/controller/backend/InquiryController.php
  57. 152
      app/controller/backend/InquiryEmailController.php
  58. 153
      app/controller/backend/JobCateController.php
  59. 154
      app/controller/backend/JobCityController.php
  60. 164
      app/controller/backend/JobController.php
  61. 356
      app/controller/backend/KeywordController.php
  62. 91
      app/controller/backend/KeywordQueryController.php
  63. 129
      app/controller/backend/KeywordWebsiteController.php
  64. 255
      app/controller/backend/LinkController.php
  65. 105
      app/controller/backend/MaterialController.php
  66. 164
      app/controller/backend/ModuleController.php
  67. 168
      app/controller/backend/ModuleFieldController.php
  68. 166
      app/controller/backend/NavCateController.php
  69. 316
      app/controller/backend/NavController.php
  70. 52
      app/controller/backend/PluginController.php
  71. 185
      app/controller/backend/PosterController.php
  72. 121
      app/controller/backend/RecycleBinController.php
  73. 177
      app/controller/backend/RoleController.php
  74. 80
      app/controller/backend/SeoAccountController.php
  75. 242
      app/controller/backend/SeoCheckController.php
  76. 54
      app/controller/backend/SeoController.php
  77. 139
      app/controller/backend/SeoSettingController.php
  78. 46
      app/controller/backend/SiteMapController.php
  79. 125
      app/controller/backend/SlideCateController.php
  80. 134
      app/controller/backend/SlideController.php
  81. 139
      app/controller/backend/SocialMarketingController.php
  82. 45
      app/controller/backend/StaticFileController.php
  83. 617
      app/controller/backend/StatisticsController.php
  84. 180
      app/controller/backend/SysSettingController.php
  85. 537
      app/controller/backend/SystemInstallController.php
  86. 169
      app/controller/backend/TagController.php
  87. 277
      app/controller/backend/ThemeController.php
  88. 206
      app/controller/backend/ThemeFileController.php
  89. 84
      app/controller/backend/UploadController.php
  90. 287
      app/controller/backend/WebsiteController.php
  91. 86
      app/controller/backend/WebsiteLangController.php
  92. 90
      app/controller/backend/WebsiteServerController.php
  93. 125
      app/controller/backend/WebsiteSettingController.php
  94. 274
      app/controller/backend/custom/ContentImportController.php
  95. 333
      app/controller/frontend/BaseController.php
  96. 132
      app/controller/frontend/DesignBaseController.php
  97. 140
      app/controller/frontend/DesignController.php
  98. 146
      app/controller/frontend/DetailController.php
  99. 279
      app/controller/frontend/FormController.php
  100. 105
      app/controller/frontend/InquiryController.php

201
LICENSE

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

32
LICENSE.txt

@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
版权所有Copyright © 2006-2016 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1. 需要给代码的用户一份Apache Licence ;
2. 如果你修改了代码,需要在被修改的文件中说明;
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4. 如果再发布的产品中包含一个Notice文件,则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可,但不可以表现为对Apache Licence构成更改。
具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

22
app/AppService.php

@ -0,0 +1,22 @@
<?php
declare (strict_types = 1);
namespace app;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register()
{
// 服务注册
}
public function boot()
{
// 服务启动
}
}

118
app/BaseController.php

@ -0,0 +1,118 @@
<?php
declare (strict_types = 1);
namespace app;
use think\App;
use think\Container;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use think\Validate;
/**
* 控制器基础类
*/
abstract class BaseController
{
/**
* Request实例
* @var \think\Request
*/
protected $request;
/**
* 应用实例
* @var \think\App
*/
protected $app;
/**
* 是否批量验证
* @var bool
*/
protected $batchValidate = false;
/**
* 构造方法
* @access public
* @param App $app 应用对象
*/
public function __construct(App $app)
{
$this->app = $app;
$this->request = $this->app->request;
// 控制器初始化
$this->initialize();
}
// 初始化
protected function initialize()
{
// 允许跨域
$this->cors();
}
public function cors()
{
header("access-control-allow-headers: Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,Login-Agent,X-Mx-ReqToken,X-Requested-With");
header("access-control-allow-methods: GET, POST, PUT, DELETE, HEAD, OPTIONS,PATCH");
header("access-control-allow-credentials: true");
header("access-control-allow-origin: *");
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();
if (!empty($scene)) {
$v->scene($scene);
}
}
$v->message($message);
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
return $v->failException(true)->check($data);
}
/**
* 获取当前的response 输出类型
* @access protected
* @return string
*/
protected function getResponseType(): string
{
if (!$this->app) {
$this->app = Container::get('app');
}
$isAjax = $this->request->isAjax();
return $isAjax || $this->request->isJson()
? 'json'
: 'html';
}
}

82
app/ExceptionHandle.php

@ -0,0 +1,82 @@
<?php
namespace app;
use app\exception\AuthException;
use app\exception\BadSysSettingException;
use app\exception\BaseException;
use app\exception\ModelException;
use app\exception\ModelNotUniqueException;
use app\exception\TokenException;
use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\facade\Log;
use think\Response;
use Throwable;
/**
* 应用异常处理类
*/
class ExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];
/**
* 记录异常信息(包括日志或者其它方式记录)
*
* @access public
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @access public
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 详细错误日志写入文件
Log::error($e->getMessage());
$str = $this->formatterTraceStr($e);
Log::error($str);
if(request()->isAjax() || request()->isJson() || env('APP_DEBUG') == 1){
// 添加自定义异常处理机制
return jsonReturn($e->getCode() ?: -1, $e->getMessage());
}
// 其他错误交给系统处理
return parent::render($request, $e);
}
public function formatterTraceStr(Throwable $e): string
{
$str = "=========手动记录日志=============\n" ;
$str .= '请求的API是【' . request()->url() . "\n";
$str .= '请求携带的参数是【' . json_encode(request() -> param()) . "\n";
$str .= $e->getTraceAsString();
$str .= "\n".date('Y-m-d H:i:s') . "】=========日志记录结束=============";
return $str;
}
}

309
app/Plugin.php

@ -0,0 +1,309 @@
<?php
namespace app;
use app\model\AdminMenu;
use think\facade\Cache;
use think\facade\Db;
use think\View;
/**
* 插件类
*/
abstract class Plugin
{
/**
* 视图实例对象
* @var view
* @access protected
*/
private $view = null;
public static $vendorLoaded = [];
/**
* $info = array(
* 'name'=>'HelloWorld',
* 'title'=>'HelloWorld',
* 'description'=>'HelloWorld',
* 'status'=>1,
* 'author'=>'ThinkCMF',
* 'version'=>'1.0'
* )
*/
public $info = [];
private $pluginPath = '';
private $name = '';
private $configFilePath = '';
private $themeRoot = "";
/**
* Plugin constructor.
*/
public function __construct()
{
$this->name = $this->getName();
$nameCStyle = parse_name($this->name);
$this->pluginPath = CMS_ROOT . 'plugins/' . $nameCStyle . '/';
$this->configFilePath = $this->pluginPath . 'config.php';
if (empty(self::$vendorLoaded[$this->name])) {
$pluginVendorAutoLoadFile = $this->pluginPath . 'vendor/autoload.php';
if (file_exists($pluginVendorAutoLoadFile)) {
require_once $pluginVendorAutoLoadFile;
}
self::$vendorLoaded[$this->name] = true;
}
$config = $this->getConfig();
$theme = isset($config['theme']) ? $config['theme'] : '';
//$depr = "/";
$root = hcGetRoot();
$themeDir = empty($theme) ? "" : '/' . $theme;
$themePath = 'view' . $themeDir;
$this->themeRoot = $this->pluginPath . $themePath . '/';
$pluginRoot = "plugins/{$nameCStyle}";
$cmfAdminThemePath = config('template.cmf_admin_theme_path');
$cmfAdminDefaultTheme = config('template.cmf_admin_default_theme');
$adminThemePath = "{$cmfAdminThemePath}{$cmfAdminDefaultTheme}";
$replaceConfig = [
'__ROOT__' => $root,
'__PLUGIN_TMPL__' => $root . '/' . $pluginRoot . '/' . $themePath,
'__PLUGIN_ROOT__' => $root . '/' . $pluginRoot,
'__ADMIN_TMPL__' => "{$root}/{$adminThemePath}",
'__STATIC__' => "{$root}/static",
'__WEB_ROOT__' => $root
];
$app = app();
$view = new View($app);
$this->view = $view;
$this->view->engine()->config([
'view_base' => $this->themeRoot,
'tpl_replace_string' => $replaceConfig
]);
//加载多语言
$langSet = $app->lang->getLangSet();
$lang_file = $this->pluginPath . "lang/" . $langSet . ".php";
$app->lang->load($lang_file);
}
/**
* 加载模板输出
* @access protected
* @param string $template 模板文件名
* @return string
* @throws \Exception
*/
final protected function fetch($template)
{
if (!is_file($template)) {
$engineConfig = config('view');
$template = $this->themeRoot . $template . '.' . $engineConfig['view_suffix'];
}
// 模板不存在 抛出异常
if (!is_file($template)) {
throw new TemplateNotFoundException('template not exists:' . $template, $template);
}
return $this->view->fetch($template);
}
/**
* 渲染内容输出
* @access protected
* @param string $content 模板内容
* @return mixed
*/
final protected function display($content = '')
{
return $this->view->display($content);
}
/**
* 模板变量赋值
* @access protected
* @param mixed $name 要显示的模板变量
* @param mixed $value 变量的值
* @return void
*/
final protected function assign($name, $value = '')
{
$this->view->assign($name, $value);
}
/**
* 获取插件名
* @return string
*/
final public function getName()
{
if (empty($this->name)) {
$class = get_class($this);
$this->name = substr($class, strrpos($class, '\\') + 1, -6);
}
return $this->name;
}
/**
* 检查插件信息完整性
* @return bool
*/
final public function checkInfo()
{
$infoCheckKeys = ['name', 'title', 'description', 'status', 'author', 'version'];
foreach ($infoCheckKeys as $value) {
if (!array_key_exists($value, $this->info))
return false;
}
return true;
}
/**
* 获取插件根目录绝对路径
* @return string
*/
final public function getPluginPath()
{
return $this->pluginPath;
}
/**
* 获取插件配置文件绝对路径
* @return string
*/
final public function getConfigFilePath()
{
return $this->configFilePath;
}
/**
*
* @return string
*/
final public function getThemeRoot()
{
return $this->themeRoot;
}
/**
* @return View
*/
public function getView()
{
return $this->view;
}
/**
* 获取插件的配置数组
* @return array
*/
final public function getConfig()
{
$name = $this->getName();
if (PHP_SAPI != 'cli') {
static $_config = [];
if (isset($_config[$name])) {
return $_config[$name];
}
}
$pluginCofingKey = 'cmf_'.$name.'_plugin_config';
if (Cache::has($pluginCofingKey)){
return Cache::get($pluginCofingKey);
}
$ttl = mt_rand(600,6000);
$config = Db::name('plugin')->cache('cmf_'.$name.'_plugin_config_db',$ttl)->where('name', $name)->value('config');
if (!empty($config) && $config != "null") {
$config = json_decode($config, true);
} else {
$config = $this->getDefaultConfig();
}
Cache::set($pluginCofingKey,$config,$ttl);
$_config[$name] = $config;
return $config;
}
/**
* 获取插件的配置数组
* @return array
*/
final public function getDefaultConfig()
{
$config = [];
if (file_exists($this->configFilePath)) {
$tempArr = include $this->configFilePath;
if (!empty($tempArr) && is_array($tempArr)) {
foreach ($tempArr as $key => $value) {
if ($value['type'] == 'group') {
foreach ($value['options'] as $gkey => $gvalue) {
foreach ($gvalue['options'] as $ikey => $ivalue) {
$config[$ikey] = $ivalue['value'];
}
}
} else {
$config[$key] = $tempArr[$key]['value'];
}
}
}
}
return $config;
}
protected function installSql()
{
//执行安装sql
$dbConfig = env();
$sqlArr = hcSplitSql($this->pluginPath . '/install.sql', $dbConfig['DATABASE_PREFIX'], $dbConfig['DATABASE_CHARSET']);
$db = Db::connect();
foreach ($sqlArr as $sql) {
$sqlToExec = $sql . ';';
$result = sp_execute_sql($db, $sqlToExec);
if (!empty($result['error'])) {
return false;
}
}
return true;
}
public function editMenu($type = 1, $remark = '') {
$adminMenuModel = new AdminMenu();
if ($type == 1) { //安装
$adminMenuModel->where('remark', $remark)->update(['status' => 1]);
} else {//卸载
$adminMenuModel->where('remark', $remark)->update(['status' => 2]);
}
}
//必须实现安装
abstract public function install();
//必须卸载插件方法
abstract public function uninstall();
}

8
app/Request.php

@ -0,0 +1,8 @@
<?php
namespace app;
// 应用请求对象类
class Request extends \think\Request
{
}

101
app/command/BaiduTongji.php

@ -0,0 +1,101 @@
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\facade\Db;
class BaiduTongji extends Command
{
protected function configure()
{
$this->setName('baiduTongji')
->setDescription('百度统计');
}
protected function execute(Input $input, Output $output)
{
set_time_limit(0);
ini_set('memory_limit', '512M');
/*************************************** 百度统计开始 ************************************************/
$url = 'http://api.baidu.com/json/tongji/v1/ReportService/getData';
$listUrl = 'https://api.baidu.com/json/tongji/v1/ReportService/getSiteList';
$accountList = Db::name('seo_account')->field('account,password,token')->where("type=1")->selectOrFail();
$insertData = [
'pv_count' => 0,
'uv_count' => 0,
'ip_count' => 0,
'avg_visit_time' => 0
];
$totalNum = 0;
foreach ($accountList as $vo) {
$header = [
'account_type' => '1',
'username' => $vo['account'],
'password' => $vo['password'],
'token' => $vo['token'],
];
$lists = curlPost($listUrl, json_encode(['header' => $header]));
$website = json_decode($lists['data'], true);
if (empty($website['header']['failures'])) {
$website = $website['body']['data']['0']['list'];
}
if (!empty($website)) {
foreach ($website as $k => $v) {
$totalNum++;
$info = curlPost($url, json_encode([
'body' => [
"site_id"=> $v['site_id'],
"method" => "overview/getOutline"
],
'header' => $header
]))['data'];
$info = json_decode($info, true);
if (empty($info['body']['data'])) {
continue;
}
$info = $info['body']['data']['0']['result'];
if (isset($info['items']['1'])) {
if (is_numeric($info['items']['1']['1'])) {
$insertData['pv_count'] += $info['items']['1']['1'];
}
if (is_numeric($info['items']['1']['2'])) {
$insertData['uv_count'] += $info['items']['1']['2'];
}
if (is_numeric($info['items']['1']['3'])) {
$insertData['ip_count'] += $info['items']['1']['3'];
}
if (is_numeric($info['items']['1']['5'])) {
$insertData['avg_visit_time'] += $info['items']['1']['5'];
}
}
}
}
}
if ($totalNum != 0) {
$insertData['avg_visit_time'] = round($insertData['avg_visit_time'] / $totalNum);
}
$insertData['count_date'] = date('Y-m-d', strtotime('-1 day'));
Db::name('baidu_tj_gather')->insert($insertData);
/*************************************** 百度统计结束 ************************************************/
}
}

61
app/command/CreateController.php

@ -0,0 +1,61 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\command\Make;
use think\console\input\Option;
use think\console\input\Argument;
class CreateController extends Make
{
protected $type = "Controller";
protected function configure()
{
// 指令配置
$this->setName('make:hc-controller')
->addArgument('name', Argument::OPTIONAL, "controller path")
->setDescription('create a HuoCms controller command');
}
protected function getStub(): string
{
$stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
return $stubPath . 'controller.stub';
}
protected function getClassName(string $name): string
{
return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\controller';
}
protected function buildClass(string $name)
{
$stub = file_get_contents($this->getStub());
$namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
$class = str_replace($namespace . '\\', '', $name);
$modelName = str_replace(($this->app->config->get('route.controller_suffix') ? 'Controller' : ''),'',$class);
$funcParam = lcfirst($modelName);
return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}','{%modelInstance%}','{%modelName%}'], [
$class,
$this->app->config->get('route.action_suffix'),
$namespace,
$this->app->getNamespace(),
$funcParam,
$modelName,
], $stub);
}
}

53
app/command/CreateLink.php

@ -0,0 +1,53 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class CreateLink extends Command
{
protected function configure()
{
// 指令配置
$this->setName('admin-view:link')
->setDescription('the app\command\createLink command');
}
protected function execute(Input $input, Output $output)
{
foreach ($this->links() as $link => $target) {
if (file_exists($link)) {
$output->writeln('<error>' . $link . ' already exists!</error>');
} else {
$this->createLink($target, $link);
$output->writeln('<info>' . $link . ' created successfully.</info>');
}
}
$output->writeln('The links have been created.');
}
protected function links(): array
{
return ['public/view' => app()->getRootPath() .'view/'];
}
public function createLink($target,$link): bool
{
if (! windows_os()) {
return symlink($target, $link);
}
$mode = is_directory($target) ? 'J' : 'H';
exec("mklink /{$mode} ".escapeshellarg($link).' '.escapeshellarg($target));
}
}

28
app/command/CreateModel.php

@ -0,0 +1,28 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\command\Make;
use think\console\input\Argument;
class CreateModel extends Make
{
protected function configure()
{
// 指令配置
$this->setName('make:hc-model')
->addArgument('name', Argument::OPTIONAL, "model path")
->setDescription('create a new model class with five base function');
}
protected function getStub(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
}
protected function getNamespace(string $app): string
{
return parent::getNamespace($app) . '\\model';
}
}

4717
app/command/Link.php
File diff suppressed because it is too large
View File

71
app/command/ReportCreateJob.php

@ -0,0 +1,71 @@
<?php
namespace app\command;
use app\model\SubContent;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use Workerman\Worker;
class ReportCreateJob extends Command
{
protected function configure()
{
$this->setName('Make Report Code Task')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman service.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman service.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman service in daemon mode.')
->setDescription('历史数据生成报告编号');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (DIRECTORY_SEPARATOR !== '\\') {
if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload|status .</error>");
return false;
}
global $argv;
array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, 'think', $action);
}
// 开启守护进程模式
if ($this->input->hasOption('daemon')) {
Worker::$daemonize = true;
}
//
$reportConfig = config('system.report_code');
// 获取报告前缀
$prefix = $reportConfig['prefix'][77];
// 获取当前栏目下的报告数量
$subContent = new SubContent();
$where = [
['sub_category_ids','like','%' . 77 . '%'],
];
$reportList = $subContent->field('id,title,report_code') -> where($where) -> select();
var_dump(count($reportList->toArray()));
// 编号规则 DYQH-W-000001
foreach($reportList as $key => $report){
$code = $key + 1;
$code = $prefix . str_pad($code,$reportConfig['num_length'],$reportConfig['fill_str'],STR_PAD_LEFT);
$report -> report_code = $code;
$report -> save();
var_dump($code . '更新成功');
}
$output->writeln('任务完成');
Worker::runAll();
}
}

59
app/command/WebDiagnosis.php

@ -0,0 +1,59 @@
<?php
namespace app\command;
use app\command\service\Server;
use app\command\service\TaskServer;
use app\command\service\TongJiServer;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class WebDiagnosis extends Command
{
protected function configure()
{
$this->setName('seo:check')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman service.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman service.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman service in daemon mode.')
->setDescription('网站诊断服务器');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (DIRECTORY_SEPARATOR !== '\\') {
if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload|status|connections .</error>");
return false;
}
global $argv;
array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, 'think', $action);
}
$logo = <<<EOL
_____ ______ ____ _____ _ _ ______ _____ _ __
/ ____ | ____ / __ \ / ____ | | | | | ____ / ____ | |/ /
| (___ | |__ | | | | | | | |__| | | |__ | | | ' /
\___ \ | __| | | | | | | | __ | | __| | | | <
____) | |___ | |__| | | |____ | | | | | |___ | |____ | . \
|_____/ |______ \____/ \_____ |_| |_| |______ \_____ |_|\_\
EOL;
$output->writeln($logo . PHP_EOL);
if (!(strtoupper(substr(PHP_OS,0,3))==='WIN')) {
TongJiServer::start();
TaskServer::start();
}
Server::start();
}
}

55
app/command/WebDiagnosis2.php

@ -0,0 +1,55 @@
<?php
namespace app\command;
use app\command\service\Server;
use app\command\service\TaskServer;
use app\command\service\TongJiServer;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class WebDiagnosis2 extends Command
{
protected function configure()
{
$this->setName('seo:check2')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman service.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman service.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman service in daemon mode.')
->setDescription('网站诊断服务器');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (DIRECTORY_SEPARATOR !== '\\') {
if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload|status|connections .</error>");
return false;
}
global $argv;
array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, 'think', $action);
}
$logo = <<<EOL
_____ ______ ____ _____ _ _ ______ _____ _ __
/ ____ | ____ / __ \ / ____ | | | | | ____ / ____ | |/ /
| (___ | |__ | | | | | | | |__| | | |__ | | | ' /
\___ \ | __| | | | | | | | __ | | __| | | | <
____) | |___ | |__| | | |____ | | | | | |___ | |____ | . \
|_____/ |______ \____/ \_____ |_| |_| |______ \_____ |_|\_\
EOL;
$output->writeln($logo . PHP_EOL);
TongJiServer::start();
}
}

55
app/command/WebDiagnosis3.php

@ -0,0 +1,55 @@
<?php
namespace app\command;
use app\command\service\Server;
use app\command\service\TaskServer;
use app\command\service\TongJiServer;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
class WebDiagnosis3 extends Command
{
protected function configure()
{
$this->setName('seo:check3')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman service.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman service.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman service in daemon mode.')
->setDescription('网站诊断服务器');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (DIRECTORY_SEPARATOR !== '\\') {
if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload|status|connections .</error>");
return false;
}
global $argv;
array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, 'think', $action);
}
$logo = <<<EOL
_____ ______ ____ _____ _ _ ______ _____ _ __
/ ____ | ____ / __ \ / ____ | | | | | ____ / ____ | |/ /
| (___ | |__ | | | | | | | |__| | | |__ | | | ' /
\___ \ | __| | | | | | | | __ | | __| | | | <
____) | |___ | |__| | | |____ | | | | | |___ | |____ | . \
|_____/ |______ \____/ \_____ |_| |_| |______ \_____ |_|\_\
EOL;
$output->writeln($logo . PHP_EOL);
TaskServer::start();
}
}

54
app/command/gather/NoticeGatherJob.php

@ -0,0 +1,54 @@
<?php
namespace app\command\gather;
use app\command\gather\service\CommodityGather;
use app\command\gather\service\EnergyGather;
use app\command\gather\service\FinanceGather;
use app\command\gather\service\FuturesGather;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use Workerman\Worker;
class NoticeGatherJob extends Command
{
protected function configure()
{
$this->setName('Notice Gather Task')
->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status", 'start')
->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman service.', null)
->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman service.', null)
->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman service in daemon mode.')
->setDescription('公告采集任务');
}
protected function execute(Input $input, Output $output)
{
$action = $input->getArgument('action');
if (DIRECTORY_SEPARATOR !== '\\') {
if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status'])) {
$output->writeln("<error>Invalid argument action:{$action}, Expected start|stop|restart|reload|status .</error>");
return false;
}
global $argv;
array_shift($argv);
array_shift($argv);
array_shift($argv);
array_unshift($argv, 'think', $action);
}
// 开启守护进程模式
if ($this->input->hasOption('daemon')) {
Worker::$daemonize = true;
}
(new FuturesGather()) -> start();
(new CommodityGather()) -> start();
(new FinanceGather()) -> start();
(new EnergyGather()) -> start();
$output->writeln('定时任务创建成功');
Worker::runAll();
}
}

55
app/command/gather/service/BaseGather.php

@ -0,0 +1,55 @@
<?php
namespace app\command\gather\service;
use app\service\ContentService;
use think\facade\Db;
use function config;
class BaseGather
{
public $code;
public $domain;
public $title;
public $author;
public $url;
public $startTime;
public $freq;
public function __construct()
{
$this->code = strtolower(str_replace('Gather','',(new \ReflectionClass($this))->getShortName()));
$this->url = config('gather.url.'.$this->code);
$this->domain = config('gather.domain.'.$this->code);
$this->title = config('gather.title.'.$this->code);
$this->author = config('gather.author.'.$this->code);
$this->freq = config('gather.freq');
$this->startTime = config('gather.startTime');
}
/**
* 插入文章
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function insertArticle($article): array
{
$param = config('gather.article');
$article['category_id'] = $param['category_id'][0];
$article['status'] = 0;
$param['sub_content'] = $article;
$service = new ContentService();
return $service->saveContent($param);
}
// 判断文章是否存在
public function checkContentIsset($article): bool
{
$query = "SELECT id,title FROM hc_sub_content WHERE gather_unique = '{$article['gather_unique']}'";
$content = Db::query($query);
return !empty($content);
}
}

110
app/command/gather/service/CommodityGather.php

@ -0,0 +1,110 @@
<?php
namespace app\command\gather\service;
use QL\QueryList;
use think\facade\Log;
use Workerman\Crontab\Crontab;
use Workerman\Timer;
use Workerman\Worker;
class CommodityGather extends BaseGather
{
public function start()
{
$task = new Worker();
date_default_timezone_set('PRC');
Worker::$pidFile = './notice-gather-task-2.pid';
$task->name = '能源中心公告采集任务';
$task->onWorkerStart = function () {
// 每天的0点0分1秒执行
new Crontab($this->startTime, function(){
Timer::add($this->freq,function(){
$this->jobHandle();
});
});
};
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function jobHandle($page = 1)
{
$articleList = $this->commodityList($page);
$date = date('Y-m-d');
krsort($articleList);
foreach($articleList as &$article){
sleep(rand(1,3));
// 判断文章是否存在
if($this->checkContentIsset($article)){
var_dump($article['title'].'已存在');
continue; // 存在跳过
}
// 查询详情
if($date <= $article['publish_time']){
$article['content'] = $this->commodityDetail($article['link']);
}
// 保存到文章
$res = $this->insertArticle($article);
var_dump($article['title'].'采集成功');
if($res['code'] != 0){
Log::error('采集失败:' . $article['title'] . ',原因:' . $res['msg']);
}
}
}
// 大商所采集
public function commodityList($page): array
{
return $this->commodityQueryList($page);
}
// 大商所带翻页采集
public function commodityQueryList($page=1): array
{
$url = $this->url;
if($page > 1){
// 翻页
$pattern = '/(-\d+)?.html/';
$url = preg_replace($pattern,'-'.$page.'.html',$url);
}
$ql = QueryList::get($url);
$article = [];
$last = [];
$ql->find('.list_tpye06')->children('li')->map(function ($item)use(&$article,&$last){
$tmp = [
'title' => $this->title . $item->find('a')->text(),
'link' => $this->domain . $item->find('a')->href,
'publish_time' => $item->find('span')->text(),
'author' => $this->author,
'seo_description' => $this->title . $item->find('a')->text(),
'gather_unique' => $this->domain . $item->find('a')->href,
'status' => 1,
];
$article[] = $tmp;
$last = $tmp;
});
// 翻页
if($last['publish_time'] >= date('Y-m-d') ){
++$page;
$article = $this->commodityList($page);
}
return $article;
}
// 大商所详情
public function commodityDetail($url): string
{
// 序列号
$serialNum = QueryList::get($url)->find('.tit_header .cj_date')->text();
$serialStr = <<<html
<p style="line-height: 2; margin-bottom: 1em;align-content: center">$serialNum</p>
html;
return $serialStr ."\r\n". QueryList::get($url)->find('.detail_content')->html();
}
}

104
app/command/gather/service/EnergyGather.php

@ -0,0 +1,104 @@
<?php
namespace app\command\gather\service;
use QL\QueryList;
use think\facade\Db;
use think\facade\Log;
use Workerman\Crontab\Crontab;
use Workerman\Timer;
use Workerman\Worker;
class EnergyGather extends BaseGather
{
public function start()
{
$task = new Worker();
date_default_timezone_set('PRC');
Worker::$pidFile = './notice-gather-task-4.pid';
$task->name = '能源中心公告采集任务';
$task->onWorkerStart = function () {
// 每天的0点0分1秒执行
new Crontab($this->startTime, function(){
Timer::add($this->freq,function(){
$this->jobHandle();
});
});
};
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function jobHandle($page = 1)
{
$articleList = $this->energyList($page);
$date = date('Y-m-d');
krsort($articleList);
foreach($articleList as &$article){
sleep(rand(1,3));
// 判断文章是否存在
if($this->checkContentIsset($article)){
var_dump($article['title'].'----已存在');
continue; // 存在跳过
}
// 查询详情
if($date <= $article['publish_time']){
$article['content'] = $this->energyDetail($article['link']);
}
// 保存到文章
$res = $this->insertArticle($article);
var_dump($article['title'].'采集成功');
if($res['code'] != 0){
Log::error('采集失败:' . $article['title'] . ',原因:' . $res['msg']);
}
}
}
// 能源中心采集
public function energyList($page=1): array
{
return $this->energyQueryList($page);
}
// 带翻页的列表页爬虫
public function energyQueryList($page = 1): array
{
$url = $this->url;
if($page > 1){
// 翻页
$pattern = '/(_\d+)?.html/';
$url = preg_replace($pattern,'_'.$page.'.html',$url);
}
$ql = QueryList::get($url);
$article = [];
$last = [];
$ql->find('.newslist ul')->children('li')->map(function ($item)use(&$article,&$last){
$tmp = [
'title' => $this->title . $item->find('a')->text(),
'link' => $this->domain . $item->find('a')->href,
'publish_time' => $item->find('span')->text(),
'author' => $this->author,
'seo_description' => $item->find('a')->text(),
'gather_unique' => md5($this->domain . $item->find('a')->href),
'status' => 1,
];
$last = $tmp;
$article[] = $tmp;
});
// 翻页
if($last['publish_time'] >= date('Y-m-d') ){
++$page;
$article = $this->energyQueryList($page);
}
return $article;
}
// 能源中心详情
public function energyDetail($url)
{
return QueryList::get($url)->find('.details')->html();
}
}

103
app/command/gather/service/FinanceGather.php

@ -0,0 +1,103 @@
<?php
namespace app\command\gather\service;
use QL\QueryList;
use think\facade\Log;
use Workerman\Crontab\Crontab;
use Workerman\Timer;
use Workerman\Worker;
class FinanceGather extends BaseGather
{
public function start()
{
$task = new Worker();
date_default_timezone_set('PRC');
Worker::$pidFile = './notice-gather-task-3.pid';
$task->name = '上金所公告采集任务';
$task->onWorkerStart = function () {
// 每天的0点0分1秒执行
new Crontab($this->startTime, function(){
Timer::add($this->freq,function(){
$this->jobHandle();
});
});
};
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function jobHandle($page = 1)
{
$articleList = $this->financeList($page);
$date = date('Y-m-d');
krsort($articleList);
foreach($articleList as &$article){
sleep(rand(1,3));
// 判断文章是否存在
if($this->checkContentIsset($article)){
continue; // 存在跳过
}
// 查询详情
if($date <= $article['publish_time']){
$article['content'] = $this->financeDetail($article['link']);
}
// 保存到文章
$res = $this->insertArticle($article);
var_dump($article['title'].'采集成功');
if($res['code'] != 0){
Log::error('采集失败:' . $article['title'] . ',原因:' . $res['msg']);
}
}
}
// 中金所采集
public function financeList($page): array
{
return $this->financeQueryList($page);
}
// 中金所带列表采集
public function financeQueryList($page = 1): array
{
$url = $this->url;
if($page > 1){
// 翻页
$pattern = '/(_\d+)?.html/';
$url = preg_replace($pattern,'_'.$page.'.html',$url);
}
$ql = QueryList::get($url);
$article = [];
$last = [];
$ql->find('.notice_list ul')->children('li')->map(function ($item)use(&$article,&$last){
$tmp = [
'title' => $this->title . $item->find('.list_a_text')->text(),
'link' => $this->domain . $item->find('.list_a_text')->href,
'publish_time' => $item->find('.comparetime')->text(),
'author' => $this->author,
'seo_description' => $item->find('.list_a_text')->text(),
'gather_unique' => md5($this->domain . $item->find('.list_a_text')->href),
'status' => 1,
];
$article[] = $tmp;
$last = $tmp;
});
// 翻页
if($last['publish_time'] >= date('Y-m-d') ){
++$page;
$article = $this->financeList($page);
}
return $article;
}
// 中金所详情
public function financeDetail($url): string
{
return QueryList::get($url)->find('.jysggnr')->html();
}
}

107
app/command/gather/service/FuturesGather.php

@ -0,0 +1,107 @@
<?php
namespace app\command\gather\service;
use QL\QueryList;
use think\facade\Log;
use Workerman\Crontab\Crontab;
use Workerman\Timer;
use Workerman\Worker;
class FuturesGather extends BaseGather
{
public function start()
{
$task = new Worker();
date_default_timezone_set('PRC');
Worker::$pidFile = './notice-gather-task-1.pid';
$task->name = '广期所公告采集任务';
var_dump($this->startTime);
$task->onWorkerStart = function () {
// 每天的0点0分1秒执行
new Crontab($this->startTime, function(){
Timer::add($this->freq,function(){
$this->jobHandle();
});
});
};
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function jobHandle($page = 1)
{
$articleList = $this->futuresList($page);
$date = date('Y-m-d');
krsort($articleList);
foreach($articleList as &$article){
sleep(rand(1,3));
// 判断文章是否存在
if($this->checkContentIsset($article)){
continue; // 存在跳过
}
// 查询详情
if($date <= $article['publish_time']){
$article['content'] = $this->futuresDetail($article['link']);
}
// 保存到文章
$res = $this->insertArticle($article);
var_dump($article['title'].'采集成功');
if($res['code'] != 0){
Log::error('采集失败:' . $article['title'] . ',原因:' . $res['msg']);
}
}
}
// 期货中心列表
public function futuresList($page): array
{
return $this->futuresQueryList($page);
}
// 期货中心带翻页采集
public function futuresQueryList($page = 1): array
{
$code = str_replace('QueryList','',__FUNCTION__);
$url = config('gather.url.' . $code);
if($page > 1){
// 翻页
$pattern = '/(_\d+)?.shtml/';
$url = preg_replace($pattern,'_'.$page.'.shtml',$url);
}
$ql = QueryList::get($url);
$article = [];
$last = [];
$ql->find('.news-list-yw ul')->children('li')->map(function ($item)use(&$article,&$last){
$tmp = [
'title' => $this->title . $item->find('a')->text(),
'link' => $this->domain . $item->find('a')->href,
'publish_time' => date('Y-m-d',strtotime(str_replace('.','-',$item->find('.yyMM')->text()) . '-' . $item->find('.dd')->text())),
'author' => $this->author,
'seo_description' => $item->find('p')->text(),
'gather_unique' => md5($this->domain . $item->find('a')->href),
'status' => 1,
];
$article[] = $tmp;
$last = $tmp;
});
// 翻页
if($last['publish_time'] >= date('Y-m-d') ){
++$page;
$article = $this->futuresList($page);
}
return $article;
}
// 期货中心详情
public function futuresDetail($url): string
{
return QueryList::get($url)->find('ucapcontent')->html();
}
}

30
app/command/service/Server.php

@ -0,0 +1,30 @@
<?php
namespace app\command\service;
use Workerman\Protocols\Http\Request;
use \Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\TcpConnection;
use Workerman\Worker;
class Server
{
public static function start()
{
$http_worker = new Worker("http://0.0.0.0:19910");
$http_worker->count = 1;
$http_worker->name = 'task accept';
$http_worker->onMessage = function(TcpConnection $connection, Request $request)
{
$task_connection = new AsyncTcpConnection('Text://127.0.0.1:19890');
$task_connection->send(json_encode($request->post()));
$task_connection->connect();
$connection->send(json_encode(['code' => 0, 'data' => '', 'msg' => '任务投递成功']));
};
if(!defined('GLOBAL_START')) {
Worker::runAll();
}
}
}

149
app/command/service/TaskServer.php

@ -0,0 +1,149 @@
<?php
namespace app\command\service;
use app\command\service\step\CheckIncluded;
use app\command\service\step\CheckKeywords;
use app\command\service\step\CheckLinks;
use app\command\service\step\CheckSSL301;
use app\command\service\step\CheckTplCode;
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
class TaskServer
{
public static $db;
public static function start()
{
$taskWorker = new Worker('Text://0.0.0.0:19890');
$taskWorker->count = 8;
$taskWorker->name = 'TaskWorker';
$taskWorker->onWorkerStart = function (Worker $worker)
{
self::$db = new \Workerman\MySQL\Connection(env('database.hostname'),
env('database.hostport', 3306), env('database.username'), env('database.password', ''), env('database.database', ''));
};
$taskWorker->onMessage = function(TcpConnection $connection, $taskData)
{
$taskData = json_decode($taskData, true);
try {
switch ($taskData['cmd']) {
case 'code':
$res = self::codeAnalysis($taskData);
break;
case 'web':
$res = self::webpageAnalysis($taskData);
break;
case 'keywords':
$res = self::keywordsAnalysis($taskData, self::$db);
break;
case 'links':
$res = self::linksAnalysis($taskData, self::$db);
break;
case 'article':
$res = self::articleAnalysis($taskData);
break;
}
self::$db->update(env('database.prefix') . 'seo_check_task_detail')
->cols([
'remark' => json_encode($res),
'status' => 2,
'update_time' => date('Y-m-d H:i:s')
])
->where('task_id=' . $taskData['task_id'] . ' AND code="' . $taskData['cmd'] . '"')->query();
$has = self::$db->select('id')->from(env('database.prefix') . 'seo_check_task_detail')
->where('task_id=' . $taskData['task_id'] . ' AND status=1')->row();
if (empty($has)) {
self::$db->update(env('database.prefix') . 'seo_check_task')->cols(array('status'=>'2'))->where("id={$taskData['task_id']}")->query();
}
} catch (\Exception $e) {
if (strpos($e->getMessage(), '404 Not Found') !== false || strpos($e->getMessage(), '503 Service Temporarily Unavailable') !== false) {
$connection->send(json_encode(['code' => -1, 'data' => [], 'msg' => '该网站暂时无法访问']));
} else {
$connection->send(json_encode(['code' => -1, 'data' => [], 'msg' => $e->getMessage()]));
}
return;
}
$connection->send(json_encode($res));
};
if (strtoupper(substr(PHP_OS,0,3))==='WIN') {
Worker::runAll();
}
}
/**
* 代码分析
* @param $taskData
* @return array
*/
private static function codeAnalysis($taskData)
{
$analysis = [];
// 检测301
$analysis301 = CheckSSL301::run($taskData);
// 检测模板代码
$analysisTplCode = CheckTplCode::run($taskData);
$analysis = $analysis + $analysis301 + $analysisTplCode;
return dataReturn(101, '代码分析完成', $analysis);
}
/**
* 网站收录分析
* @param $taskData
* @return array
*/
private static function webpageAnalysis($taskData)
{
$analysis = CheckIncluded::run($taskData);
return dataReturn(102, '收录分析完成', $analysis);
}
/**
* 关键词分析
* @param $taskData
* @param $db
* @return array
*/
private static function keywordsAnalysis($taskData, $db)
{
$analysis = CheckKeywords::run($taskData, $db);
return dataReturn(103, '关键词分析完成', $analysis);
}
/**
* 导入链接分析
* @param $taskData
* @param $db
* @return array
*/
private static function linksAnalysis($taskData, $db)
{
$analysis = CheckLinks::run($taskData, $db);
return dataReturn(104, '导入链接分析完成', $analysis);
}
/**
* 文章分析
* @param $taskData
* @return array
*/
private static function articleAnalysis($taskData)
{
return dataReturn(105, '文章分析完成');
}
}

171
app/command/service/TongJiServer.php

@ -0,0 +1,171 @@
<?php
namespace app\command\service;
use think\facade\Log;
use Workerman\Timer;
use Workerman\Worker;
class TongJiServer
{
public static function start()
{
$task = new Worker();
$task->count = 1;
$task->name = 'pv collect';
$task->onWorkerStart = function($task)
{
$db = new \Workerman\MySQL\Connection(env('database.hostname'),
env('database.hostport', 3306), env('database.username'), env('database.password', ''), env('database.database', ''));
/*************************************** 站长之家排名开始 ************************************************/
$baiduPcUrl = 'https://apidatav2.chinaz.com/single/baidupc/keywordranking';
$baiduMobileUrl = 'https://apidatav2.chinaz.com/single/baidumobile/keywordranking';
$pc360Url = 'https://apidatav2.chinaz.com/single/sopc/keywordranking';
$sogouMobileUrl = 'https://apidatav2.chinaz.com/single/sogoumobile/keywordranking';
file_put_contents('./last_rum_time.log', time()); // 上次执行时间
$info = $db->select('value')->from(env('database.prefix') . 'sys_setting')
->where('title="chinaz_token" and seller_id=1')->row();
if (!empty($info)) {
$token = $info['value'];
Timer::add(3600, function () use ($db, $token, $baiduPcUrl, $baiduMobileUrl, $pc360Url, $sogouMobileUrl) {
if (date('H:i') >= '11:52' || date('H:i') < '11:53') {
$frequency = $db->select('value')->from(env('database.prefix') . 'sys_setting')
->where('title="keywords_search_time" and seller_id=1')->row();
$lastRunTime = file_get_contents('./last_rum_time.log');
$canRun = false;
if ($frequency == 'week') {
if ((time() - $lastRunTime) / 86400 >= 7) {
$canRun = true;
file_put_contents('./last_rum_time.log', time());
}
} elseif($frequency == 'day' && (time() - $lastRunTime) >= 86400) {
$canRun = true;
file_put_contents('./last_rum_time.log', time());
} else {
$canRun = true;
file_put_contents('./last_rum_time.log', time());
}
Log::info('可执行状态:' . $canRun);
try {
if ($canRun) {
Log::info('执行统计开始' . $canRun);
$keywords = $db->select('id,name,url,website_id,seller_id')->from(env('database.prefix') . 'keyword')
->where('status=1 and is_del=1')->query();
foreach ($keywords as $key => $vo) {
$updateData = [
'baidu_pc' => 0,
'baidu_mob' => 0,
'three_pc' => 0,
'sougou_mob' => 0
];
// 百度PC
$res = curlPost($baiduPcUrl, [
'key' => $token,
'domain' => $vo['url'],
'keyword' => $vo['name'],
'top100' => true
])['data'];
Log::info($vo['name'] . ' 百度PC res>>>>>>>:' . $res);
$res = json_decode($res, true);
if (!empty($res['Result']['Ranks'])) {
$rank = explode('-', $res['Result']['Ranks']['0']['RankStr']);
$updateData['baidu_pc'] = ($rank[0] - 1) * 10 + $rank['1'];
self::writeKeywordsQuery($db, $vo, $res, $rank, 'baidu_pc');
}
// 百度移动
$res = curlPost($baiduMobileUrl, [
'key' => $token,
'domain' => $vo['url'],
'keyword' => $vo['name']
])['data'];
Log::info($vo['name'] . ' 百度移动 res>>>>>>>:' . $res);
$res = json_decode($res, true);
if (!empty($res['Result']['Ranks'])) {
$rank = explode('-', $res['Result']['Ranks']['0']['RankStr']);
$updateData['baidu_mob'] = ($rank[0] - 1) * 10 + $rank['1'];
self::writeKeywordsQuery($db, $vo, $res, $rank, 'baidu_mob');
}
// 360pc
$res = curlPost($pc360Url, [
'key' => $token,
'domain' => $vo['url'],
'keyword' => $vo['name']
])['data'];
Log::info($vo['name'] . ' 360pc res>>>>>>>:' . $res);
$res = json_decode($res, true);
if (!empty($res['Result']['Ranks'])) {
$rank = explode('-', $res['Result']['Ranks']['0']['RankStr']);
$updateData['three_pc'] = ($rank[0] - 1) * 10 + $rank['1'];
self::writeKeywordsQuery($db, $vo, $res, $rank, 'three_pc');
}
// 搜狗移动
$res = curlPost($sogouMobileUrl, [
'key' => $token,
'domain' => $vo['url'],
'keyword' => $vo['name']
])['data'];
Log::info($vo['name'] . ' 搜狗移动 res>>>>>>>:' . $res);
$res = json_decode($res, true);
if (!empty($res['Result']['Ranks'])) {
$rank = explode('-', $res['Result']['Ranks']['0']['RankStr']);
$updateData['sougou_mob'] = ($rank[0] - 1) * 10 + $rank['1'];
self::writeKeywordsQuery($db, $vo, $res, $rank, 'sougou_mob');
}
$db->update(env('database.prefix') . 'keyword')->cols($updateData)->where('id=' . $vo['id'])->query();
}
}
} catch (\Exception $e) {
Log::error($e->getMessage() . '>>>>>>
' . $e->getTraceAsString());
}
Log::info('执行结束');
}
});
}
/*************************************** 站长之家排名结束 ************************************************/
};
if (strtoupper(substr(PHP_OS,0,3))==='WIN') {
Worker::runAll();
}
}
protected static function writeKeywordsQuery($db, $vo, $res, $rank, $engine)
{
$db->insert(env('database.prefix') . 'keyword_query')->cols([
'keyword_id' => $vo['id'],
'seller_id' => $vo['seller_id'],
'website_id' => $vo['website_id'],
'keyword' => $vo['name'],
'search_engine' => $engine,
'collect_count' => isset($res['Result']['SiteCount']) ? $res['Result']['SiteCount'] : 0,
'top_rank' => ($rank[0] - 1) * 10 + $rank['1'],
'create_time' => time(),
'page_title' => $res['Result']['Ranks']['0']['Title'] ?? '',
'page_url' => $res['Result']['Ranks']['0']['Url'] ?? '',
])->query();
}
}

55
app/command/service/step/CheckIncluded.php

@ -0,0 +1,55 @@
<?php
namespace app\command\service\step;
use GuzzleHttp\Client;
class CheckIncluded
{
public static function run($taskData)
{
$included = [
'status' => 1,
'count' => 0
];
try {
$client = new Client();
$url = 'https://www.baidu.com/s?wd=site%3A' . $taskData['websiteUrl'];
$jar = new \GuzzleHttp\Cookie\CookieJar();
$userAgentMap = config('userAgent');
$userAgentKey = array_rand($userAgentMap);
$res = $client->request('GET', $url, [
'cookies' => $jar,
'headers' => [
'user-agent' => $userAgentMap[$userAgentKey]
],
]);
if ($res->getStatusCode() == 200) {
$html = \phpQuery::newDocumentHTML($res->getBody());
$hasNoInclude = $html['.content_none .nors']->html();
if (!empty($hasNoInclude)) {
$included = [
'status' => 2,
'count' => 0
];
} else {
$included = [
'status' => 1,
'count' => $html['#content_left .c-span-last p:eq(0) b']->text()
];
}
} else {
return $included;
}
} catch (\Exception $e) {
return $included;
}
return $included;
}
}

124
app/command/service/step/CheckKeywords.php

@ -0,0 +1,124 @@
<?php
namespace app\command\service\step;
use think\facade\Db;
class CheckKeywords
{
public static function run($taskData, $db)
{
$analysis = [
'deadLink' => []
];
$keywords = $taskData['keywords'];
if (empty($keywords)) {
return [];
}
// 处理栏目页
$columnPages = $taskData['cate'];
$cateIds = [];
$cateId2Alias = [];
$opts = [
'http'=> [
'method' => "GET",
'timeout' => 10,
]
];
$context = stream_context_create($opts);
foreach ($columnPages as $vo) {
$cateIds[] = $vo['id'];
$cateId2Alias[$vo['id']] = $vo['alias'];
try {
$nowPageHtml = file_get_contents('http://' . $taskData['websiteUrl'] . $vo['alias'], false, $context);
echo "查询获取到页面数据" . PHP_EOL;
$totalWords = self::getCleanContent($nowPageHtml);
foreach ($keywords as $v) {
$analysis[$vo['alias']][$v['name']] = round((substr_count($totalWords, $v['name'])
* mb_strlen($v['name'])) / mb_strlen($totalWords), 4);
}
} catch (\Exception $e) {
echo "获取到死链" . 'http://' . $taskData['websiteUrl'] . $vo['alias'] . PHP_EOL;
$analysis['deadLink'][] = 'http://' . $taskData['websiteUrl'] . $vo['alias'];
}
}
// 处理文章页
if (!empty($cateIds)) {
$pageSize = 20;
$total = $db->select('count(*) as `c_total`')->from(env('database.prefix') . 'category_sub_content')
->where("category_id in(" . implode(',', $cateIds) . ")")->row()['c_total'];
$totalPage = ceil($total / $pageSize);
for ($nowPage = 1; $nowPage <= $totalPage; $nowPage++) {
$offset = ($nowPage - 1) * $pageSize;
$articles = $db->select('category_id,sub_content_id')->from(env('database.prefix') . 'category_sub_content')
->where("category_id in(" . implode(',', $cateIds) . ")")->limit($pageSize)->offset($offset)->query();
foreach ($articles as $vo) {
if (isset($cateId2Alias[$vo['category_id']])) {
$url = $cateId2Alias[$vo['category_id']] . '/' . $vo['sub_content_id'];
try {
$nowPageHtml = file_get_contents('http://' . $taskData['websiteUrl'] . $url, false, $context);
$totalWords = self::getCleanContent($nowPageHtml);
foreach ($keywords as $v) {
$analysis[$url][$v['name']] = round((substr_count($totalWords, $v['name']) * mb_strlen($v['name'])) / mb_strlen($totalWords), 4);
}
} catch (\Exception $e) {
$analysis['deadLink'][] = 'http://' . $taskData['websiteUrl'] . $url;
}
}
}
}
}
// 最后统计总数据
$analysisFinal = [];
foreach ($analysis as $key => $vo) {
if ($key != 'deadLink') {
$analysisFinal['keywords'][] = '路径 ' . $key . ' 的关键词密度是:' . round(array_sum($vo), 2);
} else {
$analysisFinal[$key] = $vo;
}
}
return $analysisFinal;
}
private static function getCleanContent($nowPage)
{
// 获取所有的img的alt
$html = \phpQuery::newDocument($nowPage);
$images = $html['img'];
$a = $html['a'];
// 去除js标签内的内容
$html['script']->html('');
$html['noscript']->html('');
$html['style']->html('');
$imagesAltMap = [];
foreach ($images as $img) {
if (!empty(trim(pq($img)->attr('alt')))) {
$imagesAltMap[] = trim(pq($img)->attr('alt'));
}
}
$aTitleMap = [];
foreach ($a as $item) {
if (!empty(trim(pq($item)->attr('title')))) {
$aTitleMap[] = trim(pq($item)->attr('title'));
}
}
$cleanWords = trim(str_replace(' ', '', str_replace(PHP_EOL, '', strip_tags($html))));
return $cleanWords . implode('', $imagesAltMap) . implode('', $aTitleMap);
}
}

78
app/command/service/step/CheckLinks.php

@ -0,0 +1,78 @@
<?php
namespace app\command\service\step;
use think\facade\Db;
class CheckLinks
{
public static function run($taskData, $db)
{
$links = $db->select('url,type,position')->from(env('database.prefix') . 'link')
->where("is_del=1 AND website_id = " . $taskData['websiteId'])->query();
$inLinks = [];
$outLinks = [];
foreach ($links as $vo) {
if ($vo['type'] == 1) {
try {
$html = file_get_contents($vo['position']);
if (strstr($html, $vo['url']) == false) {
$inLinks['linksHave'][] = [
'status' => 2,
'msg' => $vo['position'] . '的友链已经被删除'
];
} else {
$inLinks['linksHave'][] = [
'status' => 1,
'msg' => $vo['position'] . '的友链正常显示'
];
// 检测nofollow
$html = \phpQuery::newDocument($html);
if ($html['a[href="' . $vo['url'] . '"]']->attr('rel') === 'nofollow') {
$inLinks['nofollow'][] = [
'status' => 2,
'msg' => $vo['position'] . '的友链添加了nofollow'
];
} else {
$inLinks['nofollow'][] = [
'status' => 1,
'msg' => $vo['position'] . '的友链属性正常'
];
}
}
} catch (\Exception $e) {
$inLinks['linksHave'][] = [
'status' => 2,
'msg' => $vo['position'] . '无法访问'
];
}
} else if ($vo['type'] == 2) {
try {
file_get_contents($vo['url']);
$outLinks['linksHave'][] = [
'status' => 1,
'msg' => $vo['url'] . '可以正常访问'
];
} catch (\Exception $e) {
$outLinks['linksHave'][] = [
'status' => 2,
'msg' => $vo['url'] . '无法访问'
];
}
}
}
$analysis = [
'inlinks' => $inLinks,
'outlinks' => $outLinks
];
return $analysis;
}
}

156
app/command/service/step/CheckSSL301.php

@ -0,0 +1,156 @@
<?php
namespace app\command\service\step;
use GuzzleHttp\Client;
class CheckSSL301
{
/**
* 检测 301 ssl
* @param $taskData
* @return array
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public static function run($taskData)
{
$analysis = [];
$urlMap = explode('.', $taskData['websiteUrl']);
$client = new Client();
// 二级域名的情况
if (count($urlMap) == 3 && $urlMap[0] != 'www') {
$analysis['c301'] = [
'status' => 2,
'msg' => '该网站使用的非一级域名,建议使用一级域名'
];
$analysis['ssl'] = self::requestWithHttps($client, $taskData);
return $analysis;
}
// 非二级域名的情况
if (count($urlMap) == 3 && $urlMap[0] == 'www') {
$url = 'http://' . $urlMap['1'] . '.' . $urlMap['2'];
$domain = $urlMap['1'] . '.' . $urlMap['2'];
} else {
$url = 'http://' . $taskData['websiteUrl'];
$domain = $taskData['websiteUrl'];
}
$userAgentMap = config('userAgent');
$userAgentKey = array_rand($userAgentMap);
$res = $client->request('GET', $url, [
'allow_redirects' => false,
'headers' => [
'user-agent' => $userAgentMap[$userAgentKey]
],
]);
// 做了 301 跳转的
if ($res->getStatusCode() == 301) {
// 进一步确认
$resHeaders = $res->getHeaders();
$location = $resHeaders['Location'][0];
if (strpos($location, 'https://') !== false) {
$analysis['ssl'] = [
'status' => 1,
'msg' => '该网站采用了安全的https协议'
];
} else {
$analysis['ssl'] = [
'status' => 2,
'msg' => '该网站没有采用了安全的https协议'
];
}
$ckDomain = strstr($location, 'www');
if ($ckDomain !== false) {
$analysis['c301'] = [
'status' => 1,
'msg' => '该网站采用正确的301跳转方式'
];
return $analysis;
}
// 进一步确认
if ($analysis['ssl']['status'] != 1) {
$analysis['c301'] = [
'status' => 2,
'msg' => '该网站没有采用正确的301跳转方式'
];
return $analysis;
}
// 去请求带https的www域名
$res2 = $client->request('GET', 'https://www.' . $domain, [
'allow_redirects' => false
]);
if ($res2->getStatusCode() == 200) {
$analysis['c301'] = [
'status' => 1,
'msg' => '该网站采用正确的301跳转方式'
];
} else {
$analysis['c301'] = [
'status' => 2,
'msg' => '该网站没有采用正确的301跳转方式'
];
}
return $analysis;
}
// 未做 301 跳转的
$analysis['c301'] = [
'status' => 2,
'msg' => '该网站没有采用正确的301跳转方式'
];
$analysis['ssl'] = self::requestWithHttps($client, $taskData);
return $analysis;
}
private static function requestWithHttps($client, $taskData)
{
$ssl = [];
$userAgentMap = config('userAgent');
$userAgentKey = array_rand($userAgentMap);
try {
$res2 = $client->request('GET', 'https://' . $taskData['websiteUrl'], [
'allow_redirects' => false,
'headers' => [
'user-agent' => $userAgentMap[$userAgentKey]
],
]);
if ($res2->getStatusCode() == 200) {
$ssl = [
'status' => 1,
'msg' => '该网站采用了安全的https协议'
];
} else {
$ssl = [
'status' => 2,
'msg' => '该网站没有采用了安全的https协议'
];
}
} catch (\Exception $e) {
$ssl = [
'status' => 2,
'msg' => '该网站没有采用了安全的https协议'
];
}
return $ssl;
}
}

136
app/command/service/step/CheckTplCode.php

@ -0,0 +1,136 @@
<?php
namespace app\command\service\step;
use think\facade\App;
class CheckTplCode
{
public static function run($taskData)
{
$analysis = [
'hn' => [
'status' => 1,
'msg' => []
],
'tags' => [
'status' => 1,
'msg' => []
],
'robots' => [
'status' => 1,
'msg' => 'robots.txt文件正确'
],
'sitemap' => [
'status' => 1,
'msg' => 'sitemap文件放置正确'
],
'urlLevel' => [
'status' => 1,
'msg' => []
],
'c404' => [
'status' => 1,
'msg' => '404文件放置正确'
]
];
// 目录层级
foreach ($taskData['cate'] as $vo) {
if ($vo['alias'] != '/' && !empty($vo['alias'])) {
$level = count(array_filter(explode('/', $vo['alias'])));
if ($level >= 3) {
$analysis['urlLevel']['status'] = 2;
$analysis['urlLevel']['msg'][] = $vo['title'] . '页面,url层级太深,达到了' . $level . '层';
}
}
}
// 404文件
$rootPath = App::getRootPath() . 'public';
if (!is_file($rootPath . '/system_file/hc_error/404.html')) {
$analysis['c404']['status'] = 2;
$analysis['c404']['msg'] = '未正确放置404文件';
}
$path = $rootPath . '/themes/website/' . $taskData['websiteId'] . '/' . $taskData['lang'] . '/' . $taskData['theme'];
if (!is_dir($path)) {
return $analysis;
}
$files = scandir($path);
foreach ($files as $value){
if($value != '.' && $value != '..' && is_file($path . '/' . $value)){
$html = file_get_contents($path . '/' . $value);
// 存在ajax
if (strstr($html, '$.ajax') !== false || strstr($html, '$.getJSON') !== false
|| strstr($html, '$.post') !== false) {
$analysis['tags']['status'] = 2;
$analysis['tags']['msg']['ajax'][] = $value . '页面,有ajax异步请求,建议更换。';
}
$html = \phpQuery::newDocumentHTML($html);
// 页面为vue.js编写
$html['script']->html('');
$html['noscript']->html('');
$vueJs = str_replace(PHP_EOL, '', trim(strip_tags($html)));
if (empty($vueJs)) {
$analysis['tags']['status'] = 2;
$analysis['tags']['msg']['vuejs'][] = $value . '页面,是Vue.js编写无法收录';
}
// 检测h1数量
$h1 = $html["h1"];
if (count($h1) >= 2) {
$analysis['hn']['status'] = 2;
$analysis['hn']['msg']['h1'][] = $value . '有' . count($h1) . '个<h1>标签,数量过多。';
}
// 检测iframe框架
$iframe = $html["iframe"];
if (count($iframe) > 0) {
$analysis['tags']['status'] = 2;
$analysis['tags']['msg']['iframe'][] = $value . '页面,存在iframe标签,建议更换。';
}
// 检测flash技术
$flash = $html["object"];
if (count($flash) > 0) {
$analysis['tags']['status'] = 2;
$analysis['tags']['msg']['flash'][] = $value . '页面,存在flash标签,建议更换。';
}
// alt标签
$imgs = $html["img"];
foreach($imgs as $imgObj) {
if (empty(pq($imgObj)->attr('alt'))) {
$analysis['tags']['status'] = 2;
$analysis['tags']['msg']['alt'][] = $value . '页面,img标签alt属性缺少或为空';
break;
}
}
// robots 文件
if (!is_file($rootPath . '/robots.txt')) {
$analysis['robots'] = [
'status' => 2,
'msg' => '未在正确放置robots.txt文件'
];
}
// sitemap
if (!is_file($rootPath . '/xml/' . $taskData['websiteId'] . '_sitemap.xml')) {
$analysis['sitemap'] = [
'status' => 2,
'msg' => '未正确放置sitemap.xml文件'
];
}
}
}
return $analysis;
}
}

138
app/command/stubs/controller.stub

@ -0,0 +1,138 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use app\model\{%modelName%};
use app\validate\{%modelName%}Validate;
use think\exception\ValidateException;
class {%className%} extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index{%actionSuffix%}({%modelName%} ${%modelInstance%}): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
${%modelInstance%}List = ${%modelInstance%}->get{%modelName%}List($where,$limit);
return json(pageReturn(${%modelInstance%}List));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save{%actionSuffix%}({%modelName%} ${%modelInstance%}): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate({%modelName%}Validate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = ${%modelInstance%} -> add{%modelName%}($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read{%actionSuffix%}({%modelName%} ${%modelInstance%}): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = ${%modelInstance%}->get{%modelName%}($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update{%actionSuffix%}({%modelName%} ${%modelInstance%}): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate({%modelName%}Validate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = ${%modelInstance%} -> update{%modelName%}($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete{%actionSuffix%}({%modelName%} ${%modelInstance%}): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
// TO DO
// 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = ${%modelInstance%}->del{%modelName%}($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

113
app/command/stubs/model.stub

@ -0,0 +1,113 @@
<?php
declare (strict_types = 1);
namespace {%namespace%};
use app\exception\ModelException;
use app\exception\ModelEmptyException;
/**
* @mixin \think\Model
*/
class {%className%} extends Model
{
/**
* @param array $where
* @param int $limit
* @return array
* @throws ModelException
*/
public function get{%className%}List(array $where = [],int $limit = 10): array
{
try{
$res = $this->where($where)->paginate($limit);
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->getMsg,$res);
}
/**
* @param array $where
* @return array
* @throws ModelEmptyException
* @throws ModelException
*/
public function get{%className%}(array $where = []): array
{
try{
$res = $this->where($where)->find();
if(empty($res)){
throw new ModelEmptyException();
}
}catch(ModelEmptyException $me){
throw new ModelEmptyException();
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->getMsg,$res);
}
/**
* @param $param
* @return array
* @throws ModelException
*/
public function add{%className%}($param): array
{
try{
$res = self::create($param);
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->addMsg,$res->id);
}
/**
* @param array $where
* @param array $param
* @return array
* @throws ModelException
*/
public function update{%className%}(array $where = [],array $param = []): array
{
try{
$res = self::where($where)->update($param);
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->updateMsg,$res);
}
/**
* @param $where
* @return array
* @throws ModelException
*/
public function softDel{%className%}($where): array
{
try{
$res = $this->where($where)->update($this->delData);
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->delMsg,$res);
}
/**
* @param $where
* @return array
* @throws ModelException
*/
public function del{%className%}($where): array
{
try{
$res = $this->where($where)->delete();
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return dataReturn($this->sucCode,$this->delMsg,$res);
}
}

1337
app/common.php
File diff suppressed because it is too large
View File

1
app/controller/api/.gitignore

@ -0,0 +1 @@
./*

210
app/controller/backend/AccountController.php

@ -0,0 +1,210 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\AdminMenu;
use app\model\PhoneCode;
use app\model\SysSetting;
use app\service\PhoneCodeService;
use app\service\RoleService;
use app\service\sms\ALiSms;
use app\validate\LoginValidate;
use app\model\Admin;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Env;
use think\facade\Lang;
use xiaodi\JWTAuth\Facade\Jwt;
class AccountController extends \app\BaseController
{
/**
* @param Admin $adminModel
* @return \think\response\Json
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function login(Admin $adminModel): \think\response\Json
{
if (request()->isPost()) {
$param = request()->only(['account','password']);
$pattern = "/^[\w\.-]+@\w+\.\w+(\.\w+)?$/";
if(!env('APP_DEBUG') == true || !(preg_match($pattern,$param['account']) && env('APP_DEBUG') == true)){
$param['account'] = $this->RSA_openssl($param['account'], 'decode');
$param['password'] = $this->RSA_openssl($param['password'], 'decode');
}
// 检验完整性
try {
validate(LoginValidate::class)->scene('login')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = ['account' => $param['account']];
try{
$admin = $adminModel->getAdmin($where,['role'])['data'];
}catch (\Exception $e){
return jsonReturn(-2, Lang::get('用户名密码不正确'));
}
if (!checkPassword($param['password'], $admin['password'])) {
return jsonReturn(-2, Lang::get('用户名密码不正确'));
}
if ($admin['status'] == 2) {
return jsonReturn(-3, Lang::get('该账号已经被禁用'));
}
if ($admin['code_auth'] == 1 && !empty($admin['phone'])) {
$data = [
'phone' => $admin['phone'],
'code' => random_code_type(6, 'number'),
'seller_id' => $admin['seller_id'],
'expired_time' => date('Y-m-d H:i:s', time() + 5 * 60), // 登录验证码5分钟有效
];
//保存发送code记录
$codeModel = new PhoneCode();
$codeModel->insertGetId([
'phone' => $data['phone'],
'code' => $data['code'],
'expired_time' => $data['expired_time']
]);
Cache::set($admin['phone'] . '_login', time(), 5 * 60);
if (Env::get('APP_DEBUG')) {
return jsonReturn(0, 'code_auth', $data);
}
$res = ALiSms::sendOneAuthMsg($data);
if ($res['code'] != 0) {
return json($res);
}
return jsonReturn(0, 'code_auth', ['phone' => $admin['phone']]);
}
return $this->getMenuAndToken($admin);
}
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function authCode(Admin $adminModel): \think\response\Json
{
$param = request()->only(['phone','code']);
$param['phone'] = $this->RSA_openssl($param['phone'], 'decode');
$param['code'] = $this->RSA_openssl($param['code'], 'decode');
$authCodeRes = PhoneCodeService::authCode($param);
if ($authCodeRes['code'] != 0) {
return json($authCodeRes);
}
$where = ['phone' => $param['phone']];
$admin = $adminModel->getAdmin($where,['role'])['data'];
return $this->getMenuAndToken($admin);
}
/**
* @throws \app\exception\TokenException
*/
protected function getMenuAndToken($admin): \think\response\Json
{
// 缓存权限数据
$roleRes = RoleService::getMenuAndUpdateAuth($admin['id']);
// $token = Jwt::token([
// 'uid' => $admin['id'],
// 'name' => $admin['name'],
// 'seller_id' => $admin['seller_id']
// ]);
$loginTime = time();
// 登录日志
$data = [
'seller_id' => $admin['seller_id'],
'admin_id' => $admin['id'],
'admin_name' => $admin['name'],
'title' => $admin['name'].Lang('登录后台系统'),
'ip' => request()->ip(),
'agent' => request()->header()['user-agent'],
'login_time' => $loginTime
];
event('AdminLoginLog',$data);
// 更新最后登录时间
$admin->last_login_time = $loginTime;
$admin->save();
// 获取密码配置
$setting = new SysSetting();
$passConf = $setting -> getCustomData(['id'=>120])['data'];
if(!empty($passConf['value']) && $passConf['status'] == 1){
$passConf = json_decode($passConf['value'],true);
if($passConf['change_duration'] > 0){
$admin -> pass_tips = time() - $admin -> pass_last_change > 86400 * 30 * $passConf['change_duration'] ? 1 : 0;
}
}
$menu = $roleRes['data'];
if(empty($menu)){
return jsonReturn(-1, Lang::get('请联系管理员分配权限后再登录'));
}
return jsonReturn(0, Lang::get('登录成功'), [
'token' => createUserToken([
'uid' => $admin['id'],
'name' => $admin['name'],
'seller_id' => $admin['seller_id']
],'admin'),
'user' => $admin,
'menu' => $menu,
]);
}
/**
* @description RSA公钥加密 私钥解密
* @param string $data 待加解密数据
* @param string $operate 操作类型 encode:加密 decode:解密
* @return string 返回加密内容/解密内容
* @throws \Exception
*/
protected function RSA_openssl(string $data, string $operate = 'encode'): string
{
//RSA 公钥
$rsa_public = config('system.rsa_public_key');
//RSA 私钥
$rsa_private = config('system.rsa_private_key');
//RSA 公钥加密
if ('encode' == $operate) {
$public_key = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($rsa_public, 64, "\n", true) . "\n-----END PUBLIC KEY-----";
$key = openssl_pkey_get_public($public_key);
if (!$key) {
throw new \Exception('公钥不可用,请联系系统维护人员');
}
$return_en = openssl_public_encrypt($data, $crypted, $key);
if (!$return_en) {
throw new \Exception('公钥加密失败,请联系系统维护人员');
}
return base64_encode($crypted);
}
//RSA 私钥解密
if ('decode' == $operate) {
$private_key = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($rsa_private, 64, "\n", true) . "\n-----END PRIVATE KEY-----";
$key = openssl_pkey_get_private($private_key);
if (!$key) {
throw new \Exception('私钥不可用,请联系系统维护人员');
}
$return_de = openssl_private_decrypt(base64_decode($data), $decrypted, $key);
if (!$return_de) {
throw new \Exception('私钥解密失败,请联系系统维护人员');
}
return $decrypted;
}
}
}

323
app/controller/backend/AdminController.php

@ -0,0 +1,323 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\hctrait\AdminPassTrait;
use app\model\Admin;
use app\model\AdminMenu;
use app\model\SysSetting;
use app\validate\AdminValidate;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Lang;
use think\facade\Log;
class AdminController extends BaseController
{
use AdminPassTrait;
/**
* @param Admin $admin
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Admin $admin): \think\response\Json
{
$where = [
['seller_id','=',$this->admin['seller_id']],
['is_del','=',1]
];
$name = input('name') ?? '';
if(!empty($name)){
$where[] = ['name','like','%'.$name.'%'];
}
$limit = $this->setLimit();
$adminList = $admin->getAdminList($where,$limit);
return json(pageReturn($adminList));
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function read(Admin $Admin): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('管理员ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $Admin->getAdmin($where,['role']);
return json($res);
}
/**
* 新建管理员
*
* @param Admin $admin
* @return \think\Response
* @throws \app\exception\ModelException
*/
public function save(Admin $admin): \think\Response
{
$param = input('post.');
try{
validate(AdminValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// 密码规则验证
$err = $this->checkPass($param['password'],$this->admin['seller_id']);
if(!empty($err['message'])){
return jsonReturn(-3,$err['message']);
}
$param['password'] = makePassword($param['password']);
if(isset($param['group'])){
unset($param);
}
Db::startTrans();
try{
$res = $admin -> addAdmin($param);
$res['data']->role()->attach($param['role'],['seller_id'=>$this->admin['seller_id']]);
event("AdminOptLog",[
'admin_id' => $this->admin['uid'],
'admin_name' => $this->admin['name'],
'title'=>Lang::get('管理员管理'),
"content"=>Lang::get('新增管理员').$res['data']['id'],
]);
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(-3,$e->getMessage());
}
return json($res);
}
/**
* 编辑管理员
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(Admin $admin): \think\response\Json
{
if(request()->isPost()){
$param = request()->except(['password'], 'post');
$password = request()->post('password');
try {
validate(AdminValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
Db::startTrans();
try{
if(isset($param['group']) && $param['group'] == 1){
return jsonReturn(-1,Lang::get('系统管理员不能修改'));
}
$manager = $admin -> getAdmin($where)['data'];
$manager->role()->detach();
$manager->role()->attach($param['role'],['seller_id'=>$this->admin['seller_id']]);
if(!empty($password)){
// 密码规则验证
$err = $this->checkPass($password,$this->admin['seller_id']);
if(!empty($err['message'])){
return jsonReturn(-3,$err['message']);
}
$param['password'] = makePassword($password);
$param['pass_last_change'] = time();
}
unset($param['role']);
$res = $admin->updateAdmin($where,$param);
event("AdminOptLog",[
'admin_id' => $this->admin['uid'],
'admin_name' => $this->admin['name'],
'title'=>Lang::get('管理员管理'),
"content"=>Lang::get('更新管理员信息').$param['id'],
]);
Db::commit();
}catch (\Exception $e){
Db::rollback();
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
return jsonReturn(-3,$e->getMessage());
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除管理员
*
* @throws \app\exception\ModelException
*/
public function delete(Admin $admin): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('管理员ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
event("AdminOptLog",[
'admin_id' => $this->admin['uid'],
'admin_name' => $this->admin['name'],
'title'=>Lang::get('管理员管理'),
"content"=>Lang::get('删除管理员').$id,
]);
$res = $admin->delAdmin($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 修改个人资料
*
* @throws \app\exception\ModelException
*/
public function selfUpdate(Admin $admin): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(AdminValidate::class)->scene('selfUpdate')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
if ($param['code_auth'] == 1 && empty($param['phone'])) {
return jsonReturn(-2, Lang::get('开启短信验证登录需填写手机号'));
}
$where = [
'id' => $this->admin['uid'],
'seller_id' => $this->admin['seller_id'],
];
if (empty($param['password'])) {
unset($param['password']);
} else {
// 密码规则验证
$err = $this->checkPass($param['password'],$this->admin['seller_id']);
if(!empty($err['message'])){
return jsonReturn(-5,$err['message']);
}
$param['password'] = makePassword($param['password']);
// 记录密码修改时间
$param['pass_last_change'] = time();
}
$res = $admin -> updateAdmin($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function self(Admin $Admin,AdminMenu $AdminMenu): \think\response\Json
{
$admin = $Admin->getAdmin(['id' => $this->admin['uid'],'seller_id' => $this->admin['seller_id']],['role'])['data'];
// 获取权限
$role = $admin['role']->toArray();
if(empty($role)){
$auth = [];
}else{
$roleIds = array_column($role,'id');
if(in_array(1,$roleIds)){
$auth = $AdminMenu->getAllAdminMenu(['is_menu'=> 1,'seller_id'=>1])['data']->toArray();
}else{
$menuIds = array_column($role,'auth');
$tmpMenuIds = [];
foreach ($menuIds as $menuId){
$tmp = explode(',',$menuId);
$tmpMenuIds = array_merge($tmpMenuIds,$tmp);
}
$whereIn = array_unique($tmpMenuIds);
$auth = $AdminMenu -> getAllAdminMenu([['id','in',$whereIn],['is_menu','=',1]])['data']->toArray();
}
}
$menu = makeTree($auth);
return jsonReturn(0,'success',[
'user' => $admin,
'menu' => $menu,
]);
}
/**
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function current(): \think\response\Json
{
$adminModel = new Admin();
$adminMenuModel = new AdminMenu();
$admin = $adminModel->getAdmin(['id' => $this->admin['uid']], ['role'])['data'];
// 缓存权限数据
$role = $admin['role']->toArray();
if (empty($role)) {
$auth = [];
} else {
$roleIds = array_column($role, 'id');
if (in_array(1, $roleIds)) {
$auth = $adminMenuModel->getAllCustomDataModel(['seller_id' => $admin['seller_id'], 'status' => 1],'id asc','id,controller,action,path,frontend_path')['data']->toArray();
} else {
$menuIds = array_column($role, 'auth');
$tmpMenuIds = [];
foreach ($menuIds as $menuId) {
if($menuId){
$tmp = explode(',', $menuId);
$tmpMenuIds = array_merge($tmpMenuIds, $tmp);
}
}
$whereIn = array_unique($tmpMenuIds);
$auth = $adminMenuModel->getAllCustomDataModel([
['id', 'in', $whereIn],
['seller_id', '=', $admin['seller_id']],
['status', '=', 1]
],'id asc','id,controller,action,path,frontend_path')['data']->toArray();
}
}
$allowPath = array_filter(array_column($auth, 'frontend_path'));
$dynamic_content_auth = [];
// 动态菜单
foreach($auth as $val){
// 内容 content/index
$tempPath = strtolower($val['path']);
// 如果路径是 /content/index/1 格式
if(preg_match('/^\/content\/index\/\d+$/',$tempPath)){
$dynamic_content_auth[] = str_replace('/content/index','/addCustomContent',$tempPath);
$dynamic_content_auth[] = str_replace('/content/index','/updateCustomContent',$tempPath);
}
// 审核
// 如果路径是 /content/checklist/1 格式
if(preg_match('/^\/content\/checklist\/\d+$/',$tempPath)){
$dynamic_content_auth[] = str_replace('/content/checklist','/auditingContent',$tempPath);
}
}
$allowPath = array_merge($allowPath, $dynamic_content_auth);
$allowPath = array_merge($allowPath, array_keys(config('rbac.skip_auth')));
return jsonReturn(0,'success',$allowPath);
}
}

40
app/controller/backend/AdminLoginLogController.php

@ -0,0 +1,40 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\AdminLoginLog;
use think\facade\Lang;
use think\facade\Request;
class AdminLoginLogController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\Response
* @throws \app\exception\ModelException
*/
public function index(AdminLoginLog $adminLoginLog): \think\Response
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = $this->setLimit();
$name = input('admin_name');
if($name){
$where['admin_name'] = $name;
}
$id = input('admin_id');
if($id){
$where['id'] = $id;
}
$adminLogList = $adminLoginLog->getAdminLoginLogList($where,$limit);
return json(pageReturn($adminLogList));
}
public function delete(): \think\response\Json
{
return parent::customDelete(Lang::get('登录日志'),Lang::get('登录日志删除'));
}
}

182
app/controller/backend/AdminMenuController.php

@ -0,0 +1,182 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\AdminMenu;
use app\service\RoleService;
use app\validate\AdminMenuValidate;
use Overtrue\Pinyin\Pinyin;
use think\exception\ValidateException;
use think\facade\Lang;
class AdminMenuController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(AdminMenu $adminMenu): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$adminMenuList = $adminMenu->getAllAdminMenu($where)['data']->toArray();
// foreach($adminMenuList as $key => $val){
// if($val['is_menu'] == 2){
// unset($adminMenuList[$key]);
// }
// }
$adminMenuList = generate($adminMenuList);
return jsonReturn(0,'success',$adminMenuList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(AdminMenu $adminMenu): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(AdminMenuValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['path'] = '/' .$param['controller'].'/'.$param['action'];
$res = $adminMenu -> addAdminMenu($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(AdminMenu $adminMenu): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $adminMenu->getAdminMenu($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(AdminMenu $adminMenu): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(AdminMenuValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['path'] = '/' .$param['controller'].'/'.$param['action'];
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = $adminMenu -> updateAdminMenu($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(AdminMenu $adminMenu): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-3,Lang::get('请求方法错误'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $adminMenu->softDelAdminMenu($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 单个控制菜单是否显示
* @author:🍁
* @Time:2023/5/22 11:12 上午
*/
public function updateMenuStatus(AdminMenu $adminMenu)
{
$id = $this->request->post('id/d',0);
$info = $adminMenu->where(['id'=>$id,'is_del'=>1])->field('id,status')->find();
if(empty($info)){
return jsonReturn(-1,lang('菜单不存在'));
}
$updateData = [
'status'=>$info['status']==1?2:1,
'update_time'=>date("Y-m-d H:i:s")
];
$res = $adminMenu->where(['id'=>$id])->update($updateData);
if($res){
return jsonReturn(0,lang('修改成功'),$updateData);
}
return jsonReturn(-2,lang('修改失败'));
}
/**
* 得到当前登录用户的权限节点
* @author:🍁
* @Time:2023/5/22 11:24 上午
*/
public function getUserMenuList(AdminMenu $adminMenu)
{
return json(RoleService::getMenuAndUpdateAuth($this->admin['uid']));
}
/**
* 获取动态菜单
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function getDynamicMenu(): \think\response\Json
{
return json(RoleService::getDynamicMenu($this->admin['uid']));
}
}

41
app/controller/backend/AdminOptLogController.php

@ -0,0 +1,41 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\AdminOptLog;
use think\facade\Lang;
class AdminOptLogController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(AdminOptLog $adminOptLog): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = $this->setLimit();
$name = input('admin_name');
if($name){
$where['admin_name'] = $name;
}
$id = input('admin_id');
if($id){
$where['id'] = $id;
}
$adminLogList = $adminOptLog->getAdminOptLogList($where,$limit);
return json(pageReturn($adminLogList));
}
public function delete(): \think\response\Json
{
return parent::customDelete(Lang::get('操作日志'),Lang::get('操作日志删除'));
}
}

140
app/controller/backend/AdvertisementController.php

@ -0,0 +1,140 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Advertisement;
use app\model\Attachment;
use app\validate\AdvertisementValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class AdvertisementController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Advertisement $advertisement): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$title = input('title');
if ($title) {
$where = [ 'title' => $title];
}
$limit = $this->setLimit();
$with = [
'attachment' => function($query){
$query->field('id,name,type,url');
}
];
$advertisementList = $advertisement->getAdvertisementList($where,$limit,'*',$with);
return json(pageReturn($advertisementList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(Advertisement $advertisement): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(AdvertisementValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['expiration_date'] = date('Y-m-d',strtotime($param['expiration_date']));
$param['status'] = 2; // 默认状态禁用
$res = $advertisement -> addAdvertisement($param);
optEventLog($res['data']['id'],Lang::get('广告'),Lang::get('新增'));
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Advertisement $advertisement): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('广告ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $advertisement->getAdvertisement($where,'',['attachment'=>function($q){
$q->field('id,name,type,url');
}]);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(Advertisement $advertisement): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(AdvertisementValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$param['expiration_date'] = date('Y-m-d',strtotime($param['expiration_date']));
$res = $advertisement -> updateAdvertisement($where,$param);
optEventLog($param['id'],Lang::get('广告'),Lang::get('更新'));
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(Advertisement $advertisement): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
optEventLog($id,Lang::get('广告'),Lang::get('删除'));
$res = $advertisement->softDelAdvertisement($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

158
app/controller/backend/AttachmentCateController.php

@ -0,0 +1,158 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\AttachmentCate;
use app\service\CacheService;
use app\validate\AttachmentCateValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class AttachmentCateController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(AttachmentCate $attachmentCate): \think\response\Json
{
$where = [
['seller_id' ,'=', $this->admin['seller_id']],
];
$title = input('title');
if(!empty($title)){
$where[] = ['title', 'like', '%' . $title . '%'];
}
$attachmentCateList = $attachmentCate->getAllCustomArrayData($where)['data'];
$attachmentCateList = makeTree($attachmentCateList);
array_unshift($attachmentCateList,[
'id' => 0,
'parent_id' => 0,
'path' => 0,
'title'=> Lang::get('全部分类'),
'children' => [],
]);
return jsonReturn(0,Lang::get('成功'),$attachmentCateList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
* @throws \ReflectionException
*/
public function save(AttachmentCate $attachmentCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(AttachmentCateValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$attachmentCate->saveUnique(['title'=>$param['title'],'seller_id'=>$param['seller_id'],'parent_id'=>$param['parent_id']],'栏目已经存在');
// 完善栏目数据
if ($param['parent_id']) {
$parentCate = $attachmentCate->getCustomData(['id' => $param['parent_id'], 'seller_id' => $param['seller_id']])['data'];
$param['path'] = $parentCate['path'] . '-' . $param['parent_id'];
} else {
$param['path'] = 0;
}
$res = $attachmentCate -> addAttachmentCate($param);
CacheService::deleteRelationCacheByObject($attachmentCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(AttachmentCate $attachmentCate): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('分类ID不能为空'));
}
$where = [
'id' => $id,
];
// 其他逻辑
$res = $attachmentCate->getAttachmentCate($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function update(AttachmentCate $attachmentCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(AttachmentCateValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $attachmentCate -> updateAttachmentCate($where,$param);
CacheService::deleteRelationCacheByObject($attachmentCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function delete(AttachmentCate $attachmentCate): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('分类ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$subCate = $attachmentCate -> getAllCustomArrayData(['seller_id'=>$this->admin['seller_id'],'parent_id'=>$id])['data'];
if(!empty($subCate)){
return jsonReturn(-1,Lang::get('分类下面有子分类,不能直接删除'));
}
$cate = $attachmentCate -> getCustomData($where,['attachment'=>function($q){
$q->where('is_del',1);
}])['data']->toArray();
if(!empty($cate['attachment'])){
return jsonReturn(-2,Lang::get('分类下有附件不能直接删除'));
}
$res = $attachmentCate->delAttachmentCate($where);
CacheService::deleteRelationCacheByObject($attachmentCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

572
app/controller/backend/AttachmentController.php

@ -0,0 +1,572 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelException;
use app\model\Attachment;
use app\model\AttachmentCate;
use app\model\Model;
use app\model\RecycleBin;
use app\model\SysSetting;
use app\service\CacheService;
use app\service\ThemeService;
use app\service\upload\Upload;
use app\validate\AttachmentValidate;
use think\exception\ValidateException;
use think\facade\App;
use think\facade\Db;
use think\facade\Lang;
use think\file\UploadedFile;
use think\Image;
class AttachmentController extends BaseController
{
protected $type = [
'file' => 4,
'image' => 2,
'video' => 3,
'mp3' => 5,
'zip' => 1,
];
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Attachment $attachment): \think\response\Json
{
$type = (int)input('type');
$where = [
['seller_id' ,'=', $this->admin['seller_id']],
['is_del' ,'=', 1],
];
$cate_id = (int)input('cate_id');
if($cate_id){
$Cate = new AttachmentCate();
$cate = $Cate -> getCustomData(['id'=>$cate_id,'seller_id'=>$this->admin['seller_id']])['data'];
$path = $cate['path'] . '-' . $cate['id'];
$cates = $Cate -> getAllCustomArrayData([['path','like',$path.'%'],['seller_id','=',$this->admin['seller_id']]])['data'];
$ids = array_column($cates,'id');
array_unshift($ids,$cate['id']);
if(count($ids) == 1){
$where[] = ['attachment_cate_id', '=', $ids[0]];
}else{
$where[] = ['attachment_cate_id', 'in', $ids];
}
}
if($type){
$where[] = ['type', '=', $type];
}
$name = input('name');
if(!empty($name)){
$where[] = ['name', 'like', '%' . $name . '%'];
}
$limit = $this->setLimit();
$attachmentList = $attachment->getAttachmentList($where,$limit);
$res = pageReturn($attachmentList);
foreach ($res['data'] as &$value) {
$value['prefix_url'] = '';
$value['suffix_url'] = '';
if (strpos($value['url'], 'http') === false) {
if (strpos($value['url'], 'storage/') === false) {
$value['prefix_url'] = 'http://' . $this->request->host() . 'storage/';
$value['suffix_url'] = $value['url'];
$value['url'] = 'http://' . $this->request->host() . $value['url'];
continue;
}
$value['url'] = 'http://' . $this->request->host() . $value['url'];
//前缀,后缀
$urlStr = explode('storage/', $value['url']);
$value['prefix_url'] = $urlStr[0] . 'storage/';
$value['suffix_url'] = $urlStr[1];
}
}
return json($res);
}
// 修改附件路径
public function editFileUrl()
{
$param = $this->request->param();
if (empty($param['id']) || empty($param['name']) || empty($param['suffix_url'])) {
return json(dataReturn(-1, Lang::get('必填项不能为空!')));
}
$attachmentModel = new Attachment();
$info = $attachmentModel->where('id', $param['id'])->find();
if (!isset($info['id'])) {
return json(dataReturn(-1, Lang::get('文件不存在!')));
}
$param['suffix_url'] = str_replace('..', '', $param['suffix_url']);
$param['suffix_url'] = str_replace('//', '/', $param['suffix_url']);
$oldUrl = $info['url'];
$oldPathUrl = CMS_ROOT . 'public' . $oldUrl;
$newUrl = 'storage/' . $param['suffix_url'];
$newPathUrl = CMS_ROOT . 'public/' . $newUrl;
$updateData = [
'name' => $param['name'],
'description' => $param['description'],
'url' => $newUrl,
];
$fileInfo = pathinfo($newPathUrl);
mkdirs($fileInfo['dirname']);
$copyRes = true;
if ($oldPathUrl != $newPathUrl) {
$copyRes = copy($oldPathUrl, $newPathUrl);
}
if ($copyRes) {
// 复制成功
$res = $attachmentModel->where('id', $param['id'])->update($updateData);
} else {
$res = false;
}
if ($res === false) {
return json(dataReturn(-1, lang('修改失败')));
} else {
return json(dataReturn(0, lang('修改成功')));
}
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \ReflectionException
*/
public function save(Attachment $attachment): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
foreach($param as &$val){
try{
validate(AttachmentValidate::class)->scene('save')->check($val);
$val['seller_id'] = $this->admin['seller_id'];
}catch (ValidateException $e){
return jsonReturn(-1, $e->getError());
}
if ($val['storage'] == 5 && strpos($val['url'], 'http') !== 0) {
return jsonReturn(-1, lang('网络附件链接地址请以http开头'));
}
}
$res = $attachment->addAllCustomData($param);
CacheService::deleteRelationCacheByObject($attachment);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function read(Attachment $attachment): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('附件ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = $attachment->getAttachment($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function update(Attachment $attachment): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$seller_id = $this->admin['seller_id'];
try {
validate(AttachmentValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
if (isset($param['storage']) && $param['storage'] == 5 && strpos($param['url'], 'http') !== 0) {
return jsonReturn(-1, lang('网络附件链接地址请以http开头'));
}
$where = [
'id' => $param['id'],
'seller_id' => $seller_id,
];
$res = $attachment -> updateAttachment($where,$param);
CacheService::deleteRelationCacheByObject($attachment);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(Attachment $Attachment): \think\response\Json
{
$idArr = $this->request->param('id');
if(is_string($idArr)){
$ids = explode(',',$idArr);
}else{
$ids = $idArr;
}
$where = [
['id','in',$ids],
['seller_id','=',$this->admin['seller_id']]
];
$attachment = $Attachment -> getAllCustomArrayData($where)['data'];
Db::startTrans();
try{
$binData=[];
foreach ($attachment as $val){
// 复制删除内容到回收站表
$binData[] = [
'seller_id' => $this->admin['seller_id'],
'object_id' => $val['id'],
'sub_id' => 0,
'module_id' => 0,
'table_name' => '附件-attachment',
'title' => !empty($val['name']) ? $val['name'] : '附件' . $val['id'],
'admin_id' => $this->admin['uid'],
'name' => $this->admin['name'],
];
}
$recycleBin = new RecycleBin();
$recycleBin -> addAllCustomData($binData);
$Attachment -> updateAttachment($where,['is_del'=>2,'delete_time'=>time()]);
CacheService::deleteRelationCacheByObject($Attachment);
optEventLog($idArr,Lang::get('附件'),Lang::get('删除'));
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(-1,Lang::get('删除失败'));
}
return jsonReturn(0,Lang::get('删除成功'));
}
/**
* @throws \app\exception\ModelException
*/
public function uploadAndSave(Attachment $attachment): \think\response\Json
{
if(request()->isPost()){
set_time_limit(0);
$file = request()->file('file');
$reduceImg = request()->file('reduce_img');
if(empty($file)){
return jsonReturn(-8,lang('文件上传失败,请重新尝试'));
}
$seller_id = $this->admin['seller_id'];
$attachmentModel = new Attachment();
$has = $attachmentModel->checkFileExist($this->admin['seller_id'], $file->hash());
if ($has['code'] == 0) {
$reduceImgData = !empty($reduceImg)?$this->singleUpload($reduceImg,$seller_id)['data']:[];
$updateData = [
'update_time' => time(),
'reduce_img'=> !empty($reduceImgData)?$reduceImgData['getUploadFileInfo']['url']:"",
'attachment_cate_id' => $this->request->param('attachment_cate_id/d',0),
];
// 更新時間
$attachmentModel->where('id', $has['data']['id'])->update($updateData);
return json($has);
}
$fileData = $this->singleUpload($file,$seller_id)['data'];
$reduceImgData = !empty($reduceImg)?$this->singleUpload($reduceImg,$seller_id)['data']:[];
$fileData['getUploadFileInfo']['seller_id'] = $seller_id;
$fileData['getUploadFileInfo']['type'] = $fileData['fileType'];
$fileData['getUploadFileInfo']['size'] = round($fileData['fileSize'] / 1024,2);
$fileData['getUploadFileInfo']['attachment_cate_id'] =$this->request->param('attachment_cate_id/d',0);
$fileData['getUploadFileInfo']['reduce_img'] = !empty($reduceImgData)?$reduceImgData['getUploadFileInfo']['url']:"";
$attachment = new Attachment();
$res = $attachment->addAttachment($fileData['getUploadFileInfo']);
optEventLog($res['data']['id'],Lang::get('附件'),Lang::get('添加'));
return json($res);
}
return jsonReturn(-3,lang('请求方法错误'));
}
/**
* @throws ModelException
*/
private function singleUpload($file,$seller_id)
{
// 查看文件类型
$fileName = $file->getOriginalName();
$fileExt = $file->getOriginalExtension();
$file_type = fileFormat($fileName);
$fileType = $this->type[$file_type];
// resource_name && resource_type
// 禁止上传PHP和js文件
if($fileExt == 'php' || $this->isScriptFile($fileName) ){
\think\facade\Log::error("Base path '{$fileName}' is not valid.");
return jsonReturn(-3,lang('文件格式错误,请重新上传'));
}
$Settings = new SysSetting();
$uploadSetting = $Settings->getAllCustomArrayData(['parent_id'=>1,'group'=>'upload','status'=>1],'id desc','id,group,title,value')['data'];
$uploadSetting = getColumnForKeyArray($uploadSetting,'title');
$limitSize = $uploadSetting[$file_type.'_size']['value'] * 1024; // byte
$fileSize = $file->getSize(); // 单位byte
if($fileSize > $limitSize){
throw new ModelException(lang('文件过大,请修改上传限制或者替换小的文件'),-1);
}
$extArr = explode(',',$uploadSetting[$file_type.'_ext']['value']);
if(!in_array($fileExt,$extArr)){
throw new ModelException(lang('文件格式错误,请重新上传'),-2);
}
$type = $this->getUploadType();
$upload = new Upload();
$info = $upload->create($file,$seller_id, $type,$file_type);
if(!$info){
throw new ModelException(lang('上传失败请重新尝试'),-3);
}
$getUploadFileInfo = $upload->getUploadFileInfo()['data'];
$waterConfig = SysSetting::where(['group'=>'upload'])->column("value",'title');
//上传的是图片格式,
if($type=="local" && $file_type=="image" && isset($waterConfig['water_status'])&& $waterConfig['water_status']==1){
$fullPath ='./'.$getUploadFileInfo['url'];
$waterFontPath = $this->getWaterFontPath($waterConfig['water_font_path']??"");
$originalImage = Image::open($fullPath);
$originalImage->text($waterConfig['water_text']??'HuoCMS',App::getRootPath()."public/system_file/alifont/ali-Regular.woff",
$waterConfig['water_font_size']??20,$waterConfig['water_font_color']??"#18171b",$waterFontPath)->save($fullPath);
}
return dataReturn(0,'上传成功',
[
'fileType'=>$fileType,
'fileSize'=>$fileSize,
'getUploadFileInfo'=>$getUploadFileInfo
]);
}
public function sliceUploadAndSave(Upload $uploader): \think\response\Json
{
$params = $this->request->param();
$params['resource_chunk'] = $this->request->file('resource_chunk');
$file_type = fileFormat('abc.'.$params['resource_type']);
// 检验文件路径,不允许上传到非法目录
if(empty($params['resource_temp_path'])){
return jsonReturn(-1,lang('参数错误'));
}
if(!$this->isUploadPathValid($params['resource_temp_path'])){
return jsonReturn(-1,lang('参数错误-1'));
}
$Settings = new SysSetting();
$uploadSetting = $Settings->getAllCustomArrayData(['parent_id'=>1,'group'=>'upload','status'=>1],'id desc','id,group,title,value')['data'];
$uploadSetting = getColumnForKeyArray($uploadSetting,'title');
$extArr = explode(',',$uploadSetting[$file_type.'_ext']['value']);
if(!in_array($params['resource_type'],$extArr)){
return jsonReturn(-2,lang('文件格式错误,请重新上传'));
}
// resource_name && resource_type
// 禁止上传PHP和js文件
if($params['resource_type'] == 'php' || $this->isScriptFile($params['resource_name']) ){
\think\facade\Log::error("Base path '{$params['resource_name']}' is not valid.");
return jsonReturn(-3,lang('文件格式错误,请重新上传'));
}
$res = $uploader->tmpMove($params['resource_chunk'],$file_type,$params['resource_temp_path'],$params['chunk_index']);
if($res){
$tmPath = runtime_path() . 'slice/'.$file_type.'/'.$params['resource_temp_path'];
$num = getFileNumber($tmPath);
if($num == $params['chunk_total']){
try {
$filepath = $uploader->mergeBlob($tmPath,$params['resource_temp_path'],$params['chunk_total'],$params['resource_type']);
$type = $this->getUploadType();
$file = new UploadedFile($filepath,$params['resource_temp_path'].'.'.$params['resource_type']);
if($type == 'local'){
if(!file_exists(public_path() . 'storage/' . $file_type.'/'. date('Ymd'))){
@mkdir(public_path() . 'storage/' . $file_type.'/'. date('Ymd'),0755,true);
}
copy($filepath,public_path() . 'storage/' . $file_type.'/'. date('Ymd') .'/'.$params['resource_temp_path'] .'.'.$params['resource_type']);
$cateId = (int)input('attachment_cate_id');
$uploadInfo['name'] = $params['resource_name'];
$uploadInfo['url'] = 'storage/' . $file_type.'/'. date('Ymd') .'/'.$params['resource_temp_path'] .'.'.$params['resource_type'];
$uploadInfo['mime_type'] = $params['resource_type'];
$uploadInfo['storage'] = 1;
$uploadInfo['type'] = $this->type[$file_type];
$uploadInfo['size'] = round($file->getSize() / 1024,2);
$uploadInfo['attachment_cate_id'] = $cateId;
$attachment = new Attachment();
$res = $attachment->addAttachment($uploadInfo);
optEventLog($res['data']['id'],Lang::get('附件'),Lang::get('添加'));
(new ThemeService()) -> deleteFile($tmPath);
return json($res);
}else{
$upload = new Upload();
$res = $upload->create($file,$this->admin['seller_id'], $type);
if($res){
$fileSize = $file->getSize();
$fileType = $this->type[$file_type];
$res = $this->getResponse($fileType,$fileSize,$upload);
(new ThemeService()) -> deleteFile($tmPath);
return json($res);
}else{
return jsonReturn(-1,Lang::get('上传失败'));
}
}
} catch (\Exception $e) {
return jsonReturn(-1,$e->getMessage(),[]);
}
}else{
return jsonReturn(1,Lang::get('上传成功'),$res);
}
}else{
return jsonReturn(-1,Lang::get('上传失败'),$res);
}
}
/**
* @throws \app\exception\ModelException
*/
public function getResponse($fileType, $fileSize, $upload)
{
$uploadInfo = $upload->getUploadFileInfo();
$cateId = (int)input('attachment_cate_id');
if ($uploadInfo['code'] == 0) {
$uploadInfo['data']['type'] = $fileType;
$uploadInfo['data']['size'] = round($fileSize / 1024,2);
$uploadInfo['data']['attachment_cate_id'] = $cateId;
$attachment = new Attachment();
$res = $attachment->addAttachment($uploadInfo['data']);
optEventLog($res['data']['id'],Lang::get('附件'),Lang::get('添加'));
return $res;
} else {
return $uploadInfo;
}
}
public function getFileType()
{
}
/**
* @throws \app\exception\ModelException
*/
public function getUploadType()
{
// 文件信息提取
$where = [
'seller_id' => $this->admin['seller_id'],
'group' => 'upload',
'title' => 'storage'
];
$place = new SysSetting();
return $place->getSysSetting($where)['data']->toArray()['value'];
}
/**
* 得到文字水印的位置
*/
private function getWaterFontPath($fontPath)
{
$arr = [
'右下'=>Image::WATER_SOUTHEAST,
'左上'=>Image::WATER_NORTHWEST,
'上居中'=>Image::WATER_NORTH,
'右上'=>Image::WATER_NORTHEAST,
'左居中'=>Image::WATER_WEST,
'居中'=>Image::WATER_CENTER,
'右居中'=>Image::WATER_EAST,
'左下'=>Image::WATER_SOUTHWEST,
'下居中'=>Image::WATER_SOUTH,
];
return !isset($arr[$fontPath])?Image::WATER_SOUTHEAST:$arr[$fontPath];
}
/**
* 检查上传路径是否合法
*
* @param string $directory 上传目录
* @param string|null $basePath 基础路径
* @return bool
*/
public function isUploadPathValid(string $directory, string $basePath = null): bool {
// 1. 规范化路径(处理多余的斜杠)
if(empty($basePath)){
$basePath = runtime_path();
}
$basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$directory = str_replace(['\\', '/'], DIRECTORY_SEPARATOR, $directory);
// 2. 检查路径是否包含危险字符
$dangerousPatterns = [
'../', '..\\', // 父目录穿越
'./', '.\\', // 当前目录(可能被利用)
'~', // 用户目录(UNIX系统)
'://', // URL协议(如file://)
'%00', // 空字节截断
];
foreach ($dangerousPatterns as $pattern) {
if (str_contains($directory, $pattern)) {
return false;
}
}
// 3. 检查绝对路径是否在允许目录内
$realBase = realpath($basePath);
if ($realBase === false) {
\think\facade\Log::error("Base path '$basePath' is not valid.");
return false;
}
return true;
}
/**
* 检查文件扩展名是否为PHP或JS
*
* @param string $filename 要检查的文件名
* @return bool 如果是.php或.js文件则返回true,否则false
*/
function isScriptFile(string $filename): bool {
// 获取文件扩展名(全小写)
$extension = pathinfo($filename, PATHINFO_EXTENSION);
// 允许的扩展名白名单(全小写)
$allowedExtensions = ['php', 'js'];
// 检查扩展名是否在禁用列表中
return in_array(strtolower($extension), $allowedExtensions);
}
}

90
app/controller/backend/BaseController.php

@ -0,0 +1,90 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\BaseController as AppBaseController;
use app\model\Model;
use think\facade\Lang;
use think\facade\Request;
class BaseController extends AppBaseController
{
public $admin;
public $lang;
/**
* 控制器中间件
* @var array
*/
protected $middleware = [
'login',
'auth',
];
public function initialize()
{
parent::initialize();
self::createInstallFile();
$this->admin = $this->request->admin;
session('adminId', $this->admin['uid']);
$this->lang = config('lang.allow_lang_list');
}
public function setLimit(): int
{
$limit = 10;
$limitPage = (int)input('param.limit');
if($limitPage){
$limit = $limitPage;
}
return $limit;
}
private static function createInstallFile()
{
if(!hcInstalled() && is_dir(app()->getRootPath().'data/install/')){
$url = env('app_host') . '/install';
redirect($url);
}
// 更新版本标识
updateVersion();
}
public function customDelete($opt,$title='批量删除'): \think\response\Json
{
$ids = $this->request->param('id');
if(is_string($ids)){
$ids = explode(',',$ids);
}
$className = str_replace('backend.','',Request::controller());
$modelName = '\\app\\model\\' . $className;
$model = new $modelName();
try{
$model->whereIn('id',$ids)->delete();
optEventLog(json_encode($ids),$title,$opt);
}catch (\Exception $e){
return jsonReturn(-1,Lang::get('删除失败'));
}
return jsonReturn(0,Lang::get('删除成功'));
}
public function customSoftDelete($opt,$title='批量删除'): \think\response\Json
{
$ids = $this->request->param('id');
if(is_string($ids)){
$ids = explode(',',$ids);
}
$className = str_replace('backend.','',Request::controller());
$modelName = '\\app\\model\\' . $className;
$model = new $modelName();
try{
$model->whereIn('id',$ids)->update(['is_del'=>2,'delete_time'=>time()]);
optEventLog(json_encode($ids),$title,$opt);
}catch (\Exception $e){
return jsonReturn(-1,Lang::get('删除失败'));
}
return jsonReturn(0,Lang::get('删除成功'));
}
}

537
app/controller/backend/CategoryController.php

@ -0,0 +1,537 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelException;
use app\model\AdminMenu;
use app\model\Attachment;
use app\model\Category;
use app\model\CategoryCheck;
use app\model\Route;
use app\model\ThemeFile;
use app\model\Website;
use app\service\ApiService;
use app\service\CacheService;
use app\service\CategoryService;
use app\service\StaticFileService;
use app\validate\CategoryValidate;
use Overtrue\Pinyin\Pinyin;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Lang;
class CategoryController extends BaseController
{
protected $cacheKeyArr = 'hc_module_cate_arr';
/**
* 栏目列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function index(Category $category): \think\response\Json
{
$websiteId = (int)input('param.website_id');
if(!$websiteId){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$where = [
['seller_id','=', $this->admin['seller_id']],
['website_id','=',$websiteId],
];
$lang = input('lang');
if(empty($lang)){
$lang = config('lang.default_lang');
}
$Website = new Website();
$website = $Website -> getCustomArrayData(['id'=>$websiteId,'seller_id'=>$this->admin['seller_id']])['data'];
$domain = $website['domain'];
if($lang != config('lang.default_lang')){
$domain = $domain .'/'.$lang;
}
$title = $this->request->param('title','');
$checkStatus = $this->request->param('need_check',0);
if($checkStatus == 1){
$where[] = ['need_check','=',$checkStatus];
}
if(!empty($title)) {
$where[] = ['title', 'like', '%' . $title . '%'];
}
$where[] = ['lang','=',$lang];
$with = [
'thumbnail' => function($query){
$query->field('id,name,url');
}
];
//栏目管理搜索栏目名称
$categoryTitle = $this->request->param("category_title/s",'','trim,strip_tags');
if(!empty($categoryTitle)){
$categoryIdPath = $category->where([
['seller_id','=', $this->admin['seller_id']],
['website_id','=',$websiteId],
['lang','=',$lang],
['title','like',"%{$categoryTitle}%"],
])->column('path','id');
if(empty($categoryIdPath)){
return jsonReturn(0,'内容不存在');
}
$selectIds = [];
foreach ($categoryIdPath as $pathId => $paths){
$paths = explode('-',$paths);
for ($i=1;$i<count($paths)-1;$i++){
$selectIds[$paths[$i]] = $paths[$i];
}
$selectIds[$pathId] = $pathId;
}
$where[] = ['id','in',$selectIds];
}
$themeFileModel = new ThemeFile();
$designFileList = $themeFileModel->getAllCustomArrayData([['is_design', '=', 1]])['data'];
$newDesignFileList = [];
foreach ($designFileList as $value) {
$value['file_name'] = str_replace('.html', '', $value['file']);
$newDesignFileList[$value['file']] = $value;
$newDesignFileList[$value['file_name']] = $value;
}
$categoryList = $category->getAllCustomArrayData($where,'id asc','id,seller_id,website_id,module_id,parent_id,title,lang,alias,type,path,thumbnail,sort,status,list_tpl',$with)['data'];
foreach($categoryList as &$item){
if($item['type'] == 1 || $item['type'] == 4){
if(!empty($item['alias'])){
if($item['alias'] == '/'){
$item['fullUrl'] = 'http://' . $domain;
}else{
$item['fullUrl'] = 'http://' . $domain . $item['alias'].'.'.config('route.url_html_suffix');
}
}else{
$item['fullUrl'] = 'http://' . $domain . '/list/index/'.$item['id'].'.'.config('route.url_html_suffix');
}
}else if($item['type'] == 3){
$item['fullUrl'] = $item['alias'];
}else{
$item['fullUrl'] = 'javascript:;';
}
$item['is_design'] = 0;
$item['design_path'] = '';
if (isset($newDesignFileList[$item['list_tpl']])) {
$item['is_design'] = 1;
$item['design_path'] = $newDesignFileList[$item['list_tpl']]['design_path'];
}
}
if($checkStatus != 1){
$categoryList = makeTree($categoryList);
}
return jsonReturn(0,'success',$categoryList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function save(CategoryService $categoryService,Category $Category): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['alias'] = $param['alias'] ?? '';
$param['module_id'] = $param['module_id'] ?? 1;
$param['is_menu'] = $param['is_menu'] ?? 2;
// 数据验证
try{
if(!empty($param['parent_map'])){
$param = $this->getMapCateData($param);
}
validate(CategoryValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
if (in_array($param['alias'], StaticFileService::$forbidNames)) {
return jsonReturn(-2,lang('别名已被系统占用,请修改别名'));
}
$param['seller_id'] = $this->admin['seller_id'];
$category = $Category->existCategory(['title'=>$param['title'],'seller_id'=>$param['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang'],'parent_id'=>$param['parent_id']])['data'];
if(!empty($category)){
return jsonReturn(-2,'栏目已经存在');
}
if(in_array($param['type'],[1,4]) && empty($param['alias'])){
if($param['list_tpl'] == 'index'){
$param['alias'] = '/';
}else{
$param['alias'] = $this->getAlias($param['title'],$param['website_id'],$param['lang'],$param['seller_id']);
}
}
if(!empty($param['alias']) && $param['alias'] != 'javascript:;'){
$category = $Category->existCategory(['title'=>$param['alias'],'seller_id'=>$param['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang']])['data'];
}
if(!empty($category)){
return jsonReturn(-3,lang('栏目别名已经存在'));
}
if ($param['need_check'] == 1 && (empty($param['check_list']) || !is_array($param['check_list']))) {
return jsonReturn(-3,'请设置审核人员!');
}
if (!empty($param['field_list'])) {
$keyArr = [];
foreach ($param['field_list'] as $value) {
if (isset($keyArr[$value['key']])) {
return jsonReturn(-5,lang('自定义字段名称重复:') . $value['key']);
}
$keyArr[$value['key']] = $value;
}
$param['field_list'] = json_encode($param['field_list'], JSON_UNESCAPED_UNICODE);
}
return $categoryService -> createCategory($param);
}
return jsonReturn(-3,Lang::get(lang('请求方法错误')));
}
/**
* 栏目详情
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Category $category,Attachment $attachment): \think\response\Json
{
$param = input('param.');
try {
validate(CategoryValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['seller_id']= $this->admin['seller_id'];
$with = [
'thumbnail' => function($query){
$query->field('id,name,url');
},'banner' => function($query){
$query->field('id,name,url');
}
];
$res = $category->getCategory($param,$with);
if (!empty($res['data']['content'])) {
$res['data']['content'] = htmlspecialchars_decode($res['data']['content']);
}
$res['data']['check_list'] = [];
if ($res['data']['need_check'] == 1) {
$checkModel = new CategoryCheck();
$checkList = $checkModel->where([
['category_id', '=', $res['data']['id']],
['status', '=', 1],
['type','=',1]
])->select()->toArray();
$res['data']['check_list'] = $checkList;
}
if (!empty($res['data']['field_list'])) {
$res['data']['field_list'] = json_decode($res['data']['field_list'], true);
}
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function update(CategoryService $categoryService,Category $Category): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['is_menu'] = $param['is_menu'] ?? 2;
try {
if(!empty($param['parent_map'])){
$param = $this->getMapCateData($param);
}
validate(CategoryValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
if (in_array($param['alias'], StaticFileService::$forbidNames)) {
return jsonReturn(-2,lang('别名已被系统占用,请修改别名'));
}
$param['seller_id'] = $this->admin['seller_id'];
if(in_array($param['type'],[1,4]) && empty($param['alias'])){
if($param['list_tpl'] == 'index'){
$param['alias'] = '/';
}else{
$param['alias'] = $this->getAlias($param['title'],$param['website_id'],$param['lang'],$param['seller_id']);
}
}
$category = $Category->existCategory(['title'=>$param['title'],'seller_id'=>$param['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang'],'parent_id'=>$param['parent_id']])['data'];
if(!empty($category) && $category['id'] != $param['id']){
return jsonReturn(-2,lang('栏目已经存在'));
}
$category = $Category->existCategory(['title'=>$param['alias'],'seller_id'=>$param['seller_id'],'website_id'=>$param['website_id']])['data'];
if(!empty($category) && $category['id'] != $param['id']){
return jsonReturn(-3,lang('栏目别名已经存在'));
}
if (isset($param['need_check']) && $param['need_check'] == 1 && (empty($param['check_list']) || !is_array($param['check_list']))) {
return jsonReturn(-3,lang('请设置审核人员'));
}
if (!empty($param['field_list'])) {
$keyArr = [];
foreach ($param['field_list'] as $value) {
if (isset($keyArr[$value['key']])) {
return jsonReturn(-5,lang('自定义字段名称重复:') . $value['key']);
}
$keyArr[$value['key']] = $value;
}
$param['field_list'] = json_encode($param['field_list'], JSON_UNESCAPED_UNICODE);
}
$this->deleteCateCacheKey($param['website_id'],$param['lang']);
return $categoryService -> updateCategory($param);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除栏目
* @param Category $category
* @param Route $route
* @return \think\response\Json
*/
public function delete(Category $category,Route $route): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(CategoryValidate::class)->scene('delete')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang']
];
Db::startTrans();
try {
$subCate = $category -> getAllCustomArrayData(['seller_id'=>$this->admin['seller_id'],'parent_id'=>$param['id']])['data'];
$this->deleteCateCacheKey($param['website_id'],$param['lang']);
if(!empty($subCate)){
return jsonReturn(-1,lang('栏目下面有子栏目,不能直接删除'));
}
$res = $category->delCategory($where);
$route->delRoute(['seller_id' => $this->admin['seller_id'],'category_id'=>$param['id']]);
$route->getRoutes($param['website_id'], $this->admin['seller_id'], $param['lang']);
// 把动态菜单删除
$menu = new AdminMenu();
// 审核 /content/checkList/41
$menu -> delCustomData(['path'=>'/content/checkList/'.$param['id']]);
// 内容 /content/index/5
$menu -> delCustomData(['path'=>'/content/index/'.$param['id']]);
CacheService::deleteRelationCacheByObject($route);
CacheService::deleteRelationCacheByObject($category);
$optAdmin = request()->admin;
event("AdminOptLog",[
'admin_id' => $optAdmin['uid'],
'admin_name' => $optAdmin['name'],
'title'=>lang("栏目管理"),
"content"=>lang("删除栏目,栏目ID为").$param['id'],
]);
Db::commit();
CacheService::clear();
}catch (ModelException $me){
Db::rollback();
return jsonReturn(50023,$me->getMessage());
}catch (\Exception $e){
Db::rollback();
return jsonReturn(50024,$e->getMessage());
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 分组获取栏目列表
* @param Category $Category
* @return \think\response\Json
*/
public function getModuleCate(Category $Category): \think\response\Json
{
$param = input('get.');
try {
validate(CategoryValidate::class)->scene('getModuleCate')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$Website = new Website();
$website = $Website->getAllCustomArrayData([['id','in',$param['site_id']],['seller_id','=',$this->admin['seller_id']]]
,'id asc','id,seller_id,title,domain')['data'];
$website = getColumnForKeyArray($website,'id');
$category = $Category -> getAllCustomArrayData([['website_id','in',$param['site_id']],['seller_id','=',$this->admin['seller_id']],['lang','=',$param['lang']]],'id asc')['data'];
$category = getArrayGroupBy($category,'website_id');
$response = [];
foreach($category as $key => $cate){
$tmpCategory = $this->makeTree($cate);
$tmpCategory = $this->imp($tmpCategory);
$tmpCate = $website[$key];
$tmpCate['category'] = $tmpCategory;
$response[] = $tmpCate;
}
return jsonReturn(0,lang('成功'),$response);
}
public function makeTree($data, int $pid=0, int $level=0): array
{
$tree = array();
$tmpTree = [];
foreach ($data as $key => &$value) {
$str = str_repeat('--',$level);
$value['title'] = $str . $value['title'];
if ($value['parent_id'] == $pid) {
$value['level'] = $level;
unset($data[$key]);
$tmpTree[] = $value;
$value['children'] = $this->makeTree($data, $value['id'],$level+1);
$tree[] = $value;
}else{
$tmpTree[] = $value;
}
}
return $tree;
}
public function imp($tree, $children='children'): array
{
$impArr = array();
foreach($tree as $w) {
if(isset($w[$children])) {
$t = $w[$children];
unset($w[$children]);
$impArr[] = $w;
if(is_array($t)) $impArr = array_merge($impArr, $this->imp($t, $children));
} else {
$impArr[] = $w;
}
}
return $impArr;
}
/**
* 栏目复制
* @throws ModelException
* @throws \Exception
*/
public function copy(CategoryService $categoryService): \think\response\Json
{
set_time_limit(300);
$param = input('post.');
try {
validate(CategoryValidate::class)->scene('copy')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
foreach ($param['target_site'] as $val){
if(empty($val) || !is_numeric($val)){
return jsonReturn(-1,lang('目标站点数据异常,请检查'));
}
}
$res = $categoryService -> copyCate($param['original_site'],$param['target_site'],$param['lang'],$this->admin['seller_id']);
return json($res);
}
/**
* @throws \app\exception\ModelEmptyException
* @throws ModelException
*/
public function getMapCateData(&$param)
{
$Category = new Category();
$param['parent_path'] = json_encode($param['parent_map']);
$param['parent_map'] = array_pop($param['parent_map']);
$mapCate = $Category->getCategory(['id'=>$param['parent_map'],'seller_id'=>$this->admin['seller_id']])['data']->toArray();
foreach($mapCate as $k => $v){
if($k == 'parent_id'){
continue;
}
if(empty($param[$k]) && !in_array($k,['id','create_time','update_time'])){
$param[$k] = $v;
}
}
return $param;
}
public function deleteCateCacheKey($siteId,$lang)
{
$cateCacheKey = 'hc_cate_' . $this->admin['seller_id'] . '_' . $siteId .'_' . $lang;
Cache::delete($cateCacheKey);
$moduleCateArr = Cache::get($this->cacheKeyArr);
if(!empty($moduleCateArr)){
foreach ($moduleCateArr as $cate){
$tmpCate = explode('_',str_replace('hc_module_cate_' . $this->admin['seller_id'] . '_' . $lang .'_','',$cate));
if(in_array($siteId,$tmpCate)){
$moduleCateCacheKey = 'hc_module_cate_' . $this->admin['seller_id'] . '_' . $lang .'_' . $siteId;
Cache::delete($moduleCateCacheKey);
unset($moduleCateArr[$cate]);
}
}
Cache::set($this->cacheKeyArr,$moduleCateArr);
}
}
/**
* @throws ModelException
* @throws \app\exception\ModelEmptyException
*/
public function getAlias($title, $siteId, $lang, $sellerId): string
{
$pinyin = new Pinyin();
$cacheKey = $sellerId .'_'.$siteId .'_'. $lang . '_website_cache_key';
$settingData = Cache::get($cacheKey);
if(empty($settingData)){
$settingData = ApiService::setWebsiteSetting($sellerId,$siteId,$lang);
}
if(!empty($settingData['alias_rule']) && $settingData['alias_rule'] == 1){
$alias = strtolower('/' . $pinyin -> abbr($title));
}else{
$alias = strtolower('/' . $pinyin -> permalink($title,''));
}
return $alias;
}
public function copyToOtherLang(CategoryService $categoryService): \think\response\Json
{
$param = $this->request->only(['from','to','site_id']);
try {
validate(CategoryValidate::class)->scene('copyCategoryToAnotherLang')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
return json($categoryService -> copyCategoryToOtherLang($param['from'],$param['to'],$param['site_id'],$this->admin['seller_id']));
}
public function updateStatus(CategoryService $categoryService): \think\response\Json
{
$param = $this->request->only(['id','status']);
try {
validate(CategoryValidate::class)->scene('updateStatus')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
return json($categoryService -> updateStatus($param['id'],$param['status'],$this->admin['seller_id']));
}
}

1287
app/controller/backend/ContentController.php
File diff suppressed because it is too large
View File

146
app/controller/backend/ContentTagController.php

@ -0,0 +1,146 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\ContentTag;
use app\taglib\HcTaglib;
use app\validate\ContentTagValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class ContentTagController extends BaseController
{
public function initialize()
{
define('ADMIN_SITE_ID',(int)input('site_id'));
define('ADMIN_SELLER_ID',$this->admin['seller_id']);
parent::initialize();
}
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(ContentTag $contentTag): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
$contentTagList = $contentTag->getContentTagList($where,$limit);
return json(pageReturn($contentTagList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(ContentTag $contentTag): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(ContentTagValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $contentTag -> addContentTag($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(HcTaglib $hcTaglib): \think\response\Json
{
$param = input('post.');
try {
validate(ContentTagValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$func = 'tag' . ucfirst($param['title']);
// $hcTaglib = new HcTaglib(new Template());
$hcTaglib->$func();
dd($param);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(ContentTag $contentTag): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(ContentTagValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = $contentTag -> updateContentTag($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(ContentTag $contentTag): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
// TO DO
// 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $contentTag->delContentTag($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

68
app/controller/backend/DashboardController.php

@ -0,0 +1,68 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use think\facade\Db;
class DashboardController extends BaseController
{
/**
* 后台首页看板
*
*/
public function index(): \think\Response
{
if(!hcInstalled()){
createInstallFile();
}
$install_date = filectime(app()->getRootPath().'public/system_file/install.lock');
// 运行天数
$total_day = (int)ceil((time() - $install_date) / (24*3600)) ?: 1;
// 总站点数
$site_count = Db::name('website')->where('seller_id',$this->admin['seller_id'])->count('id');
// 总访客数
$visit_count = count(Db::name('visit_log')->distinct(true)->field('ip,visited_time')->select()->toArray());
// 总文章数 (模型为系统文章模型)
$article_count = Db::name('sub_content')->where('is_del',1)->count('id');
// 总询盘数
$inquiry_count = Db::name('inquiry')->where('is_del',1)->count('id');
// 总关键词数
$keyword_count = Db::name('keyword')->where('is_del',1)->count('id');
// 总导入链接数
$in_link_count = Db::name('link')->where(['type'=>1,'is_del'=>1])->count('id');
// 总导出链接数
$out_link_count = Db::name('link')->where(['type'=>2,'is_del'=>1])->count('id');
// 授权信息 // https://www.huocms.com/auth
$domain = request()->param('domain');
if(empty($domain)){
$auth_info = null;
}else{
$authRes = curlPost(config('system.auth_query_url'),['domain'=> request()->param('domain')])['data'];
$auth_info = json_decode($authRes,true);
if(empty($auth_info['data'])){
$auth_info = null;
}else{
$auth_info = $auth_info['data'];
}
$websiteTitle = Db::name('website')->where('domain',$domain)->where('seller_id',$this->admin['seller_id'])->value('title');
}
return jsonReturn(0,'success',[
'total_day' => $total_day,
'site_count' => $site_count,
'visit_count' => $visit_count,
'article_count' => $article_count,
'inquiry_count' => $inquiry_count,
'keyword_count' => $keyword_count,
'in_link_count' => $in_link_count,
'out_link_count' => $out_link_count,
'auth_info' => $auth_info,
'version' => file_get_contents(app()->getRootPath().'version'),
'website_title' => $websiteTitle ?? '',
]);
}
}

146
app/controller/backend/DatabaseController.php

@ -0,0 +1,146 @@
<?php
namespace app\controller\backend;
use app\model\Database;
use app\service\MysqlBackupService;
use think\App;
use think\facade\Db;
use think\facade\Env;
class DatabaseController extends BaseController
{
protected $service;
/**
* @throws \Exception
*/
public function __construct(App $app)
{
parent::__construct($app);
$config = array(
'level' => 5,//数据库备份卷大小
'compress' => 0,//数据库备份文件是否启用压缩 0不压缩 1 压缩
);
$this->service = new MysqlBackupService($config);
}
/**
* 数据表列表
* @return \think\response\Json
*/
public function tableList(): \think\response\Json
{
return json($this->service->dataList());
}
/**
* 备份文件列表
* @return mixed
* @throws \app\exception\ModelException
*/
public function index()
{
$path = app()->getRootPath() .'backup/';
$glob = hcScanDir($path.'*');
if(empty($glob)){
return jsonReturn(0,'success');
}
arsort($glob);
$data = [];
foreach ($glob as $key => $val){
$tmp = [
'id' => $key+1,
'title' => $val,
'size' => sprintf("%.2f",filesize($path.$val) / 1000) .'kb' ,
];
array_push($data,$tmp);
}
return jsonReturn(0,lang('成功'),$data);
}
/**
* @param $name
* @return mixed
*/
public function detail($name)
{
return jsonReturn($this->service->detail($name));
}
/**
* 数据库备份
* @throws \app\exception\ModelException
* @throws \think\db\exception\BindParamException
*/
public function backup(): \think\response\Json
{
return $this->service->backupDatabase($this->admin['seller_id']);
}
/**
* 文件恢复
* @return \think\response\Json
*/
public function restore(): \think\response\Json
{
if(!request()->isPost()){
return jsonReturn(-1,lang('方法请求错误'));
}
$filename = input('post.filename');
$res = $this->service->import($filename,0);
return json($res);
}
/**
* 数据表优化
* @param $name
* @throws \Exception
*/
public function optimize($name): \think\response\Json
{
$this->service->optimize($name);
return jsonReturn(0,lang('优化成功'));
}
/**
* 数据表修复
* @param $name
* @throws \Exception
*/
public function repair($name): \think\response\Json
{
$this->service->repair($name);
return jsonReturn(0,lang('修复成功'));
}
/**
* @return \think\response\File
*/
public function downloadFile(): \think\response\File
{
try {
$time = intval($this->request->param('filename'));
$file =$this->service->getFile('time', $time);
$fileName = $file[0];
return download($fileName,$time);
}catch (UploadFailException $e){
return app('json')->fail(lang('下载失败'));
}
}
/**
* @throws \Exception
*/
public function delete(): \think\response\Json
{
$filename = $this->request->param('filename');
if(empty($filename)){
return jsonReturn(-2,lang('文件名不能为空'));
}
return $this->service->delFile($filename);
}
}

667
app/controller/backend/DesignController.php

@ -0,0 +1,667 @@
<?php
namespace app\controller\backend;
use app\model\BigField;
use app\model\Category;
use app\model\CategorySubContent;
use app\model\SubContent;
use app\model\Theme;
use app\model\ThemeFile;
use app\model\Website;
use app\model\WebsiteSetting;
use app\service\ApiService;
use app\service\ThemeFileService;
use app\service\ThemeService;
use app\validate\WebsiteSettingValidate;
use think\exception\ValidateException;
use think\response\Json;
class DesignController extends BaseController
{
protected $designBase = 'public/design/designPage';
protected function sanitizeFileName($file)
{
//sanitize, remove double dot .. and remove get parameters if any
$file = CMS_ROOT . 'public/design/designPage/' . preg_replace('@\?.*$@', '', preg_replace('@\.{2,}@', '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $file)));
return $file;
}
public function save()
{
return $this->publish(1);
// $param = $this->request->param();
// // 数据验证
// try {
// validate(WebsiteSettingValidate::class)->scene('read')->check($param);
// } catch (ValidateException $e) {
// return jsonReturn(-1, $e->getError());
// }
//
// define('MAX_FILE_LIMIT', 1024 * 1024 * 2);//2 Megabytes max html file size
//
// $html = "";
// if (isset($_POST['startTemplateUrl']) && !empty($_POST['startTemplateUrl'])) {
// $startTemplateUrl = $this->sanitizeFileName($_POST['startTemplateUrl']);
// $html = file_get_contents($startTemplateUrl);
// } else if (isset($_POST['html'])) {
// $html = substr($_POST['html'], 0, MAX_FILE_LIMIT);
// }
//
// $where = [
// 'seller_id' => $this->admin['seller_id'],
// 'website_id' => $param['website_id'],
// 'lang' => $param['lang'],
// 'is_active' => 1
// ];
// $Theme = new Theme();
// $theme = $Theme->getActiveTheme($where)['data']->toArray();
// $themeName = $theme['theme'];
//
// $pathInfo = pathinfo($param['file']);
// $filename = $pathInfo['filename'];
//// $file = $this->sanitizeFileName($_POST['file']);
// $file = CMS_ROOT . "view/website/{$param['website_id']}/{$param['lang']}/{$themeName}/single_{$filename}.html";
//
// if (file_put_contents($file, $html)) {
// echo "保存成功 $file";
// } else {
// header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
// echo "保存失败! $file\n可能的原因是缺少写权限或文件路径不正确!";
// }
// exit();
}
/**
* @param $saveType 1保存2发布
* @return Json|void
*/
public function publish($saveType = 2)
{
$param = $this->request->param();
// 数据验证
try {
validate(WebsiteSettingValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
define('MAX_FILE_LIMIT', 1024 * 1024 * 2);//2 Megabytes max html file size
$html = "";
if (isset($param['startTemplateUrl']) && !empty($param['startTemplateUrl'])) {
$startTemplateUrl = $this->sanitizeFileName($param['startTemplateUrl']);
$html = file_get_contents($startTemplateUrl);
} else if (isset($_POST['html'])) {
$html = substr($_POST['html'], 0, MAX_FILE_LIMIT);
}
preg_match_all( '~<img.*?src=["\']+(.*?)["\']+.*>~' , $html, $match );
$imgTagArr = $match[0];
$imgSrcArr = $match[1];
$maxImgSize = config('system.lazy_load_size'); // 读取文件大小,大于 配置文件 做懒加载
$oldImgTagArr = [];
$newImgTagArr = [];
foreach ($imgSrcArr as $key => $value) {
// 存在无需懒加载的标签,跳过
if (strpos($imgTagArr[$key], 'no-lazy') !== false) {
continue;
}
if (empty($value)) {
continue;
}
if (strpos($value, 'data:') === 0) {
continue;
}
if (strpos($value, 'http:') === 0) {
$fileUrl = $value;
$res = @get_headers($fileUrl,true);
if (!isset($res['Content-Length'])) {
continue;
}
$filesize = round($res['Content-Length']/1024,2);
}
else {
$publicPath = CMS_ROOT . 'public';
if (strpos($value, '/') !== 0) {
$publicPath .= '/';
}
$fileUrl = $publicPath . $value;
if (!file_exists($fileUrl)) {
continue;
}
$filesize = filesize($fileUrl);
$filesize /= pow(1024, 1);
}
if ($filesize > $maxImgSize) {
$imgTagArr[$key] = str_replace("data-src='{$value}'", '', $imgTagArr[$key]);
$imgTagReplace = str_replace($value, '/system_file/loading.png', $imgTagArr[$key]);
$replaceStr = " data-src='{$value}' ";
$imgTagReplace = substr_replace($imgTagReplace, $replaceStr, 4, 0);
$oldImgTagArr[] = $imgTagArr[$key];
$newImgTagArr[] = $imgTagReplace;
}
}
if (!empty($oldImgTagArr)) {
$html = str_replace($oldImgTagArr, $newImgTagArr, $html);
}
$jsStr = '
<script id="loadingJs" src="/system_file/js/huocms-loading.js"></script>
</head>';
if (strpos($html, '/system_file/js/huocms-loading.js') === false) {
$html = str_replace('</head>', $jsStr, $html);
}
$html = \phpQuery::newDocument($html);
// 根据section的顺序确定各个部分插入的顺序
$html['head #viewHelp']->remove();//移除可视化时用于方便界面编辑的css
$blockHtmlArr = [];
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang'],
'is_active' => 1
];
$Theme = new Theme();
$themeFileModel = new ThemeFile();
$theme = $Theme->getActiveTheme($where)['data']->toArray();
$themeName = $theme['theme'];
$BigField = new BigField();
$version = $BigField->where([
['seller_id', '=', $this->admin['seller_id']],
['theme_id', '=', $theme['id']],
])->max('version');
$version = $version ?: 0;
$version++;
$version = intval($version);
\phpQuery::each($html['body .huocms-block'], function ($key, $item) use ($html, &$blockHtmlArr, $param, $themeName, $saveType, $theme, $version) {
$domObj = $html['.huocms-block:eq('.$key.')'];
$includePath = $domObj->attr('data-inclue-path'); // 自己有包含路径或某个子元素有包含路径,需要将代码替换掉
$components = $domObj->find('.huocms-components');
foreach ($components as $obj) {
$subIncludePath = pq($obj)->attr('data-inclue-path');
if (!empty($subIncludePath)) {
if (pq($obj)->hasClass('huocms-static-edit')) {
$content = pq($obj)->htmlOuter();
$themeFileName = "{$subIncludePath}.html";
$filename = $subIncludePath;
ThemeService::saveThemeFileHistory($param['website_id'], $param['lang'], $theme, $this->admin['seller_id'], $themeFileName, $filename, $content, '', '', $version);
}
$includeHtml = '
{include file="'.$subIncludePath.'"}
';
$next = pq($obj)->next();
$parent = pq($obj)->parent();
if (empty($next->html())) {
pq($obj)->remove();
$parent->append($includeHtml);
} else {
pq($obj)->remove();
$next->before($includeHtml);
}
}
}
$blockHtml = '';
if (!empty($includePath)) {
if ($domObj->hasClass('huocms-static-edit')) {
$content = $domObj->htmlOuter();
$themeFileName = "{$includePath}.html";
$filename = $includePath;
ThemeService::saveThemeFileHistory($param['website_id'], $param['lang'], $theme, $this->admin['seller_id'], $themeFileName, $filename, $content, '', '', $version);
}
$domObj = '
{include file="'.$includePath.'"}
';
$blockHtml = $domObj;
$blockHtmlArr[] = $blockHtml;
return $item;
}
$tmpTemplate = $domObj->attr('data-huocms-block');
$blockId = $domObj->attr('data-huocms-blockid');
$blockType = $this->getBlockType($domObj);
// echo $blockType . '<br/>';
if (!empty($tmpTemplate) && !empty($blockId)) {
$blockTemplatePath = CMS_ROOT . $this->designBase . DIRECTORY_SEPARATOR . $tmpTemplate;
$blockHtml = file_get_contents($blockTemplatePath);
$blockHtml = $this->replaceHtml($blockType, $blockId, $blockHtml, $domObj);
} else {
$blockHtml = $domObj;
}
$blockHtmlArr[] = $blockHtml;
return $item;
});
$html['body .huocms-block']->remove();
$html['body #think_page_trace']->remove();
$html['body #think_page_trace_open']->remove();
$blockHtmlArr = array_reverse($blockHtmlArr);
foreach ($blockHtmlArr as $value) {
$html['body']->prepend($value);
}
// exit();
//保存生成的html
$pathInfo = pathinfo($param['file']);
$filename = $pathInfo['filename'];
$filePath = CMS_ROOT . "public/themes/website/{$param['website_id']}/{$param['lang']}/{$themeName}/{$filename}.html";
$viewFilePath = CMS_ROOT . "public/themes/hc_original/{$themeName}/{$filename}.html";
// 替换SEO
$html['head meta[name="description"]']->remove();
$html['head meta[name="keywords"]']->remove();
$html['head title']->remove();
$html['head']->prepend($this->getSeoHtml());
$themeFileName = "{$filename}.html";
// 保存至版本数据库中
$content = $html->html();
ThemeService::saveThemeFileHistory($param['website_id'], $param['lang'], $theme, $this->admin['seller_id'], $themeFileName, $filename, $content, '', '', $version);
if ($saveType == 1) {
echo "保存成功 !";
return;
}
//更新theme_file
$themeService = new ThemeService();
$suffix = config('view.view_suffix');
$res = $themeService->publishThemeFile($theme['id'],$param['website_id'],$this->admin['seller_id'],$suffix, $version);
$res = $res->getData();
if ($res['code'] != 0) {
echo $res['msg'];
return;
}
$themeFileModel->updateThemeFile([
['file', '=', $themeFileName],
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
], ['is_design' => 1, 'design_path' => $themeFileName]);
echo "发布成功 !";
return;
}
protected function getBlockType($node) {
$classes = explode( ' ', $node->attr('class'));
$blockType = '';
foreach ($classes as $class) {
if ($class != 'huocms-components' && $class != 'huocms-block') {
$blockType = $class;
break;
}
}
return $blockType;
}
public function replaceHtml($blockType, $id, $html, $node)
{
if (strpos($blockType, 'block') !== false) {
// 替换模板里的navID
$blockHtml = str_replace('hcTaglib:category id="1"', 'hcTaglib:category id="' . $id . '"', $html);
} elseif ($blockType == 'huocms-nav') {
// 替换模板里的navID
$blockHtml = str_replace('hcTaglib:nav cid="2"', 'hcTaglib:nav cid="' . $id . '"', $html);
} elseif ($blockType == 'huocms-banner') {
// 替换模板里的navID
$blockHtml = str_replace('hcTaglib:slide cid="1"', 'hcTaglib:slide cid="' . $id . '"', $html);
} else {
$blockHtml = $node;
}
return $blockHtml;
}
public function getSeoHtml()
{
return '
<title>{$current_cate.seo_title|default=$current_cate.title}</title>
<meta name="description" content="{$current_cate.seo_description|default=$current_cate.title}">
<meta name="keywords" content="{$current_cate.seo_keywords|default=$current_cate.title}">';
}
// setting接口
public function getSetting()
{
}
/**
* 单个链接列表接口
* 基础链接:客服链接,icp备案链接,公安备案链接
* 栏目链接
* 内容链接
* @return Json
*/
public function getLinkList()
{
$param = $this->request->param();
// 数据验证
try {
validate(WebsiteSettingValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang'],
];
$websiteSetting = new WebsiteSetting();
$res = $websiteSetting->getWebsiteSetting($where);
$res['data'] = $res['data']->toArray();
$res['data']['setting'] = json_decode($res['data']['setting'], true);
$link = [
'name' => '基本链接',
'value' => '',
'child' => [
[
'name' => '客服链接',
'value' => $res['data']['setting']['customer_code'] ?? '',
],
[
'name' => 'icp备案链接',
'value' => $res['data']['setting']['icp_link'] ?? '',
],
[
'name' => '公网安备链接',
'value' => $res['data']['setting']['gwa_link'] ?? '',
],
]
];
$categoryList = $this->categoryList();
$cate = [
'name' => '栏目链接',
'value' => '',
'child' => $categoryList,
];
$data = [
'link' => $link,
'cate' => $cate,
'content' => [],
];
return jsonReturn(0, '成功', $data);
}
// 导航接口
public function getNavList()
{
}
// 友情链接接口
public function getFriendLink()
{
}
public function getCategoryList()
{
return jsonReturn(0, '成功', $this->categoryList());
}
public function categoryListSetWhere(&$where, $type)
{
switch ($type) {
case 'suq_block1':
case 'suq_block2_contact':
case 'suq_block3':
case 'suq_block4':
case 'suq_block5':
case 'suq_block6':
case 'suq_footer':
$where[] = ['type', 'in', [2,3]];
$where[] = ['id', '>', 1];
break;
case 'suq_block2':
$where[] = ['id', '>', 1];
break;
default:
break;
}
}
// 栏目列表接口
protected function categoryList()
{
$param = $this->request->param();
$where = [
['seller_id', '=', $this->admin['seller_id']],
['website_id', '=', $param['website_id']],
];
$lang = $param['lang'] ?? '';
if (empty($lang)) {
$lang = 'zh';
}
$Website = new Website();
$website = $Website->getWebsite(['id' => $param['website_id'], 'seller_id' => $this->admin['seller_id']])['data'];
$domain = $website['domain'];
if ($lang != 'zh') {
$domain = $domain . '/' . $lang;
}
$where[] = ['lang', '=', $lang];
if(isset($param['type'])) {
$this->categoryListSetWhere($where, $param['type']);
}
$cateModel = new Category();
$categoryList = $cateModel->where($where)->field('id,type,title,alias')->select()->each(function (&$item) use ($domain) {
if ($item['type'] == 1 || $item['type'] == 4) {
if (!empty($item['alias'])) {
if ($item['alias'] == '/') {
$item['fullUrl'] = 'http://' . $domain;
} else {
$item['fullUrl'] = 'http://' . $domain . $item['alias'] . '.' . config('route.url_html_suffix');
}
} else {
$item['fullUrl'] = 'http://' . $domain . '/list/index/' . $item['id'] . '.' . config('route.url_html_suffix');
}
} else if ($item['type'] == 3) {
$item['fullUrl'] = $item['alias'];
} else {
$item['fullUrl'] = 'javascript:;';
}
});
return $categoryList->toArray();
}
// 栏目内容接口
public function getCategoryData()
{
$param = $this->request->param();
$categoryModel = new Category();
$contentModel = new SubContent();
$category = [];
$subCategory = [];
//获取此栏目数据和下面所有子栏目数据,并且拿到所有栏目下的内容数据
if ($param['type'] == 'suq_block1') {
//获取选择栏目的的标题和副标题,再获取子栏目的名称和缩略图
//最后获取子栏目的内容列表
$category = $categoryModel->getCategory([
['id', '=', $param['category_id']],
['type', 'in', [2,3]],
], [], 'id, type, title, sub_title, alias')['data'];
$subCategory = $categoryModel->where([
['parent_id', '=', $param['category_id']],
['type', 'in', [2,3]],
])->with(['thumbnail'])->field('id, type, title, sub_title, desc, thumbnail, alias')->select();
foreach ($subCategory as &$subCate) {
$subCate['content'] = $contentModel->alias('a')
->join('category_sub_content b', 'b.sub_content_id = a.id', 'left')
->with(['thumbnail'])
->where([
['b.category_id', '=', $subCate['id']]
])
->field('a.id, title, thumbnail')
->select();
}
} else if ($param['type'] == 'suq_block2' ||
$param['type'] == 'suq_block3' ||
$param['type'] == 'suq_block4' ||
$param['type'] == 'suq_footer') {
$category = $categoryModel->getCategory([
['id', '=', $param['category_id']],
['type', 'in', [2,3]],
], ['thumbnail'], 'id, type, title, sub_title, desc, alias, thumbnail, description')['data'];
$contentList = $contentModel->alias('a')
->join('category_sub_content b', 'b.sub_content_id = a.id', 'left')
->with(['thumbnail'])
->where([
['b.category_id', '=', $param['category_id']]
])
->field('a.id, title, sub_title, description, thumbnail')
->select()->toArray();
foreach ($contentList as &$value) {
if (isset($value['thumbnail']['url'])) {
$value['thumbnail_url'] = $value['thumbnail']['url'];
}
if (in_array($category['type'], [1,2,4])) {
$value['href'] = hcUrl('detail/index', ['cid' => $param['category_id'], 'id' => $value['id']]);
} else {
$value['href'] = $category['alias'];
}
}
$category['content'] = $contentList;
} else if ($param['type'] == 'suq_block5' ||
$param['type'] == 'suq_block6' ||
$param['type'] == 'suq_block2_contact') {
$category = $categoryModel->getCategory([
['id', '=', $param['category_id']],
['type', 'in', [2,3]],
], ['thumbnail'], 'id, type, title, sub_title, desc, alias, thumbnail, description, seo_description')['data'];
}
$returnData = [
'category' => $category,
'subCategory' => $subCategory,
];
return jsonReturn(0, '成功', $returnData);
}
// 内容列表接口
public function getContentList()
{
}
// 内容详情接口
// 幻灯片接口
// 广告接口
// 获取模板文件历史版本
public function getHistoryList()
{
$param = $this->request->param();
$initPage = $param['init_page'] ?? 'index';
$initPageName = $initPage . '.html';
$param['limit'] = $param['limit'] ?? 100;
$themeModel = new Theme();
$themeId = $themeModel->where([
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
['is_active', '=', 1],
])->value('id');
if (empty($themeId)) {
return jsonReturn(-1, '当前站点没有激活的模板,请先去安装模板');
}
// 获取当前模板文件id
$themeFileModel = new ThemeFile();
$themeFileId = $themeFileModel->where([
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
['theme_id', '=', $themeId],
['file', '=', $initPageName],
])->value('id');
if (empty($themeFileId)) {
return jsonReturn(-2, '模板文件不存在');
}
$bigFieldModel = new BigField();
$list = $bigFieldModel->alias('a')
->join('admin b', 'b.id = a.admin_id', 'left')
->join('theme_file c', 'a.theme_file_id = c.id')
->where([
['a.theme_id', '=', $themeId],
['a.theme_file_id', '=', $themeFileId],
])->field('a.*, b.name as admin_name, c.file')->order('id desc')->paginate($param['limit']);
return json(pageReturn(dataReturn(0, '', $list)));
}
public function setPreviewPage()
{
$param = $this->request->param();
$initPage = $param['init_page'] ?? 'index';
$initPageName = $initPage . '.html';
$version = $param['version'] ?? 1; // 将此版本的所有文件都塞入view下
$themeModel = new Theme();
$theme = $themeModel->where([
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
['is_active', '=', 1],
])->field('id, theme')->find();
if (empty($theme['id'])) {
return jsonReturn(-1, '当前站点没有激活的模板,请先去安装模板');
}
ThemeService::updatePreviewFile($param, $version, $theme);
session('history-view', time());
return jsonReturn(0, '预览初始化成功');
}
}

27
app/controller/backend/ExcelController.php

@ -0,0 +1,27 @@
<?php
namespace app\controller\backend;
use think\facade\Cache;
use think\response\Json;
class ExcelController
{
/**
* 下载
* @return array|Json
*/
public function downInquiryExcel()
{
$token = input('param.token');
$data = Cache::get( $token );
// Cache::delete($token);
if(!empty($data)){
return createDownloadExcel( $data['file_name'], $data['sheet_name'], $data['head'], $data['data'], $data['matched']);
}
return jsonReturn(-1,'已过期');
}
}

585
app/controller/backend/FormController.php

@ -0,0 +1,585 @@
<?php
namespace app\controller\backend;
use app\model\DiyForm;
use Overtrue\Pinyin\Pinyin;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\facade\Cache;
use think\facade\Db;
class FormController extends BaseController
{
public function index(DiyForm $diyFormModel)
{
$limit = input('param.limit');
$name = input('param.name');
$where[] = ['is_del', '=', 1];
$where[] = ['seller_id', '=', $this->admin['seller_id']];
if (!empty($name)) {
$where[] = ['name', 'like', '%' . $name . '%'];
}
try {
$list = $diyFormModel->where($where)->order('id desc')->paginate($limit);
} catch (\Exception $e) {
return jsonReturn(-1, $e->getMessage());
}
return json(pageReturn(dataReturn(0, lang('成功'), $list)));
}
public function info(DiyForm $diyFormModel)
{
$id = $this->request->param('id');
$info = $diyFormModel->where('id', $id)->find();
if (empty($info)) {
return jsonReturn(-3, lang('该表单不存在'));
}
return jsonReturn(0, lang('获取成功'), $info);
}
public function add(DiyForm $diyFormModel)
{
$param = input('post.');
if (empty($param['name'])) {
return jsonReturn(-1, lang('请输入表单名称'));
}
try {
$has = $diyFormModel->where('name', $param['name'])->find();
if (!empty($has)) {
return jsonReturn(-3, lang('该表单已经存在'));
}
$pinyinModel = new Pinyin();
$tableName = $pinyinModel->abbr($param['name']);
$has = $diyFormModel->where('table', $tableName)->find();
if (!empty($has)) {
$tableName = $tableName . '_' . uniqid();
}
$param['seller_id'] = $this->admin['seller_id'];
$param['table'] = $tableName;
$param['code'] = uniqid(); // 表单唯一标识
$param['create_time'] = date('Y-m-d H:i:s');
$diyFormModel->insert($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
return jsonReturn(0, lang('添加成功'));
}
public function edit(DiyForm $diyFormModel)
{
$param = input('post.');
if (empty($param['name'])) {
return jsonReturn(-1, lang('请输入表单名称'));
}
try {
$has = $diyFormModel->where('name', $param['name'])->where('id', '<>', $param['id'])->find();
if (!empty($has)) {
return jsonReturn(-3, lang('该表单已经存在'));
}
$pinyinModel = new Pinyin();
$tableName = $pinyinModel->abbr($param['name']);
$has = $diyFormModel->where('table', $tableName)->where('id', '<>', $param['id'])->find();
if (!empty($has)) {
$tableName = $tableName . '_' . uniqid();
}
$param['table'] = $tableName;
$param['update_time'] = date('Y-m-d H:i:s');
$diyFormModel->where('id', $param['id'])->update($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
return jsonReturn(0, lang('编辑成功'));
}
public function del(DiyForm $diyFormModel)
{
$id = input('param.id');
try {
$info = $diyFormModel->where('id', $id)->find();
if ($info['status'] == 2) {
return jsonReturn(-1, lang('该表单已经发布,请先卸载'));
}
$diyFormModel->where('id', $id)->delete();
} catch (\Exception $e) {
return jsonReturn(-2, $e->getMessage());
}
return jsonReturn(0, lang('删除成功'));
}
public function detail(DiyForm $diyFormModel)
{
$param = input('param.');
try {
$info = $diyFormModel->where('id', $param['id'])->find();
$formJson = json_decode($info['design_content'], true);
$field2Dict = [];
$header = [];
foreach ($formJson as $vo) {
if (!isset($vo['field'])) {
continue;
}
$header[] = [
'label' => $vo['title'],
'property' => $vo['field'],
'type' => $vo['type'],
'options' => $vo['options'] ?? ''
];
// 为了方便字典翻译,优化前端显示
if (isset($vo['options'])) {
$dictDataMap = [];
foreach ($vo['options'] as $k => $v) {
$dictDataMap[$v['value']] = $v['label'];
}
$field2Dict[$vo['field']] = $dictDataMap;
}
if ($vo['type'] == 'switch') {
$dictDataMap = [];
$dictDataMap[$vo['props']['activeValue']] = $vo['props']['activeText'];
$dictDataMap[$vo['props']['inactiveValue']] = $vo['props']['inactiveText'];
$field2Dict[$vo['field']] = $dictDataMap;
}
}
$param['queryParams'] = json_decode($param['queryParams'], true);
if (!empty($param['queryParams']) &&
!empty($param['queryParams']['condition']) &&
!empty($param['queryParams']['childTips'])) {
$where = $this->buildWhere($param['queryParams']['childTips']);
if ($param['queryParams']['condition'] == 'and') {
$data = Db::table(makeFormTable($info['table']))->where($where)
->order('id', 'desc')->paginate($param['limit']);
} else if ($param['queryParams']['condition'] == 'or') {
$data = Db::table(makeFormTable($info['table']))->whereOr($where)
->order('id', 'desc')->paginate($param['limit']);
}
} else {
$data = Db::table(makeFormTable($info['table']))
->order('id', 'desc')->paginate($param['limit']);
}
$data = $data->each(function ($item) use ($field2Dict) {
foreach ($item as $key => $vo) {
if (isset($field2Dict[$key])) {
$showValueMap = [];
$valueMap = explode(',', $vo);
foreach ($valueMap as $valueKey) {
$showValueMap[] = isset($field2Dict[$key][$valueKey]) ? $field2Dict[$key][$valueKey] : $valueKey;
}
$item[$key] = implode(',', $showValueMap);
}
}
return $item;
});
} catch (\Exception $e) {
return jsonReturn(-3, $e->getMessage() . $e->getLine());
}
return jsonReturn(0, 'success', [
'header' => $header,
'data' => $data->getCollection(),
'total' => $data->total()
]);
}
public function deploy(DiyForm $diyFormModel)
{
$id = input('param.id');
$info = $diyFormModel->where('id', $id)->find();
if ($info['status'] == 2) {
return jsonReturn(-1, lang('该表单已经发布,无需再次发布'));
}
if (empty($info['design_content'])) {
return jsonReturn(-2, lang('请先完成表单设计'));
}
$tableName = makeFormTable($info['table']);
$title = $info['name'];
$column = '';
$columnMap = json_decode($info['design_content'], true);
foreach ($columnMap as $key => $vo) {
if (empty($vo['field'])) {
continue;
}
// 强迫症为了dd打印的时候格式对其,其实没啥意义
$tab = '';
if ($key > 0) {
$tab = ' ';
}
$column .= $tab . '`' . $vo['field'] . '` varchar(255) NULL DEFAULT NULL COMMENT "' . $vo['title'] . '",' . PHP_EOL;
}
try {
$sql = <<<EOL
CREATE TABLE `{$tableName}` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT "id",
{$column} `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",
`update_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT "更新时间",
`visitor_id` varchar(255) DEFAULT '' COMMENT '前端标识',
`sub_id` int(11) DEFAULT 0 COMMENT '关联内容id',
`ip` varchar(20) DEFAULT '',
`user_agent` varchar(255) DEFAULT '' COMMENT '浏览器标识',
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 COMMENT='{$title}';
EOL;
Db::execute($sql);
$diyFormModel->where('id', $id)->update([
'status' => 2,
'update_time' => date('Y-m-d H:i:s')
]);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
return jsonReturn(0, lang('部署成功'));
}
public function undeploy(DiyForm $diyFormModel)
{
$id = input('param.id');
$info = $diyFormModel->where('id', $id)->find();
if ($info['status'] != 2) {
return jsonReturn(-1, lang('该表单尚未发布,无法卸载'));
}
try {
Db::query('DROP TABLE IF EXISTS `' . makeFormTable($info['table']) . '`');
$diyFormModel->where('id', $id)->update([
'status' => 1,
'update_time' => date('Y-m-d H:i:s')
]);
} catch (\Exception $e) {
return jsonReturn(-1, $e->getMessage());
}
return jsonReturn(0, lang('卸载成功'));
}
// 获取问题和答案详情
public function quesDetail(DiyForm $diyFormModel)
{
$param = input('param.');
try {
$info = $diyFormModel->where('id', $param['id'])->find();
$formJson = json_decode($info['design_content'], true);
$field2Dict = [];
$header = [];
foreach ($formJson as $vo) {
if (!isset($vo['field'])) {
continue;
}
$header[$vo['field']] = [
'label' => $vo['title'],
'property' => $vo['field'],
'type' => $vo['type'],
'options' => []
];
if ($vo['type'] == 'switch') {
$header[$vo['field']]['options'][$vo['props']['activeValue']] = ['label' => $vo['props']['activeText']];
$header[$vo['field']]['options'][$vo['props']['inactiveValue']] = ['label' => $vo['props']['inactiveText']];
}
if (!empty($vo['options'])) {
foreach ($vo['options'] as $v) {
$header[$vo['field']]['options'][$v['value']] = $v;
}
}
}
$param['queryParams'] = json_decode($param['queryParams'], true);
if (!empty($param['queryParams']) &&
!empty($param['queryParams']['condition']) &&
!empty($param['queryParams']['childTips'])) {
$where = $this->buildWhere($param['queryParams']['childTips']);
if ($param['queryParams']['condition'] == 'and') {
$data = Db::table(makeFormTable($info['table']))->where($where)->select();
} else if ($param['queryParams']['condition'] == 'or') {
$data = Db::table(makeFormTable($info['table']))->whereOr($where)->select();
} else {
$data = [];
}
} else {
$data = Db::table(makeFormTable($info['table']))->select();
}
$newData = [];
$quesNum = 1;
foreach ($header as $key => $value) {
foreach ($data as $item) {
if (!isset($item[$key])) {
continue;
}
$newData[$key]['quesNum'] = $quesNum;
$newData[$key]['type'] = $value['type'];
$newData[$key]['ques'] = $value['label'];
if (!isset($newData[$key]['total'])) {
$newData[$key]['total'] = 0;
}
if ($value['type'] == 'input' || $value['type'] == 'timePicker' || $value['type'] == 'datePicker') {
$newData[$key]['type_name'] = '填空';
$newData[$key]['type'] = 'list';
// 填空题 ,填的才是答案,使用列表展示
$newData[$key]['total']++;
$newData[$key]['detail'][] = [
'id' => $item['id'],
'answer' => $item[$key],
];
} else if ($value['type'] == 'radio' || $value['type'] == 'select' || $value['type'] == 'switch') {
$newData[$key]['type_name'] = '单选';
$newData[$key]['type'] = 'radio';
// 单选题 使用饼图展示,需要计算百分比
$newData[$key]['total']++;
if (isset($newData[$key]['detail'][$item[$key]]['count'])) {
$newData[$key]['detail'][$item[$key]]['count']++;
} else {
$newData[$key]['detail'][$item[$key]]['answer'] = $value['options'][$item[$key]]['label'];
$newData[$key]['detail'][$item[$key]]['count'] = 1;
}
} else if ($value['type'] == 'slider' || $value['type'] == 'rate') {
$newData[$key]['type_name'] = '滑块/打分';
$newData[$key]['type'] = 'rate';
$newData[$key]['total']++;
if (isset($newData[$key]['detail'][$item[$key]]['count'])) {
$newData[$key]['detail'][$item[$key]]['count']++;
} else {
$newData[$key]['detail'][$item[$key]]['answer'] = $item[$key];
$newData[$key]['detail'][$item[$key]]['count'] = 1;
}
} else if ($value['type'] == 'checkbox') {
$newData[$key]['type_name'] = '多选';
// 多选题 默认使用柱状图,需要计算百分比
$answer = explode(',', $item[$key]);
foreach ($answer as $v) {
$newData[$key]['total']++;
if (isset($newData[$key]['detail'][$v]['count'])) {
$newData[$key]['detail'][$v]['count']++;
} else {
$newData[$key]['detail'][$v]['answer'] = $value['options'][$v]['label'];
$newData[$key]['detail'][$v]['count'] = 1;
}
}
}
}
$quesNum++;
}
// 整理成前端echart需要的数据
foreach ($newData as &$value) {
if ($value['type'] == 'list') {
continue;
}
$newDetail = [];
foreach ($value['detail'] as $v) {
$newDetail['data'][] = [
'name' => $v['answer'],
'value' => $v['count'],
];
$newDetail['nameArr'][] = $v['answer'];
$newDetail['valueArr'][] = $v['count'];
}
$value['detail'] = $newDetail;
}
// dd($newData);
} catch (\Exception $e) {
return jsonReturn(-3, $e->getMessage() . $e->getLine());
}
return jsonReturn(0, lang('成功'), [
'header' => array_values($header),
'data' => $newData,
'total' => count($newData),
]);
}
private function buildWhere($childTips)
{
$where = [];
foreach ($childTips as $vo) {
switch ($vo['rule']) {
case 'eq':
$where[] = [$vo['field'], '=', $vo['val']];
break;
case 'like':
$where[] = [$vo['field'], 'like', '%' . $vo['val'] . '%'];
break;
case 'left_like':
$where[] = [$vo['field'], 'like', '%' . $vo['val']];
break;
case 'right_like':
$where[] = [$vo['field'], 'like', $vo['val'] . '%'];
break;
case 'neq':
$where[] = [$vo['field'], '<>', $vo['val']];
break;
case 'gt':
$where[] = [$vo['field'], '>', $vo['val']];
break;
case 'gte':
$where[] = [$vo['field'], '>=', $vo['val']];
break;
break;
case 'lt':
$where[] = [$vo['field'], '<', $vo['val']];
break;
case 'lte':
$where[] = [$vo['field'], '<=', $vo['val']];
break;
}
}
return $where;
}
/**
* 表单下载
*/
public function exportForm()
{
$id = $this->request->param('id/d', 0);
$queryParams = $this->request->param('queryParams', '');
$queryParams = !empty($queryParams) ? json_decode($queryParams, true) : [];
//通过id找到表单的表名
$diyFormModel = new DiyForm();
$tableInfo = $diyFormModel->where([
'id' => $id,
'is_del' => 1,
'seller_id' => $this->admin['seller_id']
])->field('table,id')->find();
if (empty($tableInfo)) {
return jsonReturn(-1, '表单不存在');
}
$tableName = $tableInfo['table'];
$tableName = makeFormTable($tableName);
$tableStruct = Db::query("SHOW FULL COLUMNS FROM {$tableName}");
$columnList = $keys = $title = [];
foreach ($tableStruct as $struct) {
if (in_array($struct['Field'], ['id', 'sub_id'])) {
continue;
}
$title[] = $struct['Comment'];
$keys[] =$struct['Field'];
}
$list = [];
if (!empty($queryParams) &&
!empty($queryParams['condition']) &&
!empty($queryParams['childTips'])) {
$where = $this->buildWhere($queryParams['childTips']);
if ($queryParams['condition'] == 'and') {
$list = Db::table($tableName)->where($where)
->order('id', 'desc')->select();
} else if ($queryParams['condition'] == 'or') {
$list = Db::table($tableName)->whereOr($where)
->order('id', 'desc')->select();
}
} else {
$list = Db::table($tableName)
->order('id', 'desc')->select();
}
$spreadsheet = new Spreadsheet();
$activeWorksheet = $spreadsheet->getActiveSheet();
$total = count($keys);
for ($i = 0; $i < $total; $i++) {
$column = getSheetColLabel($i + 1);
$cell = $column . '1';
$columnList[] = $column;
$activeWorksheet->setCellValue($cell, $title[$i]);
}
foreach ($list as $k => $v) {
for ($x = 0; $x < $total; $x++) {
$activeWorksheet->setCellValue($columnList[$x] . ($k + 2), $v[$keys[$x]]);
}
}
if(!file_exists('excel')){
mkdir("excel",0777,true);
}
$writer = new Xlsx($spreadsheet);
$filename = 'form'.date("YmdHis").'.xlsx';
$writer->save("./excel/".$filename);
return jsonReturn(0, '成功', ['url' => '/excel/'.$filename]);
}
}

241
app/controller/backend/GatherController.php

@ -0,0 +1,241 @@
<?php
namespace app\controller\backend;
use app\service\ContentService;
use QL\QueryList;
use think\facade\Db;
use think\facade\Log;
class GatherController extends BaseController
{
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function test()
{
// $articleList = $this->futuresList();
$articleList = [
[
"title" => "广州期货交易所关于修改《广州期货交易所工业硅期货、期权业务细则》的通知",
"link" => "http://www.gfex.com.cn/gfex/tzts/202309/bd480b23698f48c484e72ff962d29ed1.shtml",
"publish_time" => "2023-09-01",
"author" => "广期所",
"seo_description" => "广州期货交易所关于修改《广州期货交易所工业硅期货、期权业务细则》的通知",
]
];
$date = date('Y-m-d');
foreach($articleList as &$article){
// 判断文章是否存在
if($this->checkContentIsset($article)){
continue; // 存在跳过
}
// 查询详情
if($date >= $article['publish_time']){
$article['content'] = $this->futuresDetail($article['link']);
}
// 保存到文章
$res = $this->insertArticle($article);
if($res['code'] != 0){
Log::error('采集失败:' . $article['title'] . ',原因:' . $res['msg']);
}
}
}
/**
* 插入文章
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function insertArticle($article): array
{
$param = config('gather.article');
$article['category_id'] = $param['category_id'][0];
$article['status'] = 0;
$param['sub_content'] = $article;
$service = new ContentService();
return $service->saveContent($param);
}
// 判断文章是否存在
public function checkContentIsset($article): bool
{
$query = "SELECT id,title FROM hc_sub_content WHERE title = '{$article['title']}' and author = '{$article['author']}' and DATE(publish_time) = '{$article['publish_time']}' ";
$content = Db::query($query);
return !empty($content);
}
// 中金所采集
public function financeList(): array
{
return $this->financeQueryList();
}
// 中金所带列表采集
public function financeQueryList($page = 1): array
{
$code = str_replace('QueryList','',__FUNCTION__);
$url = config('gather.url.' . $code);
if($page > 1){
// 翻页
$pattern = '(_\d+)?.html';
$url = preg_replace($pattern,'-'.$page.'.html',$url);
}
$domain = config('gather.domain.' . $code);
$author = config('gather.author.' . $code);
$title = config('gather.title.' . $code);
$ql = QueryList::get($url);
$article = [];
$ql->find('.notice_list ul')->children('li')->map(function ($item)use(&$article,$domain,$author,$title){
$article[] = [
'title' => $title . $item->find('.list_a_text')->text(),
'link' => $domain . $item->find('.list_a_text')->href,
'publish_time' => $item->find('.comparetime')->text(),
'author' => $author,
'seo_description' => $item->find('.list_a_text')->text(),
];
});
return $article;
}
// 中金所详情
public function financeDetail($url): string
{
return QueryList::get($url)->find('.jysggnr')->html();
}
// 大商所采集
public function commodityList(): array
{
return $this->commodityQueryList();
}
// 大商所带翻页采集
public function commodityQueryList($page=1): array
{
$code = str_replace('QueryList','',__FUNCTION__);
$url = config('gather.url.' . $code);
if($page > 1){
// 翻页
$pattern = '(-\d+)?.html';
$url = preg_replace($pattern,'-'.$page.'.html',$url);
}
$domain = config('gather.domain.' . $code);
$author = config('gather.author.' . $code);
$title = config('gather.title.' . $code);
$ql = QueryList::get($url);
$article = [];
$ql->find('.list_tpye06')->children('li')->map(function ($item)use(&$article,$domain,$author,$title){
$article[] = [
'title' => $title . $item->find('a')->text(),
'link' => $domain . $item->find('a')->href,
'publish_time' => $item->find('span')->text(),
'author' => $author,
'seo_description' => $title . $item->find('a')->text(),
];
});
return $article;
}
// 大商所详情
public function commodityDetail($url): string
{
// 序列号
$serialNum = QueryList::get($url)->find('.tit_header .cj_date')->text();
$serialStr = <<<html
<p style="line-height: 2; margin-bottom: 1em;align-content: center">$serialNum</p>
html;
return $serialStr ."\r\n". QueryList::get($url)->find('.detail_content')->html();
}
// 期货中心列表
public function futuresList(): array
{
return $this->futuresQueryList();
}
// 期货中心带翻页采集
public function futuresQueryList($page = 1): array
{
$code = str_replace('QueryList','',__FUNCTION__);
$url = config('gather.url.' . $code);
if($page > 1){
// 翻页
$pattern = '(_\d+)?.shtml';
$url = preg_replace($pattern,'_'.$page.'.html',$url);
}
$domain = config('gather.domain.' . $code);
$author = config('gather.author.' . $code);
$title = config('gather.title.' . $code);
$ql = QueryList::get($url);
$article = [];
$ql->find('.news-list-yw ul')->children('li')->map(function ($item)use(&$article,$domain,$author,$title){
$article[] = [
'title' => $title . $item->find('a')->text(),
'link' => $domain . $item->find('a')->href,
'publish_time' => date('Y-m-d',strtotime(str_replace('.','-',$item->find('.yyMM')->text()) . '-' . $item->find('.dd')->text())),
'author' => $author,
'seo_description' => $item->find('p')->text(),
];
});
return $article;
}
// 期货中心详情
public function futuresDetail($url): string
{
return QueryList::get($url)->find('ucapcontent')->html();
}
// 能源中心采集
public function energyList(): array
{
return $this->energyQueryList();
}
// 带翻页的列表页爬虫
public function energyQueryList($page =1): array
{
$code = str_replace('QueryList','',__FUNCTION__);
$url = config('gather.url.' . $code);
if($page > 1){
// 翻页
$pattern = '(_\d+)?.html';
$url = preg_replace($pattern,'_'.$page.'.html',$url);
}
$domain = config('gather.domain.' . $code);
$author = config('gather.author.' . $code);
$title = config('gather.title.' . $code);
$ql = QueryList::get($url);
$article = [];
$last = [];
$ql->find('.newslist ul')->children('li')->map(function ($item)use(&$article,$domain,&$last,$author,$title){
$tmp = [
'title' => $title . $item->find('a')->text(),
'link' => $domain . $item->find('a')->href,
'publish_time' => $item->find('span')->text(),
'author' => $author,
'seo_description' => $item->find('a')->text(),
];
$last = $tmp;
$article[] = $tmp;
});
// 翻页
// date('Y-m-d');
if($last['publish_time'] >= '2023-07-02' ){
++$page;
$article = $this->energyQueryList($page);
}
return $article;
}
// 能源中心详情
public function energyDetail($url)
{
return QueryList::get($url)->find('.details')->html();
}
}

14
app/controller/backend/GoogleStatisticsController.php

@ -0,0 +1,14 @@
<?php
namespace app\controller\backend;
use app\service\GoogleRequest;
class GoogleStatisticsController extends \app\BaseController
{
public function read() {
$googleStatistics = new GoogleRequest();
$googleStatistics->start();
}
}

154
app/controller/backend/InnerChartController.php

@ -0,0 +1,154 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelException;
use app\model\InnerChart;
use app\validate\InnerChartValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class InnerChartController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(InnerChart $innerChart): \think\response\Json
{
$limit = $this->setLimit();
$type = input('type');
$siteId = (int)input('website_id');
if(empty($type)){
$type = 1;
}
$where = [
['seller_id' ,'=', $this->admin['seller_id']],
['type' ,'=', $type],
];
if ($type != 2) {
$where[] = ['website_id','=',$siteId];
}
$keyword = input('keyword');
if($keyword){
$where[] = ['keyword','like','%'.$keyword.'%'];
}
try{
$innerList = $innerChart->where($where)->order('id','desc')->paginate($limit)->each(function(&$item){
$count = (int)$item->content()->sum('total');
$item -> count = $count;
$item -> save();
});
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return json(['code'=>0,'total'=>$innerList->total(),'msg'=>'success','data'=>$innerList->all()]);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(InnerChart $innerChart): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
if(empty($param['website_id'])){
$param['website_id'] = 0;
}
// 数据验证
try{
validate(InnerChartValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$res = $innerChart -> addInnerChart($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(InnerChart $innerChart): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('内链ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $innerChart->getInnerChart($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(InnerChart $innerChart): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(InnerChartValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
if(empty($param['website_id'])){
$param['website_id'] = 0;
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $innerChart -> updateInnerChart($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(InnerChart $innerChart): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('内链ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $innerChart->delInnerChart($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

130
app/controller/backend/InquiryCategoryController.php

@ -0,0 +1,130 @@
<?php
namespace app\controller\backend;
use \app\validate\InquiryCategoryValidate;
use app\model\InquiryCategory;
use think\exception\ValidateException;
use \think\response\Json;
class InquiryCategoryController extends BaseController
{
/**
* 获取线索列表,可按条件查询
* @return Json
*/
public function index(): Json
{
if (request()->isGet()) {
$param['seller_id'] = $this->admin['seller_id'];
$inquiryCategory = new InquiryCategory();
$res = $inquiryCategory->getInquiryCategoryList($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 询单类别查询
* @return Json
*/
public function read(): Json
{
if (request()->isGet()) {
$param = input('get.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryCategoryValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$inquiryCategory = new InquiryCategory();
$res = $inquiryCategory->getInquiryCategory($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 新增询单类别
* @return Json
*/
public function save(): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryCategoryValidate::class)->scene('save')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$inquiryCategory = new InquiryCategory();
$res = $inquiryCategory->addInquiryCategory($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 更新询单类别
* @return Json
*/
public function update(): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryCategoryValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$inquiryCategory = new InquiryCategory();
$res = $inquiryCategory->updateInquiryCategory($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 删除询单类别
* @return Json
*/
public function delete(): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryCategoryValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$inquiryCategory = new InquiryCategory();
$res = $inquiryCategory->deleteInquiryCategory($param);
return jsonReturn($res['code'], $res['msg']);
}
return jsonReturn(-2, lang('请求方法错误'));
}
}

231
app/controller/backend/InquiryController.php

@ -0,0 +1,231 @@
<?php
namespace app\controller\backend;
use app\model\Inquiry;
use app\model\RecycleBin;
use app\validate\InquiryValidate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Lang;
use think\response\Json;
class InquiryController extends BaseController {
/**
* 查询询单列表
* @return Json
*/
public function index(): Json
{
if (request()->isGet()) {
$siteId = (int)input('website_id');
if(empty($siteId)){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
'is_del' => 1,
];
$limit = $this->setLimit();
$inquiry = new Inquiry();
$res = $inquiry->getInquiryList($where,'*', $limit);
return json(pageReturn($res));
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 新增询单信息
* @return Json
* @method Post
*/
public function backendSave(): Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
if (empty($param['phone']) && empty($param['wechat']) && empty($param['email']) && empty($param['qq']) && empty($param['telphone'])) {
return jsonReturn(-4, lang('联系方式必须填写一个'));
}
try {
validate(InquiryValidate::class)->scene('save')->check($param);
} catch (ValidateException $e){
return jsonReturn(-4, $e->getMessage());
}
$inquiry = new Inquiry();
$res = $inquiry -> addInquiry($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 询单信息查询
* @return Json
* @method Post
*/
public function read(): Json
{
if (request()->isGet()) {
$param = input('get.');
try {
validate(InquiryValidate::class)->scene('read')->check($param);
} catch (ValidateException $e){
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
$inquiry = new Inquiry();
$res = $inquiry->getInquiry($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 更新询单信息
* @return Json
* @method Post
*/
public function update(): Json
{
if(request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
if (empty($param['mobile']) && empty($param['wechat']) && empty($param['email']) && empty($param['qq'])) {
return jsonReturn(-4, lang('联系方式必须填写一个'));
}
try {
validate(InquiryValidate::class)->scene('update')->check($param);
} catch (ValidateException $e){
return jsonReturn(-4, $e->getMessage());
}
$inquiry = new Inquiry();
$res = $inquiry->updateInquiry($param);
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* 删除询单信息
* @return Json
* @throws \app\exception\ModelException
*/
public function delete(): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryValidate::class)->scene('read')->check($param);
} catch (ValidateException $e){
return jsonReturn(-4, $e->getMessage());
}
$Inquiry = new Inquiry();
$inquiry = $Inquiry->getInquiry($param)['data'];
if(empty($inquiry)){
jsonReturn(-3, lang('询盘不存在'));
}
// 复制删除内容到回收站表
$recycleBin = new RecycleBin();
$binData = [
'seller_id' => $param['seller_id'],
'object_id' => $inquiry['id'],
'sub_id' => 0,
'module_id' => 0,
'table_name' => lang('询盘'),
'title' => !empty($inquiry['title']) ? $inquiry['title'] : lang('询盘内容') . $inquiry['id'],
'admin_id' => $this->admin['uid'],
'name' => $this->admin['name'],
];
$recycleBin -> addRecycleBin($binData);
$inquiry->is_del = 2;
$inquiry->delete_time = time();
$inquiry->save();
return jsonReturn(0, lang('删除成功'));
}
return jsonReturn(-2, lang('请求方法错误'));
}
public function batch_delete(): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(InquiryValidate::class)->scene('batch')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$where = [
['seller_id' ,'=', $this->admin['seller_id']],
['ids','in', $param['ids']],
];
$linkWebsite = new Inquiry();
$res = $linkWebsite->batchDeleteInquiry($where);
return jsonReturn($res['code'], $res['msg']);
}
return jsonReturn(-1, lang('请求方法错误'));
}
/**
* 导出询盘的内容
* @return
*/
public function exportInquiry()
{
if(!file_exists('excel')){
mkdir("excel",0777,true);
}
$Inquiry = new Inquiry();
$siteId = $this->request->param("website_id/d",0);
$list = $Inquiry->where(['is_del'=>1,'website_id'=>$siteId])->alias('i')->join("inquiry_category ic",'ic.id = i.inquiry_type','left')
->column('i.*,ic.name cate_name');
$title = [
'公司名称', '联系人姓名', '询盘内容', '固定电话', "号码", '邮箱', 'qq', '询盘类型', '公司地址', '企业网址', '询盘来源', '来源网址', 'ip', '预算金额', '需求说明',
];
$keys = ['company_name', 'contacts_name' , 'content', 'telphone', 'phone', 'email', 'qq', 'cate_name',
'company_address', 'company_site', 'referer', 'referer_web', 'ip', 'expect_amount', 'explain'];
$columnList = [];
$spreadsheet = new Spreadsheet();
$activeWorksheet = $spreadsheet->getActiveSheet();
$total = count($title);
for ($i=0;$i<$total;$i++){
$column = getSheetColLabel($i + 1);
$cell = $column . '1';
$columnList[] = $column;
$activeWorksheet->setCellValue($cell, $title[$i]);
}
foreach ($list as $k=>$v){
for ($x=0;$x<$total;$x++){
$activeWorksheet->setCellValue($columnList[$x] . ($k + 2),$v[$keys[$x]]);
}
}
$writer = new Xlsx($spreadsheet);
$filename = '询盘导出'.date("YmdHis").'.xlsx';
$writer->save("./excel/".$filename);
return jsonReturn(0, '成功', ['url' => '/excel/'.$filename]);
}
}

152
app/controller/backend/InquiryEmailController.php

@ -0,0 +1,152 @@
<?php
namespace app\controller\backend;
use app\model\InquiryEmail;
use app\model\WebsiteInquiryEmail;
use app\validate\InquiryEmailValidate;
use think\facade\Db;
use think\facade\Lang;
use \think\response\Json;
class InquiryEmailController extends BaseController
{
/**
* @param InquiryEmail $InquiryEmail
* @return Json
* @throws \think\db\exception\DbException
*/
public function index (InquiryEmail $InquiryEmail): Json
{
if (request()->isGet()) {
$siteId = (int)input('website_id');
if(!$siteId){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$limit = $this->setLimit();
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
];
$WebsiteInquiryEmail = new WebsiteInquiryEmail();
$ids = $WebsiteInquiryEmail->where($where)->column('inquiry_email_id');
if(empty($ids)){
return json(['code' => 0, 'msg' => 'ok', 'count' => 0, 'data' => []]);
}
$emailWhere = [
['seller_id','=',$this->admin['seller_id']],
['id','in',$ids]
];
$inquiryEmail = $InquiryEmail -> where($emailWhere)->paginate($limit)->each(function(&$item)use($WebsiteInquiryEmail){
$ids = $WebsiteInquiryEmail->where($where = [
'seller_id' => $this->admin['seller_id'],
'inquiry_email_id' => $item['id'],
])->column('website_id');
$item['website_id'] = $ids;
});
return json(['code' => 0, 'msg' => 'ok', 'count' => $inquiryEmail->total(), 'data' => $inquiryEmail->all()]);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* @return Json
*/
public function save(InquiryEmail $InquiryEmail): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(InquiryEmailValidate::class)->scene('save')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
$siteIds = $param['website_id'];
unset($param['website_id']);
Db::startTrans();
try{
$res = $InquiryEmail -> addInquiryEmail($param);
$inquiryEmail = $res['data'];
$inquiryEmail->website()->attach($siteIds,['seller_id'=>$param['seller_id']]);
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(0,lang('系统错误请重试'));
}
return json($res);
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* @return Json
*/
public function update(InquiryEmail $InquiryEmail): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(InquiryEmailValidate::class)->scene('update')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
Db::startTrans();
try{
$param['seller_id'] = $this->admin['seller_id'];
$inquiryEmail = $InquiryEmail->getInquiryEmail(['id'=>$param['id'],'seller_id'=>$param['seller_id']])['data'];
$inquiryEmail->website()->detach();
$inquiryEmail->website()->attach($param['website_id'],['seller_id'=>$param['seller_id']]);
$inquiryEmail->email = $param['email'];
if(isset($param['email'])){
$inquiryEmail->status = $param['status'];
}
$inquiryEmail->save();
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(-1,lang('系统错误请重试'));
}
return jsonReturn(0,lang('保存成功'));
}
return jsonReturn(-2, lang('请求方法错误'));
}
/**
* @return Json
*/
public function delete(InquiryEmail $InquiryEmail): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(InquiryEmailValidate::class)->scene('read')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
Db::startTrans();
try{
$inquiryEmail = $InquiryEmail->getInquiryEmail(['id'=>$param['id'],'seller_id'=>$param['seller_id']])['data'];
$inquiryEmail->website()->detach();
$inquiryEmail->delete();
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(0,lang('系统错误请重试'));
}
return jsonReturn(0, lang('删除成功'));
}
return jsonReturn(-2, lang('请求方法错误'));
}
}

153
app/controller/backend/JobCateController.php

@ -0,0 +1,153 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Job;
use app\model\JobCate;
use app\validate\JobCateValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class JobCateController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(JobCate $jobCate): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
$jobCateList = $jobCate->getJobCateList($where,$limit);
return json(pageReturn($jobCateList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(JobCate $jobCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(JobCateValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $jobCate -> addJobCate($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(JobCate $jobCate): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $jobCate->getJobCate($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(JobCate $jobCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(JobCateValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = $jobCate -> updateJobCate($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(JobCate $jobCate, Job $job): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
$seller_id = $this->admin['seller_id'];
if(!$id){
// TODO 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
// TODO 分类使用判断
$job_where = [
'job_cate_id' => $id,
'seller_id' => $seller_id,
];
$has = $job->getJob($job_where);
if ($has['code'] == 0) {
return jsonReturn(-3, lang('工作类别在使用中'));
} else {
$where = [
'id' => $id,
'seller_id' => $seller_id,
];
$res = $jobCate->softDelJobCate($where);
return json($res);
}
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

154
app/controller/backend/JobCityController.php

@ -0,0 +1,154 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Job;
use app\model\JobCity;
use app\validate\JobCityValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class JobCityController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(JobCity $jobCity): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
$jobCityList = $jobCity->getJobCityList($where,$limit);
return json(pageReturn($jobCityList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(JobCity $jobCity): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(JobCityValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $jobCity -> addJobCity($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(JobCity $jobCity): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $jobCity->getJobCity($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(JobCity $jobCity): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(JobCityValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$res = $jobCity -> updateJobCity($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(JobCity $jobCity, Job $job): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
$seller_id = $this->admin['seller_id'];
if(!$id){
// TO DO
// 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
// TODO 分类使用判断
$job_where = [
'job_city_id' => $id,
'seller_id' => $seller_id,
];
$has = $job->getJob($job_where);
if ($has['code'] == 0) {
return jsonReturn(-3, lang('工作类别在使用中'));
} else {
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $jobCity->softDelJobCity($where);
return json($res);
}
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

164
app/controller/backend/JobController.php

@ -0,0 +1,164 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelEmptyException;
use app\exception\ModelException;
use app\model\Job;
use app\model\JobCate;
use app\model\JobCity;
use app\model\Website;
use app\validate\JobValidate;
use think\exception\ValidateException;
use think\facade\Lang;
use think\response\Json;
class JobController extends BaseController
{
/**
* 显示资源列表
*
* @return Json
* @throws ModelException
*/
public function index(Job $job): Json
{
$seller_id = $this->admin['seller_id'];
$job_city_id = input('job_city_id');
$job_cate_id = input('job_cate_id');
$title = input('title');
$where = [
'seller_id' => $seller_id,
'is_del' => 1,
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
if (!empty($job_city_id)) {
$where['job_city_id'] = $job_city_id;
}
if (!empty($job_cate_id)) {
$where['job_cate_id'] = $job_cate_id;
}
if (!empty($title)) {
// 模糊查询
$jobList = $job->searchJob($where,$title, $limit);
} else {
$jobList = $job->getJobList($where,$limit);
}
return json(pageReturn($jobList));
}
/**
* 保存新建的资源
*
* @return Json
* @throws ModelException
*/
public function save(Job $job): Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(JobValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$res = $job -> addJob($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return Json
* @throws ModelException
* @throws ModelEmptyException
*/
public function read(Job $job): Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('不能为空'));
}
$siteId = (int)input('website_id');
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
];
$jobRes = $job->getJob($where,['jobCity','jobCity'])['data']->toArray();
return jsonReturn(0, lang('查询成功'), $jobRes);
}
/**
* 保存更新的资源
* @return Json
* @throws ModelException
*/
public function update(Job $job): Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(JobValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
'website_id' => $param['website_id']
];
$res = $job -> updateJob($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws ModelException
*/
public function delete(Job $job): Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('职位ID不能为空'));
}
$siteId = (int)input('website_id');
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
];
$res = $job->delJob($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

356
app/controller/backend/KeywordController.php

@ -0,0 +1,356 @@
<?php
namespace app\controller\backend;
use app\model\Keyword;
use app\model\KeywordQuery;
use app\model\Website;
use app\service\ExcelService;
use app\service\KeywordService;
use app\service\MonitorService;
use app\validate\KeywordValidate;
use think\exception\ValidateException;
use think\facade\Lang;
use think\response\Json;
class KeywordController extends BaseController
{
public $header = ['关键词名称','关键词链接','关键词位置','关键词状态'];
public $map = ['name','url','position','status'];
/**
* 读取数据列表
* @return Json
* @method Post
* @error_number 0: 成功, -1: 数据库查询失败, -2: 请求方法错误, -3: 数据重复, -4:数据缺少或校验不通过,
*/
public function index(Keyword $Keyword): Json
{
if (request()->isGet()) {
$websiteId = (int)input('website_id', '', 'trim');
if(!$websiteId){
return jsonReturn(-1,Lang::get('站点不能为空'));
}
$where = [
'website_id' => $websiteId,
'seller_id' => $this->admin['seller_id'],
];
$limit = $this->setLimit();
$res = $Keyword->getKeywordList($where, $limit,'id,website_id,name,url,position,sort,baidu_pc,baidu_mob,three_pc,sougou_mob,status',['website']);
return json(pageReturn($res));
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 读取数据
* @return Json
*/
public function read(Keyword $Keyword)
{
if (request()->isGet()) {
$param = input('get.');
try {
validate(KeywordValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
$res = $Keyword->getKeyword($param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 保存
* @param Keyword $Keyword
* @return Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(Keyword $Keyword): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(KeywordValidate::class)->scene('save')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
$uniqueWhere = [
'website_id' => $param['website_id'],
'name' => $param['name'],
'url' => $param['url']
];
$Keyword->saveUnique($uniqueWhere,Lang::get('关键词已经存在'));
$res = $Keyword->addKeyword($param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 编辑
* @param Keyword $Keyword
* @return Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function update(Keyword $Keyword): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(KeywordValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$uniqueWhere = [
'website_id' => $param['website_id'],
'name' => $param['name'],
'url' => $param['url']
];
$Keyword->updateUnique($uniqueWhere,$param['id'],Lang::get('关键词已经存在'));
$updateWhere = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
$res = $Keyword->updateKeyword($updateWhere,$param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 删除数据
* @return Json
*/
public function delete(Keyword $Keyword): Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(KeywordValidate::class)->scene('delete')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
if(empty($param['keyword_id'])){
jsonReturn(0, lang('删除成功'));
}else{
if(count($param['keyword_id'])){
$where = [
'id' => $param['keyword_id'][0],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id']
];
}else{
$where = [
['id' , 'in',$param['keyword_id']],
['seller_id' ,'=', $param['seller_id']],
['website_id', '=', $param['website_id']]
];
}
$res = $Keyword->deleteKeyword($where);
return json($res);
}
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 导入关键词
* @param ExcelService $excelService
* @param Keyword $Keyword
* @return Json
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
*/
public function import(ExcelService $excelService,Keyword $Keyword): Json
{
$param = input('post.');
$param['file'] = request()->file('file');
try {
validate(KeywordValidate::class)->scene('import')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$data = $excelService->readData($param['file']);
if(empty($data)){
return jsonReturn(-1,Lang::get('数据读取失败或者文件加为空,请检查'));
}
$data = $this->checkData($data);
if(isset($data['code'])){
return json($data);
}
$data = $this->dealWithData($data,$param['website_id']);
$res = $Keyword->addAllCustomData($data);
return json($res);
}
/**
* @param $data
* @return array
*/
public function checkData($data): array
{
$header = array_shift($data);
if(count($header) != 4){
return dataReturn(-2,Lang::get('数据格式错误,请参考样例'));
}
$i = 0;
foreach($header as $value){
if($value != $this->header[$i]){
return dataReturn(-3,Lang::get('数据格式错误,请参考样例'));
}
$i++;
}
return $data;
}
/**
* @param $data
* @param $siteId
* @return array
*/
public function dealWithData($data,$siteId): array
{
$tmpData = [];
foreach($data as $val){
if(!empty($val)){
$tmp = ['website_id' => $siteId];
$tmp['seller_id'] = $this->admin['seller_id'];
$i = 0;
foreach($val as $vv){
if($i == 0 && empty($vv)){
break;
}
if($i == 3){
if($vv == '正常'){
$vv = 1;
}else{
$vv = 2;
}
}
$tmp[$this->map[$i]] = $vv;
if($i == 3){
array_push($tmpData,$tmp);
}
$i++;
}
}
}
return $tmpData;
}
/**
* 关键词模版
* @return Json
*/
public function template(): Json
{
return jsonReturn(0,'success',config('system.keyword_template_path'));
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function monitor(MonitorService $monitorService): Json
{
$param = input('get.');
try {
validate(KeywordValidate::class)->scene('monitor')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
return $monitorService->getMonitorData($param);
}
public function echarts()
{
$param = $this->request->param();
try {
validate(KeywordValidate::class)->scene('echarts')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$keywordQueryModel = new KeywordQuery();
$startTime = time() - ($param['days'] * 86400);
$list = $keywordQueryModel->where('create_time', '>=', $startTime)
->where('search_engine', $param['search_engine'])
->where('keyword_id', $param['id'])->select();
$list = $list ? $list->toArray() : [];
$days = [];
$values = [];
foreach ($list as $value) {
$days[] = $value['create_time'];
$values[] = $value['top_rank'];
}
$data = [
'days' => array_values($days),
'values' => $values,
];
return jsonReturn(0, 'success', $data);
}
// 单个更新排名
public function updateRank()
{
$param = $this->request->param();
try {
validate(KeywordValidate::class)->scene('updateRank')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$token = KeywordService::getChinaZToken($this->admin['seller_id']);
$keywordModel = new Keyword();
$keywordData = $keywordModel->where([
['id', '=', $param['id']]
])->findOrEmpty();
if (!isset($keywordData['name'])) {
return jsonReturn(-2, Lang::get('关键词不存在!'));
}
switch ($param['search_engine']) {
case 'baidu_pc' :
KeywordService::baiduPcRank($token, $keywordData);
break;
case 'baidu_mob':
KeywordService::baiduMobileRank($token, $keywordData);
break;
case 'three_pc':
KeywordService::pc360Rank($token, $keywordData);
break;
case 'sougou_mob':
KeywordService::sougouMobileRank($token, $keywordData);
break;
default:
return jsonReturn(-3, Lang::get('搜索引擎不存在'));
break;
}
return jsonReturn(0, Lang::get('执行成功!'));
}
}

91
app/controller/backend/KeywordQueryController.php

@ -0,0 +1,91 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\KeywordQuery;
use app\service\MonitorService;
use app\validate\KeywordQueryValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class KeywordQueryController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(MonitorService $monitorService): \think\response\Json
{
$param = input('get.');
try {
validate(KeywordQueryValidate::class)->scene('monitor')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
return $monitorService->keywordMonitor($param);
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(KeywordQuery $keywordQuery): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('关键词ID'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $keywordQuery->getKeywordQuery($where);
return json($res);
}
public function save(KeywordQuery $keywordQuery)
{
$param = input('keywordmonitor');
foreach ( $param as &$val) {
$val['ranks'] = json_encode($val['ranks']);
}
unset($val);
$res = $keywordQuery->saveAll($param);
dd($res);
}
//获取关键词查询列表
public function list()
{
$param = $this->request->param();
$param['limit'] = $param['limit'] ?? 10;
if (empty($param['keyword_id']) || empty($param['engine'])) {
return jsonReturn(-1, Lang::get('参数错误'));
}
$keywordQueryModel = new KeywordQuery();
$list = $keywordQueryModel->getKeywordQueryList([
['keyword_id', '=', $param['keyword_id']],
['search_engine', '=', $param['engine']]
], intval($param['limit']));
return json(pageReturn($list));
}
}

129
app/controller/backend/KeywordWebsiteController.php

@ -0,0 +1,129 @@
<?php
namespace app\controller\backend;
use app\model\KeywordWebsite;
use app\validate\KeywordWebsiteValidate;
use think\exception\ValidateException;
use think\facade\Lang;
use think\response\Json;
class KeywordWebsiteController extends BaseController
{
/**
* 读取数据列表
* @return Json
* @method Post
* @error_number 0: 成功, -1: 数据库查询失败, -2: 请求方法错误, -3: 数据重复, -4:数据缺少或校验不通过,
*/
public function index(): Json
{
if (\request()->isGet()) {
$param = input('get.');
$param['seller_id'] = $this->admin['seller_id'];
$keyword = new KeywordWebsite();
$res = $keyword->getKeywordWebsiteList($param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 读取数据
* @return Json
*/
public function read()
{
if (request()->isGet()) {
$param = input('get.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(KeywordWebsiteValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$KeywordWebsite = new KeywordWebsite();
$res = $KeywordWebsite->getKeywordWebsite($param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 存入数据
* @return Json
*/
public function save()
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(KeywordWebsiteValidate::class)->scene('save')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$KeywordWebsite = new KeywordWebsite();
$res = $KeywordWebsite->addKeywordWebsite($param);
return jsonReturn($res['code'], $res['msg'], $res['data']);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 更新数据
* @return Json
*/
public function update(): Json
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(KeywordWebsiteValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$KeywordWebsite = new KeywordWebsite();
$res = $KeywordWebsite->updateKeywordWebsite($param);
return jsonReturn($res['code'], $res['msg']);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 删除数据
* @return Json
*/
public function delete()
{
if (request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(KeywordWebsiteValidate::class)->scene('read')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-4, $e->getMessage());
}
$KeywordWebsite = new KeywordWebsite();
$res = $KeywordWebsite->deleteKeywordWebsite($param);
return jsonReturn($res['code'], $res['msg']);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
}

255
app/controller/backend/LinkController.php

@ -0,0 +1,255 @@
<?php
namespace app\controller\backend;
use app\exception\ModelEmptyException;
use app\exception\ModelException;
use app\model\Link;
use app\model\LinkWebsite;
use app\model\Model;
use app\model\RecycleBin;
use app\service\CacheService;
use app\validate\LinkValidate;
use think\facade\Cache;
use think\facade\Db;
use think\facade\Lang;
use think\response\Json;
class LinkController extends BaseController
{
/**
* 友情链接列表
* @key id, seller_id
* @return Json
* @throws \app\exception\ModelException
*/
public function index(Link $link): Json
{
if(request()->isGet()) {
$param = input('get.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(LinkValidate::class)->scene('index')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$where = [
'website_id' => $param['website_id'],
'seller_id' => $param['seller_id'],
'type' => $param['type'],
'is_del' => 1
];
$limit = $this->setLimit();
// 翻页数据
$res = $link->getLinkList($where,$limit);
$data = pageReturn($res);
// 统计数据
$total = $data['count'];
$changeNum = 0;
if($total){
$now = mktime(0,0,0,date('m'),1,date('Y'));
// 上月总数
$lastMonthNum = $link->where($where)->whereTime('create_time','<',$now)->count();
$changeNum = $total - $lastMonthNum;
}
$data['change'] = $changeNum;
return json($data);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 读取单条友情链接信息
* @return Json
*/
public function read(Link $link): Json
{
if (request()->isGet()) {
$param = input('get.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(LinkValidate::class)->scene('read')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['is_del'] = 1;
$res = $link->getLink($param);
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 新增友情链接
* @return Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(Link $link): Json
{
if(request()->isPost()) {
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
try {
validate(LinkValidate::class)->scene('save')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
if(isset($param['add_time']) && empty($param['add_time'])){
unset($param['add_time']);
}
if(isset($param['end_time']) && empty($param['end_time'])){
unset($param['end_time']);
}
$link->saveUnique(['seller_id'=>$param['seller_id'],'website_id'=>$param['website_id'],'type'=>$param['type'],'name'=>$param['name'],'is_del'=>1],'友情链接已经存在');
$res = $link->addLink($param);
CacheService::deleteCacheList('Link_cache_list');
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 更新友情链接信息
* @return Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function update(Link $link): Json
{
if (request()->isPost())
{
$param = input('post.');
try {
validate(LinkValidate::class)->scene('update')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
if(isset($param['add_time']) && empty($param['add_time'])){
unset($param['add_time']);
}
if(isset($param['end_time']) && empty($param['end_time'])){
unset($param['end_time']);
}
$link->updateUnique([
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'type' => $param['type'],
'name' => $param['name'],
],$param['id'],lang('友情链接名称已存在'));
$link->updateUnique([
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'type' => $param['type'],
'url' => $param['url'],
],$param['id'],lang('友情链接地址已存在'));
$res = $link->updateLink($where,$param);
CacheService::deleteCacheList('Link_cache_list');
return json($res);
}
return jsonReturn(-2, Lang::get('请求方法错误'));
}
/**
* 删除友情链接信息
* @return Json
* @throws ModelException
*/
public function delete(Link $link,RecycleBin $recycleBin): Json
{
if (!request()->isPost()) {
return jsonReturn(-2, Lang::get('请求方法错误'));
}
$param = input('post.');
try {
validate(LinkValidate::class)->scene('read')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
$param['seller_id'] = $this->admin['seller_id'];
$where = [
'id' => $param['id'],
'seller_id' => $param['seller_id'],
'website_id' => $param['website_id'],
'is_del' => 1,
];
Db::startTrans();
try{
$res = $link->softDelLink($where);
CacheService::deleteCacheList('Link_cache_list');
if($res['data'] == 1){
$binData = [
'object_id' => $param['id'],
'table_name' => 'link',
'title' => "友情链接{$param['id']}",
'admin_id' => $this->admin['uid'],
'name' => $this->admin['name'],
];
$recycleBin->addRecycleBin($binData);
}
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(-3,$e->getMessage());
}
return json($res);
}
/**
* @throws ModelException
*/
public function copy(Link $link): Json
{
if(!request()->isPost()){
return jsonReturn(-1, Lang::get('请求方法错误'));
}
$param = input('post.');
try {
validate(LinkValidate::class)->scene('copy')->check($param);
} catch (\Exception $e) {
return jsonReturn(-4, $e->getMessage());
}
if($param['website_id'] == $param['copy_site_id']){
return jsonReturn(-5, lang('目标网站ID和复制网站ID不能相等'));
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['copy_site_id'],
'is_del' => 1,
];
$selfWhere = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'is_del' => 1,
];
$field = ['name','url','sort','target','is_del'];
$linkList = $link->getAllLink($where,$field)['data']->toArray();
$selfList = $link->getAllLink($selfWhere,$field)['data']->toArray();
$flag = array_column($selfList,'name');
if(!empty($linkList)){
foreach($linkList as $key => &$val){
$val['website_id'] = $param['website_id'];
if(in_array($val['name'],$flag)){
unset($linkList[$key]);
}
}
unset($val);
$res = json($link->addAllLink($linkList));
}else{
$res = jsonReturn(0,0,Lang::get('成功'));
}
return $res;
}
}

105
app/controller/backend/MaterialController.php

@ -0,0 +1,105 @@
<?php
namespace app\controller\backend;
use app\model\PosterMaterial;
use app\model\SysSetting;
use app\service\upload\Upload;
use think\facade\Lang;
class MaterialController extends BaseController
{
public function index()
{
$posterModel = new PosterMaterial();
$list = $posterModel->getMaterialList([]);
foreach ($list['data'] as $v) {
$v['source'] = str_replace("\\", '/', $v['source']);
}
return json($list);
}
public function upload()
{
if (request()->isPost()) {
set_time_limit(0);
$file = request()->file('file');
if (empty($file)) {
return jsonReturn(-8, Lang::get('文件上传失败,请重新尝试'));
}
$imageSize = getimagesize($file);
$seller_id = $this->admin['seller_id'];
// 查看文件类型
$fileName = $file->getOriginalName();
$fileExt = $file->getOriginalExtension();
$file_type = fileFormat($fileName);
// 附件大小和类型验证
// 获取上传配置
$Settings = new SysSetting();
$uploadSetting = $Settings->getAllCustomArrayData(['parent_id' => 1, 'group' => 'upload', 'status' => 1], 'id desc', 'id,group,title,value')['data'];
$uploadSetting = getColumnForKeyArray($uploadSetting, 'title');
$limitSize = $uploadSetting[$file_type . '_size']['value'] * 1024; // byte
$fileSize = $file->getSize(); // 单位byte
if ($fileSize > $limitSize) {
return jsonReturn(-1, Lang::get('文件过大,请修改上传限制或者替换小的文件'));
}
$extArr = explode(',', $uploadSetting[$file_type . '_ext']['value']);
if (!in_array($fileExt, $extArr)) {
return jsonReturn(-2, Lang::get('文件格式错误,请重新上传'));
}
$type = $this->getUploadType();
$upload = new Upload();
$upload->create($file, $seller_id, $type, $file_type);
$res = $upload->getUploadFileInfo()['data'];
$res['width'] = $imageSize[0];
$res['height'] = $imageSize[1];
$res['type'] = $fileExt;
if (strpos($res['url'], 'http') === false) {
$res['url'] = request()->domain() . '/' . $res['url'];
}
return jsonReturn(0, Lang::get('上传成功'), $res);
}
return jsonReturn(-3, Lang::get('请求方法错误'));
}
public function add()
{
if (request()->isPost()) {
$param = input('post.');
$posterModel = new PosterMaterial();
return json($posterModel->addMaterial($param));
}
}
public function del()
{
$id = input('param.id');
$posterModel = new PosterMaterial();
return json($posterModel->delMaterial($id));
}
/**
* @throws \app\exception\ModelException
*/
public function getUploadType()
{
// 文件信息提取
$where = [
'seller_id' => $this->admin['seller_id'],
'group' => 'upload',
'title' => 'storage'
];
$place = new SysSetting();
return $place->getSysSetting($where)['data']->toArray()['value'];
}
}

164
app/controller/backend/ModuleController.php

@ -0,0 +1,164 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Module;
use app\service\CacheService;
use app\service\ModuleFieldService;
use app\service\ModuleService;
use app\validate\ModuleValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class ModuleController extends BaseController
{
/**
* 资源列表
* @param Module $module
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Module $module): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = $this->setLimit();
$moduleList = $module->getModuleList($where,$limit);
return json(pageReturn($moduleList));
}
/**
* 保存新建的资源
*
* @return \think\Response\Json
* @throws \Exception
*/
public function save(ModuleService $moduleService): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(ModuleValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// 数据库前缀
$prefix = config('database.connections')[config('database.default')]['prefix'];
$param['database_table'] = $prefix . $param['table'];
$param['status'] = 1;
$param['seller_id'] = $this->admin['seller_id'];
$res = $moduleService -> save($param);
// 系统模型添加
// $model = new Module();
// $res = $model->addModule($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\Response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Module $module): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('模型ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $module->getModule($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function update(Module $module): \think\response\Json
{
if(request()->isPost()){
$param = request()->only(['id','title','description','status']);
try {
validate(ModuleValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $module -> updateModule($where,$param);
CacheService::deleteRelationCacheByObject($module);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(ModuleService $moduleService): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,'ErrorMsg');
}
$param = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $moduleService->destroy($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 模型表
* @param ModuleService $moduleService
* @return \think\response\Json
*/
public function all(ModuleService $moduleService): \think\response\Json
{
return $moduleService -> getAllModuleTable($this->admin['seller_id']);
}
/**
* 模型表字段
* @param ModuleFieldService $moduleFieldService
* @return \think\response\Json
*/
public function field(ModuleFieldService $moduleFieldService): \think\response\Json
{
$table = $this->request->param('table');
$scene = $this->request->param('scene',null);
if(empty($table)){
return jsonReturn(-1,Lang::get('表名不能为空'));
}
$res = $moduleFieldService -> getModuleTableField($table,$scene);
return json($res);
}
}

168
app/controller/backend/ModuleFieldController.php

@ -0,0 +1,168 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\ModuleField;
use app\service\ContentService;
use app\service\ModuleFieldService;
use app\validate\ModuleFieldValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class ModuleFieldController extends BaseController
{
protected $paramKeys = ['id','module_id','form_title','table_field','validate_rule',
'type','length','default','status','placeholder','is_null','form_validate',
'order','settings','attach_data'
];
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(ModuleField $moduleField): \think\response\Json
{
$moduleId = (int)input('module_id');
if(!$moduleId){
return jsonReturn(-1,Lang::get('模型ID不能为空'));
}
$where = [
'seller_id' => $this->admin['seller_id'],
'module_id' => $moduleId,
];
$moduleFieldList = $moduleField->where($where)->select()->each(function(&$item){
})->toArray();
$list = getColumnForKeyArray($moduleFieldList,'order');
$len = count($list);
$tmp = [];
$res = $this->sortField($list,$len,'id',$tmp);
return jsonReturn(0,Lang::get('成功'),$res);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function save(ModuleFieldService $moduleFieldService,ModuleField $field): \think\response\Json
{
if(request()->isPost()){
$param = request()->only($this->paramKeys);
// 数据验证
try{
validate(ModuleFieldValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$res = $moduleFieldService->save($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(ModuleField $moduleField): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,'字段ID不能为空');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// 其他逻辑
$res = $moduleField->getModuleField($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function update(ModuleFieldService $moduleFieldService): \think\response\Json
{
if(request()->isPost()){
$param = request()->only($this->paramKeys);
try {
validate(ModuleFieldValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$res = $moduleFieldService -> update($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function delete(ModuleFieldService $moduleField): \think\response\Json
{
if(request()->isPost()){
$param = request()->only(['id','module_id']);
try {
validate(ModuleFieldValidate::class)->scene('delete')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'module_id' => $param['module_id'],
'seller_id' => $this->admin['seller_id']
];
$res = $moduleField->destroy($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
public function sortField($data,$len,$key,&$tmp)
{
if(isset($data[$key])){
$tmp[] = $data[$key];
if($len > 0){
$this->sortField($data,$len,$data[$key]['table_field'],$tmp);
}
}
return $tmp;
}
// 获取关联字段的下拉列表
public function getRelationFieldListValue(ModuleFieldService $moduleField): \think\response\Json
{
$fieldId = $this -> request -> param('id');
$searchValue = $this -> request -> param('search');
if(empty($fieldId)){
return jsonReturn(-1,Lang::get('字段ID不能为空'));
}
$sellerId = $this->admin['seller_id'];
$res = $moduleField -> getRelationFieldListValue($fieldId,$searchValue,$sellerId);
return jsonReturn(0,Lang('成功'),$res);
}
}

166
app/controller/backend/NavCateController.php

@ -0,0 +1,166 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Nav;
use app\model\NavCate;
use app\service\CacheService;
use app\validate\NavCateValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class NavCateController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(NavCate $navCate): \think\response\Json
{
$siteId = (int)input('website_id');
if(!$siteId){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$lang = input('lang');
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
'lang' => $lang
];
$navCateList = $navCate->getAllCustomArrayData($where,'id asc');
return json($navCateList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
* @throws \ReflectionException
*/
public function save(NavCate $navCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(NavCateValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// 唯一验证
$navCate -> saveUnique(
[
'seller_id'=>$this->admin['seller_id'],
'website_id' => $param['website_id'],
'title'=>$param['title'],
'lang' => $param['lang'],
],'导航分类已存在');
$res = $navCate -> addNavCate($param);
CacheService::deleteRelationCacheByObject($navCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(NavCate $navCate): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,lang('导航分类ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $navCate->getNavCate($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelNotUniqueException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function update(NavCate $navCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(NavCateValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
// 唯一验证
$navCate -> updateUnique(
[
'seller_id'=>$this->admin['seller_id'],
'website_id' => $param['website_id'],
'title'=>$param['title'],
'lang' => $param['lang'],
],$param['id'],lang('导航分类已存在'));
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $navCate -> updateNavCate($where,$param);
CacheService::deleteRelationCacheByObject($navCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function delete(NavCate $navCate,Nav $Nav): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(NavCateValidate::class)->scene('delete')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$whereNav = [
'nav_cate_id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
$nav = $Nav->getNavList($whereNav)['data']->toArray();
if(!empty($nav)){
return jsonReturn(-2,lang('分类下有菜单不能直接删除'));
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
$res = $navCate->delNavCate($where);
CacheService::deleteRelationCacheByObject($navCate);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

316
app/controller/backend/NavController.php

@ -0,0 +1,316 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Category;
use app\model\Model;
use app\model\Nav;
use app\model\NavCate;
use app\model\Route;
use app\service\CacheService;
use app\validate\NavValidate;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Lang;
class NavController extends BaseController
{
/**
* 导航列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function index(Nav $nav): \think\response\Json
{
$param = input('param.');
try{
validate(NavValidate::class)->scene('index')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
$where = [
'seller_id' => $this->admin['seller_id'],
'nav_cate_id' => $param['cate_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang'],
];
$navList = $nav->getAllCustomArrayData($where)['data'];
$navList = makeTree($navList);
return jsonReturn(0,'success',$navList);
}
/**
* 新增导航
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelNotUniqueException
* @throws \ReflectionException
*/
public function save(Nav $nav,NavCate $NavCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(NavValidate::class)->scene('save')->check($param);
}catch(ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
if(empty($param['sort'])){
// 设置排序
$maxOrder = $nav->getMaxOrderNav(['parent_id' => $param['parent_id'],'seller_id' => $this->admin['seller_id']], 'sort')['data'];
if (!empty($maxOrder)) {
$param['sort'] = (int)$maxOrder + 10;
}
}
// 设置网站ID
$navCate = $NavCate -> getNavCate(['id'=>$param['nav_cate_id'],'seller_id'=>$this->admin['seller_id']])['data'];
$param['website_id'] = $navCate['website_id'];
// 唯一性验证
$nav->saveUnique(['title'=>$param['title'],'lang'=>$param['lang'],'nav_cate_id'=>$param['nav_cate_id'],'website_id'=>$param['website_id'],'seller_id'=>$param['seller_id'],'parent_id'=>$param['parent_id']],'导航已存在');
// 设置URL
if($param['type'] == 2){
$this->setNavUrl($param);
}
$res = $nav -> addNav($param);
CacheService::deleteRelationCacheByObject($nav);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 导航详情
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Nav $nav): \think\response\Json
{
$param = input('param.');
try {
validate(NavValidate::class)->scene('read')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
$res = $nav->getNav($where);
return json($res);
}
/**
* 更新导航
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelNotUniqueException
* @throws \ReflectionException
*/
public function update(Nav $nav, NavCate $NavCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(NavValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
// 设置网站ID
$navCate = $NavCate -> getNavCate(['id'=>$param['nav_cate_id'],'seller_id'=>$this->admin['seller_id']])['data'];
$param['website_id'] = $navCate['website_id'];
// 唯一性验证
$nav->updateUnique(['title'=>$param['title'],'website_id'=>$param['website_id'],'nav_cate_id'=>$param['nav_cate_id'],'seller_id'=>$this->admin['seller_id']],$param['id'],'导航已存在');
// 设置url
if($param['type'] == 2){
$this->setNavUrl($param);
}
if(empty($param['sort'])){
// 设置排序
$maxOrder = $nav->getMaxOrderNav(['parent_id' => $param['parent_id'],'seller_id' => $this->admin['seller_id']], 'sort')['data'];
if (!empty($maxOrder)) {
$param['sort'] = (int)$maxOrder + 10;
}
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $nav -> updateNav($where,$param);
CacheService::deleteRelationCacheByObject($nav);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除导航
*
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \ReflectionException
*/
public function delete(Nav $nav): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(NavValidate::class)->scene('delete')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1,$e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
$children = $nav->getNavList([
'parent_id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
])['data']->toArray();
if(count($children)){
$res = ['code'=>-4,'msg'=>lang('有子菜单不能删除')];
}else{
$res = $nav->delNav($where);
CacheService::deleteRelationCacheByObject($nav);
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 设置导航路由
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function setNavUrl(&$param)
{
$param['href'] = $this->getHrefByCategoryId($param['category_id'],$this->admin['seller_id']);
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function getHrefByCategoryId($categoryId, $sellerId)
{
$Category = new Category();
$category = $Category -> getCustomArrayData(['id'=>$categoryId,'seller_id' => $sellerId])['data'];
if($category['type'] == 2){
$href = 'javascript:;';
}else if($category['type'] == 3){
$href = $category['alias'];
} else{
$Route = new Route();
$route = $Route -> getCustomArrayData(['full_url'=> 'List/index?id='.$categoryId,'seller_id'=> $sellerId])['data'];
$href = $route['url'];
}
return $href;
}
/**
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \ReflectionException
*/
public function batchCreate(): \think\response\Json
{
$param = $this->request->only(['category_ids','nav_cate_id','target','lang','website_id']);
try {
validate(NavValidate::class)->scene('batchCreate')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
// 获取所有栏目
$category = new Category();
$whereCate = [
['seller_id','=',$this->admin['seller_id']],
['website_id','=',$param['website_id']],
['lang','=',$param['lang']]
];
if($param['category_ids'] != 0 || !empty($param['category_ids'])){
$whereCate[] = ['id','in',$param['category_ids']];
}
$categories = $category -> getAllCustomArrayData($whereCate,'id asc')['data'];
// 查询目前导航表中的最大ID
$prefix = env('database.prefix');
$navTable = $prefix . 'nav';
$res = Db::query("SHOW TABLE STATUS WHERE NAME = '$navTable'");
$maxId = $res[0]['Auto_increment'];
$targetIds = range($maxId, $maxId + count($categories) - 1);
// 栏目ID
$cateIds = array_column($categories,'id');
$mapIds = array_combine($cateIds,$targetIds);
// 生成导航插入数据
$nav = [];
foreach($categories as $cate){
$tmp = [];
$href = $this->getHrefByCategoryId($cate['id'],$this->admin['seller_id']);
$tmp['href'] = $href;
$tmp['nav_cate_id'] = $param['nav_cate_id'];
$tmp['lang'] = $param['lang'];
$tmp['target'] = $param['target'];
$tmp['sort'] = $cate['sort'];
$tmp['title'] = $cate['title'];
$tmp['category_id'] = $cate['id'];
$tmp['status'] = 1;
$tmp['no_follow'] = 2;
$tmp['website_id'] = $param['website_id'];
$tmp['seller_id'] = $this->admin['seller_id'];
if($cate['parent_id']){
$tmp['parent_id'] = $mapIds[$cate['parent_id']];
}else{
$tmp['parent_id'] = 0;
}
$tmp['id'] = $mapIds[$cate['id']];
$nav[] = $tmp;
}
// 插入导航数据
$Nav = new Nav();
Db::name('nav')->insertAll($nav);
CacheService::deleteRelationCacheByObject($Nav);
return jsonReturn(0);
}
/**
* 更新导航状态
* @throws \ReflectionException
* @throws \app\exception\ModelException
*/
public function updateStatus(): \think\response\Json
{
$param = $this->request->only(['id','status']);
try {
validate(NavValidate::class)->scene('updateStatus')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$Nav = new Nav();
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $Nav -> updateNav($where,['status'=>$param['status']]);
CacheService::deleteRelationCacheByObject($Nav);
return json($res);
}
}

52
app/controller/backend/PluginController.php

@ -0,0 +1,52 @@
<?php
namespace app\controller\backend;
use app\model\Plugin as PluginModel;
use app\service\PluginService;
use think\facade\Lang;
class PluginController extends BaseController
{
public function index()
{
$pluginModel = new PluginModel();
$plugins = $pluginModel->getList();
return jsonReturn(0, Lang::get('成功'), $plugins);
}
public function install()
{
$param = $this->request->only(['name' => '']);
$res = PluginService::install($param['name']);
if ($res === true) {
return jsonReturn(0, lang('安装成功'));
}
return jsonReturn(-1, lang('安装失败') . $res);
}
public function uninstall()
{
$param = $this->request->only(['name' => '']);
$res = PluginService::uninstall($param['name']);
if ($res === true) {
return jsonReturn(0, lang('卸载成功'));
}
return jsonReturn(-1, lang('插件卸载失败') . $res);
}
public function editStatus()
{
$param = $this->request->only(['name' => '', 'status' => 1]);
$res = PluginService::editStatus($param['name'], $param['status']);
return json($res);
}
}

185
app/controller/backend/PosterController.php

@ -0,0 +1,185 @@
<?php
namespace app\controller\backend;
use app\model\Poster;
use Kkokk\Poster\PosterManager;
class PosterController extends BaseController
{
public function index()
{
$posterModel = new Poster();
return json($posterModel->getPosterList([]));
}
public function add()
{
if (request()->isPost()) {
$param = input('post.');
$param['id'] = $param['id'] ?? 0;
if (empty($param['name'])) {
return jsonReturn(-1, lang('请填写海报名称'));
}
$posterModel = new Poster();
if ($posterModel->where([
['id', '<>', $param['id']],
['name', '=', $param['name']]
])->value('id')) {
return jsonReturn(-1, lang('海报名称已存在'));
}
try {
$preview = $this->makePreview($param, 'cover');
} catch (\Exception $e) {
return jsonReturn(-3, 'error', $e->getTrace());
}
$addParam = [
'name' => $param['name'],
'preview' => request()->domain() . '/' . ltrim($preview['url'], './'),
'status' => 1,
'design_content' => json_encode($param)
];
if (!empty($param['id'])) {
return json($posterModel->editPoster([['id', '=', $param['id']]],$addParam));
}
return json($posterModel->addPoster($addParam));
}
}
public function copy() {
if (!request()->isPost()) {
return jsonReturn(-1, lang('请求错误'));
}
$param = input('post.');
if (empty($param['id'])) {
return jsonReturn(-2, lang('参数错误'));
}
$posterModel = new Poster();
$data = $posterModel->where('id', '=', $param['id'])->findOrEmpty();
if (empty($data)) {
return jsonReturn(-3, lang('找不到要复制的海报'));
}
$designContent = json_decode($data['design_content'], true);
$designContent['name'] = $designContent['name'] . lang('-副本');
unset($designContent['id']);
$addParam = [
'name' => $designContent['name'],
'preview' => $data['preview'],
'status' => 1,
'design_content' => json_encode($designContent)
];
$res = $posterModel->addPoster($addParam);
if ($res['code'] == 0) {
$res['msg'] = lang('复制成功');
}
return json($res);
}
public function edit()
{
$id = input('param.id');
$posterModel = new Poster();
return json($posterModel->getDesignInfoById($id));
}
public function del()
{
$id = input('param.id');
$posterModel = new Poster();
return json($posterModel->del($id));
}
public function preview()
{
try {
$posterData = input('post.');
$result = $this->makePreview($posterData);
return jsonReturn(0, 'success', request()->domain() . '/' . ltrim($result['url'], './'));
} catch (\Exception $e) {
file_put_contents('./error.log', $e->getFile() . ' --- ' . $e->getLine() . ' --- ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
return jsonReturn(-1, $e->getMessage());
}
}
/**
* @param $posterData
* @param $type
* @return mixed
*/
protected function makePreview($posterData, $type = 'temp')
{
$name = uniqid();
if ($type == 'temp') {
$PosterManager = new PosterManager('./poster_preview/temp/' . $name . '.' . $posterData['type']);
} else {
$PosterManager = new PosterManager('./poster_preview/cover/' . $name . '.' . $posterData['type']);
}
$temp = $PosterManager;
if ($posterData['activeName'] == 'img' && !empty($posterData['img_src'])) {
$temp = $temp->buildImDst($posterData['img_src'], $posterData['width'], $posterData['height']);
} else {
$temp = $temp->buildIm($posterData['width'], $posterData['height'], $this->hex2rgba($posterData['color']));
}
if (isset($posterData['item']) && !empty($posterData['item'])) {
foreach ($posterData['item'] as $vo) {
if ($vo['t'] == 'text') {
$font = '/system_file/alifont/' . $vo['fn'] . '.woff';
$temp = $temp->buildText($vo['v'], $vo['x'], $vo['y'], $vo['s'], $this->hex2rgba($vo['c']), $vo['w'], $font, $vo['a'], $vo['w'], 1, 0, $vo['rotate']);
} else if ($vo['t'] == 'image') {
$temp = $temp->buildImage($vo['v'], $vo['x'], $vo['y'], 0, 0, $vo['w'], $vo['h'], $this->hex2rgba($vo['c']), 'normal', $vo['rotate']);
} else if ($vo['t'] == 'qrcode') {
$temp = $temp->buildQr($vo['v'], $vo['x'], $vo['y'], 0, 0, $vo['w'], $vo['h'], 4, 1, $vo['rotate']);
}
}
}
return $temp->getPoster();
}
protected function hex2rgba($color)
{
$hexColor = str_replace('#', '', $color);
$lens = strlen($hexColor);
if ($lens != 3 && $lens != 6) {
return false;
}
$newColor = '';
if ($lens == 3) {
for ($i = 0; $i < $lens; $i++) {
$newColor .= $hexColor[$i] . $hexColor[$i];
}
} else {
$newColor = $hexColor;
}
$hex = str_split($newColor, 2);
$rgba = [];
foreach ($hex as $vls) {
$rgba[] = hexdec($vls);
}
$rgba[] = 1;
return $rgba;
}
}

121
app/controller/backend/RecycleBinController.php

@ -0,0 +1,121 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\RecycleBin;
use app\service\ModuleFieldService;
use think\facade\Db;
use think\facade\Lang;
class RecycleBinController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(RecycleBin $recycleBin): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$table = input('param.table_name') ?: '';
if($table){
$where['table_name'] = $table;
}
$limit = $this->setLimit();
$recycleBinList = $recycleBin->getRecycleBinList($where,$limit);
return json(pageReturn($recycleBinList));
}
/**
* 回收站清空
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function clean(RecycleBin $recycleBin): \think\response\Json
{
if(request()->isPost()){
$res = $recycleBin -> delRecycleBin(['seller_id'=>$this->admin['seller_id']]);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 单条数据恢复
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \think\db\exception\DbException
*/
public function restore(ModuleFieldService $fieldService): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('数据ID不能为空'));
}
return $fieldService->resotreData($id,$this->admin['seller_id']);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(RecycleBin $recycleBin): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('数据ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $recycleBin->delRecycleBin($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 恢复多条数据
* @throws \think\db\exception\DbException
*/
public function allRestore(RecycleBin $recycleBin): \think\response\Json
{
if(!request()->isPost()){
return jsonReturn(-4,Lang::get('请求方法错误'));
}
$ids = input('param.ids');
if(!is_array($ids)){
return jsonReturn(-1,Lang::get('恢复数据参数错误'));
}
if(empty($ids)){
return jsonReturn(-2,Lang::get('恢复数据参数不能为空'));
}
$tableName = $recycleBin->whereIn('id',$ids)->column('table_name');
if(empty($tableName)){
return jsonReturn(-5,Lang::get('恢复数据不存在'));
}
$table = array_unique($tableName);
if(count($table) > 1){
return jsonReturn(-3,Lang::get('请选择相同类型的内容恢复'));
}
$recycleBin->whereIn('id',$ids)->where('seller_id',$this->admin['seller_id'])->delete();
$res = Db::name($table[0])->whereIn('id',$ids)->update(['is_del'=>1,'delete_time'=>0,'update_time'=>time()]);
return jsonReturn(0,Lang::get('恢复成功'),$res);
}
}

177
app/controller/backend/RoleController.php

@ -0,0 +1,177 @@
<?php
declare (strict_types=1);
namespace app\controller\backend;
use app\model\Admin;
use app\model\AdminMenu;
use app\model\Role;
use app\service\RoleService;
use app\validate\RoleValidate;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Lang;
class RoleController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Role $role): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$roleList = $role->getRoleList($where);
return json($roleList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(Role $role): \think\response\Json
{
if (request()->isPost()) {
$param = request()->only(['title', 'status']);
// 数据验证
try {
validate(RoleValidate::class)->scene('save')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$role->saveUnique(['seller_id' => $param['seller_id'], 'title' => $param['title']], lang('角色已经存在'));
$res = $role->addRole($param);
return json($res);
}
return jsonReturn(-3, Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Role $role): \think\response\Json
{
$id = (int)input('id');
if (!$id) {
return jsonReturn(-1, Lang::get('角色ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $role->getRole($where, ['website']);
if (!empty($res['data']['kid_auth'])) {
$res['data']['auth'] = explode(',', $res['data']['kid_auth']);
} else {
$res['data']['auth'] = [];
}
$res['data']['website_id'] = array_column($res['data']['website']->toArray(), 'id');
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
* @throws \app\exception\ModelEmptyException
*/
public function update(Role $role): \think\response\Json
{
if (request()->isPost()) {
$param = input('post.');
try {
validate(RoleValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
if ($param['group'] == 1) {
jsonReturn(-2, Lang::get('系统默认角色,不能编辑'));
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$roleInfo = $role->getRole($where)['data'];
if (empty($roleInfo)) {
return jsonReturn(-3, Lang::get('角色不存在'));
}
$role->updateUnique(['seller_id' => $this->admin['seller_id'], 'title' => $param['title']], $param['id'], lang('角色已经存在'));
if (!empty($param['auth'])) {
$param['auth'] = implode(',', $param['auth']);
}
$roleInfo->website()->detach();
$roleInfo->website()->attach($param['website_id']);
unset($param['website_id']);
if(!empty($param['kid_auth'])){
$param['kid_auth'] = implode(',', $param['kid_auth']);
}else{
$param['kid_auth'] = '';
}
$res = $role->updateRole($where, $param);
return json($res);
}
return jsonReturn(-3, Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(Role $role): \think\response\Json
{
if (request()->isPost()) {
$id = (int)input('id');
if (!$id) {
return jsonReturn(-1, Lang::get('Id不能为空'));
}
if ($id == 1) {
return jsonReturn(-2, Lang::get('系统默认角色,不能删除'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $role->delRole($where);
return json($res);
}
return jsonReturn(-3, Lang::get('请求方法错误'));
}
/**
* @throws \app\exception\ModelException
*/
public function getAuth(AdminMenu $AdminMenu): \think\response\Json
{
$auth = $AdminMenu->getAllAdminMenu(['seller_id' => $this->admin['seller_id'], 'status' => 1])['data']->toArray();
$auth = generate($auth);
return jsonReturn(0, Lang::get('成功'), $auth);
}
public function getMenuAndUpdateAuth()
{
$adminId = $this->admin['uid'];
$res = RoleService::getMenuAndUpdateAuth($adminId);
return json($res);
}
}

80
app/controller/backend/SeoAccountController.php

@ -0,0 +1,80 @@
<?php
namespace app\controller\backend;
use app\model\SeoAccount;
use app\validate\SeoAccountValidate;
use think\exception\ValidateException;
use think\response\Json;
/**
* SEO账号灌管理
*/
class SeoAccountController extends BaseController
{
public function lists(SeoAccount $seoModel): Json
{
$limit = input('param.limit');
$where = [];
$list = $seoModel->getAccountList($where, $limit);
return json(pageReturn($list));
}
public function add(SeoAccount $seoModel): Json
{
$param = input('post.');
$has = $seoModel->field('id')->where('account', $param['account'])->find();
if (!empty($has)) {
return jsonReturn(-2, lang('该账号已经存在'));
}
try {
validate(SeoAccountValidate::class)->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['create_time'] = date('Y-m-d H:i:s');
$seoModel->insert($param);
return jsonReturn(0, lang('添加账号成功'));
}
public function info(SeoAccount $seoModel): Json
{
$info = $seoModel->where('id', input('param.id'))->find();
return jsonReturn(0, lang('查询成功'), $info);
}
public function edit(SeoAccount $seoModel): Json
{
$param = input('post.');
$has = $seoModel->field('id')->where('account', $param['account'])->where('id', '<>', $param['id'])->find();
if (!empty($has)) {
return jsonReturn(-2, lang('该账号已经存在'));
}
try {
validate(SeoAccountValidate::class)->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$param['update_time'] = date('Y-m-d H:i:s');
$seoModel->where('id', $param['id'])->update($param);
return jsonReturn(0, lang('编辑账号成功'));
}
public function del(SeoAccount $seoModel)
{
$seoModel->where('id', input('param.id'))->delete();
return jsonReturn(0, lang('删除成功'));
}
}

242
app/controller/backend/SeoCheckController.php

@ -0,0 +1,242 @@
<?php
declare (strict_types=1);
namespace app\controller\backend;
use think\facade\Db;
class SeoCheckController extends BaseController
{
public function index()
{
$limit = input('param.limit');
$where = [];
$list = Db::name('seo_check_task')->where($where)->order('create_time desc')->paginate($limit);
return json(pageReturn(['code' => 0, 'data' => $list, 'msg' => lang('成功')]));
}
public function check()
{
$param = $this->request->param();
if ($param['flag'] == 'progress') {
$taskDetail = Db::name('seo_check_task_detail')->where('task_id', $param['task_id'])->select();
$isEnd = true;
$checkData = [];
$success = 0;
$error = 0;
foreach ($taskDetail as $vo) {
if ($vo['status'] == 1) {
$isEnd = false;
}
if (!empty($vo['remark'])) {
$data = json_decode($vo['remark'], true);
$checkData[] = $data;
if ($data['code'] == 101) {
foreach ($data['data'] as $key => $v) {
if ($v['status'] == 1) {
$success++;
continue;
}
if ($key == 'tags') {
$error += isset($v['msg']['ajax']) ? count($v['msg']['ajax']) : 0;
$error += isset($v['msg']['alt']) ? count($v['msg']['alt']) : 0;
continue;
}
if (is_array($v['msg'])) {
$error += count($v['msg']);
} else {
$error ++;
}
}
}
if ($data['code'] == 102) {
if ($data['data']['status'] == 1) {
$success++;
} else {
$error++;
}
}
if ($data['code'] == 103) {
$error += isset($data['data']['deadLink']) ? count($data['data']['deadLink']) : 0;
$error += isset($data['data']['keywords']) ? count($data['data']['keywords']) : 0;
}
if ($data['code'] == 104) {
if (isset($data['data']['inlinks']['linksHave'])) {
foreach ($data['data']['inlinks']['linksHave'] as $v) {
if ($v['status'] == 1) {
$success++;
} else {
$error++;
}
}
}
if (isset($data['data']['inlinks']['nofollow'])) {
foreach ($data['data']['inlinks']['nofollow'] as $v) {
if ($v['status'] == 1) {
$success++;
} else {
$error++;
}
}
}
if (isset($data['data']['outlinks']['linksHave'])) {
foreach ($data['data']['outlinks']['linksHave'] as $v) {
if ($v['status'] == 1) {
$success++;
} else {
$error++;
}
}
}
}
}
}
if ($isEnd) {
Db::name('seo_check_task')->where('id', $param['task_id'])->update([
'status' => 2,
'update_time' => date('Y-m-d H:i:s')
]);
}
return json(['code' => 0, 'data' => [
'success' => $success,
'error' => $error,
'check_data' => $checkData,
'is_end' => $isEnd,
'task' => Db::name('seo_check_task')->where('id', $param['task_id'])->find()
], 'msg' => 'success']);
}
$websiteId = input('param.website_id');
$list = Db::name('theme')->field('lang,theme')->where('is_active', 1)->where('website_id', $websiteId)->select();
return jsonReturn(0, 'success', $list);
}
public function start()
{
if (request()->isPost()) {
$param = input('post.');
$hasRun = Db::name('seo_check_task')->field('id')->where('website_id', $param['website_id'])->where('status', 1)->find();
if (!empty($hasRun)) {
return jsonReturn(-1, lang('该站点正在检测中'));
}
Db::startTrans();
try {
$taskId = Db::name('seo_check_task')->insertGetId([
'website_id' => $param['website_id'],
'website_url' => $param['website_url'],
'lang' => $param['lang'],
'theme' => $param['theme'],
'status' => 1,
'create_time' => date('Y-m-d H:i:s')
]);
$params = [
[
'task_id' => $taskId,
'status' => 1,
'code' => 'code',
'create_time' => date('Y-m-d H:i:s')
],
[
'task_id' => $taskId,
'status' => 1,
'code' => 'web',
'create_time' => date('Y-m-d H:i:s')
],
[
'task_id' => $taskId,
'status' => 1,
'code' => 'keywords',
'create_time' => date('Y-m-d H:i:s')
],
[
'task_id' => $taskId,
'status' => 1,
'code' => 'links',
'create_time' => date('Y-m-d H:i:s')
],
[
'task_id' => $taskId,
'status' => 1,
'code' => 'article',
'create_time' => date('Y-m-d H:i:s')
],
];
Db::name('seo_check_task_detail')->insertAll($params);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
return jsonReturn(-2, $e->getMessage());
}
$taskData = [
'task_id' => $taskId,
'websiteId' => $param['website_id'],
'start_time' => date('Y-m-d H:i:s'),
'lang' => $param['lang'],
'theme' => $param['theme'],
'websiteUrl' => $param['website_url'],
'cate' => Db::name('category')->field('id,title,alias')->where('status', 1)
->where('lang', $param['lang'])
->where('type', '<>', 3)->where('website_id', $param['website_id'])->select(),
'keywords' => Db::name('keyword')->field('name')->where('is_del', 1)->where('website_id', $param['website_id'])->select()
];
// 以text协议发送$task_data数据
$taskData['cmd'] = 'code';
$this->sendData($taskData);
$taskData['cmd'] = 'web';
$this->sendData($taskData);
$taskData['cmd'] = 'keywords';
$this->sendData($taskData);
$taskData['cmd'] = 'links';
$this->sendData($taskData);
$taskData['cmd'] = 'article';
$this->sendData($taskData);
return jsonReturn(0, lang('启动成功'), $taskData);
}
}
public function del()
{
if (!request()->isPost()) {
return jsonReturn(-1, lang('请求错误'));
}
$param = $this->request->only(['id']);
Db::name('seo_check_task')->where('id', '=', $param['id'])->delete();
Db::name('seo_check_task_detail')->where('task_id', '=', $param['id'])->delete();
return jsonReturn(0, lang('删除成功'));
}
protected function sendData($data)
{
// 与服务端建立连接
$client = stream_socket_client('tcp://127.0.0.1:19890');
fwrite($client, json_encode($data) . "\n");
//关闭句柄
fclose($client);
}
}

54
app/controller/backend/SeoController.php

@ -0,0 +1,54 @@
<?php
namespace app\controller\backend;
use app\model\BaiduTjGather;
use app\model\SeoAccount;
class SeoController extends BaseController
{
public function baidu(SeoAccount $seoAccount, BaiduTjGather $baiduTjGather): \think\response\Json
{
$account = $seoAccount->field('id,account')->where('type', 1)->where('status', 1)->select();
$pvData = $baiduTjGather->getYesterdayData()['data'];
if (empty($pvData)) {
$pvData = [
'pv_count' => 0,
'uv_count' => 0,
'ip_count' => 0,
'avg_visit_time' => '00:00:00'
];
} else {
$pvData['avg_visit_time'] = $this->changeTimeType($pvData['avg_visit_time']);
}
$data = [
'account' => $account,
'pvData' => $pvData
];
return jsonReturn(0, lang('查询成功'), $data);
}
/**
* 计算时长
* @param $seconds
* @return string
*/
protected function changeTimeType($seconds): string
{
if ($seconds > 3600) {
$hours = intval($seconds / 3600);
$minutes = $seconds % 3600;
$time = $hours . ":" . gmstrftime('%M:%S', $minutes);
} else {
$time = gmstrftime('%H:%M:%S', $seconds);
}
return $time;
}
}

139
app/controller/backend/SeoSettingController.php

@ -0,0 +1,139 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\SeoSetting;
use app\validate\SeoSettingValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class SeoSettingController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(SeoSetting $seoSetting): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
$seoSettingList = $seoSetting->getSeoSettingList($where,$limit);
return json(pageReturn($seoSettingList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(SeoSetting $seoSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(SeoSettingValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $seoSetting -> addSeoSetting($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(SeoSetting $seoSetting): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $seoSetting->getSeoSetting($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(SeoSetting $seoSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(SeoSettingValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $seoSetting -> updateSeoSetting($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(SeoSetting $seoSetting): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
// TO DO
// 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $seoSetting->delSeoSetting($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

46
app/controller/backend/SiteMapController.php

@ -0,0 +1,46 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Website;
use app\service\Sitemap;
use think\facade\Lang;
class SiteMapController extends BaseController
{
/**
* @throws \app\exception\ModelException
*/
public function gen() {
$website_id = input('website_id');
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $website_id,
];
$sitemap = new Sitemap($where['seller_id'], $where['website_id']);
$sitemap->scan();
$silian = $sitemap->siliamGen();
// 生成xml文件
$path = $sitemap->sitemapGen();
$websiteWhere = [
'id' => $website_id
];
$param = [
'sitemap_url' => $path,
'silian_url' => $silian,
];
$website = new Website();
$res = $website->updateWebsite($websiteWhere, $param);
return jsonReturn($res['code'], Lang('生成成功'), $res['data']);
}
}

125
app/controller/backend/SlideCateController.php

@ -0,0 +1,125 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\SlideCate;
use app\validate\SlideCateValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class SlideCateController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(SlideCate $slideCate): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = $this->setLimit();
$slideCateList = $slideCate->getSlideCateList($where,$limit);
return json(pageReturn($slideCateList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(SlideCate $slideCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(SlideCateValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$res = $slideCate -> addSlideCate($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(SlideCate $slideCate): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('分类ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $slideCate->getSlideCate($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(SlideCate $slideCate): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(SlideCateValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $slideCate -> updateSlideCate($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(SlideCate $slideCate): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('分类ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $slideCate->delSlideCate($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

134
app/controller/backend/SlideController.php

@ -0,0 +1,134 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Slide;
use app\service\CacheService;
use app\validate\SlideValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class SlideController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Slide $slide): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'slide_cate_id' => (int)input('slide_cate_id'),
];
$slideList = $slide->getAllCustomArrayData($where,'sort desc','*',['attachment'=>function($q){
$q->field('id,name,url,type');
}]);
return json($slideList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function save(Slide $slide): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(SlideValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$res = $slide -> addSlide($param);
CacheService::deleteRelationCacheByObject($slide);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Slide $slide): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('幻灯片ID'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// 其他逻辑
$res = $slide->getSlide($where,['attachment'=>function($q){
$q->field('id,name,url,type');
}]);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*@throws \ReflectionException
*/
public function update(Slide $slide): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(SlideValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $slide -> updateSlide($where,$param);
CacheService::deleteRelationCacheByObject($slide);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function delete(Slide $slide): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('幻灯片ID'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $slide->delSlide($where);
CacheService::deleteRelationCacheByObject($slide);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

139
app/controller/backend/SocialMarketingController.php

@ -0,0 +1,139 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\SocialMarketing;
use app\validate\SocialMarketingValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class SocialMarketingController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(SocialMarketing $socialMarketing): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
];
$limit = 10;
$limitParam = (int)input('limit');
if($limitParam){
$limit = $limitParam;
}
// TODO
// 添加其他逻辑
$socialMarketingList = $socialMarketing->getSocialMarketingList($where,$limit);
return json(pageReturn($socialMarketingList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(SocialMarketing $socialMarketing): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
$param['seller_id'] = $this->admin['seller_id'];
// 数据验证
try{
validate(SocialMarketingValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $socialMarketing -> addSocialMarketing($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(SocialMarketing $socialMarketing): \think\response\Json
{
$id = (int)input('id');
if(!$id){
// TODO
// 修改错误消息
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
// TODO
// 其他逻辑
$res = $socialMarketing->getSocialMarketing($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(SocialMarketing $socialMarketing): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(SocialMarketingValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$res = $socialMarketing -> updateSocialMarketing($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
*/
public function delete(SocialMarketing $socialMarketing): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
// TO DO
// 替换错误提示
return jsonReturn(-1,'ErrorMsg');
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $socialMarketing->delSocialMarketing($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

45
app/controller/backend/StaticFileController.php

@ -0,0 +1,45 @@
<?php
namespace app\controller\backend;
use app\service\StaticFileService;
class StaticFileController extends BaseController
{
public function updateCategoryCache()
{
$param = $this->request->only(['site_id', 'lang']);
$param['admin'] = $this->admin;
$staticService = new StaticFileService();
$res = $staticService->updateCategoryCache($param);
return json($res);
}
public function createCategory()
{
$param = $this->request->only(['site_id', 'lang', 'category_id' => 0, 'has_child']);
$param['admin'] = $this->admin;
$param['category_id'] = $param['category_id'] ?? 0;
$staticService = new StaticFileService();
$res = $staticService->createCategory($param['category_id'], $param, $param['has_child']);
return json($res);
}
public function createContent()
{
$param = $this->request->only(['site_id', 'lang', 'category_id' => 0]);
$param['admin'] = $this->admin;
$param['category_id'] = $param['category_id'] ?? 0;
$staticService = new StaticFileService();
$res = $staticService->createContent($param['category_id'], $param);
return json($res);
}
}

617
app/controller/backend/StatisticsController.php

@ -0,0 +1,617 @@
<?php
namespace app\controller\backend;
use app\exception\BadSysSettingException;
use app\model\BaiduTjGather;
use app\model\SeoAccount;
use app\model\SysSetting;
use app\model\Visit;
use app\model\VisitLog;
use app\model\Website;
use think\facade\Lang;
class StatisticsController extends BaseController
{
public $header = '';
public $url = 'http://api.baidu.com/json/tongji/v1/ReportService/getData';
protected $baiduInfo;
private $site_id = 0;
/**
* @throws \app\exception\ModelException
*/
public function setConfig()
{
$id = input('param.id') ?? 0;
if (!empty($id)) {
$info = (new SeoAccount())->where('id', $id)->find();
$this->baiduInfo = [
'account_type' => '1',
'username' => $info['account'],
'password' => $info['password'],
'token' => $info['token'],
];
} else {
$this->baiduInfo = null;
}
}
/**
* @throws \app\exception\ModelException
* @throws BadSysSettingException
*/
public function initialize()
{
parent::initialize();
}
/**
* @throws BadSysSettingException
*/
public function baiduErrorReturn($response)
{
if(!empty($response['header']['failures'])){
throw new BadSysSettingException($response['header']['failures'][0]['message']);
}
}
/**
* @throws BadSysSettingException
*/
public function getResponse($data)
{
$data = json_encode(['body'=>$data, "header"=>$this->header]);
$response = $this->https_request($this->url, $data);
$response = json_decode($response,true);
$this->baiduErrorReturn($response);
return $response;
}
/**
* @throws BadSysSettingException
*/
public function lists(): \think\response\Json
{
$url = 'https://api.baidu.com/json/tongji/v1/ReportService/getSiteList';
$data = json_encode(["header"=>$this->header]);
$response = $this->https_request($url, $data);
$response = json_decode($response, true);
$this->baiduErrorReturn($response);
return jsonReturn(0, Lang::get('查询成功'), $response['body']['data']['0']['list']);
}
public function indexLists()
{
$websiteModel = new Website();
$list = $websiteModel->field('*, id as site_id, domain')->select();
return jsonReturn(0, Lang::get('查询成功'), $list);
}
// 首页趋势图
public function indexQST()
{
ini_set('memory_limit', '512M');
$websiteId = $this->request->request('website_id')?$this->request->request('website_id'):1;
$type = $this->request->request('type')?$this->request->request('type'):1;
$nowDay = date('Y-m-d');
// 今天
$startTime = strtotime($nowDay);
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
if ($type==2) { // 昨天
$startTime = strtotime(date('Y-m-d', strtotime('-1 day')));
$endTime = strtotime($nowDay) - 1;
} elseif ($type==3) { // 最近7天
$startTime = strtotime(date('Y-m-d', strtotime('-6 day')));
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
} elseif ($type==4) { // 最近30天
$startTime = strtotime(date('Y-m-d', strtotime('-29 day')));
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
}
$visitModel = new VisitLog();
$list = $visitModel->where([
['create_time', '>=', $startTime],
['create_time', '<=', $endTime],
['website_id', '=', $websiteId],
])->select()->toArray();
$newList = [];
if ($type == 1 || $type == 2) {
$maxH = 23;
if ($type == 1) {
$maxH = date('H');
}
for ($i = 0; $i <= $maxH; $i++) {
if ($i < 10) {
$i = '0' . $i;
}
$i = (string)$i;
$newList[$i] = 0;
}
} else {
for ($i = $startTime; $i <= $endTime;) {
$dateStr = date('Y-m-d', $i);
$newList[$dateStr] = 0;
$i += 86400;
}
}
$type1List = $type2List = $type3List = $newList;
$type2Unique = []; // 校验唯一
$type3Unique = []; // 校验唯一
// type 为1和2,时间间隔为小时。否则为天
foreach ($list as $value) {
if ($type == 1 || $type == 2) {
$key = date('H', strtotime($value['create_time']));
} else {
$key = date('Y-m-d', strtotime($value['create_time']));
}
$type1List[$key] ++;
if (!isset($type2Unique[$key][$value['agent']])) {
$type2Unique[$key][$value['agent']] = 1;
$type2List[$key] ++;
}
if (!isset($type3Unique[$key][$value['ip']])) {
$type3Unique[$key][$value['ip']] = 1;
$type3List[$key] ++;
}
}
$returnData['pv_list'] = array_values($type1List);
$returnData['uv_list'] = array_values($type2List);
$returnData['ip_list'] = array_values($type3List);
$returnData['time'] = array_keys($type1List);
foreach ($returnData['time'] as &$value) {
$value = (string)$value;
}
return jsonReturn(0, Lang::get('查询成功'), $returnData);
}
// 首页地区分布图
public function indexArea()
{
$websiteId = $this->request->request('website_id')?$this->request->request('website_id'):1;
$type = $this->request->request('type')?$this->request->request('type'):1;
$nowDay = date('Y-m-d');
// 今天
$startTime = strtotime($nowDay);
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
if ($type==2) { // 昨天
$startTime = strtotime(date('Y-m-d', strtotime('-1 day')));
$endTime = strtotime($nowDay) - 1;
} elseif ($type==3) { // 最近7天
$startTime = strtotime(date('Y-m-d', strtotime('-6 day')));
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
} elseif ($type==4) { // 最近30天
$startTime = strtotime(date('Y-m-d', strtotime('-29 day')));
$endTime = strtotime(date('Y-m-d', strtotime('+1 day')));
}
$visitModel = new VisitLog();
$list = $visitModel->where([
['create_time', '>=', $startTime],
['create_time', '<=', $endTime],
['website_id', '=', $websiteId],
['visited_area', '<>', '内网ip'],
])->select()->toArray();
$total = count($list);
$newData = [];
foreach ($list as $value) {
$areaArr = explode('-', $value['visited_area']);
$province = $areaArr[0];
if (!isset($newData[$province])) {
$newData[$province] = [
'name' => $province,
'value' => 1,
'pv_ratio' => bcadd(1 / $total * 100, 0,2),
];
} else {
$newData[$province]['value']++;
$newData[$province]['pv_ratio'] = bcmul($newData[$province]['value']/$total * 100, 0,2);
}
}
return jsonReturn(0, Lang::get('查询成功'), array_values($newData));
}
/**
* 访客年龄分布
* @return false|string|\think\response\Json
* @throws BadSysSettingException
*/
public function age()
{
$data = [
"site_id"=> $this->request->param('site_id') ? $this->request->param('site_id') : $this->site_id,
"method" => "overview/getAge"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
return jsonReturn(0, '查询成功', $response);
}
/**
* 今日昨日浏览量分析
* @throws BadSysSettingException
*/
public function outline(): \think\response\Json
{
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"method" => "overview/getOutline"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
foreach ($response['items'] as $key => $value) {
if(is_numeric($value[5]) && $value[5] != '--'){
$response['items'][$key][5] = $this->secToTime($value[5]);
}elseif(is_array($value[5]) && $value[5]['val'] != '--'){
$response['items'][$key][5]['val'] = $this->secToTime($value[5]['val']);
}
}
return jsonReturn(0, Lang::get('查询成功'),$response);
}
/**
* 趋势图
* pv_count (浏览量PV)
* visitor_count (访客数UV)
* ip_count (IP )
* bounce_ratio (跳出率,%)
* avg_visit_time (平均访问时长,秒)
* trans_count (转化次数)
* @throws BadSysSettingException
*/
public function qxt()
{
$type = $this->request->request('type')?$this->request->request('type'):1;
$contrast = $this->request->request('contrast')?$this->request->request('contrast'):1;
$metrics = $this->request->request('metrics')?$this->request->request('metrics'):'pv_count';
switch ($metrics) {
case 'visitor_count':
$name = Lang::get('访客数(UV)');
break;
case 'ip_count':
$name = Lang::get('IP数');
break;
case 'bounce_ratio':
$name = Lang::get('跳出率');
break;
case 'avg_visit_time':
$name = Lang::get('平均访问时长');
break;
case 'trans_count':
$name = Lang::get('转化次数');
break;
default:
$name = Lang::get('浏览量(PV)');
break;
}
$et = date('Ymd', time());
if($type==1){
$st = date('Ymd', time());
if($contrast==1){
$start_date2 = date('Ymd', time()-(3600*24));
$end_date2 = date('Ymd', time()-(3600*24));
}elseif($contrast==2){
$start_date2 = date('Ymd', time()-(3600*24*7));
$end_date2 = date('Ymd', time()-(3600*24*7));
}
}elseif ($type==2) {
$st = date('Ymd', strtotime('-1 day'));
$et = date('Ymd', strtotime('-1 day'));
if($contrast==1){
$start_date2 = date('Ymd', strtotime('-1 day')-(3600*24));
$end_date2 = date('Ymd', strtotime('-1 day')-(3600*24));
}elseif($contrast==2){
$start_date2 = date('Ymd', time()-(3600*24*7));
$end_date2 = date('Ymd', time()-(3600*24*7));
}
}elseif ($type==3) {
$st = date('Ymd', strtotime('-6 day'));
}elseif ($type==4) {
$st = date('Ymd', strtotime('-29 day'));
}
if($contrast && ($type == 1 or $type == 2)){
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $st,
"end_date" => $et,
"start_date2" => $start_date2,
"end_date2" => $end_date2,
"metrics" => $metrics,
"method" => "overview/getTimeTrendRpt"
];
}else{
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $st,
"end_date" => $et,
"metrics" => $metrics,
"method" => "overview/getTimeTrendRpt"
];
}
$response = $this->getResponse($data);
$result = $response['body']['data']['0']['result']['items'];
$new = [];
$new[0] = $result[1];
$new[1] = $result[2];
if($metrics != 'avg_visit_time'){
foreach ($new as $key => $value) {
foreach ($value as $k => $v) {
$count = count($value);
if($type == 3 || $type == 4){
$new[$key][$k][0] = date('Y/m/d', strtotime('-'.($count-$k-1).' day'));
$new[$key][$k][1] = $v[0];
}
if($k<=10){
array_push($new[$key][$k], $k.':00-'.$k.':59');
}else{
array_push($new[$key][$k], $k.':00-'.$k.':59');
}
}
}
}else{
if($type == 3 || $type ==4){
$anew = [];
$anew[0] = $result[1];
foreach ($anew as $key => $value) {
foreach ($value as $k => $v) {
$count = count($value);
$anew[$key][$k][0] = date('Y/m/d', strtotime('-'.($count-$k-1).' day'));
$anew[$key][$k][1] = $v[0];
if($k<=10){
array_push($anew[$key][$k], $k.':00-'.$k.':59');
}else{
array_push($anew[$key][$k], $k.':00-'.$k.':59');
}
array_push($anew[$key][$k],$this->secToTime($v[0]));
}
}
return json_encode(['code'=>0, 'msg'=> Lang::get('查询成功'),'name'=>$name,'data'=>$anew]);
}else{
foreach ($new as $key => $value) {
foreach ($value as $k => $v) {
if($k<=10){
array_push($new[$key][$k], $k.':00-'.$k.':59');
}else{
array_push($new[$key][$k], $k.':00-'.$k.':59');
}
array_push($new[$key][$k],$this->secToTime($v[1]));
}
}
}
}
return jsonReturn(0, Lang::get('查询成功'),['name'=>$name,'data'=>$new]);
}
// /**
// * 关键词消费排名
// * @throws BadSysSettingException
// */
// public function search_word()
// {
// $type = $this->request->request('type')?$this->request->request('type'):1;
// $se = $this->getSTAndET($type);
// $data = [
// "site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
// "start_date" => $se['st'],
// "end_date" => $se['st'],
// "metrics" => "pv_count,visit_count,visitor_count",
// "method" => "overview/getWord"
// ];
// $response = $this->getResponse($data);
// $response = $response['body']['data']['0']['result'];
// return jsonReturn(0, '查询成功',$response);
// }
/**
* Top10搜索词
* @throws BadSysSettingException
*/
public function search_word()
{
$type = $this->request->request('type')?$this->request->request('type'):1;
$se = $this->getSTAndET($type);
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $se['st'],
"end_date" => $se['st'],
"metrics" => "pv_count,pv_ratio",
"method" => "source/searchword/a"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
$list = $response['items'][0] ?? [];
if (count($list) > 10) {
$list = array_slice($list, 0, 10);
$response['items'][0] = $list;
}
return jsonReturn(0, Lang::get('查询成功'),$response);
}
public function https_request($url,$data)
{
// 初始化
$curl = curl_init();
// 设置
curl_setopt($curl,CURLOPT_URL,$url);
// 检查ssl证书
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
// 从检查本地证书检查是否ssl加密
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,$url);
// 判断$data 判断是否post
if ( !empty($data) ) {
curl_setopt($curl,CURLOPT_POST,1);// 开启post
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);// 发送post $data
}
// 返回结果 是文件流的方式返回
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if($err){
return false;
}else{
return $response;
}
}
/**
* 网站概况(来源网站、搜索词、入口页面、受访页面、新老访客)
* @throws BadSysSettingException
*/
public function get_common_track_rpt(): \think\response\Json
{
$type = $this->request->request('type') ? $this->request->request('type') : 1;
$se = $this->getSTAndET($type);
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $se['st'],
"end_date" => $se['et'],
"method" => "overview/getCommonTrackRpt"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
foreach ($response['visitType'] as $key => $value) {
$response['visitType'][$key]['avg_visit_time'] = $this->secToTime($value['avg_visit_time']);
}
return jsonReturn(0, Lang::get('查询成功'), $response);
}
/**
* 网站概况(地域分布)
* @throws BadSysSettingException
*/
public function area()
{
$type = $this->request->request('type')?$this->request->request('type'):2;
$se = $this->getSTAndET($type);
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $se['st'],
"end_date" => $se['et'],
"metrics"=> "pv_count,pv_ratio",
"method" => "visit/district/a"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
$new = [];
foreach ($response['items'][0] as $key => $value) {
$new[$key]['name'] = $value[0]['name'];
$new[$key]['area'] = $value[0]['area'];
}
foreach ($response['items'][1] as $key => $value) {
$new[$key]['pv_count'] = $value[0];
$new[$key]['pv_ratio'] = $value[1];
}
return jsonReturn(0, Lang::get('查询成功'),$new);
}
/**
*
* 网站概况(地域分布Top10)
*
* @throws BadSysSettingException
*/
public function area_top(): \think\response\Json
{
$type = $this->request->request('type')?$this->request->request('type'):2;
$se = $this->getSTAndET($type);
$data = [
"site_id"=> $this->request->param('site_id')?$this->request->param('site_id'):$this->site_id,
"start_date" => $se['st'],
"end_date" => $se['et'],
"metrics"=> "pv_count,pv_ratio",
"method" => "visit/district/a"
];
$response = $this->getResponse($data);
$response = $response['body']['data']['0']['result'];
$new = [];
foreach ($response['items'][0] as $key => $value) {
$new[$key]['name'] = $value[0]['name'];
$new[$key]['area'] = $value[0]['area'];
}
foreach ($response['items'][1] as $key => $value) {
$new[$key]['pv_count'] = $value[0];
$new[$key]['pv_ratio'] = $value[1];
}
$new = array_slice($new, 0, 10);
return jsonReturn(0, Lang::get('查询成功'), $new);
}
public function http_request($url,$data)
{
// 初始化
$curl = curl_init();
// 设置
curl_setopt($curl,CURLOPT_URL,$url);
// 检查ssl证书
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE);
// 从检查本地证书检查是否ssl加密
curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,$url);
// 判断$data 判断是否post
if ( !empty($data) ) {
curl_setopt($curl,CURLOPT_POST,1);// 开启post
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);// 发送post $data
}
// 返回结果 是文件流的方式返回
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
return $response;
}
// 获取开始日期和结束日期
public function getSTAndET($type): array
{
$et = date('Ymd', time());
if($type==1){
$st = date('Ymd', time());
}elseif ($type==2) {
$st = date('Ymd', strtotime('-1 day'));
$et = date('Ymd', strtotime('-1 day'));
}elseif ($type==3) {
$st = date('Ymd', strtotime('-6 day'));
}else {
$st = date('Ymd', strtotime('-29 day'));
}
return ['st' => $st,'et'=>$et];
}
public function secToTime($sec)
{
if (is_numeric($sec)) {
$strSecond = gmstrftime('%H:%M:%S', $sec);
} else {
$strSecond = $sec;
}
return $strSecond;
}
}

180
app/controller/backend/SysSettingController.php

@ -0,0 +1,180 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\SysSetting;
use app\service\CacheService;
use app\validate\SysSettingValidate;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Lang;
class SysSettingController extends BaseController
{
protected $group = ['email','upload','sem','analysis','others','company'];
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(SysSetting $sysSetting): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'status' => 1,
];
$order = 'sort asc,id asc';
$sysSettingList = $sysSetting->getAllCustomArrayData($where,$order)['data'];
$sysSettingList = makeTree($sysSettingList);
return jsonReturn(0,Lang::get('成功'),$sysSettingList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(SysSetting $sysSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(SysSettingValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$res = $sysSetting -> addCustomData($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function read(SysSetting $sysSetting): \think\response\Json
{
$param = input('param.');
// 数据验证
try{
validate(SysSettingValidate::class)->scene('read')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$where = [
['group','=',$param['group']],
['seller_id','=',$this->admin['seller_id']],
];
$order = 'sort asc';
$res = $sysSetting->getAllCustomArrayData($where,$order)['data'];
$res = generate($res);
return jsonReturn(0,Lang::get('成功'),$res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException*@throws \ReflectionException
* @throws \ReflectionException
*/
public function update(SysSetting $sysSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
if(empty($param['group'])){
return jsonReturn(-1,Lang::get('配置分组不能为空'));
}
if(!in_array($param['group'],$this->group)){
return jsonReturn(-2,Lang::get('配置分组类型错误'));
}
if($param['group'] == 'setting'){
Cache::delete('hc_company_'. $this->admin['seller_id']);
}
try {
validate(SysSettingValidate::class)->check($param);
} catch (\Exception $e) {
return jsonReturn(-1, $e->getMessage());
}
$where = [
'seller_id' => $this->admin['seller_id'],
];
foreach($param as $key => $val){
$whereVal = array_merge($where,['title'=>$key]);
$sysSetting -> updateSysSetting($whereVal,['value'=>$val]);
}
CacheService::deleteRelationCacheByObject($sysSetting);
return jsonReturn(0,Lang::get("保存成功"));
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function emailClear(SysSetting $sysSetting): \think\response\Json
{
$sysSetting -> updateSysSetting(['seller_id' => $this->admin['seller_id'],'group'=>'email'],['value'=>'']);
CacheService::deleteRelationCacheByObject($sysSetting);
return jsonReturn(0,Lang::get("成功"));
}
public function clearCache(): \think\response\Json
{
CacheService::clear();
return jsonReturn(0, Lang::get("成功"));
}
/**
*
* @throws \app\exception\ModelException
*/
public function password(): \think\response\Json
{
$param = $this->request->only([
'status','length','string_type','special_char','change_duration'
]);
try {
validate(SysSettingValidate::class)->scene('pass')->check($param);
} catch (\Exception $e) {
return jsonReturn(-1, $e->getMessage());
}
$sysSetting = new SysSetting();
$status = $param['status'] ?? 2;
unset($param['status']);
$value = json_encode($param);
$where = [
'id' => '120',
'seller_id' => $this->admin['seller_id'],
];
$res = $sysSetting -> updateSysSetting($where,['value'=>$value,'status'=>$status]);
return json($res);
}
/**
* @throws \app\exception\ModelException
*/
public function passInfo(): \think\response\Json
{
$sysSetting = new SysSetting();
$where = [
'id' => '120',
'seller_id' => $this->admin['seller_id'],
];
$res = $sysSetting -> getSysSetting($where);
if(!empty($res['data']['value'])){
$res['data']['value'] = json_decode($res['data']['value'],true);
}
return json($res);
}
}

537
app/controller/backend/SystemInstallController.php

@ -0,0 +1,537 @@
<?php
declare (strict_types=1);
namespace app\controller\backend;
use app\BaseController;
use app\exception\InstallException;
use app\model\Admin;
use app\model\Model;
use app\model\Route;
use app\model\Website;
use app\model\WebsiteLang;
use app\model\WebsiteServer;
use app\model\WebsiteSetting;
use app\service\ThemeService;
use app\validate\SystemInstallValidate;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Log;
use think\facade\View;
use think\Validate;
class SystemInstallController extends BaseController
{
/**
* 显示资源列表
*
*/
public function index()
{
if (hcInstalled()) {
return redirect('/admin.php');
}
// 向huocms官网请求是否认证
$domain = request()->host();
if (!empty($domain)) {
$authRes = curlPost(config('system.auth_query_url'), ['domain' => $domain])['data'];
Log::info('auth_query_url:' . $authRes);
}
$year = date('Y', time());
$month = date('m', time());
$day = date('d', time());
View::assign(['year' => $year, 'month' => $month, 'day' => $day]);
return View::fetch(CMS_ROOT . 'data/install/install.html');
}
public function monitor(): string
{
// if (file_exists_case('data/conf/config.php')) {
// @unlink('data/conf/config.php');
// }
$data = [];
$data['phpversion'] = @phpversion();
$data['os'] = PHP_OS;
$tmp = function_exists('gd_info') ? gd_info() : [];
// $server = $_SERVER["SERVER_SOFTWARE"];
// $host = $this->request->host();
// $name = $_SERVER["SERVER_NAME"];
// $max_execution_time = ini_get('max_execution_time');
// $allow_reference = (ini_get('allow_call_time_pass_reference') ? '<font color=green>[√]On</font>' : '<font color=red>[×]Off</font>');
// $allow_url_fopen = (ini_get('allow_url_fopen') ? '<font color=green>[√]On</font>' : '<font color=red>[×]Off</font>');
// $safe_mode = (ini_get('safe_mode') ? '<font color=red>[×]On</font>' : '<font color=green>[√]Off</font>');
$err = 0;
if (empty($tmp['GD Version'])) {
$gd = '/system_file/install/image/success_icon.jpg';
$err++;
} else {
$gd = '/system_file/install/image/error_icon.png ' . $tmp['GD Version'];
}
if (class_exists('pdo')) {
$data['pdo'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['pdo'] = '/system_file/install/image/error_icon.png';
$err++;
}
if (extension_loaded('pdo_mysql')) {
$data['pdo_mysql'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['pdo_mysql'] = '/system_file/install/image/error_icon.png';
$err++;
}
if (extension_loaded('curl')) {
$data['curl'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['curl'] = '/system_file/install/image/error_icon.png';
$err++;
}
if (extension_loaded('gd')) {
$data['gd'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['gd'] = '/system_file/install/image/error_icon.png';
if (function_exists('imagettftext')) {
$data['gd'] .= '/system_file/install/image/error_icon.png';
}
$err++;
}
if (extension_loaded('mbstring')) {
$data['mbstring'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['mbstring'] = '/system_file/install/image/error_icon.png';
if (function_exists('imagettftext')) {
$data['mbstring'] .= '/system_file/install/image/error_icon.png';
}
$err++;
}
if (extension_loaded('fileinfo')) {
$data['fileinfo'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['fileinfo'] = '/system_file/install/image/error_icon.png';
$err++;
}
if (ini_get('file_uploads')) {
$data['upload_size'] = ini_get('upload_max_filesize');
} else {
$data['upload_size'] = '未开启';
$err++;
}
if (function_exists('session_start')) {
$data['session'] = '/system_file/install/image/success_icon.jpg';
} else {
$data['session'] = '/system_file/install/image/error_icon.png';
$err++;
}
if (version_compare(phpversion(), '7.2.0', '>=') && version_compare(phpversion(), '7.0.0', '<') && ini_get('always_populate_raw_post_data') != -1) {
$data['always_populate_raw_post_data'] = '未关闭';
$data['show_always_populate_raw_post_data_tip'] = true;
$err++;
} else {
$data['always_populate_raw_post_data'] = '已关闭';
}
$folders = [
realpath(CMS_ROOT . 'data') . DIRECTORY_SEPARATOR,
realpath(CMS_ROOT . 'runtime') . DIRECTORY_SEPARATOR,
realpath(CMS_ROOT . 'public/themes') . DIRECTORY_SEPARATOR,
realpath(CMS_ROOT . 'public/storage') . DIRECTORY_SEPARATOR,
realpath(CMS_ROOT . 'public/poster_preview') . DIRECTORY_SEPARATOR,
];
$newFolders = [];
foreach ($folders as $dir) {
$testDir = $dir;
sp_dir_create($testDir);
if (sp_testwrite($testDir)) {
$newFolders[$dir]['w'] = true;
} else {
$newFolders[$dir]['w'] = false;
$err++;
}
if (is_readable($testDir)) {
$newFolders[$dir]['r'] = true;
} else {
$newFolders[$dir]['r'] = false;
$err++;
}
if ($newFolders[$dir]['w'] && $newFolders[$dir]['r']) {
$newFolders[$dir]['icon'] = '/system_file/install/image/success_icon.jpg';
} else {
$newFolders[$dir]['icon'] = '/system_file/install/image/error_icon.png';
}
}
$data['err'] = $err;
$data['folders'] = $newFolders;
View::assign($data);
return View::fetch(CMS_ROOT . 'data/install/monitor.html');
}
public function configure(): string
{
return View::fetch(CMS_ROOT . 'data/install/configure.html');
}
public function create(): string
{
$param = request()->param();
if (empty($param['username'])) {
$param['username'] = 'admin@admin.com';
}
if (empty($param['password'])) {
$param['password'] = 'huocms.com';
}
$dbConfig = [
'type' => 'mysql',
// 连接名
'hostname' => $param['hostname'],
// 用户名
'username' => $param['username'],
// 密码
'password' => $param['password'],
// 端口
'hostport' => $param['hostport'],
// 数据库编码默认采用utf8mb4
'charset' => $param['charset'],
// 数据库表前缀
'prefix' => $param['prefix'],
];
$this->updateDbConfig($dbConfig);
$sql = "CREATE DATABASE IF NOT EXISTS `{$param['database']}` DEFAULT CHARACTER SET " . $param['charset'];
$db = Db::connect('install_db');
$db->execute($sql);
$dbConfig['database'] = $param['database'];
$this->exchangeEnv($dbConfig);
session('install.db_config', $dbConfig);
$sql = hcSplitSql(CMS_ROOT . '/data/install/install.sql', $dbConfig['prefix'], $dbConfig['charset']);
session('install.sql', $sql);
View::assign('sql_count', count($sql));
session('install.error', 0);
session('install.admin_info', [
'name' => $param['admin'],
'account' => $param['email'],
'password' => makePassword($param['admin_pass'])
]);
return View::fetch(CMS_ROOT . 'data/install/create.html');
}
public function install()
{
$dbConfig = session('install.db_config');
$sql = session('install.sql');
if (empty($dbConfig) || empty($sql)) {
return json([
'code' => -1,
'data' => '',
'msg' => '非法安装!'
]);
}
$sqlIndex = $this->request->param('sql_index', 0, 'intval');
$this->updateDbConfig($dbConfig);
$db = Db::connect('install_db');
if ($sqlIndex >= count($sql)) {
return json([
'code' => 200,
'data' => '',
'msg' => '安装完成!'
]);
}
$sqlToExec = $sql[$sqlIndex] . ';';
$result = sp_execute_sql($db, $sqlToExec);
if (!empty($result['error'])) {
return json([
'code' => -1,
'data' => [
'sql' => $sqlToExec,
'exception' => $result['exception']
],
'msg' => '安装失败!'
]);
} else {
return json([
'code' => 0,
'data' => $result,
'msg' => '安装成功!'
]);
}
}
public function exchangeEnv($dbConfig)
{
$envFile = CMS_ROOT . '.env';
$example = CMS_ROOT . '.example.env';
if (file_exists($example)) {
copy($example, $envFile);
} else {
touch($envFile);
$initArray = [
"APP_DEBUG = true",
"APP_HOST = http://localhost",
"",
"[APP]",
"DEFAULT_TIMEZONE = Asia/Shanghai",
"",
"[DATABASE]",
"TYPE = mysql",
"HOSTNAME = 127.0.0.1",
"DATABASE = huo_cms",
"USERNAME = root",
"PASSWORD = ",
"HOSTPORT = 3306",
"CHARSET = utf8mb4",
"DEBUG = true",
"PREFIX = hc_",
"",
"[LANG]",
"default_lang = zh",
];
file_put_contents($envFile, implode(PHP_EOL, $initArray));
}
$merge = [
'app_debug' => true,
'app_host' => request()->domain(),
];
$data = [];
foreach (array_merge($merge, $dbConfig) as $key => $val) {
$key = strtoupper($key);
$data[$key] = $val;
}
$this->updateEnv($envFile, $data);
}
public function updateEnv($envFile, $data = array())
{
$oldEnv = file($envFile);
if (!count($data)) {
return;
}
if (array_keys($data) === range(0, count($data) - 1)) {
return;
}
$pattern = '/([^\=]*)\=[^\n]*/';
$newEnv = [];
foreach ($oldEnv as $line) {
preg_match($pattern, $line, $matches);
if (!count($matches)) {
$newEnv[] = $line;
continue;
}
if (!key_exists(trim($matches[1]), $data)) {
$newEnv[] = $line;
continue;
}
$line = trim($matches[1]) . "={$data[trim($matches[1])]}\n";
$newEnv[] = $line;
}
$newContent = implode('', $newEnv);
file_put_contents($envFile, $newContent);
}
public function userCheck(): \think\response\Json
{
$param = request()->param();
try {
validate(SystemInstallValidate::class)->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
return jsonReturn(0, 'success');
}
public function testPass(): \think\response\Json
{
$param = request()->param();
$dbConfig = [
'type' => 'mysql',
// 连接名
'hostname' => $param['hostname'],
// 数据库名
'username' => $param['username'],
// 密码
'password' => $param['password'],
// 端口
'hostport' => $param['hostport'],
];
$this->updateDbConfig($dbConfig);
$supportInnoDb = false;
try {
$db = Db::connect('install_db');
$engines = $db->query("SHOW ENGINES;");
foreach ($engines as $engine) {
if ($engine['Engine'] == 'InnoDB' && $engine['Support'] != 'NO') {
$supportInnoDb = true;
break;
}
}
} catch (\Exception $e) {
return jsonReturn(-1, '数据库账号或密码不正确!' . $e->getMessage());
}
if (!$supportInnoDb) {
return jsonReturn(-2, '数据库账号密码验证通过,但不支持InnoDb!');
} else {
return jsonReturn(0, 'success');
}
}
public function installCompleteCheck(): \think\response\Json
{
$admin = session("install.admin_info");
if (empty($admin)) {
return jsonReturn(-1, '用户创建失败,请重试');
}
Db::startTrans();
try {
// 创建管理员
$Admin = new Admin();
$admin['seller_id'] = 1;
$admin['status'] = 1;
$admin['group'] = 1;
$res = $Admin->addAdmin($admin)['data'];
$res->role()->attach(1, ['seller_id' => 1]);
session('adminId', $res['id']);
$website = new Website();
$siteParam = [
'title' => '演示站点',
'domain' => request()->host()
];
// 创建站点
// 1. 保存站点到数据库
$res = $website->addWebsite($siteParam);
if ($res['data'] != 1) {
throw new InstallException();
}
// 2. 生成一份默认的站点配置,配置的值为空
$tmpData = config('system.website_setting');
$sysData = [
"setting" => json_encode($tmpData),
"website_id" => $res['data'],
"lang" => "zh"
];
$siteSetting = new WebsiteSetting();
$siteSetting->addWebsiteSetting($sysData);
// 3. 选择站点语言简体中文,服务器为本地服务器
$langData[] = [
'seller_id' => 1,
'website_id' => $res['data'],
'lang_name' => '简体中文',
'lang' => 'zh',
];
$WebsiteLang = new WebsiteLang();
$WebsiteLang->addWebsiteLang($langData);
$serverData = [
'seller_id' => 1,
'website_id' => $res['data'],
'type' => 1,
];
$WebsiteServer = new WebsiteServer();
$WebsiteServer->addWebsiteServer($serverData);
// 4.生成路由
$Route = new Route();
$Route->updateRoute(['seller_id' => 1], ['website_id' => $res['data']]);
$Route->getRoutes($res['data'], 1, 'zh');
// 5.安装模版
$ThemeService = new ThemeService();
$ThemeService->installTheme('demo', $res['data'], 1);
createInstallFile();
session("install.step", 4);
Db::commit();
} catch (InstallException $e) {
@unlink(CMS_ROOT . 'public/system_file/install.lock');
Db::rollback();
return jsonReturn(-3, '安装失败,请清空数据库后重试');
} catch (\Exception $e) {
@unlink(CMS_ROOT . 'public/system_file/install.lock');
Db::rollback();
return jsonReturn(-1, '用户创建失败,请重试' . $e->getMessage());
}
return jsonReturn(0, 'success');
}
public function completeCheck(): \think\response\Json
{
if (session("install.step") == 4) {
return jsonReturn(0, '安装成功');
} else {
return jsonReturn(-1, '安装失败');
}
}
public function complete(): string
{
return View::fetch(CMS_ROOT . 'data/install/complete.html');
}
private function updateDbConfig($dbConfig)
{
$oldDbConfig = config('database');
$oldDbConfig['connections']['install_db'] = $dbConfig;
config($oldDbConfig, 'database');
}
public function write_ini_file($assoc_arr, $path, $has_sections = FALSE): bool
{
$content = "";
if ($has_sections) {
foreach ($assoc_arr as $key => $elem) {
$content .= "[" . $key . "]n";
foreach ($elem as $key2 => $elem2) {
if (is_array($elem2)) {
for ($i = 0; $i < count($elem2); $i++) {
$content .= $key2 . "[] = " . $elem2[$i] . "\r\n";
}
} else if ($elem2 == "") {
$content .= $key2 . " = n";
} else {
$content .= $key2 . " = " . $elem2 . "\r\n";
}
}
}
} else {
foreach ($assoc_arr as $key => $elem) {
if (is_array($elem)) {
for ($i = 0; $i < count($elem); $i++) {
$content .= $key . "[] = " . $elem[$i] . "\r\n";
}
} else if ($elem == "") {
$content .= $key . " = n";
} else {
$content .= $key . " = " . $elem . "\r\n";
}
}
}
if (!$handle = fopen($path, 'w')) {
return false;
}
if (!fwrite($handle, $content)) {
return false;
}
fclose($handle);
return true;
}
}

169
app/controller/backend/TagController.php

@ -0,0 +1,169 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelException;
use app\model\RecycleBin;
use app\model\Tag;
use app\validate\TagValidate;
use Overtrue\Pinyin\Pinyin;
use think\exception\ValidateException;
use think\facade\Lang;
class TagController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(Tag $tag): \think\response\Json
{
$websiteId = (int)input('param.website_id');
if(!$websiteId){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$lang = input('lang');
if(empty($lang)){
$lang = 'zh';
}
$limit = $this->setLimit();
$title = input('title') ?: '';
$where = [
['seller_id', '=' ,$this->admin['seller_id']],
['website_id' ,'=', $websiteId],
['lang','=',$lang],
['is_del','=',1],
];
if($title){
array_push($where,['title','like','%'.$title.'%']);
}
try{
$tagList = $tag->where($where)->paginate($limit)->each(function(&$item){
$contentNum = $item->subContent()->where('is_del',1)->count();
$item -> article_count = $contentNum;
$item -> save();
});
}catch(\Exception $e){
throw new ModelException($e->getMessage());
}
return json(['code'=>0,'total'=>$tagList->total(),'msg'=>Lang::get('成功'),'data'=>$tagList->all()]);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(Tag $tag): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(TagValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$param['unique_tag'] = $param['seller_id'] . '-' . $param['website_id'] . '-' . $param['lang'] .'-' . $param['title'];
$tag->saveUnique(['unique_tag'=> $param['unique_tag'],'is_del'=>1],Lang::get('标签名称已经存在'));
$pinyin = new Pinyin();
$param['first_letter'] = strtoupper(substr($pinyin->abbr($param['title']),0,1));
$param = $this->setSEO($param);
$res = $tag -> addTag($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException*@throws \app\exception\ModelNotUniqueException
* @throws \app\exception\ModelNotUniqueException
*/
public function update(Tag $tag): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(TagValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$param = $this->setSEO($param);
$param['unique_tag'] = $this->admin['seller_id'] . '-' . $param['website_id'] . '-' . $param['lang'] .'-'. $param['title'];
$tag->updateUnique(['unique_tag' => $param['unique_tag']],$param['id'],Lang::get('标签名称已经存在'));
$pinyin = new Pinyin();
$param['first_letter'] = strtoupper(substr($pinyin->abbr($param['title']),0,1));
$res = $tag -> updateTag($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function delete(Tag $Tag,RecycleBin $recycleBin): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('标签ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id'],
];
$tag = $Tag->getTag($where)['data'];
// 复制删除内容到回收站表
$binData = [
'seller_id' => $this->admin['seller_id'],
'object_id' => $id,
'sub_id' => '',
'module_id' => '',
'table_name' => 'tag',
'title' => $tag['title'],
'admin_id' => $this->admin['seller_id'],
'name' => $this->admin['name'],
];
$recycleBin -> addRecycleBin($binData);
$res = $Tag -> softDelTag($where);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 配置SEO
* @param $param
* @return mixed
*/
public function setSEO($param)
{
if(empty($param['seo_title'])){
$param['seo_title'] = $param['title'];
}
if(empty($param['seo_keywords'])){
$param['seo_keywords'] = $param['title'];
}
if(empty($param['seo_description'])){
$param['seo_description'] = $param['title'];
}
return $param;
}
}

277
app/controller/backend/ThemeController.php

@ -0,0 +1,277 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelEmptyException;
use app\exception\ModelException;
use app\model\Theme;
use app\model\ThemeFile;
use app\service\ThemeService;
use app\validate\ThemeValidate;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Lang;
use think\response\Json;
class ThemeController extends BaseController
{
/**
* 已安装模板
*
* @return Json
* @throws ModelException
*/
public function index(Theme $theme): Json
{
$website_id = (int)input('param.website_id');
if(!$website_id){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$lang = input('param.lang') ?: config('lang.default_lang');
$where = [
['seller_id','=',$this->admin['seller_id']],
['website_id','=',$website_id],
['lang','=',$lang]
];
$limit = $this->setLimit();
$themeList = $theme->getThemeList($where,$limit);
return json(pageReturn($themeList));
}
/**
* 模板完整更新
*
* @return Json
* @throws ModelException*@throws \app\exception\ModelEmptyException
* @throws ModelEmptyException
*/
public function update(ThemeService $themeService): Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(ThemeValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$suffix = config('view.view_suffix');
return $themeService->updateTheme($param['theme_id'],$param['website_id'],$this->admin['seller_id'],$suffix);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 系统模版
*
* @param Theme $themeModel
* @return Json
*/
public function install(Theme $themeModel): Json
{
$themesDirs = hcScanDir("themes/hc_original/*", GLOB_ONLYDIR);
$themes = [];
foreach ($themesDirs as $dir) {
$manifest = "themes/hc_original/$dir/manifest.json";
if (hcFileExist($manifest)) {
$manifest = file_get_contents($manifest);
$theme = json_decode($manifest, true);
}
$theme['theme'] = $dir;
$themes[] = $theme;
}
return jsonReturn(0,Lang::get('成功'),$themes);
}
/**
* 模版安装
*
* @throws ModelException
*/
public function installTheme(ThemeService $service): Json
{
if ($this->request->isPost()) {
set_time_limit(300);
$param = input('post.');
try {
validate(ThemeValidate::class)->scene('installTheme')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
$suffix = config('view.view_suffix');
return $service->installTheme($param['theme'],$param['website_id'],$this->admin['seller_id'],$suffix,$param['lang']);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 卸载模板
* @throws ModelEmptyException
* @throws ModelException
*/
public function uninstall(ThemeService $service): Json
{
if ($this->request->isPost()) {
$param = input('post.');
try {
validate(ThemeValidate::class)->scene('uninstall')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
return $service->uninstallTheme($param['theme_id'],$param['website_id'],$this->admin['seller_id']);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 模板文件
*
* @throws ModelException
*/
public function tmpFile(ThemeFile $ThemeFile): Json
{
$param = input('param.');
try {
validate(ThemeValidate::class)->scene('tmpFile')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$limit = $this->setLimit();
$where = [
'seller_id' => $this->admin['seller_id'],
'theme_id' => $param['theme_id'],
'website_id' => $param['website_id'],
];
$res = $ThemeFile -> getThemeFileList($where,'id,name,file,real_path',$limit);
return json(pageReturn($res));
}
/**
*
* @param ThemeFile $ThemeFile
* @return Json
* @throws ModelException
*/
public function templateFile(ThemeFile $ThemeFile): Json
{
$param = input('param.');
try {
validate(ThemeValidate::class)->scene('templateFile')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$theme = new Theme();
$activeTheme = $theme->getActiveTheme(['seller_id'=>$this->admin['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang'],'is_active'=>1])['data'];
if(empty($activeTheme)){
return jsonReturn(-1,Lang::get('未安装或启用模版'));
}
$where = [
'seller_id' => $this->admin['seller_id'],
'theme_id' => $activeTheme['id'],
'website_id' => $param['website_id'],
];
$template = $ThemeFile -> getAllCustomArrayData($where)['data'];
return jsonReturn(0,Lang::get('成功'),$template);
}
/**
* 启用模版
*
* @throws ModelException
*/
public function active(Theme $Theme): Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(ThemeValidate::class)->scene('active')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
Db::startTrans();
try{
// 将所有模版暂停启用
$Theme->updateTheme(['seller_id'=>$this->admin['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang']],['is_active'=>2]);
// 启用当前模版
$res = $Theme->updateTheme(['id'=>$param['theme_id'],'seller_id'=>$this->admin['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang']],['is_active'=>1]);
Db::commit();
}catch (\Exception $e){
Db::rollback();
return jsonReturn(-3,Lang::get('模版安装启用错误'));
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 所有目录
* @throws ModelEmptyException
* @throws ModelException
*/
public function allFilePath(ThemeService $service): Json
{
$param = input('param.');
try {
validate(ThemeValidate::class)->scene('allFilePath')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
return $service->filesPath($param['website_id'],$param['lang']);
}
public function allFiles(ThemeService $service): Json
{
$param = input('param.');
try {
validate(ThemeValidate::class)->scene('allFiles')->check($param);
}catch (ValidateException $e){
return jsonReturn(-1,$e->getError());
}
return $service->files($param['path'],(int)$param['website_id'],$param['lang']);
}
public function dealWithImg(): Json
{
$sourcePath = input('post.source');
$terminalPath = input('post.terminal');
$sourceFiles = glob($sourcePath.'/*');
$tmpFiles = [];
foreach ($sourceFiles as $sourceFile) {
$key = pathinfo($sourceFile,PATHINFO_FILENAME);
$ext = pathinfo($sourceFile,PATHINFO_EXTENSION);
$tmpFiles[$key] = $ext;
}
$terminalFiles = glob($terminalPath.'/*');
$termFiles = [];
foreach ($terminalFiles as $val) {
$key = pathinfo($val,PATHINFO_FILENAME);
$ext = pathinfo($val,PATHINFO_EXTENSION);
$termFiles[$key] = $ext;
}
foreach ($termFiles as $key => $term){
if($term != $tmpFiles[$key]){
$formName = $terminalPath .'/' . $key . '.' . $term;
$toName = $terminalPath.'/' . $key . '.' . $tmpFiles[$key];
rename($formName,$toName);
}
}
return jsonReturn(0,'success');
}
public function delete(ThemeService $service): Json
{
$theme = input('param.theme');
if(empty($theme)){
return jsonReturn(-1,Lang::get('模版名称不能为空'));
}
$res = $service -> delOriginalTheme($theme);
return json($res);
}
}

206
app/controller/backend/ThemeFileController.php

@ -0,0 +1,206 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\exception\ModelEmptyException;
use app\model\Theme;
use app\model\ThemeFile;
use app\service\CacheService;
use app\service\ComponentDataService;
use app\service\ThemeFileService;
use app\validate\ThemeFileValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class ThemeFileController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(ThemeFile $themeFile): \think\response\Json
{
$where = [
'seller_id' => $this->admin['seller_id'],
'is_del' => 1,
];
$limit = $this->setLimit();
$themeFileList = $themeFile->getThemeFileList($where,$limit);
return json(pageReturn($themeFileList));
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function save(ThemeFile $themeFile): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(ThemeFileValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// TODO
// 其他逻辑
$res = $themeFile -> addThemeFile($param);
CacheService::deleteRelationCacheByObject($themeFile);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
*/
public function read(ThemeFileService $themeFileService): \think\response\Json
{
$path = input('param.path');
if (strpos($path, '..') !== false) {
return jsonReturn(-1,Lang::get('文件路径有误'));
}
if(empty($path)){
return jsonReturn(-1,Lang::get('文件路径不能为空'));
}
// 不是可编辑文件
$ext = pathinfo($path, PATHINFO_EXTENSION);
if (!in_array($ext, ['js', 'json', 'html', 'css', 'less', 'htm', 'sass'])) {
return jsonReturn(-1,Lang::get('不支持的编辑文件'));
}
return $themeFileService->readSource($path);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \ReflectionException
*/
public function update(ThemeFileService $themeFileService): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(ThemeFileValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
return $themeFileService -> updateResource($param['path'],$param['content']);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 更新单个文件
* @param ThemeFileService $themeFileService
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function singleUpdate(ThemeFileService $themeFileService): \think\response\Json
{
if(request()->isPost()){
$path = input('param.path');
$path = str_replace('\\', '/', $path);
$path = trim($path,'/');
if(empty($path)){
return jsonReturn(-1,Lang::get('文件路径不能为空'));
}
$res = $themeFileService -> updateThemeFile($path);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 模板设计
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function design(ThemeFileService $themeFileService)
{
$param = input('param.');
try {
validate(ThemeFileValidate::class)->scene('design')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$url = '/';
if(!empty($param['url'])){
$url = $param['url'];
}
define('ADMIN_SITE_ID',$param['website_id']);
define('ADMIN_SELLER_ID',$this->admin['seller_id']);
return $themeFileService -> design($param['theme_id'],$param['website_id'],$this->admin['seller_id'],$url);
}
/**
* @throws ModelEmptyException
* @throws \app\exception\ModelException
*/
public function compData(ThemeFileService $themeFileService)
{
$param = input('param.');
try {
validate(ThemeFileValidate::class)->scene('design')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
define('ADMIN_SITE_ID',$param['website_id']);
define('ADMIN_SELLER_ID',$this->admin['seller_id']);
define('ADMIN_LANG',$param['lang']);
return $themeFileService -> compData($this->admin['seller_id'],$param['theme_id'],$param['website_id'],$param['url'],$param['block']);
}
/**
* @throws \app\exception\ModelException
*/
public function active(Theme $Theme,ThemeFile $ThemeFile): \think\response\Json
{
$siteId = (int)input('website_id');
$lang = input('lang');
if(empty($siteId)){
return jsonReturn(-1,Lang::get('站点ID不能为空'));
}
$themeWhere = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
'lang' => $lang,
'is_active' => 1
];
try{
$theme = $Theme->getTheme($themeWhere)['data'];
}catch (ModelEmptyException $e){
return jsonReturn(-1,Lang::get('该站点没有启用的模板'));
}
$fileWhere = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
'theme_id' => $theme['id'],
];
$list = $ThemeFile->getThemeAllFile($fileWhere);
return json($list);
}
public function singleUpdateFile()
{
}
}

84
app/controller/backend/UploadController.php

@ -0,0 +1,84 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\SysSetting;
use app\service\upload\Upload;
use think\facade\Lang;
use think\Request;
class UploadController extends BaseController
{
protected $type = [
'file' => 4,
'image' => 2,
'video' => 3,
'mp3' => 5,
'zip' => 1,
];
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
* @throws \app\exception\ModelException
*/
public function save(Request $request): \think\Response
{
if(request()->isPost()){
$file = request()->file('file');
if(empty($file)){
return jsonReturn(-2,Lang::get('文件不能为空'));
}
$seller_id = $this->admin['seller_id'];
// 查看文件类型
$fileName = $file->getOriginalName();
$fileExt = $file->getOriginalExtension();
$file_type = fileFormat($fileName);
$fileType = $this->type[$file_type];
// 附件大小和类型验证
// 获取上传配置
$Settings = new SysSetting();
$uploadSetting = $Settings->getAllCustomArrayData(['parent_id'=>1,'group'=>'upload','status'=>1],'id desc','id,group,title,value')['data'];
$uploadSetting = getColumnForKeyArray($uploadSetting,'title');
$limitSize = $uploadSetting[$file_type.'_size']['value'] * 1024; // byte
$fileSize = $file->getSize(); // 单位byte
if($fileSize > $limitSize){
return jsonReturn(-1,Lang::get('文件过大,请修改上传限制或者替换小的文件'));
}
$extArr = explode(',',$uploadSetting[$file_type.'_ext']['value']);
if(!in_array($fileExt,$extArr)){
return jsonReturn(-2,Lang::get('文件格式错误,请重新上传'));
}
// 文件信息提取
$where = [
'seller_id' => $seller_id,
'group' => 'upload',
'title' => 'storage'
];
$place = new SysSetting();
$type = $place->getSysSetting($where)['data']->toArray()['value'];
$upload = new Upload();
$info = $upload->create($file,$seller_id, $type,$file_type);
if ($info) {
$uploadInfo = $upload->getUploadFileInfo();
if ($uploadInfo['code'] == 0) {
$uploadInfo['data']['type'] = $fileType;
$uploadInfo['data']['size'] = round($fileSize / 1024,2);
$uploadInfo['data']['mime_type'] = $fileExt;
}
return json($uploadInfo);
}else{
return jsonReturn(-1, Lang::get('上传失败请重新尝试'));
}
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

287
app/controller/backend/WebsiteController.php

@ -0,0 +1,287 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Admin;
use app\model\Route;
use app\model\Website;
use app\model\WebsiteLang;
use app\model\WebsiteSetting;
use app\service\CacheService;
use app\service\SitemapService;
use app\service\WebsiteLangService;
use app\validate\WebsiteValidate;
use app\service\WebsiteService;
use think\facade\Db;
use think\exception\ValidateException;
use think\facade\Lang;
class WebsiteController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\Response
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function index(Website $website): \think\response\Json
{
$where = [
['seller_id','=', $this->admin['seller_id']],
];
$uid = $this -> admin['uid'];
$authSiteId = [];
if($uid != 1){
$admin = new Admin();
$adminInfo = $admin -> getAdmin(['seller_id' => $this->admin['seller_id'],'id' => $uid],['role.website'])['data'] -> toArray();
$authSite = array_column($adminInfo['role'],'website');
foreach($authSite as $val){
$authSiteId = array_merge($authSiteId,array_column($val,'id'));
}
$authSiteId = array_unique($authSiteId);
$where[] = ['id','in',$authSiteId];
}
$websiteList = $website->getAllCustomArrayData($where)['data'];
$websiteList = makeTree($websiteList);
return jsonReturn(0,Lang::get('成功'),$websiteList);
}
/**
* 保存新建的资源
*
* @return \think\Response
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(Website $website): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(WebsiteValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$website->saveUnique(['seller_id'=>$this->admin['seller_id'],'title'=>$param['title']],'网站名称已存在');
$param['seller_id'] = $this->admin['seller_id'];
$param['status'] = 1;
Db::startTrans();
try{
// 1. 保存站点到数据库
$res = $website -> addWebsite($param);
if($param['parent_id'] == 0){
$this->selfData($res['data'],$param['domain'],$param['parent_id']);
}else{
$this->sonData($res['data'],$param['domain'],$param['parent_id']);
}
CacheService::deleteRelationCacheByObject($website);
Db::commit();
}catch(\Exception $e){
Db::rollback();
return jsonReturn(-5,$e->getMessage());
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* @throws \app\exception\ModelException
*/
public function selfData($siteId, $domain, $parentId)
{
// 2. 生成一份默认的站点配置,配置的值为空
$tmpData = config('system.website_setting');
$sysData = [
"setting"=> json_encode($tmpData),
"website_id"=>$siteId,
"lang"=>config('lang.default_lang')
];
$siteSetting = new WebsiteSetting();
$siteSetting -> addWebsiteSetting($sysData);
// 3. 选择站点语言简体中文,服务器为本地服务器
$langData[]= [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
'lang_name' => '简体中文',
'lang' => config('lang.default_lang'),
];
$WebsiteLang = new WebsiteLang();
$WebsiteLang->addWebsiteLang($langData);
}
/**
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function sonData($siteId, $domain, $parentId)
{
$website = new Website();
// 1. 写入父站点的路由
$parent = $website -> getWebsite(['id'=>$parentId,'seller_id'=>$this->admin['seller_id']])['data'];
$parentDomain = $parent['domain'];
$start = $parentId . '_' ;
$path = CMS_ROOT . 'route';
$files = glob("$path/$start*.php");
foreach ($files as $file){
$tmpStr = str_replace($parentDomain,$domain,file_get_contents($file));
$basename = str_replace($start,$siteId.'_',basename($file));
file_put_contents($path . DIRECTORY_SEPARATOR . $basename,$tmpStr);
}
// 父站点路由copy一份到子站点保存到数据库
$route = new Route();
$routes = $route -> getAllCustomArrayData(['seller_id'=>$this->admin['seller_id'],'website_id'=>$parentId])['data'];
foreach ($routes as &$val){
$val['website_id'] = $siteId;
$val['seller_id'] = $this->admin['seller_id'];
unset($val['id']);
unset($val['create_time']);
unset($val['update_time']);
}
unset($val);
$route->addAllCustomData($routes);
// 2. 写入语言+配置
$WebsiteLang = new WebsiteLang();
$parentLang = $WebsiteLang->getAllCustomArrayData(['id'=>$parentId,'seller_id'=>$this->admin['seller_id']])['data'];
$lang = array_column($parentLang,'lang');
$websiteLangService = new WebsiteLangService();
$websiteLangService->setSiteLang(['seller_id'=>$this->admin['seller_id'],'site_id'=>$siteId,'lang'=>$lang]);
}
/**
* 显示指定的资源
*
* @return \think\Response
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(Website $website): \think\response\Json
{
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$where = [
'id' => $id,
'seller_id' => $this->admin['seller_id']
];
$res = $website->getWebsite($where);
return json($res);
}
/**
* 设置网站状态
* @param Website $website
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function setStatus(Website $website)
{
if(!request()->isPost()){
return jsonReturn(-3,Lang::get('请求方法错误'));
}
$param = $this->request->only(['id' => 0, 'status' => 1]);
try {
validate(WebsiteValidate::class)->scene('setStatus')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
if ($param['status'] == 2) {
$childWhere = [
'parent_id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$website->updateWebsite($childWhere, ['status' => $param['status']]);
}
$res = $website->updateWebsite($where, ['status' => $param['status']]);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
* @throws \ReflectionException
*/
public function update(Website $website): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(WebsiteValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'id' => $param['id'],
'seller_id' => $this->admin['seller_id'],
];
$site = $website->getWebsite($where)['data'];
if($param['domain'] != $site['domain']){
// 修改路由域名配置
$routePath = app()->getRootPath() . 'route'. DIRECTORY_SEPARATOR;
$fileArr = hcScanDir($routePath.'*');
foreach($fileArr as $val){
$siteId = $param['id'];
$pattern = '/^'.$siteId.'_/';
if(is_file($routePath . $val) && preg_match($pattern,$val)){
$conArr = str_replace($site['domain'],$param['domain'],file_get_contents($routePath.$val));
file_put_contents($routePath.$val,$conArr);
}
}
}
$res = $website -> updateWebsite($where,$param);
CacheService::deleteRelationCacheByObject($website);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 删除指定资源
*
*/
public function delete(WebsiteService $websiteService): \think\response\Json
{
if(request()->isPost()){
$id = (int)input('id');
if(!$id){
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$res = $websiteService->deleteSite($id,$this->admin['seller_id']);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* xml文件
* @param SitemapService $sitemapService
* @return \think\response\Json
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function sitemap(SitemapService $sitemapService): \think\response\Json
{
$siteId = (int)$this->request->param('website_id');
$sellerId = $this->admin['seller_id'];
$res = $sitemapService -> makeSitemap($siteId,$sellerId);
return json($res);
}
}

86
app/controller/backend/WebsiteLangController.php

@ -0,0 +1,86 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Model;
use app\model\Website;
use app\model\WebsiteLang;
use app\model\WebsiteSetting;
use app\service\CacheService;
use app\service\WebsiteLangService;
use app\validate\WebsiteLangValidate;
use think\Cookie;
use think\exception\ValidateException;
use think\facade\Lang;
class WebsiteLangController extends BaseController
{
/**
* 显示资源列表
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function index(WebsiteLang $websiteLang): \think\response\Json
{
$siteId = (int)input('site_id');
if(!$siteId){
return jsonReturn(-1, Lang::get('站点ID不能为空'));
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $siteId,
];
$websiteLangList = $websiteLang->getAllCustomArrayData($where,'id asc');
return json($websiteLangList);
}
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function save(WebsiteLangService $websiteLangService): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(WebsiteLangValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$res = $websiteLangService -> setSiteLang($param);
CacheService::deleteRelationCacheByObject(WebsiteLang::class);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 系统语言
* @return \think\response\Json
*/
public function system(): \think\response\Json
{
if(request()->isGet()){
$lang = config('system.lang');
return json(['code'=>0,'msg'=>'success','data'=>$lang]);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 切换语言
*/
public function changeLang(\think\Lang $lang,Cookie $cookie){
$val = $this->request->param('lang/s','');
$cookie->set($lang->getConfig()['cookie_var'], $val);
return jsonReturn(0,Lang::get('操作成功'));
}
}

90
app/controller/backend/WebsiteServerController.php

@ -0,0 +1,90 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\WebsiteServer;
use app\validate\WebsiteServerValidate;
use think\exception\ValidateException;
use think\facade\Lang;
class WebsiteServerController extends BaseController
{
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function save(WebsiteServer $websiteServer): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(WebsiteServerValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$res = $websiteServer -> addWebsiteServer($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(WebsiteServer $websiteServer): \think\response\Json
{
$siteId = (int)input('site_id');
if(!$siteId){
// 修改错误消息
return jsonReturn(-1,Lang::get('网站ID不能为空'));
}
$where = [
'website_id' => $siteId,
'seller_id' => $this->admin['seller_id']
];
$res = $websiteServer->getWebsiteServer($where);
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(WebsiteServer $websiteServer): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(WebsiteServerValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
];
unset($param['website_id']);
$res = $websiteServer -> updateWebsiteServer($where,$param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

125
app/controller/backend/WebsiteSettingController.php

@ -0,0 +1,125 @@
<?php
declare (strict_types = 1);
namespace app\controller\backend;
use app\model\Attachment;
use app\model\Model;
use app\model\WebsiteSetting;
use app\validate\WebsiteSettingValidate;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Cookie;
use think\facade\Lang;
class WebsiteSettingController extends BaseController
{
/**
* 保存新建的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelNotUniqueException
*/
public function save(WebsiteSetting $websiteSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
// 数据验证
try{
validate(WebsiteSettingValidate::class)->scene('save')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$param['seller_id'] = $this->admin['seller_id'];
$param['setting'] = json_encode($param['setting']);
$websiteSetting->saveUnique(['seller_id'=>$param['seller_id'],'website_id'=>$param['website_id'],'lang'=>$param['lang']],'相同配置已经存在,请编辑');
$res = $websiteSetting -> addWebsiteSetting($param);
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
/**
* 显示指定的资源
*
* @return \think\response\Json
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function read(WebsiteSetting $websiteSetting,Attachment $attachment): \think\response\Json
{
$param = input('param.');
// 数据验证
try{
validate(WebsiteSettingValidate::class)->scene('read')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang'],
];
$cacheKey = $this->admin['seller_id'] .'_'.$param['website_id'] .'_'. $param['lang'] . '_website_cache_key';
Cache::delete($cacheKey);
$res = $websiteSetting->getWebsiteSetting($where);
$res['data'] = $res['data']->toArray();
$res['data']['setting'] = json_decode($res['data']['setting'],true);
if(!empty($res['data']['setting']['logo'])){
$attach = $attachment->getAttachment(['id'=>$res['data']['setting']['logo'],'seller_id'=>$this->admin['seller_id']])['data'];
if(!empty($attach)){
$res['data']['setting']['logo'] = $attach;
}
}
if(!empty($res['data']['setting']['wechat_code'])){
$attach = $attachment->getAttachment(['id'=>$res['data']['setting']['wechat_code'],'seller_id'=>$this->admin['seller_id']])['data'];
if(!empty($attach)){
$res['data']['setting']['wechat_code'] = $attach;
}
}
if(!empty($res['data']['setting']['dark_logo'])){
$attach = $attachment->getAttachment(['id'=>$res['data']['setting']['dark_logo'],'seller_id'=>$this->admin['seller_id']])['data'];
if(!empty($attach)){
$res['data']['setting']['dark_logo'] = $attach;
}
}
return json($res);
}
/**
* 保存更新的资源
* @return \think\response\Json
* @throws \app\exception\ModelException
*/
public function update(WebsiteSetting $websiteSetting): \think\response\Json
{
if(request()->isPost()){
$param = input('post.');
try {
validate(WebsiteSettingValidate::class)->scene('update')->check($param);
} catch (ValidateException $e) {
return jsonReturn(-1, $e->getError());
}
$where = [
'seller_id' => $this->admin['seller_id'],
'website_id' => $param['website_id'],
'lang' => $param['lang'],
];
$setting = json_encode($param['setting']);
$res = $websiteSetting -> updateWebsiteSetting($where,['setting'=>$setting]);
$siteCacheKey = $this->admin['seller_id'] .'_'.$param['website_id'] .'_'. $param['lang'] . '_website_cache_key';
Cache::delete($siteCacheKey);
if(config('lang')['use_cookie']){
Cookie::set(config('lang')['cookie_var'],$param['lang']);
}
return json($res);
}
return jsonReturn(-3,Lang::get('请求方法错误'));
}
}

274
app/controller/backend/custom/ContentImportController.php

@ -0,0 +1,274 @@
<?php
namespace app\controller\backend\custom;
use app\controller\backend\BaseController;
use app\exception\ExcelException;
use app\hctrait\ExcelTrait;
use app\model\Category;
use app\model\SubContent;
use app\service\CacheService;
use app\service\ContentService;
use think\facade\Log;
class ContentImportController extends BaseController
{
use ExcelTrait;
/**
* 手续费导入
* @throws \app\exception\ExcelException
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws \ReflectionException
*/
public function commission(): \think\response\Json
{
try{
$lang = $this->request->post('lang');
$file = $this->request->file('file');
}catch (\Exception $e){
return jsonReturn(-1,'请上传手续费导入文件');
}
$insertData = $this->getImportData($file,'commission');
return json($this->commonInsert($insertData,$lang,18,'手续费'));
}
/**
* 保证金上传
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \ReflectionException
*/
public function margin(): \think\response\Json
{
try{
$lang = $this->request->post('lang');
$file = $this->request->file('file');
}catch (\Exception $e){
return jsonReturn(-1,'请上传保证金导入文件');
}
$insertData = $this->getImportData($file,'margin');
return json($this->commonInsert($insertData,$lang,19,'保证金'));
}
/**
* 品种上传
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \ReflectionException
*/
public function variety(): \think\response\Json
{
try{
$lang = $this->request->post('lang');
$file = $this->request->file('file');
}catch (\Exception $e){
return jsonReturn(-1,'请上传品种导入文件');
}
$insertData = $this->getImportData($file,'variety');
return json($this->commonInsert($insertData,$lang,20,'交易品种'));
}
/**
* 交易规则
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \ReflectionException
*/
public function rule(): \think\response\Json
{
try{
$lang = $this->request->post('lang');
$file = $this->request->file('file');
}catch (\Exception $e){
return jsonReturn(-1,'请上传交易规则导入文件');
}
$insertData = $this->getImportData($file,'rule');
return json($this->commonInsert($insertData,$lang,21,'交易规则'));
}
/**
* 交易日历上传
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \ReflectionException
*/
public function calendar(): \think\response\Json
{
try{
$lang = $this->request->post('lang');
$file = $this->request->file('file');
}catch (\Exception $e){
return jsonReturn(-1,'请上传交易日历导入文件');
}
$insertData = $this->getImportData($file,'calendar',0);
return json($this->currentCateInsert($insertData,$lang,17,'交易日历'));
}
/**
* 日历导入+居间人导入
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \ReflectionException
*/
public function currentCateInsert($insertData, $lang, $cateId,$type): array
{
$Category = new Category();
$SubContent = new SubContent();
$Service = new ContentService();
$len = 0;
$err = 0;
$curr = 0;
$total = count($insertData);
foreach($insertData as $item){
$tmp = [
'admin' => $this->admin
];
$subContent = [
'content' => '',
'publish_time' => date('Y-m-d H:i:s')
];
if($cateId == 17 && $item['sub.title'] < '20230101'){
$len++;
continue;
}
$mainContent = [];
foreach($item as $k => $v){
// 栏目名称
$category = $Category->getCustomData(['id'=>$cateId])['data'];
if(empty($category)){
throw new ExcelException($v . '栏目不存在,请先添加栏目');
}
$subContent['sub_category_ids'] = $cateId;
$subContent['category_id'] = $category['id'];
$tmp['module_id'] = $category['module_id'];
$tmp['category_id'] = [$category['id']];
$tmp['lang'] = $lang;
if(strpos($k,'sub.') !== false){
// 附表字段sub—content
$kk = str_replace('sub.','',$k);
$subContent[$kk] = $v;
}else if(strpos($k,'main.') !== false){
// 主表字段 自定义表
$kk = str_replace('main.','',$k);
$mainContent[$kk] = $v;
}
$tmp['sub_content'] = $subContent;
$tmp['main_content'] = $mainContent;
}
$old = $SubContent->getCustomData(['category_id'=>$tmp['sub_content']['category_id'],'title'=>$tmp['sub_content']['title']],[],'id,title')['data'];
if(empty($old)){
// 不存在新增
$res = $Service->saveContent($tmp);
}else{
if($cateId == 17){
$len++;
continue;
}
// 存在更新
$tmp['id'] = $old['id'];
$res = $Service->updateContent($tmp);
}
$curr++;
CacheService::deleteRelationCacheByClassName('SubContent');
if($res['code'] == 0){
$len++;
}else{
$err++;
Log::error(json_encode($item,JSON_UNESCAPED_UNICODE));
Log::error('第'.$curr.$type.'导入失败:'.json_encode($res,JSON_UNESCAPED_UNICODE));
}
}
if($len == $total){
return dataReturn(0,'导入成功');
}else{
return dataReturn(-1,"{$err}条数据导入失败,请重新尝试");
}
}
/**
* 公告导入方法
* @throws \app\exception\ModelException
* @throws ExcelException
* @throws \app\exception\ModelEmptyException
* @throws \ReflectionException
*/
public function commonInsert($insertData, $lang,$cateId,$type): array
{
$Category = new Category();
$SubContent = new SubContent();
$Service = new ContentService();
$len = 0;
$err = 0;
$curr = 0;
$total = count($insertData);
foreach($insertData as $item){
$tmp = [
'admin' => $this->admin
];
$subContent = [
'content' => '',
'publish_time' => date('Y-m-d H:i:s')
];
$mainContent = [];
if(empty($item['category_title'])){
$len++;
$curr++;
continue;
}
foreach($item as $k => $v){
if(strpos($k,'category_title') !== false){
// 栏目名称
$category = $Category->getCustomData(['parent_id'=>$cateId,'sub_title'=>$v])['data'];
if(empty($category)){
throw new ExcelException($v . '栏目不存在,请先添加栏目');
}
$subContent['sub_category_ids'] = $cateId;
$subContent['category_id'] = $category['id'];
$tmp['module_id'] = $category['module_id'];
$tmp['category_id'] = [$category['id']];
$tmp['lang'] = $lang;
}else if(strpos($k,'sub.') !== false){
// 附表字段sub—content
$kk = str_replace('sub.','',$k);
$subContent[$kk] = $v;
}else if(strpos($k,'main.') !== false){
// 主表字段 自定义表
$kk = str_replace('main.','',$k);
$mainContent[$kk] = $v;
}
$tmp['sub_content'] = $subContent;
$tmp['main_content'] = $mainContent;
}
$old = $SubContent->getCustomData(['category_id'=>$tmp['sub_content']['category_id'],'title'=>$tmp['sub_content']['title']])['data'];
if(empty($old)){
// 不存在新增
$res = $Service->saveContent($tmp);
}else{
// 存在更新
$tmp['id'] = $old['id'];
$res = $Service->updateContent($tmp);
}
$curr++;
CacheService::deleteRelationCacheByClassName('SubContent');
if($res['code'] == 0){
$len++;
}else{
$err++;
Log::error(json_encode($item,JSON_UNESCAPED_UNICODE));
Log::error('第'.$curr.$type.'导入失败:'.json_encode($res,JSON_UNESCAPED_UNICODE));
}
}
if($len == $total){
return dataReturn(0,'导入成功');
}else{
return dataReturn(-1,"{$err}条数据导入失败,请重新尝试");
}
}
}

333
app/controller/frontend/BaseController.php

@ -0,0 +1,333 @@
<?php
declare (strict_types = 1);
namespace app\controller\frontend;
use app\BaseController as AppBaseController;
use app\model\Theme;
use app\model\VisitLog;
use app\model\Website;
use app\service\ApiService;
use think\facade\Cache;
use think\facade\View;
use Overtrue\PHPOpenCC\OpenCC;
class BaseController extends AppBaseController
{
protected $rootDomain;
protected $domain;
protected $siteId;
protected $sellerId;
protected $theme;
protected $lang;
protected $realLang;
protected $viewBase;
protected $settingData;
protected $parentSiteId;
protected $realSiteId;
protected $viewSiteId;
protected $preview;
/**
* @throws \app\exception\ModelException
* @throws \Exception
*/
public function initialize()
{
$this->setCommonParam();
$this->getRootDomain();
$this->setRealSiteId();
$this->getActiveTheme();
$this->setVisitedData();
parent::initialize();
}
/**
* @throws \app\exception\ModelException
*/
public function setCommonParam()
{
// 更新版本标识
updateVersion();
$domain = $this->request->host();
$Website = new Website();
$website = $Website->getWebsiteByDomain($domain,'id,domain,seller_id,parent_id,status')['data'];
if ($website['status'] == 2) {
$this->to404();
}
// 是否为预览状态
$historyViewTime = session('history-view');
$preview = $this->request->param('preview', '');
if (!empty($preview)) {
if ((time() - $historyViewTime) < 30 * 60) {
// 30分钟内可预览
$this->preview = 1;
} else {
header('Location: /');
return;
}
}
$this->domain = $domain;
$this->siteId = $website['id'];
$this->parentSiteId = $website['parent_id'];
$this->sellerId = $website['seller_id'];
$this->realLang = $this->setLang();
$this->lang = $this->setRealLang();
$this->viewBase = config('view.view_dir_name');
define('INDEX_SITE_ID',$this->siteId);
define('INDEX_SELLER_ID',$this->sellerId);
define('INDEX_LANG',$this->lang);
}
public function getRootDomain()
{
$rootDomain = $this->request->rootDomain();
if( $rootDomain === 'com.cn'){
$domainArr = explode('.',$this->domain);
array_shift($domainArr);
$this->rootDomain = implode('.',$domainArr);
}else{
$this->rootDomain = $rootDomain;
}
}
/**
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function getActiveTheme()
{
$Theme = new Theme();
$theme = $Theme->getActiveTheme(['seller_id'=>$this->sellerId,'website_id'=>$this->viewSiteId,'lang'=>$this->lang,'is_active'=>1])['data']->toArray();
$cacheKey = $this->sellerId .'_'.$this->siteId .'_'. $this->lang . '_website_cache_key';
$settingData = Cache::get($cacheKey);
if(empty($settingData)){
$settingData = ApiService::setWebsiteSetting($this->sellerId,$this->siteId,$this->lang);
}
$this->settingData = $settingData;
if(isMobile() && !empty($settingData['common_template']) && $settingData['common_template'] == 2){
$this->theme = 'm_' . $theme['theme'];
}else{
$this->theme = $theme['theme'];
}
}
/**
* @throws \app\exception\ModelException
*/
public function setRealSiteId()
{
$siteId = $this->siteId;
$Theme = new Theme();
$theme = $Theme->getActiveTheme(['seller_id'=>$this->sellerId,'website_id'=>$siteId,'lang'=>$this->lang,'is_active'=>1])['data'];
$this->viewSiteId = $siteId;
// 本站点没有安装启用模板,页面加载用父级的模板
if ($this->parentSiteId && empty($theme)){
$this->viewSiteId = $this->parentSiteId;
}
if($this->parentSiteId){
$siteId = $this->parentSiteId;
}
define('INDEX_REAL_SITE_ID',$siteId);
return $this->realSiteId = $siteId;
}
public function setRealLang(): string
{
return $this->realLang == config('lang.traditional_chinese') ? 'zh' : $this->realLang;
}
public function setLang(): string
{
return getLang();
}
protected function assign($name,$value = ''): \think\View
{
return View::assign($name, $value);
}
/**
* 加载模板输出
* @access protected
* @param string $template 模板文件名
* @param array $vars 模板输出变量
* @return mixed
*/
protected function fetch(string $template = '', array $vars = [])
{
$template = $this->parseTemplate($template);
$this->_initializeView($this->viewSiteId,$this->lang,$this->theme);
$html = View::fetch($template, $vars);
$replace = <<<EOL
<script id="baseJs" src="/system_file/js/base.min.js"></script>
</head>
EOL;
$html = str_replace('</head>', $replace, $html);
if($this->realLang == config('lang.traditional_chinese')){
$html = OpenCC::s2tw($html);
}
return $html;
}
/**
* 加载模板输出(system_file)
* @access protected
* @param string $template 模板文件名
* @param array $vars 模板输出变量
* @return mixed
*/
protected function fetchSystem(string $template = '', array $vars = [])
{
$template = $this->parseTemplateSystem($template);
$this->_initializeViewSystem($this->viewSiteId,$this->lang,$this->theme);
$html = View::fetch($template, $vars);
$replace = <<<EOL
<script id="baseJs" src="/system_file/js/base.min.js"></script>
</head>
EOL;
$html = str_replace('</head>', $replace, $html);
if($this->realLang == config('lang.traditional_chinese')){
$html = OpenCC::s2tw($html);
}
return $html;
}
/**
* 定位模板
* @param $template
* @return string
*/
private function parseTemplate($template): string
{
$path = CMS_ROOT . $this->viewBase . DIRECTORY_SEPARATOR . $this->viewSiteId.DIRECTORY_SEPARATOR .$this->lang.DIRECTORY_SEPARATOR.$this->theme;
if (!empty($this->preview)) {
$path = CMS_ROOT . 'public/themes/preview/'.$this->theme;
}
$ext = getFileExt($template);
$path .= DIRECTORY_SEPARATOR . ltrim($template,'/');
if( $ext != config('view.view_suffix')){
$path .= '.'.config('view.view_suffix');
}
return $path ;
}
/**
* 定位模板
* @param $template
* @return string
*/
private function parseTemplateSystem($template): string
{
$path = CMS_ROOT . 'public/system_file' . DIRECTORY_SEPARATOR;
$ext = getFileExt($template);
$path .= DIRECTORY_SEPARATOR . ltrim($template,'/');
if( $ext != config('view.view_suffix')){
$path .= '.'.config('view.view_suffix');
}
return $path ;
}
/**
* 常量替换
* @param $siteId
* @param $lang
* @param $themeName
*/
protected function _initializeView($siteId,$lang,$themeName)
{
if (!empty($this->preview)) {
$viewReplaceStr = [
'__TMPL__' => "/themes/preview/{$themeName}",
'__STATIC__' => empty($this->settingData['static_file']) ? "/themes/preview/{$themeName}/static" : $this->settingData['static_file'] ,
];
View::engine()->config([
'view_path' => CMS_ROOT . '/public/themes/preview/' .$this->theme . '/',
'tpl_replace_string' => $viewReplaceStr,
'display_cache' => false,
'tpl_cache' => false,
]);
} else {
$viewReplaceStr = [
'__TMPL__' => "/themes/website/{$siteId}/{$lang}/{$themeName}",
'__STATIC__' => empty($this->settingData['static_file']) ? "/themes/website/{$siteId}/{$lang}/{$themeName}/static" : $this->settingData['static_file'] ,
];
View::engine()->config([
'view_path' => CMS_ROOT . $this->viewBase . DIRECTORY_SEPARATOR . $siteId. DIRECTORY_SEPARATOR .$this->lang.DIRECTORY_SEPARATOR.$this->theme .DIRECTORY_SEPARATOR,
'tpl_replace_string' => $viewReplaceStr,
'display_cache' => false,
'tpl_cache' => false,
]);
}
}
/**
* 常量替换
* @param $siteId
* @param $lang
* @param $themeName
*/
protected function _initializeViewSystem($siteId,$lang,$themeName)
{
$viewReplaceStr = [
'__TMPL__' => "/themes/website/{$siteId}/{$lang}/{$themeName}",
'__STATIC__' => empty($this->settingData['static_file']) ? "/themes/website/{$siteId}/{$lang}/{$themeName}/static" : $this->settingData['static_file'] ,
];
View::engine()->config([
'view_path' => CMS_ROOT . 'public/system_file' . DIRECTORY_SEPARATOR,
'tpl_replace_string' => $viewReplaceStr,
'display_cache' => false,
'tpl_cache' => false,
]);
}
/**
* @throws \Exception
*/
private function setVisitedData()
{
$VisitLog = new VisitLog();
$ip = request()->ip();
$agent = !empty(request()->header()['user-agent']) ? request()->header()['user-agent'] : '未知设备';
$address = getLocationByIp($ip,2);
if(empty($address['province'])){
$area = "内网ip";
}else{
$area = $address['province']."-".$address['city'];
}
$url = $this->request->domain().$this->request->url();
$device = getDeviceInfo($agent);
$param = [
'seller_id' => $this->sellerId,
'website_id' => $this->siteId,
'domain' => $this->domain,
'referrer' => empty($_SERVER['HTTP_REFERER']) ? '未知' : $_SERVER['HTTP_REFERER'],
'url' => $url,
'ip' => $ip,
'agent' => $agent,
'visited_area' => $area,
'visited_device' => $device['deviceOs'] .'/'.$device['deviceVersion'],
'visited_time' => date('Y-m-d',time())
];
$VisitLog -> addCustomData($param);
}
public function to404()
{
header('Location: /system_file/hc_error/404.html');
exit();
}
}

132
app/controller/frontend/DesignBaseController.php

@ -0,0 +1,132 @@
<?php
namespace app\controller\frontend;
use think\facade\View;
class DesignBaseController extends BaseController
{
protected $designBase = 'public/design/designPage';
protected $designPath = '';
public function initialize()
{
parent::initialize();
$this->setPath();
}
public function setPath()
{
$this->designPath = CMS_ROOT . 'public/design/';
}
/**
* 加载模板输出(system_file)
* @access protected
* @param string $template 模板文件名
* @param array $vars 模板输出变量
* @return mixed
*/
protected function fetchDesign(string $template = '', array $vars = [])
{
$template = $this->parseTemplateDesign($template);
$this->_initializeViewDesign($this->realSiteId,$this->lang);
$html = View::fetch($template, $vars);
$replace = <<<EOL
<script id="baseJs" src="/system_file/js/base.min.js"></script>
</head>
EOL;
$html = str_replace('</head>', $replace, $html);
return $html;
}
/**
*
* 定位模板
* @param $template
* @return string
*/
private function parseTemplateDesign($template): string
{
$path = CMS_ROOT . "public/design/designPage" . DIRECTORY_SEPARATOR;
$ext = getFileExt($template);
$path .= DIRECTORY_SEPARATOR . ltrim($template,'/');
if( $ext != config('view.view_suffix')){
$path .= '.'.config('view.view_suffix');
}
return $path ;
}
/**
* 常量替换
* @param $siteId
* @param $lang
*/
protected function _initializeViewDesign($siteId,$lang)
{
$viewReplaceStr = [
'__TMPL__' => "/design/designPage",
'__STATIC__' => empty($this->settingData['static_file']) ? "/design/designPage/static" : $this->settingData['static_file'] ,
];
View::engine()->config([
'view_path' => CMS_ROOT . $this->designBase . DIRECTORY_SEPARATOR,
'tpl_replace_string' => $viewReplaceStr,
'display_cache' => false,
'tpl_cache' => false,
]);
}
/**
* 加载模板输出(system_file)
* @access protected
* @param string $template 模板文件名
* @param array $vars 模板输出变量
* @return mixed
*/
protected function fetchDesignPage(string $template = '', array $vars = [])
{
$template = $this->parseTemplateDesignPage($template);
$this->_initializeViewDesignPage($this->realSiteId,$this->lang);
return View::fetch($template, $vars);
}
/**
*
* 定位模板
* @param $template
* @return string
*/
private function parseTemplateDesignPage($template): string
{
$path = CMS_ROOT . "design/designPage/{$this->realSiteId}/{$this->lang}" . DIRECTORY_SEPARATOR;
$ext = getFileExt($template);
$path .= DIRECTORY_SEPARATOR . ltrim($template,'/');
if( $ext != config('view.view_suffix')){
$path .= '.'.config('view.view_suffix');
}
return $path ;
}
/**
* 常量替换
* @param $siteId
* @param $lang
*/
protected function _initializeViewDesignPage($siteId,$lang)
{
$viewReplaceStr = [
'__TMPL__' => "/design/designPage/{$siteId}/{$lang}",
'__STATIC__' => empty($this->settingData['static_file']) ? "/design/designPage/{$siteId}/{$lang}/static" : $this->settingData['static_file'] ,
];
View::engine()->config([
'view_path' => CMS_ROOT . $this->designBase . DIRECTORY_SEPARATOR . $siteId. DIRECTORY_SEPARATOR .$this->lang.DIRECTORY_SEPARATOR,
'tpl_replace_string' => $viewReplaceStr,
'display_cache' => false,
'tpl_cache' => false,
]);
}
}

140
app/controller/frontend/DesignController.php

@ -0,0 +1,140 @@
<?php
namespace app\controller\frontend;
use app\model\BigField;
use app\model\Category;
use app\model\Theme;
use app\model\ThemeFile;
use app\model\WebsiteSetting;
use app\validate\DesignValidate;
use think\exception\ValidateException;
class DesignController extends DesignBaseController
{
public function index()
{
$param = $this->request->param();
$param['html'] = $param['html'] ?? '';
// 数据验证
try{
validate(DesignValidate::class)->scene('index')->check($param);
}catch(ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// 获取当前站的模板目录
$themeModel = new Theme();
$theme = $themeModel->where([
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
['is_active', '=', 1],
])->value('theme');
if (empty($theme)) {
return jsonReturn(-1, lang('当前站点没有激活的模板,请去安装模板'));
}
//search for html files in demo and my-pages folders
// $htmlFiles = glob("{design/designPage/*\/*.html,design/designPage/*.html,design/demo/*\/*.html,design/demo/*.html}", GLOB_BRACE);
// $htmlFiles = glob("{design/designPage/*\/*.html,design/designPage/*.html}", GLOB_BRACE);
// $htmlFiles = glob("{themes/hc_original/{$theme}/*.html,themes/hc_original/{$theme}/*\/*.html}", GLOB_BRACE);
//
//
// $files = [];
// foreach ($htmlFiles as $file) {
// $file = mb_substr($file, 7);
// $pathInfo = pathinfo($file);
// if (in_array($pathInfo['basename'], array('new-page-blank-template.html', 'editor.html'))) continue;//skip template files
//
// $filename = $pathInfo['filename'];
//// var_dump($filename);
//
// $folder = preg_replace('@/.+?$@', '', $pathInfo['dirname']);
// $subfolder = preg_replace('@^.+?/@', '', $pathInfo['dirname']);
//
// if ($filename == 'index' && $subfolder) {
//// $filename = $subfolder;
// }
// $url = $pathInfo['dirname'] . '/' . $pathInfo['basename'];
// $name = ucfirst($filename);
//
//// dump($pathInfo['basename']);
//
// $files[] = [
// 'name' => $name,
// 'file' => $filename,
// 'title' => $name,
//// 'url' => '../themes/' . $url,
// 'url' => '/' . $pathInfo['basename'],
// 'folder' => '../themes/' . $folder,
// ];
//
//// $files .= "{name:'$name', file:'$filename', title:'$name', url: '$url', folder:'$folder'},";
// }
// 获取当前网站所有有模板的栏目的路由
$category = new Category();
$list = $category->where([
['website_id', '=', $param['website_id']],
['lang', '=', $param['lang']],
['type', 'in', [4]],
['status', '=', 1],
['list_tpl', '<>', ''],
])->field('id, title, list_tpl, alias')->group('list_tpl')->select()->toArray();
if (empty($list)) {
return jsonReturn(-1, lang('当前站点已安装模板,但没有栏目使用单页模板,如当前站是子站,需要编辑父站点的数据,请回后台切换至父站点'));
}
$files = [];
$initPage = '';
foreach ($list as $value) {
$pathInfo = pathinfo($value['list_tpl']);
if ($param['html'] == $value['list_tpl']) {
$initPage = $pathInfo['filename'];
}
$file = [
'name' => $pathInfo['filename'],
'file' => $value['list_tpl'],
'title' => $value['list_tpl'],
// 'url' => '../themes/' . $url,
'url' => $value['alias'],
'folder' => $value['title'],
];
$files[] = $file;
}
// dd($files);
$where = [
'website_id' => $param['website_id'],
'lang' => $param['lang'],
];
// 获取系统配置
$websiteSetting = new WebsiteSetting();
$res = $websiteSetting->getWebsiteSetting($where);
$res['data'] = $res['data']->toArray();
$setting = $res['data']['setting'];
$initPage = $initPage ?: $files[0]['name'];
$data = [
'website_id' => $param['website_id'],
'lang' => $param['lang'],
'setting' => $setting,
'pages' => json_encode($files),
'init_page' => $initPage,
];
$this->assign($data);
return $this->fetchDesign('editor');
}
public function getDesignHtml()
{
$param = $this->request->param();
return $this->fetchDesign($param['html_path']);
}
}

146
app/controller/frontend/DetailController.php

@ -0,0 +1,146 @@
<?php
declare (strict_types = 1);
namespace app\controller\frontend;
use app\exception\ModelEmptyException;
use app\exception\ModelException;
use app\model\Category;
use app\model\SysSetting;
use app\service\ContentService;
use app\validate\ContentValidate;
use think\App;
use think\exception\HttpException;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Log;
use think\facade\View;
class DetailController extends BaseController
{
/**
* 内容详情
* @param Category $Category
* @return mixed
* @throws \app\exception\ModelEmptyException
* @throws \app\exception\ModelException
*/
public function index(Category $Category)
{
$id = (int)input('id');
$cid = (int)input('cid');
$cateCacheKey = 'hc_detail_cate_' .$this->sellerId.'_'.$this->siteId.'_'.$this->lang.'_'. $cid ;
$category = cache($cateCacheKey);
if(empty($category)){
try {
$category = $Category->getCategory(['id' => $cid, 'seller_id' => $this->sellerId, 'website_id' => $this->siteId,'lang' => $this->lang],['module.moduleField'])['data']->toArray();
Cache::set($cateCacheKey,$category);
}catch (ModelEmptyException $me){
$category = $Category->getCategory(['id' => $cid, 'seller_id' => $this->sellerId, 'website_id' => $this->realSiteId,'lang' => $this->lang],['module.moduleField'])['data']->toArray();
Cache::set($cateCacheKey,$category);
}
}
if (!empty($category['content'])) {
$category['content'] = htmlspecialchars_decode($category['content']);
}
$contentService = new ContentService();
$param = [
'cate' => $category,
'id' => $id,
'seller_id' => $this->sellerId,
'website_id' => $this->siteId,
'lang' => $this->lang,
'field' => '*',
'main_content' => 1
];
$content = $contentService->getContentDetail($param);
$content->hits = $content->hits + 1;
$content->save();
if (!empty($content['content'])) {
$content['content'] = htmlspecialchars_decode($content['content']);
}
$where = [
'group' => 'company',
'title' => 'huocms_powerby',
];
$huocmsPowerby = SysSetting::where($where)->value('value');
if ($huocmsPowerby) {
$category['seo_title'] = $content['seo_title'] ?: $content['title'];
$category['seo_keyword'] = $content['seo_keyword'] ?: $content['title'];
$category['seo_description'] = $content['seo_description'] ?: $content['title'];
$category['seo_title'] .= "-$huocmsPowerby";
$category['seo_keyword'] .= ",$huocmsPowerby";
$category['seo_description'] .= ",$huocmsPowerby";
}
// 获取分类栏目的层级名称 ======== START
// var_dump($category['path']);exit;
$catePathStr = $category['path'] ?? '';
$catePathArr = explode('-',$catePathStr);
// 去掉空path或者=0的path
$catePathArr = array_filter($catePathArr, function($value) {
return $value !== "" && $value != 0;
});
$cateGroup = ListController::getAllCategoryByIds($catePathArr);
$categoryWithOrderArr = [];
// 保留分类path中的顺序
foreach ($catePathArr as $k => $v) {
$categoryWithOrderArr[] = $cateGroup[$v] ?? [];
}
$this->assign('category_with_order_arr',$categoryWithOrderArr);
// 获取分类栏目的层级名称 ======== END
$this->assign('hc_content',$content->toArray());
$this->assign('current_cate',$category);
$this->assign('root_domain',$this->rootDomain);
$template = !empty($content['detail_tpl']) ? $content['detail_tpl'] : $category['detail_tpl'];
return $this->fetch($template);
}
/**
* 文件预览
* @throws ModelException
* @throws ModelEmptyException
*/
public function preview()
{
$param = $this->request->only(['id','cid','lang'=>$this->lang,'website_id'=>$this->realSiteId]);
$Category = new Category();
$category = $Category->getCustomArrayData(['id' => $param['cid'], 'website_id' => $param['website_id'],'lang' => $param['lang']],['module.moduleField'])['data'];
if(empty($category)){
Log::error("栏目不存在");
throw new HttpException(404,'资源不存在');
}
$param['cate'] = $category;
$param['seller_id'] = $this->sellerId;
$param['field'] = '*';
$param['main_content'] = 1;
if (!empty($category['content'])) {
$category['content'] = htmlspecialchars_decode($category['content']);
}
$contentService = new ContentService();
$content = $contentService->getPreviewContent($param);
if(empty($content)){
Log::error("内容不存在");
throw new HttpException(404,'资源不存在');
}
if (!empty($content['content'])) {
$content['content'] = htmlspecialchars_decode($content['content']);
}
if($category['id'] == 41){
if(empty($content['main_content']['file']['url'])){
return redirect('/404.html');
}else{
$url = $content['main_content']['file']['url'];
return redirect($url);
}
}
View::assign('hc_content',$content->toArray());
View::assign('current_cate',$category);
$template = !empty($content['detail_tpl']) ? $content['detail_tpl'] : $category['detail_tpl'];
return $this->fetch($template);
}
}

279
app/controller/frontend/FormController.php

@ -0,0 +1,279 @@
<?php
namespace app\controller\frontend;
use app\exception\ModelEmptyException;
use app\exception\ModelException;
use app\model\Attachment;
use app\model\customModel\Down;
use app\model\DiyForm;
use app\model\SubContent;
use app\model\SysSetting;
use app\service\upload\Upload;
use app\validate\ContentValidate;
use think\exception\ValidateException;
use think\facade\Db;
class FormController extends BaseController
{
protected $type = [
'file' => 4,
'image' => 2,
'video' => 3,
'mp3' => 5,
'zip' => 1,
];
public function index()
{
$code = $this->request->param('code');
$formModel = new DiyForm();
$formData = $formModel->where('code', $code)->findOrEmpty();
if (empty($formData)) {
$this->to404();
}
$this->assign('formData', $formData);
$html = $this->formHtml();
$html = str_replace(['{formData_name}', '{formData_designContent}', '{formData_designOption}', '{formData_code}'],
[$formData['name'],$formData['design_content'],$formData['design_option'],$formData['code']], $html);
return $html;
}
public function save()
{
$param = $this->request->param();
$code = $param['code'];
unset($param['code']);
$formModel = new DiyForm();
$formData = $formModel->where('code', $code)->findOrEmpty();
if (empty($formData)) {
return jsonReturn(-1, lang('提交失败'));
}
$tableName = 'diyform_' . $formData['table'];
foreach ($param as &$value) {
if (is_array($value)) {
$value = implode(',', $value);
}
}
$param['ip'] = $this->request->ip();
$param['user_agent'] = $this->request->header('user-agent');
Db::name($tableName)->insert($param);
return jsonReturn(0, lang('提交成功'));
}
// 上传文件
public function uploadFormFile()
{
if(!request()->isPost()){
return jsonReturn(-1, lang('请求错误'));
}
$file = request()->file('file');
if(empty($file)){
return jsonReturn(-2,lang('文件不能为空'));
}
$seller_id = $this->sellerId;
// 查看文件类型
$fileName = $file->getOriginalName();
$fileExt = $file->getOriginalExtension();
$file_type = fileFormat($fileName);
$fileType = $this->type[$file_type];
// 附件大小和类型验证
// 获取上传配置
$Settings = new SysSetting();
$uploadSetting = $Settings->getAllCustomArrayData(['parent_id'=>1,'group'=>'upload','status'=>1],'id desc','id,group,title,value')['data'];
$uploadSetting = getColumnForKeyArray($uploadSetting,'title');
$limitSize = $uploadSetting[$file_type.'_size']['value'] * 1024; // byte
$fileSize = $file->getSize(); // 单位byte
if($fileSize > $limitSize){
return jsonReturn(-1,lang('文件过大,请修改上传限制或者替换小的文件'));
}
$extArr = explode(',',$uploadSetting[$file_type.'_ext']['value']);
if(!in_array($fileExt,$extArr)){
return jsonReturn(-2,lang('文件格式错误,请重新上传'));
}
// 文件信息提取
$where = [
'seller_id' => $seller_id,
'group' => 'upload',
'title' => 'storage'
];
$place = new SysSetting();
$type = $place->getSysSetting($where)['data']->toArray()['value'];
$upload = new Upload();
$info = $upload->create($file,$seller_id, $type,$file_type);
if ($info) {
$uploadInfo = $upload->getUploadFileInfo();
if ($uploadInfo['code'] == 0) {
$uploadInfo['data']['type'] = $fileType;
$uploadInfo['data']['size'] = round($fileSize / 1024,2);
$uploadInfo['data']['mime_type'] = $fileExt;
}
return json($uploadInfo);
}else{
return jsonReturn(-1, lang('上传失败请重新尝试'));
}
}
/**
* 下载文件
* @return void
*/
public function downFile()
{
$param = $this->request->param();
if (empty($param)) {
$this->to404();
}
$id = intval($param['id']);
$downFlag = false; // 可下载标识
$downModel = new Down();
$downData = $downModel->where('sub_id', '=', $id)->findOrEmpty();
if (empty($downData)) {
$this->to404();
}
$subContentModel = new SubContent();
$downData['title'] = $subContentModel->where('id', '=', $downData['sub_id'])->value('title');
$formData = [];
if ($downData['is_form'] == '是') {
$formModel = new DiyForm();
$formData = $formModel->where('id', '=', $downData['form_id'])->where('status', '=', 2)->findOrEmpty();
if (empty($formData)) {
$this->to404();
}
$tableName = 'diyform_' . $formData['table'];
$where = [
['sub_id', '=', $id],
['ip', '=', $this->request->ip()],
['user_agent', '=', $this->request->header('user-agent')],
['visitor_id', '=', $this->request->param('visitor_id')]
];
$id = Db::name($tableName)->where($where)->value('id');
if (!empty($id)) {
$downFlag = true;
}
} else {
$downFlag = true;
}
if ($downFlag) {
$fileModel = new Attachment();
$downData['fileArr'] = $fileModel->where('id', 'in', json_decode($downData['file'], true))->select()->toArray();
}
$this->assign('downData', $downData);
$this->assign('formData', $formData);
$this->assign('downFlag', $downFlag);
$this->assign('visitor_id', $this->request->param('visitor_id'));
return $this->fetchSystem('down/index');
}
/**
* 问卷html
* @return string
*/
protected function formHtml()
{
$html = <<<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{formData_name}</title>
</head>
<body>
<div class="container" style="display: block;max-width:1200px;margin: 0 auto; ">
<div id="app-3" class="formBox" style="padding-top: 50px">
<template>
<form-create
v-model="fapi"
:rule="rule"
:option="option"
@submit="onSubmit"
></form-create>
</template>
</div>
</div>
</body>
<!-- import Vue.js -->
<!-- <script src="//vuejs.org/js/vue.min.js"></script>-->
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="/system_file/form/vue@2"></script>
<!-- import stylesheet -->
<link rel="stylesheet" href="/system_file/form/index.css">
<!-- import element -->
<script src="/system_file/form/index.js"></script>
<!-- import form-create/element -->
<script src="/system_file/form/form-create.min.js"></script>
<!-- import form-create/designer -->
<script src="/system_file/form/index.min.js"></script>
<script src="/system_file/form/jquery-3.4.1.min.js"></script>
<script>
//FcDesigner 生成的`JSON`
// const FcDesignerRule = '[{"type":"input","field":"cuk5qqdw3umc","title":"输入框","info":"","_fc_drag_tag":"input","hidden":false,"display":true}]';
const FcDesignerRule = '{formData_designContent}';
//FcDesigner 生成的`options`
// const FcDesignerOptions = '{"form":{"labelPosition":"right","size":"mini","labelWidth":"125px","hideRequiredAsterisk":false,"showMessage":true,"inlineMessage":false}}';
const FcDesignerOptions = '{formData_designOption}';
const code = '{formData_code}';
console.log(FcDesignerOptions, 'asdasdasdasdsada')
var app3 = new Vue({
el: '#app-3',
data: {
fapi: null,
rule: formCreate.parseJson(FcDesignerRule),
option: formCreate.parseJson(FcDesignerOptions)
},
methods: {
onSubmit (formData) {
formData.code = code
//todo 提交表单
console.log(formData, '提交表单提交表单提交表单')
$.post('/addFormData.html', formData, function(res) {
console.log(res, '接口返回');
if (res.code == 0) {
alert('提交成功')
} else {
alert('提交失败')
}
});
}
}
})
</script>
</html>
EOF;
return $html;
}
}

105
app/controller/frontend/InquiryController.php

@ -0,0 +1,105 @@
<?php
declare (strict_types = 1);
namespace app\controller\frontend;
use app\model\Inquiry;
use app\model\SysSetting;
use app\model\Website;
use app\service\EmailService;
use app\validate\frontend\InquiryValidate;
use GuzzleHttp\Client;
use think\exception\ValidateException;
use think\facade\Cache;
use think\facade\Log;
class InquiryController extends BaseController
{
/**
* 保存新建的资源
*
* @return \think\Response
* @throws \app\exception\ModelException
* @throws \app\exception\ModelEmptyException
*/
public function save(): \think\Response
{
// 获取用户的 IP 地址或其他标识
$identifier = request()->ip();
// 设置限制时间窗口(例如:1分钟)
$window = 60;
// 设置允许的最大请求数
$maxRequests = 5;
// 生成存储请求计数的缓存键
$cacheKey = 'request_rate_limit:' . $identifier;
// 获取当前时间内已发送的请求次数
$requestCount = (int)Cache::get($cacheKey,0);
// 如果请求次数超过限制,则返回错误响应
if ($requestCount >= $maxRequests) {
return json(['status' => 'error', 'message' => '请求频率过高,请稍后再试']);
}
$param = request()->param();
if(empty($param)){
return jsonReturn(-1,lang('请填写询盘信息'));
}
try{
validate(InquiryValidate::class)->check($param);
}catch (ValidateException $e){
return jsonReturn(-1, $e->getError());
}
// 获取询盘收件箱
$webSite = new Website();
$site = $webSite->getWebsite(['domain' => $this->domain],['inquiryEmail'])['data']->toArray();
$email = [];
if(!empty($site['inquiryEmail'])){
$email = array_column($site['inquiryEmail'],'email');
}
$inquiry = new Inquiry();
$param['seller_id'] = $this->sellerId;
$param['website_id'] = $this->siteId;
$param['ip'] = request()->ip();
$param['inquiry_type'] = 1;
$res = $inquiry -> addInquiry($param);
try{
// 获取suwork配置
$sysSetting = new SysSetting();
$setting = $sysSetting->getAllSysSetting([['title','like','suwork_%']])['data']->toArray();
$key = array_column($setting,'title');
$setting = array_combine($key,$setting);
if(!empty($setting['suwork_appid']['value']) && !empty($setting['suwork_secret']['value']) && !empty($setting['suwork_api']['value'])){
$suworkAccept = config('system.suwork_accept');
$data = array_intersect_key($param,$suworkAccept);
$data['referer_type'] = 1;
$data['referer_web'] = empty($_SERVER['HTTP_REFERER']) ? '未知' : $_SERVER['HTTP_REFERER'];
$data['appid'] = $setting['suwork_appid']['value'];
$data['secret'] = $setting['suwork_secret']['value'];
$data['inquiry_time'] = date('Y-m-d H:i:s',time());
$option = [
'form_params' => $data,
];
$client = new Client();
$response = $client->post($setting['suwork_api']['value'],$option);
$result = json_decode($response->getBody()->getContents(),true);
if($result['code'] != 0){
Log::record($result['msg'],'info');
}
}
// 发送邮件
$EmailService = new EmailService();
$EmailService->sendEmail($this->domain.lang('网站收到询盘'),$email);
}catch (\Exception $e){
Log::error($e->getMessage());
return jsonReturn(0,lang('提交成功'),1);
}
// 增加请求计数
Cache::set($cacheKey,$requestCount + 1, $window);
return json($res);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save