Compare commits

...
26 Commits
Author SHA1 Message Date
hero920103 e4eab9ad89 更新 2026-04-01 10:12:37 +08:00
hero920103 2a6b14f698 增加企业产品分类 2026-03-21 14:12:21 +08:00
hero920103 9281c1dc35 提交文件 2026-03-21 09:10:24 +08:00
hero920103 d30a2531a7 更新blog界面 2026-03-20 20:35:00 +08:00
hero920103 b3a25ceb82 增加产品模块 2026-03-20 19:48:32 +08:00
hero920103 14c305f518 增加特色服务 2026-03-20 09:12:57 +08:00
hero920103 a7908e0677 新增模板,修复模板选择bug 2026-03-19 15:47:01 +08:00
hero920103 bf21006d4e 更新模板功能 2026-03-12 17:53:36 +08:00
hero920103 1f8d876cef 增加友链功能 2026-03-11 23:44:55 +08:00
hero920103 e46a4100c7 更新若干bug 2026-03-11 17:05:58 +08:00
hero920103 f134b0e759 更新若干bug 2026-03-11 11:11:10 +08:00
hero920103 5dffe62adc 完善模板功能 2026-03-10 22:59:46 +08:00
hero920103 98156991c1 修复若干bug 2026-03-10 22:24:06 +08:00
hero920103 7cdb591f69 更新租户端网站显示 2026-03-10 18:05:10 +08:00
hero920103 c00229f2ee 更新上传规则 2026-03-09 23:01:39 +08:00
hero920103 e27cc8f457 批量更新租户规则 2026-03-09 23:00:20 +08:00
hero920103 e09b4c639c 优化tenant模式 2026-03-09 18:27:05 +08:00
hero920103 bf0daf987b 更新网站架构 2026-03-09 16:35:17 +08:00
hero920103 4cbc52c51b 更新显示架构 2026-03-09 09:40:36 +08:00
hero920103 99fc99f8ad 更新后端传tenantid 2026-02-26 23:43:01 +08:00
hero920103 21480f43b1 改组织架构不合理地方 2026-02-26 20:56:58 +08:00
hero920103 050d1adc0b 增加极验模块 2026-02-26 17:38:11 +08:00
hero920103 f80e1f71d4 增加登录验证 2026-02-26 11:47:46 +08:00
hero920103 bfa5292962 更新 2026-02-26 09:53:51 +08:00
hero920103 1e8b6fe77d 增加租户模式 2026-02-26 09:40:36 +08:00
hero920103 67244496f1 erp功能增加 2026-02-25 10:58:44 +08:00
438 changed files with 148416 additions and 877 deletions
+4 -1
View File
@@ -1,5 +1,6 @@
# Logs # Logs
logs logs
runtime
*.log *.log
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
@@ -25,4 +26,6 @@ dist-ssr
.env .env
.env.* .env.*
!.example.env !.example.env
public\storage
+11
View File
@@ -187,4 +187,15 @@ abstract class BaseController
} }
} }
/**
* 获取当前用户的 tid
*
* @return int 租户ID
*/
protected function getTenantId(): int
{
$userInfo = $this->getAdminUserInfo();
return isset($userInfo['tid']) ? intval($userInfo['tid']) : 0;
}
} }
@@ -2,16 +2,16 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\admin\controller; namespace app\admin\controller\Cms\Analytics;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
use think\facade\Db; use think\facade\Db;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Articles; use app\model\Cms\Articles;
use app\model\ArticlesCategory; use app\model\Cms\ArticlesCategory;
use app\model\AdminUser; use app\model\System\AdminUser;
class AnalyticsController extends BaseController class AnalyticsController extends BaseController
{ {
@@ -29,11 +29,13 @@ class AnalyticsController extends BaseController
// 总发布量 // 总发布量
$totalArticles = Articles::where('delete_time', null) $totalArticles = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->count(); ->count();
// 昨日新增发布 // 昨日新增发布
$yesterdayArticles = Articles::where('delete_time', null) $yesterdayArticles = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->where('publish_date', '>=', $yesterday . ' 00:00:00') ->where('publish_date', '>=', $yesterday . ' 00:00:00')
->where('publish_date', '<=', $yesterday . ' 23:59:59') ->where('publish_date', '<=', $yesterday . ' 23:59:59')
@@ -41,6 +43,7 @@ class AnalyticsController extends BaseController
// 本月新增发布 // 本月新增发布
$monthNewArticles = Articles::where('delete_time', null) $monthNewArticles = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->where('publish_date', '>=', $monthStart . ' 00:00:00') ->where('publish_date', '>=', $monthStart . ' 00:00:00')
->where('publish_date', '<=', $monthEnd) ->where('publish_date', '<=', $monthEnd)
@@ -48,16 +51,19 @@ class AnalyticsController extends BaseController
// 总点赞量 // 总点赞量
$totalLikes = Articles::where('delete_time', null) $totalLikes = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->sum('likes'); ->sum('likes');
// 总访问量 // 总访问量
$totalViews = Articles::where('delete_time', null) $totalViews = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->sum('views'); ->sum('views');
// 热门内容TOP5 // 热门内容TOP5
$hotArticles = Articles::where('delete_time', null) $hotArticles = Articles::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 2) ->where('status', 2)
->order('views', 'desc') ->order('views', 'desc')
->limit(5) ->limit(5)
@@ -100,7 +106,9 @@ class AnalyticsController extends BaseController
{ {
try { try {
$stats = [ $stats = [
'total_users' => AdminUser::where('delete_time', null)->count(), 'total_users' => AdminUser::where('delete_time', null)
->where('tid', $this->getTenantId())
->count(),
]; ];
// 记录操作日志 // 记录操作日志
$this->logSuccess('用户统计', '获取用户统计', ['data' => $stats]); $this->logSuccess('用户统计', '获取用户统计', ['data' => $stats]);
@@ -1,7 +1,7 @@
<?php <?php
declare (strict_types = 1); declare (strict_types = 1);
namespace app\admin\controller\Article; namespace app\admin\controller\Cms\Article;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -9,6 +9,7 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Cms\ArticlesCategory;
class ArticleCategoryController extends BaseController class ArticleCategoryController extends BaseController
{ {
@@ -24,8 +25,8 @@ class ArticleCategoryController extends BaseController
$keyword = $keyword ?? ''; $keyword = $keyword ?? '';
// 获取文章分类列表 // 获取文章分类列表
$categories = Db::name('mete_articles_category') $categories = ArticlesCategory::where('delete_time', null)
->where('delete_time', null) ->where('tid', $this->getTenantId())
->where('status', 1) ->where('status', 1)
->where('name', 'like', '%' . $keyword . '%') ->where('name', 'like', '%' . $keyword . '%')
->order('sort', 'desc') ->order('sort', 'desc')
@@ -55,8 +56,8 @@ class ArticleCategoryController extends BaseController
{ {
try { try {
// 获取文章分类列表 // 获取文章分类列表
$categories = Db::name('mete_articles_category') $categories = ArticlesCategory::where('delete_time', null)
->where('delete_time', null) ->where('tid', $this->getTenantId())
->where('status', 1) ->where('status', 1)
->field('id,cid,name,image,sort') ->field('id,cid,name,image,sort')
->order('sort', 'desc') ->order('sort', 'desc')
@@ -93,10 +94,11 @@ class ArticleCategoryController extends BaseController
$data['cid'] = isset($data['cid']) ? (int)$data['cid'] : 0; $data['cid'] = isset($data['cid']) ? (int)$data['cid'] : 0;
$data['sort'] = isset($data['sort']) ? (int)$data['sort'] : 0; $data['sort'] = isset($data['sort']) ? (int)$data['sort'] : 0;
$data['status']= isset($data['status']) ? (int)$data['status'] : 1; $data['status']= isset($data['status']) ? (int)$data['status'] : 1;
$data['tid'] = $this->getTenantId();
$data['create_time'] = date('Y-m-d H:i:s'); $data['create_time'] = date('Y-m-d H:i:s');
$data['update_time'] = $data['create_time']; $data['update_time'] = $data['create_time'];
$id = Db::name('mete_articles_category')->insertGetId($data); $id = ArticlesCategory::insertGetId($data);
return json(['code'=>200,'msg'=>'success','data'=>['id'=>$id]]); return json(['code'=>200,'msg'=>'success','data'=>['id'=>$id]]);
} catch (ValidateException $ve) { } catch (ValidateException $ve) {
// 记录失败日志 // 记录失败日志
@@ -122,7 +124,7 @@ class ArticleCategoryController extends BaseController
return json(['code'=>400,'msg'=>'无更新数据','data'=>null]); return json(['code'=>400,'msg'=>'无更新数据','data'=>null]);
} }
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
$affected = Db::name('mete_articles_category')->where('id',$id)->update($data); $affected = ArticlesCategory::where('id',$id)->where('tid', $this->getTenantId())->update($data);
if ($affected) { if ($affected) {
// 记录成功日志 // 记录成功日志
$this->logSuccess('文章分类管理', '更新文章分类', ['id' => $id]); $this->logSuccess('文章分类管理', '更新文章分类', ['id' => $id]);
@@ -144,8 +146,8 @@ class ArticleCategoryController extends BaseController
public function deleteCategory(int $id): Json public function deleteCategory(int $id): Json
{ {
try { try {
$affected = Db::name('mete_articles_category') $affected = ArticlesCategory::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]); ->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($affected) { if ($affected) {
@@ -179,7 +181,7 @@ class ArticleCategoryController extends BaseController
} }
$data['status'] = isset($data['status']) ? (int)$data['status'] : 1; $data['status'] = isset($data['status']) ? (int)$data['status'] : 1;
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
$affected = Db::name('mete_articles_category')->where('id',$id)->update($data); $affected = ArticlesCategory::where('id',$id)->where('tid', $this->getTenantId())->update($data);
if ($affected) { if ($affected) {
// 记录成功日志 // 记录成功日志
$this->logSuccess('文章分类管理', '更新分类状态', ['id' => $id]); $this->logSuccess('文章分类管理', '更新分类状态', ['id' => $id]);
@@ -1,7 +1,7 @@
<?php <?php
declare (strict_types = 1); declare (strict_types = 1);
namespace app\admin\controller\Article; namespace app\admin\controller\Cms\Article;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -10,6 +10,8 @@ use think\facade\Request;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Cms\Articles;
use app\model\System\AdminUser;
class ArticleController extends BaseController class ArticleController extends BaseController
{ {
@@ -21,18 +23,20 @@ class ArticleController extends BaseController
* @param int $pageSize 每页条数(默认10 * @param int $pageSize 每页条数(默认10
* @return Json * @return Json
*/ */
public function getArticlesList(string $keyword = '', string $cate = '', int $page = 1, int $pageSize = 10): Json public function getArticlesList(string $author = '', string $keyword = '', string $cate = '', int $page = 1, int $pageSize = 10): Json
{ {
try { try {
// 安全处理参数(防止非法参数) // 安全处理参数(防止非法参数)
$page = max(1, $page); // 页码最小为1 $page = max(1, $page); // 页码最小为1
$pageSize = max(1, min(100, $pageSize)); // 每页条数限制1-100条 $pageSize = max(1, min(100, $pageSize)); // 每页条数限制1-100条
$author = trim($author);
$keyword = trim($keyword); $keyword = trim($keyword);
$cate = trim($cate); $cate = trim($cate);
// 输出参数值 // 输出参数值
trace('查询参数:', 'debug'); trace('查询参数:', 'debug');
trace([ trace([
'author' => $author,
'keyword' => $keyword, 'keyword' => $keyword,
'cate' => $cate, 'cate' => $cate,
'page' => $page, 'page' => $page,
@@ -40,8 +44,9 @@ class ArticleController extends BaseController
], 'debug'); ], 'debug');
// 构建查询条件,合并表连接 // 构建查询条件,合并表连接
$query = Db::name('mete_articles')->alias('a') $query = Articles::alias('a')
->where('a.delete_time', null) ->where('a.delete_time', null)
->where('a.tid', $this->getTenantId())
->leftJoin('mete_admin_user u', 'a.publisher = u.id') ->leftJoin('mete_admin_user u', 'a.publisher = u.id')
->leftJoin('mete_articles_category c', 'a.cate = c.id'); ->leftJoin('mete_articles_category c', 'a.cate = c.id');
@@ -49,6 +54,11 @@ class ArticleController extends BaseController
if ($keyword) { if ($keyword) {
$query->where('a.title', 'like', '%' . $keyword . '%'); $query->where('a.title', 'like', '%' . $keyword . '%');
} }
// 作者筛选
if ($author) {
$query->where('a.publisher', 'like', '%' . $author . '%');
}
// 分类筛选 // 分类筛选
if ($cate) { if ($cate) {
@@ -98,8 +108,7 @@ class ArticleController extends BaseController
$newTitleLength = strlen($newTitleLower); $newTitleLength = strlen($newTitleLower);
// 获取所有文章标题 // 获取所有文章标题
$allTitles = Db::name('mete_articles') $allTitles = Articles::field('id, title')
->field('id, title')
->select(); ->select();
$similarArticles = []; $similarArticles = [];
@@ -180,10 +189,11 @@ class ArticleController extends BaseController
'is_trans' => $data['is_trans'] ?? 0, 'is_trans' => $data['is_trans'] ?? 0,
'transurl' => $data['is_trans'] == 1 ? ($data['transurl'] ?? '') : '', 'transurl' => $data['is_trans'] == 1 ? ($data['transurl'] ?? '') : '',
'publisher' => $userId, 'publisher' => $userId,
'tid' => $this->getTenantId(),
'create_time' => date('Y-m-d H:i:s'), 'create_time' => date('Y-m-d H:i:s'),
]; ];
$id = Db::name('mete_articles')->insertGetId($insertData); $id = Articles::insertGetId($insertData);
// 记录成功日志 // 记录成功日志
$this->logSuccess('文章管理', '创建文章', ['id' => $id]); $this->logSuccess('文章管理', '创建文章', ['id' => $id]);
@@ -206,10 +216,17 @@ class ArticleController extends BaseController
public function getArticle(int $id): Json public function getArticle(int $id): Json
{ {
try { try {
$article = Db::name('mete_articles') $article = Articles::alias('a')
->where('id', $id) ->where('a.id', $id)
->field('id,title,cate,image,desc,author,content,is_trans,transurl,views,likes,publisher,create_time,publish_date,update_time') ->where('a.tid', $this->getTenantId())
->find(); ->leftJoin('mete_admin_user u', 'a.publisher = u.id')
->field('a.id,a.title,a.cate,a.image,a.desc,a.author,a.content,a.is_trans,a.transurl,a.views,a.likes,u.name as publisher_name,a.create_time,a.publish_date,a.update_time')
->find()
->toArray();
// 将 publisher_name 赋值给 publisher
$article['publisher'] = $article['publisher_name'] ?? '';
unset($article['publisher_name']);
if (!$article) { if (!$article) {
return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]); return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]);
@@ -234,12 +251,17 @@ class ArticleController extends BaseController
return json(['code'=>400,'msg'=>'无更新数据','data'=>null]); return json(['code'=>400,'msg'=>'无更新数据','data'=>null]);
} }
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
$affected = Db::name('mete_articles')->where('id',$id)->update($data); $article = Articles::where('id', $id)->where('tid', $this->getTenantId())->find();
if (!$article) {
return json(['code'=>404,'msg'=>'文章不存在','data'=>null]);
}
$affected = Articles::where('id',$id)->where('tid', $this->getTenantId())->update($data);
if ($affected) { if ($affected) {
// 记录成功日志
$this->logSuccess('文章管理', '编辑文章', ['id' => $id]);
return json(['code'=>200,'msg'=>'success','data'=>null]); return json(['code'=>200,'msg'=>'success','data'=>null]);
} }
// 记录成功日志
$this->logSuccess('文章管理', '编辑文章', ['id' => $id]);
return json(['code'=>404,'msg'=>'not found','data'=>null]); return json(['code'=>404,'msg'=>'not found','data'=>null]);
} catch (\Exception $e) { } catch (\Exception $e) {
// 记录失败日志 // 记录失败日志
@@ -256,24 +278,18 @@ class ArticleController extends BaseController
public function deleteArticle(int $id): Json public function deleteArticle(int $id): Json
{ {
try { try {
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]); ->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($affected) { if ($affected) {
// 记录成功日志 $this->logSuccess('文章管理', '删除文章', ['id' => $id]);
$this->logSuccess('文章管理', '删除文章', ['id' => $id]);
return json(['code' => 200, 'msg' => 'success', 'data' => null]); return json(['code' => 200, 'msg' => 'success', 'data' => null]);
} }
return json(['code' => 404, 'msg' => 'not found', 'data' => null]); return json(['code' => 404, 'msg' => 'not found', 'data' => null]);
} catch (\Exception $e) { } catch (\Exception $e) {
// 记录失败日志 $this->logFail('文章管理', '删除文章', $e->getMessage());
$this->logFail('文章管理', '删除文章', $e->getMessage()); return json(['code' => 500, 'msg' => 'fail' . $e->getMessage(), 'data' => null]);
return json([
'code' => 500,
'msg' => 'fail' . $e->getMessage(),
'data' => $e->getTraceAsString()
]);
} }
} }
@@ -290,7 +306,7 @@ class ArticleController extends BaseController
return json(['code'=>400,'msg'=>'无更新数据','data'=>null]); return json(['code'=>400,'msg'=>'无更新数据','data'=>null]);
} }
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
$affected = Db::name('mete_articles')->where('id',$id)->update($data); $affected = Articles::where('id',$id)->where('tid', $this->getTenantId())->update($data);
if ($affected) { if ($affected) {
// 记录成功日志 // 记录成功日志
$this->logSuccess('文章管理', '更新文章分类状态', ['id' => $id]); $this->logSuccess('文章管理', '更新文章分类状态', ['id' => $id]);
@@ -329,8 +345,8 @@ class ArticleController extends BaseController
'publisher' => $user_id, 'publisher' => $user_id,
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -366,8 +382,8 @@ class ArticleController extends BaseController
'update_time' => $currentTime 'update_time' => $currentTime
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -400,8 +416,8 @@ class ArticleController extends BaseController
'recommend' => 1, // 推荐状态 'recommend' => 1, // 推荐状态
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -434,8 +450,8 @@ class ArticleController extends BaseController
'recommend' => 0, // 取消推荐状态 'recommend' => 0, // 取消推荐状态
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -464,8 +480,8 @@ class ArticleController extends BaseController
'top' => 1, // 置顶状态 'top' => 1, // 置顶状态
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -494,8 +510,8 @@ class ArticleController extends BaseController
'top' => 0, // 取消置顶状态 'top' => 0, // 取消置顶状态
]; ];
$affected = Db::name('mete_articles') $affected = Articles::where('id', $id)
->where('id', $id) ->where('tid', $this->getTenantId())
->update($data); ->update($data);
if ($affected) { if ($affected) {
@@ -539,9 +555,9 @@ class ArticleController extends BaseController
], 'debug'); ], 'debug');
// 构建查询条件 // 构建查询条件
$query = Db::name('mete_articles') $query = Articles::alias('a')
->alias('a') ->where('a.delete_time', null)
->where('a.delete_time', null); ->where('a.tid', $this->getTenantId());
// 关键词筛选(标题模糊匹配) // 关键词筛选(标题模糊匹配)
if (!empty($keyword)) { if (!empty($keyword)) {
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\admin\controller; namespace app\admin\controller\Cms\Banner;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -10,7 +10,7 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Banner; use app\model\Cms\Banner;
class BannerController extends BaseController class BannerController extends BaseController
{ {
@@ -22,7 +22,8 @@ class BannerController extends BaseController
{ {
try { try {
$banners = Banner::where('delete_time', null) $banners = Banner::where('delete_time', null)
->order('sort', 'asc') ->where('tid', $this->getTenantId())
->order('sort desc, id desc')
->field('id, title, desc, url, image, sort, create_time, update_time') ->field('id, title, desc, url, image, sort, create_time, update_time')
->select() ->select()
->toArray(); ->toArray();
@@ -66,6 +67,7 @@ class BannerController extends BaseController
// 准备Banner数据 // 准备Banner数据
$bannerData = [ $bannerData = [
'title' => $data['title'], 'title' => $data['title'],
'tid' => $this->getTenantId(),
'desc' => $data['desc'] ?? '', 'desc' => $data['desc'] ?? '',
'url' => $data['url'] ?? '', 'url' => $data['url'] ?? '',
'image' => $data['image'] ?? '', 'image' => $data['image'] ?? '',
@@ -127,8 +129,9 @@ class BannerController extends BaseController
'sort|排序号' => 'integer', 'sort|排序号' => 'integer',
]); ]);
// 检查Banner是否存在 // 检查Banner是否存在(验证tid
$banner = Banner::where('id', $id) $banner = Banner::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -150,10 +153,14 @@ class BannerController extends BaseController
]; ];
// 执行更新 // 执行更新
Banner::where('id', $id)->update($updateData); Banner::where('id', $id)
->where('tid', $this->getTenantId())
->update($updateData);
// 获取更新后的Banner信息 // 获取更新后的Banner信息
$updatedBanner = Banner::where('id', $id)->find(); $updatedBanner = Banner::where('id', $id)
->where('tid', $this->getTenantId())
->find();
// 记录操作日志 // 记录操作日志
$this->logSuccess('Banner管理', '更新Banner', ['id' => $id]); $this->logSuccess('Banner管理', '更新Banner', ['id' => $id]);
@@ -187,8 +194,9 @@ class BannerController extends BaseController
public function deleteBanner(int $id) public function deleteBanner(int $id)
{ {
try { try {
// 检查Banner是否存在 // 检查Banner是否存在(验证tid
$banner = Banner::where('id', $id) $banner = Banner::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -201,6 +209,7 @@ class BannerController extends BaseController
// 逻辑删除Banner // 逻辑删除Banner
$result = Banner::where('id', $id) $result = Banner::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->update([ ->update([
'delete_time' => time(), 'delete_time' => time(),
@@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Demand;
use app\admin\BaseController;
use Symfony\Component\VarDumper\VarDumper;
use think\exception\ValidateException;
use think\facade\Request;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\Cms\Demand;
use app\model\Cms\DemandCategory;
class DemandController extends BaseController
{
/**
* 获取需求列表
* @return Json
*/
public function getDemandList(): Json
{
// 查询分类
$demandList = Demand::where('delete_time', null)
->where('tid', $this->getTenantId())
->order('id', 'desc')
->select();
if (!$demandList) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
]);
}
return json([
'code' => 200,
'msg' => 'success',
'list' => $demandList,
]);
}
/**
* 增加需求
* @return Json
*/
public function addDemand(): Json
{
try {
$data = Request::only(['title', 'desc', 'applicant', 'status']);
// 验证数据
if (empty($data['title']) || empty($data['desc'])) {
return json([
'code' => 400,
'msg' => '标题和描述不能为空',
]);
}
// 创建需求
$demand = Demand::create([
'title' => $data['title'],
'desc' => $data['desc'],
'applicant' => $data['applicant'] ?? '',
'status' => $data['status'] ?? 'pending',
'tid' => $this->getTenantId(),
]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => $demand,
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '添加失败:' . $e->getMessage(),
]);
}
}
/**
* 编辑需求
* @return Json
*/
public function editDemand(): Json
{
try {
$id = Request::param('id');
$data = Request::only(['title', 'desc', 'applicant', 'status']);
// 验证数据
if (empty($id)) {
return json([
'code' => 400,
'msg' => '需求ID不能为空',
]);
}
if (empty($data['title']) || empty($data['desc'])) {
return json([
'code' => 400,
'msg' => '标题和描述不能为空',
]);
}
// 查找需求(验证tid
$demand = Demand::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if (!$demand) {
return json([
'code' => 404,
'msg' => '需求不存在',
]);
}
// 更新需求
$demand->save([
'title' => $data['title'],
'desc' => $data['desc'],
'applicant' => $data['applicant'] ?? '',
'status' => $data['status'] ?? 'pending',
]);
return json([
'code' => 200,
'msg' => '编辑成功',
'data' => $demand,
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '编辑失败:' . $e->getMessage(),
]);
}
}
/**
* 删除需求
* @return Json
*/
public function deleteDemand(): Json
{
try {
$id = Request::param('id');
// 验证数据
if (empty($id)) {
return json([
'code' => 400,
'msg' => '需求ID不能为空',
]);
}
// 查找需求(验证tid
$demand = Demand::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if (!$demand) {
return json([
'code' => 404,
'msg' => '需求不存在',
]);
}
// 软删除
$demand->delete();
return json([
'code' => 200,
'msg' => '删除成功',
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage(),
]);
}
}
}
@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Domain;
use app\admin\BaseController;
use think\facade\Db;
use think\Request;
use app\model\System\SystemDomainPool;
/**
* 主域名池管理控制器
*/
class DomainPoolController extends BaseController
{
/**
* 获取域名池列表
*/
public function index(Request $request)
{
$page = $request->param('page', 1, 'int');
$pageSize = $request->param('pageSize', 10, 'int');
$mainDomain = $request->param('main_domain', '');
$status = $request->param('status', '');
$where = [['delete_time', '=', null]];
if ($mainDomain) {
$where[] = ['main_domain', 'like', "%$mainDomain%"];
}
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', $status];
}
$list = SystemDomainPool::where($where)
->page($page, $pageSize)
->order('id', 'desc')
->select()
->toArray();
$total = SystemDomainPool::where($where)
->count();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
}
/**
* 获取启用状态的主域名列表(供租户选择)
*/
public function getEnabledDomains()
{
$list = SystemDomainPool::where('status', 1)
->where('delete_time', null)
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => $list
]);
}
/**
* 创建主域名
*/
public function create(Request $request)
{
$mainDomain = $request->param('main_domain', '');
if (empty($mainDomain)) {
return json([
'code' => 400,
'msg' => '主域名不能为空'
]);
}
// 检查域名是否已存在
$exists = SystemDomainPool::where('main_domain', $mainDomain)
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '该域名已存在'
]);
}
$now = date('Y-m-d H:i:s');
$id = SystemDomainPool::insertGetId([
'main_domain' => $mainDomain,
'status' => 1,
'create_time' => $now,
'update_time' => $now
]);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => ['id' => $id]
]);
}
/**
* 编辑主域名
*/
public function update(Request $request)
{
$id = $request->param('id', 0, 'int');
$mainDomain = $request->param('main_domain', '');
$status = $request->param('status', null);
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
if (empty($mainDomain)) {
return json([
'code' => 400,
'msg' => '主域名不能为空'
]);
}
// 检查域名是否与其他记录重复
$exists = SystemDomainPool::where('main_domain', $mainDomain)
->where('id', '<>', $id)
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '该域名已存在'
]);
}
$data = [
'main_domain' => $mainDomain,
'update_time' => date('Y-m-d H:i:s')
];
if ($status !== null) {
$data['status'] = $status;
}
SystemDomainPool::where('id', $id)->update($data);
return json([
'code' => 200,
'msg' => '更新成功'
]);
}
/**
* 删除主域名(软删除)
*/
public function delete($id)
{
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
SystemDomainPool::where('id', $id)
->update([
'delete_time' => date('Y-m-d H:i:s')
]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
}
/**
* 切换主域名状态
*/
public function toggleStatus(Request $request)
{
$id = $request->param('id', 0, 'int');
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
$domain = SystemDomainPool::where('id', $id)
->find();
if (!$domain) {
return json([
'code' => 404,
'msg' => '域名不存在'
]);
}
$newStatus = $domain['status'] == 1 ? 0 : 1;
SystemDomainPool::where('id', $id)
->update([
'status' => $newStatus,
'update_time' => date('Y-m-d H:i:s')
]);
return json([
'code' => 200,
'msg' => '状态更新成功',
'data' => ['status' => $newStatus]
]);
}
}
@@ -0,0 +1,315 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Domain;
use app\admin\BaseController;
use think\facade\Db;
use think\Request;
use think\facade\Session;
use app\model\Tenant\TenantDomain;
use app\model\Tenant\Tenant;
use app\model\System\SystemDomainPool;
/**
* 租户域名绑定控制器
*/
class TenantDomainController extends BaseController
{
/**
* 获取租户域名列表(管理员查看所有)
*/
public function index(Request $request)
{
$page = $request->param('page', 1, 'int');
$pageSize = $request->param('pageSize', 10, 'int');
$tenantId = $request->param('tid', 0, 'int');
$status = $request->param('status', '');
$subDomain = $request->param('sub_domain', '');
$where = [['delete_time', '=', null]];
if ($tenantId > 0) {
$where[] = ['tid', '=', $tenantId];
}
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', $status];
}
if ($subDomain) {
$where[] = ['sub_domain', 'like', "%$subDomain%"];
}
$list = TenantDomain::where($where)
->page($page, $pageSize)
->order('id', 'desc')
->select()
->toArray();
$total = TenantDomain::where($where)
->count();
// 获取租户名称
$tenantIds = array_column($list, 'tid');
$tenants = [];
if ($tenantIds) {
$tenantList = Tenant::whereIn('id', $tenantIds)
->select()
->toArray();
$tenants = array_column($tenantList, null, 'id');
}
// 附加租户名称
foreach ($list as &$item) {
$item['tenant_name'] = $tenants[$item['tid']]['tenant_name'] ?? '';
}
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
}
/**
* 获取当前租户的域名列表(租户端)
*/
public function myDomains(Request $request)
{
$tid = $request->param('tid', 0, 'int');
if ($tid <= 0) {
return json([
'code' => 400,
'msg' => '租户ID不能为空'
]);
}
$list = TenantDomain::where('tid', $tid)
->where('delete_time', null)
->order('id', 'desc')
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => $list
]);
}
/**
* 租户申请二级域名
*/
public function apply(Request $request)
{
$tid = $request->param('tid', 0, 'int');
$subDomain = $request->param('sub_domain', '');
$mainDomain = $request->param('main_domain', '');
if ($tid <= 0) {
return json([
'code' => 400,
'msg' => '租户ID不能为空'
]);
}
// 检查该租户是否已有域名数据
$hasDomain = TenantDomain::where('tid', $tid)
->where('delete_time', null)
->find();
if ($hasDomain) {
return json([
'code' => 400,
'msg' => '该租户已有域名,请删除后再次申请'
]);
}
if (empty($subDomain)) {
return json([
'code' => 400,
'msg' => '二级域名前缀不能为空'
]);
}
if (empty($mainDomain)) {
return json([
'code' => 400,
'msg' => '请选择主域名'
]);
}
// 验证域名格式(只能包含字母、数字、连字符)
if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/', $subDomain)) {
return json([
'code' => 400,
'msg' => '二级域名前缀格式不正确'
]);
}
// 检查主域名是否存在且启用
$mainDomainInfo = SystemDomainPool::where('main_domain', $mainDomain)
->where('status', 1)
->where('delete_time', null)
->find();
if (!$mainDomainInfo) {
return json([
'code' => 400,
'msg' => '主域名不存在或已禁用'
]);
}
// 检查二级域名是否已被使用
$exists = TenantDomain::where('sub_domain', $subDomain)
->where('main_domain', $mainDomain)
->where('delete_time', null)
->find();
if ($exists) {
return json([
'code' => 400,
'msg' => '该二级域名已被使用'
]);
}
$fullDomain = $subDomain . '.' . $mainDomain;
$now = date('Y-m-d H:i:s');
$id = TenantDomain::insertGetId([
'tid' => $tid,
'sub_domain' => $subDomain,
'main_domain' => $mainDomain,
'full_domain' => $fullDomain,
'status' => 0, // 审核中
'create_time' => $now,
'update_time' => $now
]);
return json([
'code' => 200,
'msg' => '申请提交成功,等待审核',
'data' => ['id' => $id]
]);
}
/**
* 审核租户域名(通过/拒绝)
*/
public function audit(Request $request)
{
$id = $request->param('id', 0, 'int');
$action = $request->param('action', ''); // 'approve' 或 'reject'
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
$domain = TenantDomain::where('id', $id)
->find();
if (!$domain) {
return json([
'code' => 404,
'msg' => '域名不存在'
]);
}
if ($domain['status'] != 0) {
return json([
'code' => 400,
'msg' => '该域名已审核过了'
]);
}
$newStatus = $action === 'approve' ? 1 : 2; // 1-已生效 2-已拒绝
TenantDomain::where('id', $id)
->update([
'status' => $newStatus,
'update_time' => date('Y-m-d H:i:s')
]);
$msg = $action === 'approve' ? '审核通过' : '已拒绝';
return json([
'code' => 200,
'msg' => $msg
]);
}
/**
* 禁用/启用租户域名
*/
public function toggleStatus(Request $request)
{
$id = $request->param('id', 0, 'int');
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
$domain = TenantDomain::where('id', $id)
->find();
if (!$domain) {
return json([
'code' => 404,
'msg' => '域名不存在'
]);
}
// 只有已生效的域名才能被禁用
if ($domain['status'] != 1) {
return json([
'code' => 400,
'msg' => '只有已生效的域名才能被禁用'
]);
}
// 切换为禁用状态
TenantDomain::where('id', $id)
->update([
'status' => 2,
'update_time' => date('Y-m-d H:i:s')
]);
return json([
'code' => 200,
'msg' => '已禁用'
]);
}
/**
* 删除租户域名(软删除)
*/
public function delete($id)
{
if ($id <= 0) {
return json([
'code' => 400,
'msg' => '参数错误'
]);
}
TenantDomain::where('id', $id)
->update([
'delete_time' => date('Y-m-d H:i:s')
]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
}
}
@@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Friendlink;
use app\admin\BaseController;
use think\response\Json;
use app\model\Cms\Friendlink;
use think\exception\ValidateException;
/**
* 友情链接管理控制器
*/
class FriendlinkController extends BaseController
{
/**
* 获取友情链接列表
* @return Json
*/
public function getList(): Json
{
try {
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$status = $this->request->param('status', '');
$query = Friendlink::where('delete_time', null)
->where('tid', $this->getTenantId());
// 关键词搜索
if (!empty($keyword)) {
$query->where('link_name', 'like', '%' . $keyword . '%');
}
// 状态筛选
if ($status !== '') {
$query->where('status', (int)$status);
}
$total = $query->count();
$list = $query->order('sort', 'asc')
->order('id', 'desc')
->page($page, $limit)
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 获取所有友情链接(用于下拉选择)
* @return Json
*/
public function getAll(): Json
{
try {
$list = Friendlink::where('delete_time', null)
->where('tid', $this->getTenantId())
->where('status', 1)
->order('sort', 'asc')
->field('id,link_name')
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => $list
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 添加友情链接
* @return Json
*/
public function add(): Json
{
try {
$data = $this->request->param();
// 验证参数
$this->validate($data, [
'link_name|链接名称' => 'require|max:100',
'link_url|链接地址' => 'require|url|max:255',
'sort|排序' => 'integer',
'status|状态' => 'in:0,1'
]);
$friendlink = new Friendlink();
$friendlink->tid = $this->getTenantId();
$friendlink->link_name = $data['link_name'];
$friendlink->link_url = $data['link_url'];
$friendlink->link_logo = $data['link_logo'] ?? '';
$friendlink->description = $data['description'] ?? '';
$friendlink->sort = $data['sort'] ?? 0;
$friendlink->status = $data['status'] ?? 1;
$friendlink->create_time = date('Y-m-d H:i:s');
$friendlink->save();
$this->logSuccess('友情链接', '添加链接', ['id' => $friendlink->id]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => $friendlink->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('友情链接', '添加链接', $e->getMessage());
return json([
'code' => 500,
'msg' => '添加失败:' . $e->getMessage()
]);
}
}
/**
* 更新友情链接
* @param int $id
* @return Json
*/
public function update(int $id): Json
{
try {
$data = $this->request->param();
$friendlink = Friendlink::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$friendlink) {
return json([
'code' => 404,
'msg' => '链接不存在'
]);
}
// 验证参数
if (isset($data['link_name'])) {
$this->validate($data, [
'link_name|链接名称' => 'require|max:100'
]);
}
if (isset($data['link_url'])) {
$this->validate($data, [
'link_url|链接地址' => 'require|url|max:255'
]);
}
if (isset($data['link_name'])) $friendlink->link_name = $data['link_name'];
if (isset($data['link_url'])) $friendlink->link_url = $data['link_url'];
if (isset($data['link_logo'])) $friendlink->link_logo = $data['link_logo'];
if (isset($data['description'])) $friendlink->description = $data['description'];
if (isset($data['sort'])) $friendlink->sort = $data['sort'];
if (isset($data['status'])) $friendlink->status = $data['status'];
$friendlink->update_time = date('Y-m-d H:i:s');
$friendlink->save();
$this->logSuccess('友情链接', '更新链接', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $friendlink->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('友情链接', '更新链接', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除友情链接
* @param int $id
* @return Json
*/
public function delete(int $id): Json
{
try {
$friendlink = Friendlink::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$friendlink) {
return json([
'code' => 404,
'msg' => '链接不存在'
]);
}
$friendlink->delete();
$this->logSuccess('友情链接', '删除链接', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
$this->logFail('友情链接', '删除链接', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
/**
* 批量删除友情链接
* @return Json
*/
public function batchDelete(): Json
{
try {
$ids = $this->request->param('ids', []);
if (empty($ids)) {
return json([
'code' => 400,
'msg' => '请选择要删除的链接'
]);
}
Friendlink::whereIn('id', $ids)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->update(['delete_time' => date('Y-m-d H:i:s')]);
$this->logSuccess('友情链接', '批量删除链接', ['ids' => $ids]);
return json([
'code' => 200,
'msg' => '批量删除成功'
]);
} catch (\Exception $e) {
$this->logFail('友情链接', '批量删除链接', $e->getMessage());
return json([
'code' => 500,
'msg' => '批量删除失败:' . $e->getMessage()
]);
}
}
}
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\admin\controller; namespace app\admin\controller\Cms\FrontMenu;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -10,7 +10,7 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\FrontMenu; use app\model\Cms\FrontMenu;
class FrontMenuController extends BaseController class FrontMenuController extends BaseController
{ {
@@ -23,6 +23,7 @@ class FrontMenuController extends BaseController
try { try {
// 获取所有未删除的菜单 // 获取所有未删除的菜单
$frontMenus = FrontMenu::where('delete_time', null) $frontMenus = FrontMenu::where('delete_time', null)
->where('tid', $this->getTenantId())
->field('id,pid,title,type,image,path,component_path,sort,desc') ->field('id,pid,title,type,image,path,component_path,sort,desc')
->order('sort', 'desc') ->order('sort', 'desc')
->select() ->select()
@@ -69,6 +70,7 @@ class FrontMenuController extends BaseController
// 准备菜单数据 // 准备菜单数据
$menuData = [ $menuData = [
'pid' => $data['pid'] ?? 0, 'pid' => $data['pid'] ?? 0,
'tid' => $this->getTenantId(),
'title' => $data['title'], 'title' => $data['title'],
'type' => $data['type'], 'type' => $data['type'],
'sort' => $data['sort'] ?? 0, 'sort' => $data['sort'] ?? 0,
@@ -130,6 +132,7 @@ class FrontMenuController extends BaseController
// 准备更新数据 // 准备更新数据
$updateData = [ $updateData = [
'title' => $data['title'], 'title' => $data['title'],
'tid' => $this->getTenantId(),
'pid' => $data['pid'] ?? null, 'pid' => $data['pid'] ?? null,
'type' => $data['type'], 'type' => $data['type'],
'path' => $data['path'] ?? null, 'path' => $data['path'] ?? null,
@@ -177,6 +180,7 @@ class FrontMenuController extends BaseController
try { try {
// 检查是否有子前端导航 // 检查是否有子前端导航
$hasChildren = FrontMenu::where('pid', $id) $hasChildren = FrontMenu::where('pid', $id)
->where('tid', $this->getTenantId())
->where('pid', $id) ->where('pid', $id)
->where('delete_time', null) ->where('delete_time', null)
->count() > 0; ->count() > 0;
@@ -192,6 +196,7 @@ class FrontMenuController extends BaseController
$result = FrontMenu::where('id', $id) $result = FrontMenu::where('id', $id)
->where('id', $id) ->where('id', $id)
->where('delete_time', null) ->where('delete_time', null)
->where('tid', $this->getTenantId())
->update([ ->update([
'delete_time' => date('Y-m-d H:i:s'), 'delete_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s') 'update_time' => date('Y-m-d H:i:s')
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\admin\controller; namespace app\admin\controller\Cms\OnePage;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -10,7 +10,7 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\OnePage; use app\model\Cms\OnePage;
class OnePageController extends BaseController class OnePageController extends BaseController
{ {
@@ -22,6 +22,7 @@ class OnePageController extends BaseController
{ {
try { try {
$onePages = OnePage::where('delete_time', null) $onePages = OnePage::where('delete_time', null)
->where('tid', $this->getTenantId())
->order('sort', 'asc') ->order('sort', 'asc')
->field('id, title, content, path, sort, status, create_time, update_time') ->field('id, title, content, path, sort, status, create_time, update_time')
->select() ->select()
@@ -69,6 +70,7 @@ class OnePageController extends BaseController
// 检查路由是否已存在 // 检查路由是否已存在
$exists = OnePage::where('path', $data['path']) $exists = OnePage::where('path', $data['path'])
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if ($exists) { if ($exists) {
@@ -85,6 +87,7 @@ class OnePageController extends BaseController
'path' => $data['path'], 'path' => $data['path'],
'sort' => $data['sort'] ?? 0, 'sort' => $data['sort'] ?? 0,
'status' => $data['status'] ?? 1, 'status' => $data['status'] ?? 1,
'tid' => $this->getTenantId(),
'create_time' => date('Y-m-d H:i:s'), 'create_time' => date('Y-m-d H:i:s'),
]; ];
@@ -150,8 +153,9 @@ class OnePageController extends BaseController
]); ]);
} }
// 检查单页是否存在 // 检查单页是否存在(验证tid
$onePage = OnePage::where('id', $id) $onePage = OnePage::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -165,6 +169,7 @@ class OnePageController extends BaseController
// 检查路由是否被其他单页使用 // 检查路由是否被其他单页使用
$exists = OnePage::where('path', $data['path']) $exists = OnePage::where('path', $data['path'])
->where('id', '<>', $id) ->where('id', '<>', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if ($exists) { if ($exists) {
@@ -222,8 +227,9 @@ class OnePageController extends BaseController
public function deleteOnePage(int $id) public function deleteOnePage(int $id)
{ {
try { try {
// 检查单页是否存在 // 检查单页是否存在(验证tid
$onePage = OnePage::where('id', $id) $onePage = OnePage::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -236,6 +242,7 @@ class OnePageController extends BaseController
// 逻辑删除单页 // 逻辑删除单页
$result = OnePage::where('id', $id) $result = OnePage::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->update([ ->update([
'delete_time' => date('Y-m-d H:i:s'), 'delete_time' => date('Y-m-d H:i:s'),
@@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Products;
use app\admin\BaseController;
use think\response\Json;
use app\model\Cms\Products;
use think\exception\ValidateException;
/**
* 特色产品管理控制器
*/
class ProductsController extends BaseController
{
/**
* 获取特色产品列表
* @return Json
*/
public function productsList(): Json
{
try {
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$query = Products::where('delete_time', null)
->where('tid', $this->getTenantId());
// 关键词搜索
if (!empty($keyword)) {
$query->where('title', 'like', '%' . $keyword . '%');
}
$total = $query->count();
$list = $query->order('sort', 'asc')
->order('id', 'desc')
->page($page, $limit)
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 添加特色产品
* @return Json
*/
public function addProducts(): Json
{
try {
$data = $this->request->param();
$product = new Products();
$product->tid = $this->getTenantId();
$product->title = $data['title'];
$product->url = $data['url'];
$product->thumb = $data['thumb'] ?? '';
$product->desc = $data['desc'] ?? '';
$product->content = $data['content'] ?? '';
$product->sort = $data['sort'] ?? 0;
$product->create_time = date('Y-m-d H:i:s');
$product->save();
$this->logSuccess('特色产品', '添加产品', ['id' => $product->id]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => $product->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('特色产品', '添加产品', $e->getMessage());
return json([
'code' => 500,
'msg' => '添加失败:' . $e->getMessage()
]);
}
}
/**
* 更新特色产品
* @param int $id
* @return Json
*/
public function editProducts(int $id): Json
{
try {
$data = $this->request->param();
$product = Products::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$product) {
return json([
'code' => 404,
'msg' => '产品不存在'
]);
}
if (isset($data['title'])) $product->title = $data['title'];
if (isset($data['url'])) $product->url = $data['url'];
if (isset($data['thumb'])) $product->thumb = $data['thumb'];
if (isset($data['desc'])) $product->desc = $data['desc'];
if (isset($data['sort'])) $product->sort = $data['sort'];
$product->update_time = date('Y-m-d H:i:s');
$product->save();
$this->logSuccess('特色产品', '更新产品', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $product->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('特色产品', '更新产品', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除特色产品
* @param int $id
* @return Json
*/
public function deleteProducts(int $id): Json
{
try {
$product = Products::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$product) {
return json([
'code' => 404,
'msg' => '产品不存在'
]);
}
$product->delete();
$this->logSuccess('特色产品', '删除产品', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
$this->logFail('特色产品', '删除产品', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Products;
use app\admin\BaseController;
use app\model\Cms\ProductsTypes;
use think\exception\ValidateException;
use think\response\Json;
/**
* 产品分类管理控制器
*/
class ProductsTypesController extends BaseController
{
/**
* 获取产品分类列表
* @return Json
*/
public function productsTypesList(): Json
{
try {
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$keyword = (string)$this->request->param('keyword', '');
$query = ProductsTypes::where('delete_time', null)
->where('tid', $this->getTenantId());
if ($keyword !== '') {
$query->where('title', 'like', '%' . $keyword . '%');
}
$total = $query->count();
$list = $query->order('sort', 'asc')
->order('id', 'desc')
->page($page, $limit)
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => [
'list' => [],
'total' => 0
]
]);
}
}
/**
* 添加产品分类
* @return Json
*/
public function addProductsTypes(): Json
{
try {
$data = $this->request->param();
$title = (string)($data['title'] ?? '');
if ($title === '') {
return json([
'code' => 400,
'msg' => '分类名称不能为空'
]);
}
$type = new ProductsTypes();
$type->tid = $this->getTenantId();
$type->pid = isset($data['pid']) ? (int)$data['pid'] : 0;
$type->title = $title;
$type->desc = (string)($data['desc'] ?? '');
$type->sort = isset($data['sort']) ? (int)$data['sort'] : 0;
$type->create_time = date('Y-m-d H:i:s');
$type->update_time = date('Y-m-d H:i:s');
$type->save();
$this->logSuccess('产品分类管理', '添加分类', ['id' => $type->id]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => $type->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('产品分类管理', '添加分类', $e->getMessage());
return json([
'code' => 500,
'msg' => '添加失败:' . $e->getMessage()
]);
}
}
/**
* 更新产品分类
* @param int $id
* @return Json
*/
public function editProductsTypes(int $id): Json
{
try {
$data = $this->request->param();
$type = ProductsTypes::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$type) {
return json([
'code' => 404,
'msg' => '分类不存在'
]);
}
if (isset($data['title'])) $type->title = (string)$data['title'];
if (isset($data['pid'])) $type->pid = (int)$data['pid'];
if (isset($data['desc'])) $type->desc = (string)$data['desc'];
if (isset($data['sort'])) $type->sort = (int)$data['sort'];
if ((string)$type->title === '') {
return json([
'code' => 400,
'msg' => '分类名称不能为空'
]);
}
$type->update_time = date('Y-m-d H:i:s');
$type->save();
$this->logSuccess('产品分类管理', '更新分类', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $type->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('产品分类管理', '更新分类', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除产品分类(软删除)
* @param int $id
* @return Json
*/
public function deleteProductsTypes(int $id): Json
{
try {
$type = ProductsTypes::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$type) {
return json([
'code' => 404,
'msg' => '分类不存在'
]);
}
$type->delete();
$this->logSuccess('产品分类管理', '删除分类', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
$this->logFail('产品分类管理', '删除分类', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Services;
use app\admin\BaseController;
use think\response\Json;
use app\model\Cms\Services;
use think\exception\ValidateException;
/**
* 特色服务管理控制器
*/
class ServicesController extends BaseController
{
/**
* 获取特色服务列表
* @return Json
*/
public function servicesList(): Json
{
try {
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$query = Services::where('delete_time', null)
->where('tid', $this->getTenantId());
// 关键词搜索
if (!empty($keyword)) {
$query->where('name', 'like', '%' . $keyword . '%');
}
$total = $query->count();
$list = $query->order('sort', 'asc')
->order('id', 'desc')
->page($page, $limit)
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $list,
'total' => $total
]
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/**
* 添加特色服务
* @return Json
*/
public function addServices(): Json
{
try {
$data = $this->request->param();
$service = new Services();
$service->tid = $this->getTenantId();
$service->title = $data['title'];
$service->url = $data['url'];
$service->thumb = $data['thumb'] ?? '';
$service->desc = $data['desc'] ?? '';
$service->sort = $data['sort'] ?? 0;
$service->create_time = date('Y-m-d H:i:s');
$service->save();
$this->logSuccess('特色服务', '添加服务', ['id' => $service->id]);
return json([
'code' => 200,
'msg' => '添加成功',
'data' => $service->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('特色服务', '添加服务', $e->getMessage());
return json([
'code' => 500,
'msg' => '添加失败:' . $e->getMessage()
]);
}
}
/**
* 更新特色服务
* @param int $id
* @return Json
*/
public function editServices(int $id): Json
{
try {
$data = $this->request->param();
$service = Services::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$service) {
return json([
'code' => 404,
'msg' => '服务不存在'
]);
}
if (isset($data['title'])) $service->title = $data['title'];
if (isset($data['url'])) $service->url = $data['url'];
if (isset($data['thumb'])) $service->thumb = $data['thumb'];
if (isset($data['desc'])) $service->desc = $data['desc'];
if (isset($data['sort'])) $service->sort = $data['sort'];
$service->update_time = date('Y-m-d H:i:s');
$service->save();
$this->logSuccess('特色服务', '更新服务', ['id' => $id]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $service->toArray()
]);
} catch (ValidateException $e) {
return json([
'code' => 400,
'msg' => $e->getError()
]);
} catch (\Exception $e) {
$this->logFail('特色服务', '更新服务', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage()
]);
}
}
/**
* 删除特色服务
* @param int $id
* @return Json
*/
public function deleteServices(int $id): Json
{
try {
$service = Services::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$service) {
return json([
'code' => 404,
'msg' => '服务不存在'
]);
}
$service->delete();
$this->logSuccess('特色服务', '删除服务', ['id' => $id]);
return json([
'code' => 200,
'msg' => '删除成功'
]);
} catch (\Exception $e) {
$this->logFail('特色服务', '删除服务', $e->getMessage());
return json([
'code' => 500,
'msg' => '删除失败:' . $e->getMessage()
]);
}
}
}
@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Cms\Theme;
use app\admin\BaseController;
use app\service\ThemeService;
use think\facade\Request;
use app\model\Cms\TemplateSiteConfig;
/**
* 模板管理控制器
*/
class ThemeController extends BaseController
{
private ThemeService $themeService;
public function __construct(\think\App $app)
{
parent::__construct($app);
$this->themeService = new ThemeService();
}
/**
* 获取模板列表(后台管理)
* @return \think\response\Json
*/
public function index()
{
$tid = $this->getTenantId();
$themes = $this->themeService->getThemeList();
$currentTheme = $this->themeService->getCurrentTheme($tid);
$theme_useing = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->value('value');
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'list' => $themes,
'currentTheme' => $currentTheme
]
]);
}
/**
* 切换模板
* @return \think\response\Json
*/
public function switch()
{
$tid = Request::post('tid', 0, 'int');
$themeKey = Request::post('theme_key', '');
if (empty($tid) || $tid == 0) {
return json([
'code' => 401,
'msg' => '未获取到租户ID,请先选择租户'
]);
}
if (empty($themeKey)) {
return json([
'code' => 400,
'msg' => '模板标识不能为空'
]);
}
$result = $this->themeService->switchTheme($tid, $themeKey);
if ($result) {
return json([
'code' => 200,
'msg' => '切换成功'
]);
}
return json([
'code' => 400,
'msg' => '切换失败,模板不存在'
]);
}
/**
* 获取模板字段数据
* @return \think\response\Json
*/
public function getData()
{
$tid = Request::get('tid', 0, 'int');
$themeKey = Request::get('theme_key', '');
$themeData = $this->themeService->getThemeData($tid, $themeKey ?: null);
return json([
'code' => 200,
'msg' => 'success',
'data' => $themeData
]);
}
/**
* 保存模板字段数据
* @return \think\response\Json
*/
public function saveData()
{
$tid = Request::post('tid', 0, 'int');
$themeKey = Request::post('theme_key', '');
$fieldKey = Request::post('field_key', '');
$fieldValue = Request::post('field_value', '');
if (empty($themeKey) || empty($fieldKey)) {
return json([
'code' => 400,
'msg' => '参数不完整'
]);
}
$result = $this->themeService->saveThemeField($tid, $themeKey, $fieldKey, $fieldValue);
if ($result) {
return json([
'code' => 200,
'msg' => '保存成功'
]);
}
return json([
'code' => 400,
'msg' => '保存失败'
]);
}
}
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Erp;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use app\model\Erp\Employee;
use app\model\System\AdminUser;
class EmployeeController extends BaseController
{
/**
* 获取员工列表
*/
public function getEmployee()
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$list = Employee::where('delete_time', null)
->where('tid', $tid)
->select()
->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $list
]);
}
/**
* 获取员工详情
*/
public function getEmployeeDetail($id)
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$detail = Employee::where('id', $id)
->where('delete_time', null)
->where('tid', $tid)
->find()
->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $detail
]);
}
/**
* 创建员工
*/
public function createEmployee()
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$data = $this->request->post();
$data['tid'] = $tid;
$employee = Employee::create($data);
if ($employee) {
return json([
'code' => 200,
'msg' => '创建成功',
'data' => $employee
]);
} else {
return json([
'code' => 500,
'msg' => '创建失败',
]);
}
}
/**
* 编辑员工
*/
public function editEmployee($id)
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$data = $this->request->post();
unset($data['tid']); // 不允许修改租户ID
$employee = Employee::where('id', $id)
->where('tid', $tid)
->update($data);
if ($employee !== false) {
return json([
'code' => 200,
'msg' => '编辑成功',
'data' => $employee
]);
} else {
return json([
'code' => 500,
'msg' => '编辑失败',
]);
}
}
/**
* 删除员工
*/
public function deleteEmployee($id)
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$employee = Employee::where('id', $id)
->where('tid', $tid)
->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($employee) {
return json([
'code' => 200,
'msg' => '删除成功',
]);
} else {
return json([
'code' => 500,
'msg' => '删除失败',
]);
}
}
}
@@ -11,7 +11,7 @@ use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Erp\Organization; use app\model\Erp\Organization;
use app\model\AdminUser; use app\model\System\AdminUser;
class OrganizationController extends BaseController class OrganizationController extends BaseController
{ {
@@ -20,7 +20,15 @@ class OrganizationController extends BaseController
*/ */
public function getOrganization() public function getOrganization()
{ {
$list = Organization::where('delete_time', null)->select()->toArray(); $tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$list = Organization::where('delete_time', null)
->where('tid', $tid)
->select()
->toArray();
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '获取成功', 'msg' => '获取成功',
@@ -33,8 +41,18 @@ class OrganizationController extends BaseController
*/ */
public function getOrganizationDetail($id) public function getOrganizationDetail($id)
{ {
$detail = Organization::where('id', $id)->where('delete_time', null)->find()->toArray(); $tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$detail = Organization::where('id', $id)
->where('delete_time', null)
->where('tid', $tid)
->find()
->toArray();
$detail['leader_name'] = AdminUser::where('id', $detail['leader_id'])->value('name'); $detail['leader_name'] = AdminUser::where('id', $detail['leader_id'])->value('name');
$detail['parent_name'] = Organization::where('id', $detail['parent_id'])->value('org_name');
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '获取成功', 'msg' => '获取成功',
@@ -47,7 +65,14 @@ class OrganizationController extends BaseController
*/ */
public function createOrganization() public function createOrganization()
{ {
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$data = $this->request->post(); $data = $this->request->post();
$data['tid'] = $tid;
$organization = Organization::create($data); $organization = Organization::create($data);
if ($organization) { if ($organization) {
return json([ return json([
@@ -68,9 +93,18 @@ class OrganizationController extends BaseController
*/ */
public function editOrganization($id) public function editOrganization($id)
{ {
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$data = $this->request->post(); $data = $this->request->post();
$organization = Organization::where('id', $id)->update($data); unset($data['tid']); // 不允许修改租户ID
if ($organization) {
$organization = Organization::where('id', $id)
->where('tid', $tid)
->update($data);
if ($organization !== false) {
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '编辑成功', 'msg' => '编辑成功',
@@ -89,7 +123,14 @@ class OrganizationController extends BaseController
*/ */
public function deleteOrganization($id) public function deleteOrganization($id)
{ {
$organization = Organization::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]); $tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$organization = Organization::where('id', $id)
->where('tid', $tid)
->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($organization) { if ($organization) {
return json([ return json([
'code' => 200, 'code' => 200,
@@ -102,4 +143,50 @@ class OrganizationController extends BaseController
]); ]);
} }
} }
/**
* 获取企业单位
*/
public function getCompanys()
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$where = [['delete_time', '=', null], ['is_company', '=', 1], ['tid', '=', $tid]];
$list = Organization::where($where)->select()->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $list
]);
}
/**
* 获取部门
*/
public function getDepartments()
{
$tid = $this->getTenantId();
if (!$tid) {
return json(['code' => 403, 'msg' => '无法获取租户信息']);
}
$parentId = input('parent_id/d', 0);
$where = [['delete_time', '=', null], ['is_company', '=', 0], ['tid', '=', $tid]];
if ($parentId > 0) {
$where[] = ['parent_id', '=', $parentId];
}
$list = Organization::where($where)->select()->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $list
]);
}
} }
+546 -8
View File
@@ -11,12 +11,17 @@ use think\facade\Cache;
use think\response\Json; use think\response\Json;
use app\service\JwtService; use app\service\JwtService;
use app\model\AdminUser; use app\model\System\AdminUser;
use app\model\System\AdminUserGroup;
use app\model\System\SystemSiteSettings; use app\model\System\SystemSiteSettings;
use app\model\Tenant\Tenant; use app\model\Tenant\Tenant;
use app\model\System\OperationLog;
class LoginController extends BaseController class LoginController extends BaseController
{ {
private const SMS_CODE_TTL_SECONDS = 300; // 5分钟
private const SMS_CODE_RESEND_SECONDS = 60; // 60秒可重发
private function generateToken($userInfo): string private function generateToken($userInfo): string
{ {
return JwtService::generateToken($userInfo); return JwtService::generateToken($userInfo);
@@ -27,6 +32,186 @@ class LoginController extends BaseController
return JwtService::verifyToken($token); return JwtService::verifyToken($token);
} }
private function getSiteSettingValue(string $label): string
{
$row = SystemSiteSettings::where('label', $label)
->where('delete_time', null)
->order('id', 'asc')
->find();
return $row ? (string)$row['value'] : '';
}
private function smsCodeCacheKey(string $scene, string $tenantName, string $account, string $phone): string
{
return 'sms_code:' . md5($scene . '|' . $tenantName . '|' . $account . '|' . $phone);
}
private function smsCodeThrottleKey(string $scene, string $tenantName, string $account, string $phone): string
{
return 'sms_code_throttle:' . md5($scene . '|' . $tenantName . '|' . $account . '|' . $phone);
}
private function postJson(string $url, array $payload, array $headers): array
{
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
if ($body === false) {
return ['ok' => false, 'error' => 'json_encode failed'];
}
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => implode("\r\n", $headers),
'content' => $body,
'timeout' => 10,
'ignore_errors' => true,
],
]);
$respBody = @file_get_contents($url, false, $context);
$status = 0;
if (!empty($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $line) {
if (preg_match('#^HTTP/\\S+\\s+(\\d+)#', $line, $m)) {
$status = (int)$m[1];
break;
}
}
}
if ($respBody === false) {
$last = error_get_last();
return ['ok' => false, 'status' => $status, 'error' => (string)($last['message'] ?? 'http request failed')];
}
$decoded = json_decode((string)$respBody, true);
if (!is_array($decoded)) {
$decoded = [];
}
return [
'ok' => $status >= 200 && $status < 300,
'status' => $status,
'body' => (string)$respBody,
'json' => $decoded,
];
}
private function sendSmsCodeInternal(string $scene, string $tenantName, string $account, string $phone): Json
{
$throttleKey = $this->smsCodeThrottleKey($scene, $tenantName, $account, $phone);
if (Cache::has($throttleKey)) {
return json(['code' => 429, 'msg' => '验证码发送过于频繁,请稍后再试']);
}
$backendUrl = $this->getSiteSettingValue('backendUrl');
$apiKey = $this->getSiteSettingValue('apiKey');
if ($backendUrl === '' || $apiKey === '') {
return json(['code' => 500, 'msg' => '短信网关配置缺失,请联系管理员']);
}
$code = (string)random_int(100000, 999999);
$sceneTextMap = [
'register' => '注册',
'reset' => '找回密码',
'login' => '登录',
];
$sceneText = $sceneTextMap[$scene] ?? '验证';
$content = "【云泽网】{$sceneText}验证码:{$code}5分钟内有效。";
$enqueueUrl = rtrim($backendUrl, '/') . '/api/v1/business/outbound-tasks';
$resp = $this->postJson($enqueueUrl, [
'phone' => $phone,
'content' => $content,
], [
'X-Api-Key: ' . $apiKey,
'Content-Type: application/json; charset=utf-8',
'Accept: application/json',
]);
if (empty($resp['ok'])) {
return json([
'code' => 500,
'msg' => '验证码短信发送失败',
'detail' => [
'http_status' => $resp['status'] ?? null,
'body_preview' => isset($resp['body']) ? substr((string)$resp['body'], 0, 200) : '',
]
]);
}
$codeKey = $this->smsCodeCacheKey($scene, $tenantName, $account, $phone);
Cache::set($codeKey, $code, self::SMS_CODE_TTL_SECONDS);
Cache::set($throttleKey, 1, self::SMS_CODE_RESEND_SECONDS);
return json(['code' => 200, 'msg' => '验证码已发送']);
}
private function buildLoginSuccessResponse($user, $tenant): Json
{
$tid = (int)$tenant->id;
try {
$loginCount = isset($user['login_count']) && $user['login_count'] !== null ? (int)$user['login_count'] : 0;
AdminUser::where('id', $user['id'])->update([
'login_count' => $loginCount + 1,
'last_login_ip' => $this->request->ip(),
'last_login_time' => date('Y-m-d H:i:s')
]);
} catch (\Exception $e) {
error_log('更新登录信息失败: ' . $e->getMessage());
}
$userInfo = [
'id' => $user['id'],
'account' => $user['account'],
'name' => $user['name'],
'group_id' => $user['group_id'],
'tid' => $tid,
'tenant' => $tenant
];
if ($user['group_id']) {
$userGroup = AdminUserGroup::where('id', $user['group_id'])->find();
if ($userGroup && $userGroup->rights) {
$userInfo['rights'] = json_decode($userGroup->rights, true);
}
}
try {
$token = $this->generateToken($userInfo);
} catch (\Exception $e) {
$this->logFail('登录管理', '登录', 'Token生成失败: ' . $e->getMessage());
return json(['code' => 500, 'msg' => '登录失败,请稍后重试']);
}
try {
$cacheKey = 'admin_user_' . $user['id'] . '_' . $tid;
\think\facade\Cache::set($cacheKey, $userInfo, 86400 * 7);
} catch (\Exception $e) {
error_log('用户缓存写入失败: ' . $e->getMessage());
}
try {
$this->logSuccess('登录管理', '登录', [
'id' => $user['id'],
'tid' => $tid,
'tenant' => $tenant
], $userInfo);
} catch (\Exception $e) {
error_log('登录日志记录失败: ' . $e->getMessage());
}
return json([
'code' => 200,
'msg' => '登录成功',
'data' => [
'token' => $token,
'user' => $userInfo
]
]);
}
/** /**
* 登录接口 * 登录接口
* @return Json * @return Json
@@ -75,12 +260,12 @@ class LoginController extends BaseController
'msg' => '租户不存在或已禁用' 'msg' => '租户不存在或已禁用'
]); ]);
} }
$tenant_id = $tenant->id; $tid = $tenant->id;
$tenant_name = $tenant->tenant_name; $tenant_name = $tenant->tenant_name;
// 5. 查询用户(新增:关联租户ID,确保用户属于该租户) // 5. 查询用户(新增:关联租户ID,确保用户属于该租户)
$user = AdminUser::where('account', $data['account']) $user = AdminUser::where('account', $data['account'])
->where('tenant_id', $tenant_id) // 核心:验证用户所属租户 ->where('tid', $tid) // 核心:验证用户所属租户
->where('status', 1) ->where('status', 1)
->find(); ->find();
@@ -114,15 +299,23 @@ class LoginController extends BaseController
error_log('更新登录信息失败: ' . $e->getMessage()); error_log('更新登录信息失败: ' . $e->getMessage());
} }
// 9. 组装用户信息(新增:加入租户ID和租户名称) // 9. 组装用户信息(新增:加入租户ID和租户名称和角色权限
$userInfo = [ $userInfo = [
'id' => $user['id'], 'id' => $user['id'],
'account' => $user['account'], 'account' => $user['account'],
'name' => $user['name'], 'name' => $user['name'],
'group_id' => $user['group_id'], 'group_id' => $user['group_id'],
'tenant_id' => $tenant_id, // 新增:租户ID 'tid' => $tid, // 新增:租户ID
'tenant' => $tenant // 新增:租户名称 'tenant' => $tenant // 新增:租户名称
]; ];
// 获取角色权限
if ($user['group_id']) {
$userGroup = AdminUserGroup::where('id', $user['group_id'])->find();
if ($userGroup && $userGroup->rights) {
$userInfo['rights'] = json_decode($userGroup->rights, true);
}
}
// 10. 生成Token(Token中已包含租户信息,后续可通过Token解析获取) // 10. 生成Token(Token中已包含租户信息,后续可通过Token解析获取)
try { try {
@@ -137,7 +330,7 @@ class LoginController extends BaseController
// 11. 写入用户数据缓存(核心:缓存包含租户信息,示例用Redis,可根据你的缓存工具调整) // 11. 写入用户数据缓存(核心:缓存包含租户信息,示例用Redis,可根据你的缓存工具调整)
try { try {
$cacheKey = 'admin_user_' . $user['id'] . '_' . $tenant_id; // 缓存键加入租户ID,避免多租户冲突 $cacheKey = 'admin_user_' . $user['id'] . '_' . $tid; // 缓存键加入租户ID,避免多租户冲突
$cacheExpire = 86400 * 7; // 缓存7天,可根据需求调整 $cacheExpire = 86400 * 7; // 缓存7天,可根据需求调整
// 写入缓存(这里假设你使用thinkphp的Cache类,若用其他工具可替换) // 写入缓存(这里假设你使用thinkphp的Cache类,若用其他工具可替换)
\think\facade\Cache::set($cacheKey, $userInfo, $cacheExpire); \think\facade\Cache::set($cacheKey, $userInfo, $cacheExpire);
@@ -150,7 +343,7 @@ class LoginController extends BaseController
try { try {
$this->logSuccess('登录管理', '登录', [ $this->logSuccess('登录管理', '登录', [
'id' => $user['id'], 'id' => $user['id'],
'tenant_id' => $tenant_id, 'tid' => $tid,
'tenant' => $tenant 'tenant' => $tenant
], $userInfo); ], $userInfo);
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -181,6 +374,87 @@ class LoginController extends BaseController
} }
} }
/**
* 发送手机号登录验证码
*/
public function sendLoginCode(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'phone|手机号' => 'require|mobile',
]);
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
->where('status', 1)
->find();
if (!$tenant) {
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
}
$user = AdminUser::where('tid', (int)$tenant['id'])
->where('phone', (string)$data['phone'])
->where('status', 1)
->where('delete_time', null)
->find();
if (!$user) {
return json(['code' => 404, 'msg' => '手机号未绑定可用账号']);
}
return $this->sendSmsCodeInternal('login', (string)$data['tenant_name'], (string)$data['phone'], (string)$data['phone']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
}
}
/**
* 手机号验证码登录
*/
public function loginBySms(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'phone|手机号' => 'require|mobile',
'sms_code|短信验证码' => 'require|length:4,8',
]);
$codeKey = $this->smsCodeCacheKey('login', (string)$data['tenant_name'], (string)$data['phone'], (string)$data['phone']);
$cachedCode = (string)Cache::get($codeKey, '');
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
}
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
->where('status', 1)
->field(['id', 'tenant_name'])
->find();
if (!$tenant) {
return json(['code' => 401, 'msg' => '租户不存在或已禁用']);
}
$user = AdminUser::where('tid', (int)$tenant['id'])
->where('phone', (string)$data['phone'])
->where('status', 1)
->where('delete_time', null)
->find();
if (!$user) {
return json(['code' => 401, 'msg' => '手机号未绑定可用账号']);
}
Cache::delete($codeKey);
return $this->buildLoginSuccessResponse($user, $tenant);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '登录失败:' . $e->getMessage()]);
}
}
/** /**
* 退出登录 * 退出登录
* @return Json * @return Json
@@ -200,7 +474,7 @@ class LoginController extends BaseController
if ($userInfo && isset($userInfo['id'])) { if ($userInfo && isset($userInfo['id'])) {
$this->logSuccess('登录管理', '退出登录', ['result' => 'success'], $userInfo); $this->logSuccess('登录管理', '退出登录', ['result' => 'success'], $userInfo);
} else { } else {
\app\model\OperationLog::create([ OperationLog::create([
'user_id' => 0, 'user_id' => 0,
'user_account' => '', 'user_account' => '',
'user_name' => '未知用户', 'user_name' => '未知用户',
@@ -287,4 +561,268 @@ class LoginController extends BaseController
'data' => $loginInfo 'data' => $loginInfo
]); ]);
} }
/**
* 注册账号(按租户维度)
*/
public function sendRegisterCode(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'account|账号' => 'require|length:3,32',
'phone|手机号' => 'require|mobile',
]);
return $this->sendSmsCodeInternal('register', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
}
}
public function register(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'account|账号' => 'require|length:3,32',
'name|姓名' => 'require|length:2,32',
'password|密码' => 'require|length:6,32',
'confirm_password|确认密码' => 'require',
'phone|手机号' => 'require|mobile',
'sms_code|短信验证码' => 'require|length:4,8',
]);
if ((string)$data['password'] !== (string)$data['confirm_password']) {
return json(['code' => 400, 'msg' => '两次输入的密码不一致']);
}
$codeKey = $this->smsCodeCacheKey('register', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
$cachedCode = (string)Cache::get($codeKey, '');
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
}
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
->where('status', 1)
->find();
if (!$tenant) {
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
}
$exists = AdminUser::where('tid', (int)$tenant['id'])
->where('account', (string)$data['account'])
->where('delete_time', null)
->find();
if ($exists) {
return json(['code' => 400, 'msg' => '账号已存在']);
}
$now = date('Y-m-d H:i:s');
$user = new AdminUser();
$user->save([
'tid' => (int)$tenant['id'],
'account' => (string)$data['account'],
'name' => (string)$data['name'],
'phone' => (string)$data['phone'],
'email' => (string)($data['email'] ?? ''),
'password' => md5((string)$data['password']),
'status' => 1,
'group_id' => 0,
'login_count' => 0,
'create_time' => $now,
'update_time' => $now,
]);
Cache::delete($codeKey);
return json(['code' => 200, 'msg' => '注册成功']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '注册失败:' . $e->getMessage()]);
}
}
/**
* 忘记密码:通过租户 + 账号 + 手机号重置密码
*/
public function sendResetCode(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'account|账号' => 'require|length:3,32',
'phone|手机号' => 'require|mobile',
]);
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
->where('status', 1)
->find();
if (!$tenant) {
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
}
$user = AdminUser::where('tid', (int)$tenant['id'])
->where('account', (string)$data['account'])
->where('phone', (string)$data['phone'])
->where('delete_time', null)
->find();
if (!$user) {
return json(['code' => 404, 'msg' => '账号不存在或手机号不匹配']);
}
return $this->sendSmsCodeInternal('reset', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
}
}
public function resetPassword(): Json
{
try {
$data = $this->request->post();
$this->validate($data, [
'tenant_name|租户名称' => 'require|length:1,128',
'account|账号' => 'require|length:3,32',
'phone|手机号' => 'require|mobile',
'new_password|新密码' => 'require|length:6,32',
'confirm_password|确认密码' => 'require',
'sms_code|短信验证码' => 'require|length:4,8',
]);
if ((string)$data['new_password'] !== (string)$data['confirm_password']) {
return json(['code' => 400, 'msg' => '两次输入的密码不一致']);
}
$codeKey = $this->smsCodeCacheKey('reset', (string)$data['tenant_name'], (string)$data['account'], (string)$data['phone']);
$cachedCode = (string)Cache::get($codeKey, '');
if ($cachedCode === '' || $cachedCode !== (string)$data['sms_code']) {
return json(['code' => 400, 'msg' => '短信验证码错误或已过期']);
}
$tenant = Tenant::where('tenant_name', (string)$data['tenant_name'])
->where('status', 1)
->find();
if (!$tenant) {
return json(['code' => 400, 'msg' => '租户不存在或已禁用']);
}
$user = AdminUser::where('tid', (int)$tenant['id'])
->where('account', (string)$data['account'])
->where('phone', (string)$data['phone'])
->where('delete_time', null)
->find();
if (!$user) {
return json(['code' => 404, 'msg' => '账号不存在或手机号不匹配']);
}
AdminUser::where('id', (int)$user['id'])->update([
'password' => md5((string)$data['new_password']),
'update_time' => date('Y-m-d H:i:s'),
]);
Cache::delete($codeKey);
return json(['code' => 200, 'msg' => '密码重置成功']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '重置失败:' . $e->getMessage()]);
}
}
/**
* 获取极验3.0的id和key
* @return Json
*/
public function getGeetest3Infos()
{
// 定义你需要的 label 列表
$targetLabels = [
'geetest3ID',
'geetest3KEY'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select();
$this->logSuccess('站点设置管理', '查看极验3.0的id和key', [], $this->getAdminUserInfo());
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $legalInfos->toArray()
]);
}
/**
* 获取极验4.0的id和key
* @return Json
*/
public function getGeetest4Infos()
{
// 定义你需要的 label 列表
$targetLabels = [
'geetest4ID',
'geetest4KEY'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select()
->toArray();
// 转换为前端需要的格式
$data = [];
foreach ($legalInfos as $item) {
if ($item['label'] === 'geetest4ID') {
$data['captcha_id'] = $item['value'];
}
if ($item['label'] === 'geetest4KEY') {
$data['captcha_key'] = $item['value'];
}
}
$this->logSuccess('站点设置管理', '查看极验4.0的id和key', [], $this->getAdminUserInfo());
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $data
]);
}
/**
* 判断是否开启验证
* @return Json
*/
public function getOpenVerify()
{
// 定义你需要的 label 列表
$targetLabels = [
'openVerify',
'verifyModel'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $legalInfos->toArray()
]);
}
} }
+53 -6
View File
@@ -11,8 +11,8 @@ use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\SystemMenu; use app\model\SystemMenu;
use app\model\AdminUser; use app\model\System\AdminUser;
use app\model\AdminUserGroup; use app\model\System\AdminUserGroup;
class MenuController extends BaseController class MenuController extends BaseController
{ {
@@ -69,6 +69,10 @@ class MenuController extends BaseController
$userGroup = AdminUserGroup::where('id', $user['group_id']) $userGroup = AdminUserGroup::where('id', $user['group_id'])
->find(); ->find();
// 调试日志
error_log('getMenus - user group_id: ' . $user['group_id']);
error_log('getMenus - userGroup: ' . json_encode($userGroup));
// var_dump($userGroup); // var_dump($userGroup);
if (!$userGroup) { if (!$userGroup) {
@@ -81,8 +85,9 @@ class MenuController extends BaseController
// 解析权限数组 // 解析权限数组
$menuIds = []; $menuIds = [];
if (!empty($userGroup['rights'])) { if ($userGroup->rights) {
$menuIds = is_array($userGroup['rights']) ? $userGroup['rights'] : json_decode($userGroup['rights'], true); $decoded = json_decode($userGroup->rights, true);
$menuIds = is_array($decoded) ? $decoded : [];
} }
// var_dump($menuIds); // var_dump($menuIds);
@@ -95,11 +100,14 @@ class MenuController extends BaseController
]); ]);
} }
// 获取有权限的菜单及其所有父级菜单
$allMenuIds = $this->getMenuIdsWithParents($menuIds);
// 获取有权限的菜单 // 获取有权限的菜单
$menus = SystemMenu::where('delete_time', null) $menus = SystemMenu::where('delete_time', null)
->where('status', 1) ->where('status', 1)
->whereIn('id', $menuIds) ->whereIn('id', $allMenuIds)
->field('id,pid,title,path,component_path,icon,sort') ->field('id,pid,title,path,component_path,icon,sort,is_visible')
->order('sort', 'asc') ->order('sort', 'asc')
->select(); ->select();
@@ -253,6 +261,7 @@ class MenuController extends BaseController
'title|菜单名称' => 'require|max:50', 'title|菜单名称' => 'require|max:50',
'type|菜单类型' => 'require|in:1,2,3', 'type|菜单类型' => 'require|in:1,2,3',
'status|菜单状态' => 'require|in:0,1', 'status|菜单状态' => 'require|in:0,1',
'is_visible|是否显示' => 'require|in:0,1',
'sort|排序号' => 'integer', 'sort|排序号' => 'integer',
'path|路由路径' => 'max:200', 'path|路由路径' => 'max:200',
'icon|菜单图标' => 'max:100', 'icon|菜单图标' => 'max:100',
@@ -266,6 +275,7 @@ class MenuController extends BaseController
'title' => $data['title'], 'title' => $data['title'],
'type' => $data['type'], 'type' => $data['type'],
'status' => $data['status'] ?? 1, 'status' => $data['status'] ?? 1,
'is_visible' => $data['is_visible'] ?? 1,
'sort' => $data['sort'] ?? 0, 'sort' => $data['sort'] ?? 0,
'path' => $data['path'] ?? '', 'path' => $data['path'] ?? '',
'component_path' => $data['component_path'] ?? '', 'component_path' => $data['component_path'] ?? '',
@@ -321,6 +331,7 @@ class MenuController extends BaseController
'pid|上级菜单ID' => 'integer', 'pid|上级菜单ID' => 'integer',
'type|菜单类型' => 'require|in:1,2,3', 'type|菜单类型' => 'require|in:1,2,3',
'status|菜单状态' => 'require|in:0,1', 'status|菜单状态' => 'require|in:0,1',
'is_visible|是否显示' => 'require|in:0,1',
'sort|排序号' => 'integer', 'sort|排序号' => 'integer',
'path|路由路径' => 'max:200', 'path|路由路径' => 'max:200',
'icon|菜单图标' => 'max:100', 'icon|菜单图标' => 'max:100',
@@ -338,6 +349,7 @@ class MenuController extends BaseController
'icon' => $data['icon'] ?? null, 'icon' => $data['icon'] ?? null,
'sort' => $data['sort'] ?? 0, 'sort' => $data['sort'] ?? 0,
'status' => $data['status'], 'status' => $data['status'],
'is_visible' => $data['is_visible'],
'permission' => $data['permission'] ?? null, 'permission' => $data['permission'] ?? null,
'remark' => $data['remark'] ?? null, 'remark' => $data['remark'] ?? null,
'update_time' => date('Y-m-d H:i:s') 'update_time' => date('Y-m-d H:i:s')
@@ -484,6 +496,41 @@ class MenuController extends BaseController
} }
} }
/**
* 获取菜单ID及其所有父级菜单ID
* @param array $menuIds 菜单ID数组
* @return array 包含所有父级菜单的ID数组
*/
private function getMenuIdsWithParents(array $menuIds): array
{
if (empty($menuIds)) {
return [];
}
$allIds = $menuIds;
$toCheck = $menuIds;
while (!empty($toCheck)) {
// 查询这些菜单的父级
$parents = SystemMenu::whereIn('id', $toCheck)
->where('delete_time', null)
->column('pid');
// 过滤掉已经存在的和为0的父ID
$newParents = [];
foreach ($parents as $pid) {
if ($pid > 0 && !in_array($pid, $allIds)) {
$newParents[] = $pid;
$allIds[] = $pid;
}
}
$toCheck = $newParents;
}
return $allIds;
}
/** /**
* 构建菜单树形结构 * 构建菜单树形结构
* @param array $menus 菜单列表 * @param array $menus 菜单列表
@@ -12,7 +12,7 @@ use think\response\Json;
use app\model\SystemModuleCenter; use app\model\SystemModuleCenter;
use app\model\SystemModuleCategory; use app\model\SystemModuleCategory;
use app\model\AdminUser; use app\model\System\AdminUser;
class ModuleCenterController extends BaseController class ModuleCenterController extends BaseController
{ {
+126 -1
View File
@@ -7,6 +7,9 @@ namespace app\admin\controller;
use app\admin\BaseController; use app\admin\BaseController;
use think\response\Json; use think\response\Json;
use app\model\AdminModules; use app\model\AdminModules;
use app\model\System\AdminUserGroup;
use app\model\SystemMenu;
use Symfony\Component\VarDumper\VarDumper;
class ModulesController extends BaseController class ModulesController extends BaseController
{ {
@@ -43,13 +46,77 @@ class ModulesController extends BaseController
} }
} }
/**
* 获取租户模块列表
* @return Json
*/
public function getTenantList()
{
try {
$userInfo = $this->getAdminUserInfo();
$groupId = $userInfo['group_id'] ?? null;
$menuIds = [];
if ($groupId) {
$userGroup = AdminUserGroup::where('id', $groupId)->find();
if ($userGroup && $userGroup->rights) {
$rights = $userGroup->rights;
$decoded = json_decode($rights, true);
$menuIds = is_array($decoded) ? $decoded : [];
}
}
if (empty($menuIds)) {
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => [],
'total' => 0
]
]);
}
// 根据子菜单ID查询所有父级模块ID
$moduleIds = $this->getModuleIdsByMenuIds($menuIds);
$modules = AdminModules::where('delete_time', null)
->whereIn('id', $moduleIds)
->where('status', 1)
->where('is_show', 1)
->order('sort', 'asc')
->order('id', 'asc')
->select()
->toArray();
$this->logSuccess('模块管理', '获取模块列表', ['count' => count($modules)]);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $modules,
'total' => count($modules)
]
]);
} catch (\Exception $e) {
$this->logFail('模块管理', '获取模块列表', $e->getMessage());
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => []
]);
}
}
/** /**
* 获取模块详情 * 获取模块详情
* @return Json * @return Json
*/ */
public function getDetail(int $id) public function getDetail($id)
{ {
try { try {
$id = (int) $id;
$module = AdminModules::where('id', $id) $module = AdminModules::where('id', $id)
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -325,6 +392,64 @@ class ModulesController extends BaseController
} }
} }
/**
* 根据菜单ID列表获取对应的模块ID列表(包括父级模块)
* @param array $menuIds 菜单ID列表(子菜单)
* @return array 模块ID列表
*/
private function getModuleIdsByMenuIds(array $menuIds): array
{
if (empty($menuIds)) {
return [];
}
// 1. 获取所有子菜单及其父级菜单ID
$allMenuIds = $this->getMenuIdsWithParents($menuIds);
// 2. 获取所有匹配的模块(mid在allMenuIds中)
$moduleIds = AdminModules::where('delete_time', null)
->whereIn('mid', $allMenuIds)
->column('id');
// 去重并返回
return array_unique($moduleIds);
}
/**
* 获取菜单ID及其所有父级菜单ID
* @param array $menuIds 菜单ID数组
* @return array 包含所有父级菜单的ID数组
*/
private function getMenuIdsWithParents(array $menuIds): array
{
if (empty($menuIds)) {
return [];
}
$allIds = $menuIds;
$toCheck = $menuIds;
while (!empty($toCheck)) {
// 查询这些菜单的父级
$parents = SystemMenu::whereIn('id', $toCheck)
->where('delete_time', null)
->column('pid');
// 过滤掉已经存在的和为0的父ID
$newParents = [];
foreach ($parents as $pid) {
if ($pid > 0 && !in_array($pid, $allIds)) {
$newParents[] = $pid;
$allIds[] = $pid;
}
}
$toCheck = $newParents;
}
return $allIds;
}
/** /**
* 获取模块选择列表 * 获取模块选择列表
* @return Json * @return Json
@@ -7,8 +7,8 @@ namespace app\admin\controller\OperationLog;
use app\admin\BaseController; use app\admin\BaseController;
use think\facade\Request; use think\facade\Request;
use think\response\Json; use think\response\Json;
use app\model\OperationLog; use app\model\System\OperationLog;
use app\model\AdminUser; use app\model\System\AdminUser;
class OperationLogController extends BaseController class OperationLogController extends BaseController
{ {
@@ -28,7 +28,8 @@ class OperationLogController extends BaseController
$startTime = Request::param('startTime/s', ''); $startTime = Request::param('startTime/s', '');
$endTime = Request::param('endTime/s', ''); $endTime = Request::param('endTime/s', '');
$query = OperationLog::where('delete_time', null); $query = OperationLog::where('delete_time', null)
->where('tid', $this->getTenantId());
// 关键词搜索(用户姓名、URL // 关键词搜索(用户姓名、URL
if ($keyword) { if ($keyword) {
@@ -129,6 +130,7 @@ class OperationLogController extends BaseController
{ {
try { try {
$log = OperationLog::where('id', $id) $log = OperationLog::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -164,6 +166,7 @@ class OperationLogController extends BaseController
{ {
try { try {
$log = OperationLog::where('id', $id) $log = OperationLog::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
@@ -206,6 +209,7 @@ class OperationLogController extends BaseController
} }
OperationLog::whereIn('id', $ids) OperationLog::whereIn('id', $ids)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->delete(); ->delete();
@@ -230,11 +234,13 @@ class OperationLogController extends BaseController
try { try {
// 获取模块列表 // 获取模块列表
$modules = OperationLog::where('delete_time', null) $modules = OperationLog::where('delete_time', null)
->where('tid', $this->getTenantId())
->group('module') ->group('module')
->column('module'); ->column('module');
// 获取动作列表 // 获取动作列表
$actions = OperationLog::where('delete_time', null) $actions = OperationLog::where('delete_time', null)
->where('tid', $this->getTenantId())
->group('action') ->group('action')
->column('action'); ->column('action');
@@ -2,7 +2,7 @@
namespace app\admin\controller\OperationLog; namespace app\admin\controller\OperationLog;
use app\model\OperationLog; use app\model\System\OperationLog;
use think\facade\Request; use think\facade\Request;
use think\facade\Session; use think\facade\Session;
@@ -37,6 +37,7 @@ class OperationLogHelper
$userId = $userInfo['id'] ?? 0; $userId = $userInfo['id'] ?? 0;
$userAccount = $userInfo['account'] ?? ''; $userAccount = $userInfo['account'] ?? '';
$userName = $userInfo['name'] ?? ''; $userName = $userInfo['name'] ?? '';
$tid = $userInfo['tid'] ?? ($userInfo['tid'] ?? 0);
// 获取请求信息 // 获取请求信息
$method = Request::method(); $method = Request::method();
@@ -52,6 +53,7 @@ class OperationLogHelper
'user_id' => $userId, 'user_id' => $userId,
'user_account' => $userAccount, 'user_account' => $userAccount,
'user_name' => $userName, 'user_name' => $userName,
'tid' => $tid,
'module' => $module, 'module' => $module,
'action' => $action, 'action' => $action,
'method' => $method, 'method' => $method,
@@ -3,7 +3,7 @@
namespace app\admin\controller\OperationLog; namespace app\admin\controller\OperationLog;
use think\facade\Request; use think\facade\Request;
use app\model\OperationLog; use app\model\System\OperationLog;
use app\service\JwtService; use app\service\JwtService;
/** /**
@@ -33,6 +33,7 @@ class OperationLogger
$userId = $userInfo['id'] ?? 0; $userId = $userInfo['id'] ?? 0;
$userAccount = $userInfo['account'] ?? ''; $userAccount = $userInfo['account'] ?? '';
$userName = $userInfo['name'] ?? ''; $userName = $userInfo['name'] ?? '';
$tid = $userInfo['tid'] ?? ($userInfo['tid'] ?? 0);
if (empty($requestData)) { if (empty($requestData)) {
$requestData = Request::param(); $requestData = Request::param();
@@ -49,6 +50,7 @@ class OperationLogger
'user_id' => $userId, 'user_id' => $userId,
'user_account' => $userAccount, 'user_account' => $userAccount,
'user_name' => $userName, 'user_name' => $userName,
'tid' => $tid,
'module' => $module, 'module' => $module,
'action' => $action, 'action' => $action,
'method' => $method, 'method' => $method,
+304 -80
View File
@@ -8,24 +8,59 @@ use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
use think\facade\Db; use think\facade\Db;
use think\response\Json; use think\response\Json;
use app\model\System\SystemSiteSetting;
use app\model\System\SystemSiteSettings; use app\model\System\SystemSiteSettings;
use app\model\Tenant\Tenant;
class SiteSettingsController extends BaseController class SiteSettingsController extends BaseController
{ {
/** /**
* 获取所有站点设置列表 * 获取指定站点设置列表
* @return Json * @return Json
*/ */
public function getNormalInfos() public function getNormalInfos()
{ {
$siteSettings = SystemSiteSettings::where('delete_time', null) // 获取当前租户ID
->field('id, label, value, sort') $tid = $this->request->param('tid', 0, 'int');
->order('id', 'asc')
->select(); // 查询站点设置
$siteSetting = SystemSiteSetting::where('delete_time', null)
->where('tid', $tid)
->find();
// 如果没有数据,返回空值结构
if (!$siteSetting) {
$data = [
'sitename' => '',
'logo' => '',
'logow' => '',
'ico' => '',
'companyintroduction' => '',
'description' => '',
'copyright' => '',
'companyname' => '',
'icp' => ''
];
} else {
$data = [
'sitename' => $siteSetting->sitename ?? '',
'logo' => $siteSetting->logo ?? '',
'logow' => $siteSetting->logow ?? '',
'ico' => $siteSetting->ico ?? '',
'companyintroduction' => $siteSetting->companyintroduction ?? '',
'description' => $siteSetting->description ?? '',
'copyright' => $siteSetting->copyright ?? '',
'companyname' => $siteSetting->companyname ?? '',
'icp' => $siteSetting->icp ?? ''
];
}
$this->logSuccess('站点设置管理', '查看基本信息配置', [], $this->getAdminUserInfo());
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '获取成功', 'msg' => '获取成功',
'data' => $siteSettings->toArray() 'data' => $data
]); ]);
} }
@@ -36,65 +71,137 @@ class SiteSettingsController extends BaseController
public function saveNormalInfos() public function saveNormalInfos()
{ {
$params = $this->request->param(); $params = $this->request->param();
$rawData = isset($params['data']) ? json_decode($params['data'], true) : $params;
$tid = $params['tid'] ?? 0;
if (!isset($params['data']) || empty($params['data'])) { // 查找或创建记录
return json(['code' => 400, 'msg' => '数据不能为空', 'data' => null]); $siteSetting = SystemSiteSetting::where('delete_time', null)
->where('tid', $tid)
->find();
if (!$siteSetting) {
$siteSetting = new SystemSiteSetting();
$siteSetting->tid = $tid;
$siteSetting->create_time = date('Y-m-d H:i:s');
} }
$data = json_decode($params['data'], true); // 更新字段
if (!is_array($data)) { if (isset($rawData['sitename'])) {
return json(['code' => 400, 'msg' => '数据格式错误', 'data' => null]); $siteSetting->sitename = (string)$rawData['sitename'];
}
if (isset($rawData['companyintroduction'])) {
$siteSetting->companyintroduction = (string)$rawData['companyintroduction'];
}
if (isset($rawData['logo'])) {
$siteSetting->logo = (string)$rawData['logo'];
}
if (isset($rawData['logow'])) {
$siteSetting->logow = (string)$rawData['logow'];
}
if (isset($rawData['ico'])) {
$siteSetting->ico = (string)$rawData['ico'];
}
if (isset($rawData['description'])) {
$siteSetting->description = (string)$rawData['description'];
}
if (isset($rawData['copyright'])) {
$siteSetting->copyright = (string)$rawData['copyright'];
}
if (isset($rawData['companyname'])) {
$siteSetting->companyname = (string)$rawData['companyname'];
}
if (isset($rawData['icp'])) {
$siteSetting->icp = (string)$rawData['icp'];
} }
foreach ($data as $item) { $siteSetting->update_time = date('Y-m-d H:i:s');
if (!isset($item['label']) || !isset($item['value'])) { $siteSetting->save();
continue;
}
$label = $item['label']; $this->logSuccess('站点设置管理', '保存基本信息', ['tid' => $tid], $this->getAdminUserInfo());
$value = $item['value'] ?? '';
$setting = SystemSiteSettings::where('label', $label)->find(); return json(['code' => 200, 'msg' => '基本信息保存成功']);
if ($setting) { }
$setting->value = $value;
$setting->save();
} else {
SystemSiteSettings::create([
'label' => $label,
'value' => $value,
'create_time' => date('Y-m-d H:i:s')
]);
}
}
$allSettings = SystemSiteSettings::column('value', 'label'); /**
$allSettings['type'] = 'normal'; * 获取登录验证数据
* @return Json
*/
public function getVerifyInfos()
{
// 定义你需要的 label 列表
$targetLabels = [
'openVerify',
'verifyModel',
'geetest3ID',
'geetest3KEY',
'geetest4ID',
'geetest4KEY'
];
$labels = array_column($data, 'label'); $legalInfos = SystemSiteSettings::where('delete_time', null)
$userInfo = $this->getAdminUserInfo(); ->whereIn('label', $targetLabels) // 仅筛选指定的 label
$this->logSuccess('站点设置管理', '保存基本信息', ['labels' => $labels], $userInfo); ->field('label, value')
->select();
$this->logSuccess('站点设置管理', '查看登录验证数据', [], $this->getAdminUserInfo());
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '保存成功', 'msg' => '获取成功',
'data' => $allSettings 'data' => $legalInfos->toArray()
]); ]);
} }
/** /**
* 获取所有站点设置列表 * 保存登录验证数据
* @return Json
*/
public function saveVerifyInfos()
{
$params = $this->request->param();
$allowedLabels = ['openVerify', 'verifyModel', 'geetest3ID', 'geetest3KEY', 'geetest4ID', 'geetest4KEY'];
foreach ($allowedLabels as $label) {
if (isset($params[$label])) {
$val = $params[$label];
if (is_bool($val)) {
$val = $val ? '1' : '0';
}
SystemSiteSettings::where('label', $label)->update([
'value' => (string)$val
]);
}
}
$this->logSuccess('站点设置管理', '保存登录验证配置', $params, $this->getAdminUserInfo());
return json(['code' => 200, 'msg' => '保存成功']);
}
/**
* 获取法律声明和隐私条款
* @return Json * @return Json
*/ */
public function getLegalInfos() public function getLegalInfos()
{ {
$siteSettings = SystemSiteSettings::where('delete_time', null) // 定义你需要的 label 列表
->field('id, label, value, sort') $targetLabels = [
->order('id', 'asc') 'legalNotice',
'privacyTerms'
];
$legalInfos = SystemSiteSettings::where('delete_time', null)
->whereIn('label', $targetLabels) // 仅筛选指定的 label
->field('label, value')
->select(); ->select();
$this->logSuccess('站点设置管理', '查看法律声明和隐私条款', [], $this->getAdminUserInfo());
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '获取成功', 'msg' => '获取成功',
'data' => $siteSettings->toArray() 'data' => $legalInfos->toArray()
]); ]);
} }
@@ -105,58 +212,175 @@ class SiteSettingsController extends BaseController
public function saveLegalInfos() public function saveLegalInfos()
{ {
$params = $this->request->param(); $params = $this->request->param();
$rawData = isset($params['data']) ? json_decode($params['data'], true) : $params;
$allowedLabels = ['legalNotice', 'privacyTerms'];
if (!isset($params['data']) || empty($params['data'])) { foreach ($allowedLabels as $label) {
return json(['code' => 400, 'msg' => '数据不能为空', 'data' => null]); if (isset($rawData[$label])) {
} SystemSiteSettings::update(
['value' => (string)$rawData[$label]],
$data = json_decode($params['data'], true); ['label' => $label]
if (!is_array($data)) { );
return json(['code' => 400, 'msg' => '数据格式错误', 'data' => null]);
}
foreach ($data as $item) {
if (!isset($item['label']) || !isset($item['value'])) {
continue;
}
$label = $item['label'];
$value = $item['value'] ?? '';
$setting = SystemSiteSettings::where('label', $label)->find();
if ($setting) {
$setting->value = $value;
$setting->save();
} else {
SystemSiteSettings::create([
'label' => $label,
'value' => $value,
'create_time' => date('Y-m-d H:i:s')
]);
} }
} }
$this->logSuccess('站点设置管理', '保存法律声明和隐私条款', ['labels' => $allowedLabels], $this->getAdminUserInfo());
$allSettings = SystemSiteSettings::column('value', 'label'); return json(['code' => 200, 'msg' => '法律声明和隐私条款保存成功']);
$allSettings['type'] = 'legal_notice'; }
$labels = array_column($data, 'label'); /**
* 获取企业信息
* @return Json
*/
public function getCompanyInfos()
{
// 获取当前租户ID
$tid = $this->request->param('tid', 0, 'int');
$userId = Session::get('user_id', 0); // 查询租户信息
$userFromSession = Session::get('user', []); $tenant = Tenant::where('delete_time', null)
$userFromCache = Cache::get('userInfo_' . $userId, []); ->where('id', $tid)
$account = Session::get('account', ''); ->find();
$name = Session::get('name', '');
\think\facade\Log::record('SiteSettings Debug - user_id: ' . $userId . ', userFromSession: ' . json_encode($userFromSession) . ', userFromCache: ' . json_encode($userFromCache) . ', account: ' . $account . ', name: ' . $name); // 如果没有数据,返回空值结构
if (!$tenant) {
$data = [
'contact_phone' => '',
'contact_email' => '',
'address' => '',
'worktime' => ''
];
} else {
$data = [
'contact_phone' => $tenant->contact_phone ?? '',
'contact_email' => $tenant->contact_email ?? '',
'address' => $tenant->address ?? '',
'worktime' => $tenant->worktime ?? ''
];
}
$this->logSuccess('站点设置管理', '保存法律声明和隐私条款', ['labels' => $labels]); $this->logSuccess('站点设置管理', '查看企业信息', [], $this->getAdminUserInfo());
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '保存成功', 'msg' => '获取成功',
'data' => $allSettings 'data' => $data
]); ]);
} }
/**
* 保存企业信息
* @return Json
*/
public function saveCompanyInfos()
{
$params = $this->request->param();
$tid = $params['tid'] ?? 0;
// 查找租户记录
$tenant = Tenant::where('delete_time', null)
->where('id', $tid)
->find();
if (!$tenant) {
return json([
'code' => 404,
'msg' => '租户不存在'
]);
}
// 更新字段
if (isset($params['contact_phone'])) {
$tenant->contact_phone = (string)$params['contact_phone'];
}
if (isset($params['contact_email'])) {
$tenant->contact_email = (string)$params['contact_email'];
}
if (isset($params['address'])) {
$tenant->address = (string)$params['address'];
}
if (isset($params['worktime'])) {
$tenant->worktime = (string)$params['worktime'];
}
$tenant->save();
$this->logSuccess('站点设置管理', '保存企业信息', ['tid' => $tid], $this->getAdminUserInfo());
return json(['code' => 200, 'msg' => '企业信息保存成功']);
}
/**
* 获取企业SEO
* @return Json
*/
public function getCompanySeo()
{
// 获取当前租户ID
$tid = $this->getTenantId();
// 直接根据 ID 查询租户记录
$tenant = Tenant::where('delete_time', null)
->where('id', $tid)
->field('seoTitle, seoKeywords, seoDescription')
->find();
// 如果没有数据,返回默认空值
if (!$tenant) {
$data = [
'seoTitle' => '',
'seoKeywords' => '',
'seoDescription' => ''
];
} else {
// 直接读取模型对象的属性
$data = [
'seoTitle' => $tenant->seoTitle ?? '',
'seoKeywords' => $tenant->seoKeywords ?? '',
'seoDescription' => $tenant->seoDescription ?? ''
];
}
$this->logSuccess('站点设置管理', '查看企业SEO', ['tid' => $tid], $this->getAdminUserInfo());
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $data
]);
}
/**
* 保存企业SEO
* @return Json
*/
public function saveCompanySeo()
{
$params = $this->request->param();
$id = $this->getTenantId();
$tenant = Tenant::where('delete_time', null)
->where('id', $id)
->find();
if (!$tenant) {
return json(['code' => 404, 'msg' => '租户不存在']);
}
// 构建更新数据
$updateData = [];
if (isset($params['seoTitle'])) $updateData['seoTitle'] = (string)$params['seoTitle'];
if (isset($params['seoKeywords'])) $updateData['seoKeywords'] = (string)$params['seoKeywords'];
if (isset($params['seoDescription'])) $updateData['seoDescription'] = (string)$params['seoDescription'];
if (!empty($updateData)) {
$tenant->save($updateData);
}
$this->logSuccess('站点设置管理', '保存企业SEO', ['tid' => $id], $this->getAdminUserInfo());
return json(['code' => 200, 'msg' => '企业SEO保存成功']);
}
} }
@@ -1,6 +1,6 @@
<?php <?php
namespace app\admin\controller; namespace app\admin\controller\System;
use app\admin\BaseController; use app\admin\BaseController;
use think\facade\Filesystem; use think\facade\Filesystem;
@@ -8,8 +8,8 @@ use think\facade\Request;
use think\facade\Db; use think\facade\Db;
use think\Response; use think\Response;
use app\model\FilesCategory; use app\model\System\FilesCategory;
use app\model\Files; use app\model\System\Files;
class FileController extends BaseController class FileController extends BaseController
{ {
@@ -38,7 +38,8 @@ class FileController extends BaseController
$cate = Request::param('cate/d', 0); $cate = Request::param('cate/d', 0);
$keyword = Request::param('keyword/s', ''); $keyword = Request::param('keyword/s', '');
$query = Files::where('delete_time', null); $query = Files::where('delete_time', null)
->where('tid', $this->getTenantId());
if ($cate) { if ($cate) {
$query->where('cate', $cate); $query->where('cate', $cate);
@@ -75,11 +76,20 @@ class FileController extends BaseController
public function getUserCate() public function getUserCate()
{ {
try { try {
$cate = FilesCategory::where('delete_time', null)->field('id,name')->select(); $tid = $this->getTenantId();
// 只根据 tid 筛选分类
$cate = FilesCategory::where('delete_time', null)
->where('tid', $tid)
->field('id,name')
->select();
// 获取每个分类下的文件数量 // 获取每个分类下的文件数量
foreach ($cate as &$c) { foreach ($cate as &$c) {
$c['total'] = Files::where('cate', $c['id'])->where('delete_time', null)->count(); $c['total'] = Files::where('cate', $c['id'])
->where('tid', $tid)
->where('delete_time', null)
->count();
} }
return json([ return json([
@@ -101,6 +111,7 @@ class FileController extends BaseController
try { try {
$data = Request::param(); $data = Request::param();
$data['create_time'] = date('Y-m-d H:i:s'); $data['create_time'] = date('Y-m-d H:i:s');
$data['tid'] = $this->getTenantId();
$id = FilesCategory::insertGetId($data); $id = FilesCategory::insertGetId($data);
// 记录操作日志 // 记录操作日志
@@ -129,7 +140,9 @@ class FileController extends BaseController
{ {
try { try {
$data = Request::param(); $data = Request::param();
$result = FilesCategory::where('id', $id)->update(['name' => $data['name']]); $result = FilesCategory::where('id', $id)
->where('tid', $this->getTenantId())
->update(['name' => $data['name']]);
// 记录操作日志 // 记录操作日志
$this->logSuccess('文件管理', '重命名文件分组', ['id' => $id]); $this->logSuccess('文件管理', '重命名文件分组', ['id' => $id]);
return json([ return json([
@@ -150,7 +163,21 @@ class FileController extends BaseController
public function deleteFileCate($id) public function deleteFileCate($id)
{ {
try { try {
$result = FilesCategory::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]); // 检查分组里是否有关联文件
$fileCount = Files::where('cate', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->count();
if ($fileCount > 0) {
return json([
'code' => 400,
'msg' => '该分组下还有 ' . $fileCount . ' 个文件,请先删除分组内文件!'
]);
}
$result = FilesCategory::where('id', $id)
->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]);
// 记录操作日志 // 记录操作日志
$this->logSuccess('文件管理', '删除文件分组', ['id' => $id]); $this->logSuccess('文件管理', '删除文件分组', ['id' => $id]);
return json([ return json([
@@ -175,12 +202,16 @@ class FileController extends BaseController
$pageSize = Request::param('pageSize/d', 10); $pageSize = Request::param('pageSize/d', 10);
$keyword = Request::param('keyword/s', ''); $keyword = Request::param('keyword/s', '');
$query = Files::where('cate', $id)->where('delete_time', null); $query = Files::where('cate', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null);
if ($keyword) { if ($keyword) {
$query->whereLike('name', "%{$keyword}%"); $query->whereLike('name', "%{$keyword}%");
} }
// 获取总条数
$total = $query->count();
$list = $query->page($page, $pageSize) $list = $query->page($page, $pageSize)
->order('create_time', 'desc') ->order('create_time', 'desc')
->select(); ->select();
@@ -196,6 +227,7 @@ class FileController extends BaseController
'msg' => 'success', 'msg' => 'success',
'data' => [ 'data' => [
'list' => $list, 'list' => $list,
'total' => $total,
'page' => $page, 'page' => $page,
'pageSize' => $pageSize, 'pageSize' => $pageSize,
'categoryId' => $id 'categoryId' => $id
@@ -209,7 +241,7 @@ class FileController extends BaseController
} }
} }
// 上传文件 // 文件上传
public function uploadFile() public function uploadFile()
{ {
try { try {
@@ -230,7 +262,10 @@ class FileController extends BaseController
$fileMd5 = md5_file($file->getRealPath()); $fileMd5 = md5_file($file->getRealPath());
// 检查是否已存在相同文件 // 检查是否已存在相同文件
$existFile = Files::where('md5', $fileMd5)->where('delete_time', null)->find(); $existFile = Files::where('md5', $fileMd5)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if ($existFile) { if ($existFile) {
return json([ return json([
@@ -272,6 +307,7 @@ class FileController extends BaseController
'src' => $fileUrl, 'src' => $fileUrl,
'md5' => $fileMd5, 'md5' => $fileMd5,
'uploader' => $userId, 'uploader' => $userId,
'tid' => $this->getTenantId(),
'create_time' => date('Y-m-d H:i:s'), 'create_time' => date('Y-m-d H:i:s'),
]; ];
@@ -309,7 +345,10 @@ class FileController extends BaseController
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
$result = Files::where('id', $id)->where('delete_time', null)->update($data); $result = Files::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->update($data);
if ($result) { if ($result) {
// 记录操作日志 // 记录操作日志
@@ -329,7 +368,17 @@ class FileController extends BaseController
public function deleteFile($id) public function deleteFile($id)
{ {
try { try {
$result = Files::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]); // 先获取文件信息
$file = Files::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if (!$file) {
return json(['code' => 404, 'msg' => '文件不存在']);
}
$result = Files::where('id', $id)
->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($result) { if ($result) {
// 记录操作日志 // 记录操作日志
@@ -345,11 +394,49 @@ class FileController extends BaseController
} }
} }
//永久删除文件
public function deleteFilePermanently($id)
{
try {
// 先获取文件信息
$file = Files::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if (!$file) {
return json(['code' => 404, 'msg' => '文件不存在']);
}
// 删除服务器上的文件
if ($file['src']) {
$this->deleteFileFromServer($file['src']);
}
$result = Files::where('id', $id)
->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($result) {
// 记录操作日志
$this->logSuccess('文件管理', '永久删除文件', ['id' => $id]);
return json(['code' => 200, 'msg' => '永久删除成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '永久删除文件', $e->getMessage());
return json(['code' => 500, 'msg' => '永久删除失败: ' . $e->getMessage()]);
}
}
// 下载文件 // 下载文件
public function download($id) public function download($id)
{ {
try { try {
$file = Files::where('id', $id)->where('delete_time', null)->find(); $file = Files::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null)
->find();
if (!$file) { if (!$file) {
return json(['code' => 404, 'msg' => '文件不存在']); return json(['code' => 404, 'msg' => '文件不存在']);
@@ -374,7 +461,9 @@ class FileController extends BaseController
{ {
try { try {
$cate = Request::param('cate/d', 0); $cate = Request::param('cate/d', 0);
$result = Files::where('id', $id)->update(['cate' => $cate]); $result = Files::where('id', $id)
->where('tid', $this->getTenantId())
->update(['cate' => $cate]);
if ($result) { if ($result) {
// 记录操作日志 // 记录操作日志
$this->logSuccess('文件管理', '移动文件', ['id' => $id]); $this->logSuccess('文件管理', '移动文件', ['id' => $id]);
@@ -389,128 +478,120 @@ class FileController extends BaseController
} }
} }
//删除服务器上文件
private function deleteFileFromServer($fileUrl)
// 上传头像
public function uploadAvatar()
{ {
try { // 去掉开头的 /storage/,得到相对路径
$file = Request::file('file'); $relativePath = ltrim($fileUrl, '/');
if (!$file) { if (strpos($relativePath, 'storage/') === 0) {
return json(['code' => 400, 'msg' => '请选择要上传的文件']); $relativePath = substr($relativePath, 8); // 去掉 'storage/' 部分
} }
// 验证文件大小和类型 // 获取实际服务器路径
$maxSize = 50 * 1024 * 1024; // 50MB $fullPath = root_path() . 'public/storage/' . $relativePath;
$fileExt = strtolower($file->getOriginalExtension());
if ($file->getSize() > $maxSize) { if (file_exists($fullPath)) {
return json(['code' => 400, 'msg' => '文件大小不能超过50MB']); unlink($fullPath);
}
// 计算文件MD5
$fileMd5 = md5_file($file->getRealPath());
// 检查是否已存在相同文件
$existFile = Files::where('md5', $fileMd5)->where('delete_time', null)->find();
if ($existFile) {
// 检查物理文件是否存在
$existFilePath = public_path() . $existFile['src'];
if (!file_exists($existFilePath)) {
// 物理文件不存在,删除数据库记录,继续上传
Files::where('id', $existFile['id'])->delete();
} else {
return json([
'code' => 201,
'msg' => '文件已存在',
'data' => [
'url' => $existFile['src'],
'id' => $existFile['id'],
'name' => $existFile['name']
]
]);
}
}
// 确定文件类型
$fileType = 1; // 默认为文件
foreach ($this->allowedExtensions as $type => $extensions) {
if (in_array($fileExt, $extensions)) {
$fileType = $this->fileTypes[$type];
break;
}
}
$cate = Request::param('cate/d', 0);
// 生成按日期分类的目录结构
$datePath = date('Y/m/d');
$saveName = $datePath . '/' . uniqid() . '.' . $fileExt;
$fullPath = Filesystem::disk('public')->putFileAs('avatar', $file, $saveName);
$fileUrl = '/storage/' . str_replace('\\', '/', $fullPath);
// 获取当前登录用户ID
$userId = Request::middleware('user_id', '');
// 保存文件信息到数据库
$fileData = [
'name' => $file->getOriginalName(),
'type' => $fileType,
'cate' => $cate,
'size' => $file->getSize(),
'src' => $fileUrl,
'md5' => $fileMd5,
'uploader' => $userId,
'create_time' => date('Y-m-d H:i:s'),
];
$fileId = Files::insertGetId($fileData);
// 记录操作日志
$this->logSuccess('文件管理', '上传图片', ['id' => $fileId]);
return json([
'code' => 200,
'msg' => '上传成功',
'data' => [
'url' => $fileUrl,
'id' => $fileId,
'name' => $fileData['name']
]
]);
} catch (\Exception $e) {
// 记录失败日志
$this->logFail('文件管理', '上传图片', $e->getMessage());
return json([
'code' => 500,
'msg' => '上传失败: ' . $e->getMessage()
]);
} }
} }
// 更新头像 // 批量删除文件
public function updateAvatar($id) public function batchDeleteFiles()
{ {
try { try {
$data = Request::only(['name', 'cate']); $ids = Request::param('ids/a', []);
if (empty($data)) {
return json(['code' => 400, 'msg' => '无更新数据']); if (empty($ids)) {
return json(['code' => 400, 'msg' => '请选择要删除的文件']);
} }
$data['update_time'] = date('Y-m-d H:i:s'); foreach ($ids as $id) {
$file = Files::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if ($file && $file['src']) {
$this->deleteFileFromServer($file['src']);
}
}
$result = Files::where('id', $id)->where('delete_time', null)->update($data); $result = Files::where('id', 'in', $ids)
->where('tid', $this->getTenantId())
->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($result) { if ($result) {
// 记录操作日志 $this->logSuccess('文件管理', '批量删除文件', ['ids' => $ids]);
$this->logSuccess('文件管理', '更新头像', ['id' => $id]); return json(['code' => 200, 'msg' => '批量删除成功']);
return json(['code' => 200, 'msg' => '更新成功']);
} }
return json(['code' => 404, 'msg' => '头像不存在']); return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) { } catch (\Exception $e) {
// 记录失败日志 $this->logFail('文件管理', '批量删除文件', $e->getMessage());
$this->logFail('文件管理', '更新头像', $e->getMessage()); return json(['code' => 500, 'msg' => '批量删除失败: ' . $e->getMessage()]);
return json(['code' => 500, 'msg' => '更新失败: ' . $e->getMessage()]); }
}
// 批量彻底删除文件
public function batchDeleteFilesPermanently()
{
try {
$ids = Request::param('ids/a', []);
if (empty($ids)) {
return json(['code' => 400, 'msg' => '请选择要彻底删除的文件']);
}
foreach ($ids as $id) {
$file = Files::where('id', $id)
->where('tid', $this->getTenantId())
->find();
if ($file && $file['src']) {
$this->deleteFileFromServer($file['src']);
}
}
$result = Files::where('id', 'in', $ids)
->where('tid', $this->getTenantId())
->force(true)
->update(['delete_time' => date('Y-m-d H:i:s')])
->delete();
if ($result) {
$this->logSuccess('文件管理', '批量彻底删除文件', ['ids' => $ids]);
return json(['code' => 200, 'msg' => '批量彻底删除成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
$this->logFail('文件管理', '批量彻底删除文件', $e->getMessage());
return json(['code' => 500, 'msg' => '批量彻底删除失败: ' . $e->getMessage()]);
}
}
// 批量移动文件
public function batchMoveFiles()
{
try {
$ids = Request::param('ids/a', []);
$cate = Request::param('cate/d', 0);
if (empty($ids)) {
return json(['code' => 400, 'msg' => '请选择要移动的文件']);
}
$result = Files::where('id', 'in', $ids)
->where('tid', $this->getTenantId())
->update(['cate' => $cate]);
if ($result) {
$this->logSuccess('文件管理', '批量移动文件', ['ids' => $ids, 'cate' => $cate]);
return json(['code' => 200, 'msg' => '批量移动成功']);
}
return json(['code' => 404, 'msg' => '文件不存在']);
} catch (\Exception $e) {
$this->logFail('文件管理', '批量移动文件', $e->getMessage());
return json(['code' => 500, 'msg' => '批量移动失败: ' . $e->getMessage()]);
} }
} }
} }
@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace app\admin\controller; namespace app\admin\controller\System;
use app\admin\BaseController; use app\admin\BaseController;
use think\exception\ValidateException; use think\exception\ValidateException;
@@ -10,8 +10,8 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use app\model\AdminUser; use app\model\System\AdminUser;
use app\model\AdminUserGroup; use app\model\System\AdminUserGroup;
class RoleController extends BaseController class RoleController extends BaseController
{ {
@@ -21,7 +21,11 @@ class RoleController extends BaseController
*/ */
public function getAllRoles() public function getAllRoles()
{ {
$tid = $this->getTenantId();
$roles = AdminUserGroup::where('delete_time', null) $roles = AdminUserGroup::where('delete_time', null)
->where(function ($query) use ($tid) {
$query->where('tid', $tid)->whereOr('tid', 0);
})
->order('id', 'asc') ->order('id', 'asc')
->select(); ->select();
return json([ return json([
@@ -39,6 +43,9 @@ class RoleController extends BaseController
public function getRoleById(int $id) public function getRoleById(int $id)
{ {
$role = AdminUserGroup::where('id', $id) $role = AdminUserGroup::where('id', $id)
->where(function ($query) {
$query->where('tid', $this->getTenantId())->whereOr('tid', 0);
})
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if (!$role) { if (!$role) {
@@ -73,6 +80,7 @@ class RoleController extends BaseController
// 检查角色名称是否已存在 // 检查角色名称是否已存在
$exists = AdminUserGroup::where('name', $data['name']) $exists = AdminUserGroup::where('name', $data['name'])
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if ($exists) { if ($exists) {
@@ -85,6 +93,7 @@ class RoleController extends BaseController
// 准备数据 // 准备数据
$roleData = [ $roleData = [
'name' => $data['name'], 'name' => $data['name'],
'tid' => $this->getTenantId(),
'status' => $data['status'] ?? 1, 'status' => $data['status'] ?? 1,
'rights' => !empty($data['rights']) ? json_encode($data['rights']) : null, 'rights' => !empty($data['rights']) ? json_encode($data['rights']) : null,
'create_time' => date('Y-m-d H:i:s'), 'create_time' => date('Y-m-d H:i:s'),
@@ -136,8 +145,11 @@ class RoleController extends BaseController
'rights|权限' => 'array' 'rights|权限' => 'array'
]); ]);
// 查找角色 // 查找角色(验证tid,tid为0的系统角色也可编辑)
$role = AdminUserGroup::where('id', $id) $role = AdminUserGroup::where('id', $id)
->where(function ($query) {
$query->where('tid', $this->getTenantId())->whereOr('tid', 0);
})
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if (!$role) { if (!$role) {
@@ -147,9 +159,12 @@ class RoleController extends BaseController
]); ]);
} }
// 检查角色名称是否已被其他角色使用 // 检查角色名称是否已被其他角色使用tid为0或当前用户tid的角色)
$exists = AdminUserGroup::where('name', $data['name']) $exists = AdminUserGroup::where('name', $data['name'])
->where('id', '<>', $id) ->where('id', '<>', $id)
->where(function ($query) {
$query->where('tid', $this->getTenantId())->whereOr('tid', 0);
})
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if ($exists) { if ($exists) {
@@ -160,18 +175,23 @@ class RoleController extends BaseController
} }
// 更新数据 // 更新数据
$role->name = $data['name']; $updateData = [
$role->status = $data['status'] ?? 1; 'name' => $data['name'],
$role->rights = !empty($data['rights']) ? json_encode($data['rights']) : null; 'status' => $data['status'] ?? 1,
$role->update_time = date('Y-m-d H:i:s'); 'rights' => !empty($data['rights']) ? json_encode($data['rights']) : null,
$role->save(); 'update_time' => date('Y-m-d H:i:s')
];
AdminUserGroup::where('id', $id)->update($updateData);
// 重新查询获取最新数据
$updatedRole = AdminUserGroup::where('id', $id)->find();
// 记录操作日志 // 记录操作日志
$this->logSuccess('角色管理', '更新角色', ['id' => $id]); $this->logSuccess('角色管理', '更新角色', ['id' => $id]);
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => '更新成功', 'msg' => '更新成功',
'data' => $role->toArray() 'data' => $updatedRole->toArray()
]); ]);
} catch (ValidateException $e) { } catch (ValidateException $e) {
// 记录失败日志 // 记录失败日志
@@ -206,8 +226,9 @@ class RoleController extends BaseController
]); ]);
} }
// 查找角色 // 查找角色(验证tid
$role = AdminUserGroup::where('id', $id) $role = AdminUserGroup::where('id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->find(); ->find();
if (!$role) { if (!$role) {
@@ -219,6 +240,7 @@ class RoleController extends BaseController
// 检查是否有用户正在使用该角色 // 检查是否有用户正在使用该角色
$userCount = AdminUser::where('group_id', $id) $userCount = AdminUser::where('group_id', $id)
->where('tid', $this->getTenantId())
->where('delete_time', null) ->where('delete_time', null)
->count(); ->count();
if ($userCount > 0) { if ($userCount > 0) {
@@ -0,0 +1,676 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\System;
use app\admin\BaseController;
use app\model\System\SystemSiteSettings;
use think\facade\Db;
use think\facade\Log;
use think\db\exception\DbException;
use think\exception\ValidateException;
use think\facade\Request;
class SmsController extends BaseController
{
private function getSiteSettingValue(string $label): string
{
$row = SystemSiteSettings::where('label', $label)->order('id', 'asc')->find();
if (!$row) {
return '';
}
return (string)$row['value'];
}
private function upsertSiteSetting(string $label, string $value): void
{
$row = SystemSiteSettings::where('label', $label)->order('id', 'asc')->find();
if ($row) {
SystemSiteSettings::update(['value' => $value], ['id' => $row['id']]);
return;
}
$model = new SystemSiteSettings();
$model->label = $label;
$model->value = $value;
$model->sort = 0;
$model->save();
}
/**
* 获取短信网关配置
*/
public function getSmsInfo()
{
try {
$backendUrl = $this->getSiteSettingValue('backendUrl');
$apiKey = $this->getSiteSettingValue('apiKey');
// 前端约定返回 data[0],字段名兼容 backend_url/api_key 与 backendUrl/apiKey
$data = [[
'backend_url' => $backendUrl,
'api_key' => $apiKey,
'backendUrl' => $backendUrl,
'apiKey' => $apiKey,
]];
$this->logSuccess('短信管理', '获取短信网关配置', ['data' => $data]);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $data,
]);
} catch (DbException $e) {
$this->logFail('短信管理', '获取短信网关配置', $e->getMessage());
return json([
'code' => 500,
'msg' => '获取失败:' . $e->getMessage(),
'data' => [],
]);
}
}
/**
* 编辑短信网关配置(本表设计为单条配置)
*/
public function editSmsInfo()
{
try {
$raw = $this->request->post();
$data = [
'backend_url' => $raw['backendUrl'] ?? $raw['backend_url'] ?? '',
'api_key' => $raw['apiKey'] ?? $raw['api_key'] ?? '',
];
$this->validate($data, [
'backend_url|短信网关地址' => 'require|max:255',
'api_key|短信网关 API KEY' => 'require|max:255',
]);
$this->upsertSiteSetting('backendUrl', (string)$data['backend_url']);
$this->upsertSiteSetting('apiKey', (string)$data['api_key']);
$updated = [[
'backend_url' => $this->getSiteSettingValue('backendUrl'),
'api_key' => $this->getSiteSettingValue('apiKey'),
]];
$this->logSuccess('短信管理', '更新短信网关配置', ['data' => $updated]);
return json([
'code' => 200,
'msg' => '更新成功',
'data' => $updated,
]);
} catch (ValidateException $e) {
$this->logFail('短信管理', '更新短信网关配置', $e->getMessage());
return json([
'code' => 400,
'msg' => $e->getError(),
]);
} catch (DbException $e) {
$this->logFail('短信管理', '更新短信网关配置', $e->getMessage());
return json([
'code' => 500,
'msg' => '更新失败:' . $e->getMessage(),
]);
}
}
/**
* 后台发起“短信测试”:入队一条任务,等待 Android 网关轮询取走并发送
*/
public function sendTestSms()
{
try {
$raw = $this->request->post();
$phone = trim((string)($raw['phone'] ?? $raw['testPhone'] ?? ''));
$content = trim((string)($raw['content'] ?? ''));
if (empty($phone)) {
return json(['code' => 400, 'msg' => '缺少测试手机号']);
}
// Android 网关文档:国际格式 +8613712345678
if (!preg_match('/^\+\d{6,15}$/', $phone)) {
return json(['code' => 400, 'msg' => '请使用国际格式手机号(以 + 开头,后为数字)']);
}
// 获取当前网关配置(来自 mete_system_site_settings label/value
$apiKey = $this->getSiteSettingValue('apiKey');
$backendUrl = $this->getSiteSettingValue('backendUrl');
if (empty($apiKey)) {
return json(['code' => 400, 'msg' => '请先配置短信网关 API KEY']);
}
if (empty($backendUrl)) {
return json(['code' => 400, 'msg' => '请先配置短信网关地址 backendUrl']);
}
$code = (string)random_int(100000, 999999);
if ($content === '') {
$content = '短信测试验证码:' . $code;
}
// 兼容:调用 Uniapp/sms 后端入队发送任务
$enqueueUrl = rtrim($backendUrl, '/') . '/api/v1/business/outbound-tasks';
$enqueuePayload = [
'phone' => $phone,
'content' => $content,
];
$enqueueHeaders = [
'X-Api-Key: ' . (string)$apiKey,
'Content-Type: application/json; charset=utf-8',
'Accept: application/json',
];
Log::write('[sms_enqueue] url=' . $enqueueUrl . ' phone=' . $phone);
$enqueueResp = $this->postJson($enqueueUrl, $enqueuePayload, $enqueueHeaders);
if (empty($enqueueResp['ok'])) {
Log::write('[sms_enqueue] failed http_status=' . ($enqueueResp['status'] ?? '') . ' body=' . ($enqueueResp['body'] ?? ''));
return json([
'code' => 500,
'msg' => '短信网关入队失败:' . ($enqueueResp['error'] ?? 'unknown'),
]);
}
$smsGatewayTaskId = (string)($enqueueResp['json']['taskId'] ?? '');
Log::write('[sms_enqueue] success taskId=' . $smsGatewayTaskId);
$enqueueDebug = [
'enqueueOk' => (bool)($enqueueResp['ok'] ?? false),
'enqueueStatus' => $enqueueResp['status'] ?? null,
'enqueueJsonKeys' => array_keys((array)($enqueueResp['json'] ?? [])),
'gatewayTaskId' => $smsGatewayTaskId,
];
// 同步一条本地记录(用于前端列表展示/对账)
$now = date('Y-m-d H:i:s');
$taskId = Db::name('mete_system_sms_tasks')->insertGetId([
'api_key' => (string)$apiKey,
'tid' => $this->getTenantId(),
'phone' => $phone,
'content' => $content,
'status' => 0, // pending(本地不自动同步网关状态)
'code' => $code, // 预期验证码(用于对账/展示)
'create_time' => $now,
'update_time' => $now,
]);
$this->logSuccess('短信管理', '发送测试短信(调用网关入队)', [
'to' => $phone,
'local_task_id' => $taskId ?? null,
'gateway_task_id' => $smsGatewayTaskId,
]);
return json([
'code' => 200,
'msg' => '测试短信任务已入队,等待网关发送',
'data' => [
'taskId' => $smsGatewayTaskId ?: ($taskId ?? null),
'code' => $code,
],
'debug' => empty($smsGatewayTaskId) ? $enqueueDebug : null,
]);
} catch (ValidateException $e) {
$this->logFail('短信管理', '发送测试短信', $e->getMessage());
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
$this->logFail('短信管理', '发送测试短信', $e->getMessage());
return json(['code' => 500, 'msg' => '发送失败:' . $e->getMessage()]);
}
}
/**
* 简单 JSON POST(无 curl 扩展时使用)
* @return array{ok:bool,status?:int,body?:string,error?:string,json?:array}
*/
private function postJson(string $url, array $payload, array $headers): array
{
$body = json_encode($payload, JSON_UNESCAPED_UNICODE);
if ($body === false) {
return ['ok' => false, 'error' => 'json_encode failed'];
}
$headerStr = implode("\r\n", $headers);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => $headerStr,
'content' => $body,
'timeout' => 10,
],
]);
$respBody = @file_get_contents($url, false, $context);
$status = 0;
if (!empty($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $line) {
if (preg_match('#^HTTP/\\S+\\s+(\\d+)#', $line, $m)) {
$status = (int)$m[1];
break;
}
}
}
if ($respBody === false) {
$last = error_get_last();
$errMsg = $last['message'] ?? 'http request failed';
return ['ok' => false, 'status' => $status, 'error' => (string)$errMsg];
}
$decoded = null;
$decoded = json_decode((string)$respBody, true);
if (!is_array($decoded)) {
$decoded = [];
}
$ok = $status >= 200 && $status < 300;
return [
'ok' => $ok,
'status' => $status,
'body' => (string)$respBody,
'json' => $decoded,
];
}
/**
* 简单 JSON GET(无 curl 扩展时使用)
* @return array{ok:bool,status?:int,body?:string,error?:string,json?:array}
*/
private function httpGetJson(string $url, array $headers): array
{
$headerStr = implode("\r\n", $headers);
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => $headerStr,
'timeout' => 10,
// 对 4xx/5xx 也返回响应体,便于排障
'ignore_errors' => true,
],
]);
$respBody = @file_get_contents($url, false, $context);
$status = 0;
if (!empty($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $line) {
if (preg_match('#^HTTP/\\S+\\s+(\\d+)#', $line, $m)) {
$status = (int)$m[1];
break;
}
}
}
if ($respBody === false) {
$last = error_get_last();
$errMsg = $last['message'] ?? 'http request failed';
return ['ok' => false, 'status' => $status, 'error' => (string)$errMsg];
}
$decoded = json_decode((string)$respBody, true);
if (!is_array($decoded)) {
$decoded = [];
}
$ok = $status >= 200 && $status < 300;
return [
'ok' => $ok,
'status' => $status,
'body' => (string)$respBody,
'json' => $decoded,
];
}
/**
* Android 网关轮询:获取待发送任务
*
* 约定:
* - apiKey 通过 header `X-API-KEY` / `X-SMS-GATEWAY-API-KEY` 或 body/query `apiKey`
*/
public function gatewayPoll()
{
$apiKey = $this->getApiKeyFromRequest();
if (empty($apiKey)) {
Log::write('[sms_gateway_poll] missing apiKey');
return json(['code' => 401, 'msg' => '缺少 apiKey']);
}
// 不强制要求 mete_system_site_settings(apiKey) 与网关入参完全一致;
// 直接按任务表的 api_key 匹配,提高兼容性与排障速度。
$apiKeySetting = $this->getSiteSettingValue('apiKey');
if (empty($apiKeySetting) || (string)$apiKeySetting !== (string)$apiKey) {
Log::write('[sms_gateway_poll] apiKey differs from setting; setting=' . $this->maskKey($apiKeySetting) . ' req=' . $this->maskKey($apiKey));
}
$task = Db::name('mete_system_sms_tasks')
->where('api_key', (string)$apiKey)
->where('status', 0)
->where('delete_time', null)
->order('id', 'asc')
->find();
// 没有任务
if (!$task) {
Log::write('[sms_gateway_poll] no task for apiKey=' . $this->maskKey($apiKey));
return json([
'code' => 200,
'msg' => 'no task',
'data' => null,
]);
}
// 标记为处理中,避免被重复领取
Db::name('mete_system_sms_tasks')->where('id', (int)$task['id'])->update([
'status' => 1,
'update_time' => date('Y-m-d H:i:s'),
]);
Log::write('[sms_gateway_poll] send task id=' . (int)$task['id'] . ' phone=' . (string)$task['phone']);
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'taskId' => $task['id'],
'task_id' => $task['id'],
'phone' => $task['phone'],
'content' => $task['content'],
'smsContent' => $task['content'],
],
]);
}
/**
* Android 网关上报:上传实际收到的短信内容并解析验证码
*/
public function gatewayReport()
{
try {
$raw = $this->request->post();
$apiKey = $this->getApiKeyFromRequest();
$taskId = $raw['taskId'] ?? $raw['task_id'] ?? $raw['id'] ?? null;
$smsContent = trim((string)($raw['smsContent'] ?? $raw['sms_content'] ?? $raw['content'] ?? $raw['message'] ?? $raw['sms'] ?? ''));
$status = $raw['status'] ?? $raw['result'] ?? $raw['state'] ?? null; // success/failed/int
if (empty($apiKey)) {
return json(['code' => 401, 'msg' => '缺少 apiKey']);
}
if (empty($taskId)) {
return json(['code' => 400, 'msg' => '缺少 taskId']);
}
if ($smsContent === '') {
return json(['code' => 400, 'msg' => '缺少 smsContent']);
}
$task = Db::name('mete_system_sms_tasks')
->where('id', (int)$taskId)
->where('api_key', (string)$apiKey)
->where('delete_time', null)
->find();
if (!$task) {
Log::write('[sms_gateway_report] task not found; taskId=' . (int)$taskId . ' apiKey=' . $this->maskKey($apiKey));
return json(['code' => 404, 'msg' => '任务不存在或 apiKey 不匹配']);
}
Log::write('[sms_gateway_report] received taskId=' . (int)$taskId . ' status=' . (string)($status ?? ''));
// 从短信内容里抓取验证码(通用策略:4-6 位数字)
$parsedCode = '';
if (preg_match('/(\d{4,6})/', $smsContent, $m)) {
$parsedCode = (string)$m[1];
}
$update = [
'report_raw' => $smsContent,
'code' => $parsedCode !== '' ? $parsedCode : (string)$task['code'],
'update_time' => date('Y-m-d H:i:s'),
];
if ($status === 'failed' || $status === 'error' || $status === 2) {
$update['status'] = 2;
} elseif ($status === 'success' || $status === 'reported' || $status === 3 || $status === 1) {
// success => reported/done
$update['status'] = 3;
} else {
// 默认当做已上报
$update['status'] = 3;
}
Db::name('mete_system_sms_tasks')->where('id', (int)$task['id'])->update($update);
$this->logSuccess('短信管理', '网关上报短信(解析验证码)', [
'task_id' => $task['id'],
'parsed_code' => $update['code'],
]);
return json(['code' => 200, 'msg' => '上报成功']);
} catch (DbException $e) {
$this->logFail('短信管理', '网关上报短信', $e->getMessage());
return json(['code' => 500, 'msg' => '服务器错误:' . $e->getMessage()]);
} catch (\Throwable $e) {
$this->logFail('短信管理', '网关上报短信', $e->getMessage());
return json(['code' => 500, 'msg' => '服务器错误:' . $e->getMessage()]);
}
}
/**
* 管理员:获取短信任务列表(租户隔离)
*
* 支持参数(可选):
* - status: 任务状态
* - phone: 手机号关键词
*/
public function getSmsTaskList()
{
$backendUrl = $this->getSiteSettingValue('backendUrl');
$apiKey = $this->getSiteSettingValue('apiKey');
if (empty($backendUrl) || empty($apiKey)) {
return json([
'code' => 400,
'msg' => '短信网关配置未完成(backendUrl/apiKey 缺失)',
'list' => [],
]);
}
$status = (string)Request::param('status', '');
$phoneKeyword = trim((string)Request::param('phone', ''));
$limit = (int)Request::param('limit', 50, 'int');
$limit = $limit > 0 ? min($limit, 200) : 50;
// 前端 status: 0/1/2/3 -> uniapp backend status: pending/sending/failed/success
$statusMap = [
'0' => 'pending',
'1' => 'sending',
'2' => 'failed',
'3' => 'success',
];
$uniStatus = $status !== '' && isset($statusMap[$status]) ? $statusMap[$status] : '';
$url = rtrim($backendUrl, '/') . '/api/v1/business/outbound-tasks?limit=' . $limit;
if ($uniStatus !== '') {
$url .= '&status=' . urlencode($uniStatus);
}
if ($phoneKeyword !== '') {
$url .= '&phone=' . urlencode($phoneKeyword);
}
$headers = [
'X-Api-Key: ' . (string)$apiKey,
'Accept: application/json',
];
$resp = $this->httpGetJson($url, $headers);
if (empty($resp['ok'])) {
// 兼容:如果 uniapp 后端还未部署新增查询接口,则 fallback 到本地队列表(只能看到“已入队”,无法看到发送成功/失败)
$fallback = [];
try {
$fWhere = [
['api_key', '=', (string)$apiKey],
['delete_time', '=', null],
];
if ($phoneKeyword !== '') {
$fWhere[] = ['phone', 'like', '%' . $phoneKeyword . '%'];
}
// 前端 status=0/1/2/3,局部队列表字段同样使用数字状态
if ($status !== '') {
$fWhere[] = ['status', '=', (int)$status];
}
$fallback = Db::name('mete_system_sms_tasks')
->where($fWhere)
->order('id', 'desc')
->limit($limit)
->select()
->toArray();
} catch (\Throwable $e) {
$fallback = [];
}
return json([
'code' => 200,
'msg' => 'uniapp 查询任务接口不可用,已使用本地队列表(仅展示入队状态)',
'detail' => [
'http_status' => $resp['status'] ?? null,
'body_preview' => isset($resp['body']) ? substr((string)$resp['body'], 0, 300) : '',
],
'list' => $fallback,
]);
}
$tasks = (array)($resp['json']['tasks'] ?? []);
$statusNumMap = [
'pending' => 0,
'sending' => 1,
'failed' => 2,
'success' => 3,
];
$list = array_map(function ($t) use ($statusNumMap) {
$content = (string)($t['content'] ?? '');
$parsedCode = '';
if (preg_match('/(\d{4,6})/', $content, $m)) {
$parsedCode = (string)$m[1];
}
$statusStr = (string)($t['status'] ?? '');
return [
'id' => $t['taskId'] ?? '',
'phone' => $t['phone'] ?? '',
'content' => $content,
'code' => $parsedCode,
'status' => $statusNumMap[$statusStr] ?? 0,
'create_time' => isset($t['createdAt']) ? date('Y-m-d H:i:s', (int)($t['createdAt'] / 1000)) : '',
'update_time' => isset($t['updatedAt']) ? date('Y-m-d H:i:s', (int)($t['updatedAt'] / 1000)) : '',
];
}, $tasks);
return json([
'code' => 200,
'msg' => 'success',
'list' => $list,
]);
}
/**
* 管理员:编辑短信任务(租户隔离)
*
* body:
* - status
* - code
* - report_raw
*/
public function editSmsTask($id = null)
{
try {
$tid = $this->getTenantId();
$id = $this->request->param('id', $id);
$data = Request::only(['status', 'code', 'report_raw']);
if (empty($id)) {
return json(['code' => 400, 'msg' => '任务ID不能为空']);
}
if (!isset($data['status'])) {
return json(['code' => 400, 'msg' => '状态不能为空']);
}
$this->validate($data, [
'status|状态' => 'integer',
'code|验证码' => 'max:20',
'report_raw|上报内容' => 'max:100000',
]);
$task = Db::name('mete_system_sms_tasks')
->where('id', (int)$id)
->where('tid', (int)$tid)
->find();
if (!$task) {
return json(['code' => 404, 'msg' => '任务不存在或无权限']);
}
$update = [
'status' => (int)$data['status'],
'code' => (string)($data['code'] ?? ''),
'report_raw' => (string)($data['report_raw'] ?? ''),
'update_time' => date('Y-m-d H:i:s'),
];
Db::name('mete_system_sms_tasks')
->where('id', (int)$id)
->where('tid', (int)$tid)
->update($update);
return json(['code' => 200, 'msg' => '编辑成功']);
} catch (ValidateException $e) {
return json(['code' => 400, 'msg' => $e->getError()]);
} catch (\Throwable $e) {
return json(['code' => 500, 'msg' => '编辑失败:' . $e->getMessage()]);
}
}
private function getApiKeyFromRequest(): string
{
$req = Request::instance();
$apiKey = (string)$req->header('X-SMS-GATEWAY-API-KEY', '');
if ($apiKey !== '') {
return $apiKey;
}
$apiKey = (string)$req->header('X-API-KEY', '');
if ($apiKey !== '') {
return $apiKey;
}
$apiKey = (string)$req->header('X-SMS-API-KEY', '');
if ($apiKey !== '') {
return $apiKey;
}
$apiKey = (string)$req->header('Authorization', '');
if ($apiKey !== '' && preg_match('/Bearer\\s+(.+)/i', $apiKey, $m)) {
return (string)$m[1];
}
// body/query
$apiKey = (string)($req->post('apiKey') ?? $req->post('api_key') ?? $req->param('apiKey', '') ?? $req->param('api_key', ''));
return $apiKey;
}
private function maskKey(string $key): string
{
$key = (string)$key;
if ($key === '') return '';
if (strlen($key) <= 6) return '***';
return substr($key, 0, 3) . '***' . substr($key, -3);
}
}
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace app\admin\controller\Tenant;
use app\admin\BaseController;
use think\exception\ValidateException;
use think\facade\Db;
use think\facade\Session;
use think\response\Json;
use think\db\exception\DbException;
use think\Request;
use app\model\Tenant\Tenant;
use app\model\System\AdminUser;
use app\model\Template\TemplateSiteConfig;
class TenantController extends BaseController
{
/**
* 获取租户列表
*/
public function getTenant(Request $request)
{
// 获取参数
$page = $request->param('page', 1, 'int');
$pageSize = $request->param('pageSize', 10, 'int');
$tenantName = $request->param('tenant_name', '');
$tenantCode = $request->param('tenant_code', '');
$contactPerson = $request->param('contact_person', '');
$contactPhone = $request->param('contact_phone', '');
$where = [['delete_time', '=', null]];
// 动态增加模糊搜索条件
if ($tenantName) $where[] = ['tenant_name', 'like', "%$tenantName%"];
if ($tenantCode) $where[] = ['tenant_code', 'like', "%$tenantCode%"];
if ($contactPerson) $where[] = ['contact_person', 'like', "%$contactPerson%"];
if ($contactPhone) $where[] = ['contact_phone', 'like', "%$contactPhone%"];
$list = Tenant::where($where)->page($page, $pageSize)->select()->toArray();
$total = Tenant::where($where)->count();
return json(['code' => 200, 'data' => ['list' => $list, 'total' => $total]]);
}
/**
* 获取租户详情
*/
public function getTenantDetail($id)
{
$detail = Tenant::where('id', $id)->where('delete_time', null)->find()->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $detail
]);
}
/**
* 创建租户
*/
public function createTenant()
{
$data = $this->request->post();
$tenant = Tenant::create($data);
if ($tenant) {
// 创建租户默认数据
$this->createTenantDefaultData($tenant->id);
return json([
'code' => 200,
'msg' => '创建成功',
'data' => $tenant
]);
} else {
return json([
'code' => 500,
'msg' => '创建失败',
]);
}
}
/**
* 创建租户默认数据
* @param int $tenantId 租户ID
*/
private function createTenantDefaultData(int $tenantId)
{
$now = date('Y-m-d H:i:s');
// 创建租户默认模板配置
TemplateSiteConfig::create([
'tid' => $tenantId,
'key' => 'current_theme',
'value' => 'default',
'create_time' => $now,
'update_time' => $now
]);
}
/**
* 编辑租户
*/
public function editTenant($id)
{
$data = $this->request->post();
$tenant = Tenant::where('id', $id)->update($data);
if ($tenant) {
return json([
'code' => 200,
'msg' => '编辑成功',
'data' => $tenant
]);
} else {
return json([
'code' => 500,
'msg' => '编辑失败',
]);
}
}
/**
* 删除租户
*/
public function deleteTenant($id)
{
$tenant = Tenant::where('id', $id)->update(['delete_time' => date('Y-m-d H:i:s')]);
if ($tenant) {
return json([
'code' => 200,
'msg' => '删除成功',
]);
} else {
return json([
'code' => 500,
'msg' => '删除失败',
]);
}
}
/**
* 查询租户编码重复(不筛选删除数据)
*/
public function findTenantCode(Request $request)
{
$tenantCode = $request->param('tenant_code', '');
if (!$tenantCode) {
return json(['code' => 400, 'msg' => '缺少编码参数']);
}
// 直接去表里查,只要有一条一样的就返回重复
$hasOne = Tenant::where('tenant_code', $tenantCode)->find();
if ($hasOne) {
return json([
'code' => 201,
'msg' => '编码已存在',
'data' => $hasOne
]);
}
return json([
'code' => 200,
'msg' => '编码可用'
]);
}
}
+126 -25
View File
@@ -10,25 +10,50 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use app\model\AdminUser; use app\model\System\AdminUser;
class UserController extends BaseController class UserController extends BaseController
{ {
private function getCurrentTenantIdOrFail(): int
{
$tid = $this->getTenantId();
if ($tid <= 0) {
throw new \RuntimeException('未获取到有效租户信息');
}
return $tid;
}
/** /**
* 获取所有用户信息 * 获取所有用户信息
* @return Json * @return Json
*/ */
public function getAllUsers() public function getAllUsers()
{ {
$users = AdminUser::where('delete_time', null)->field('id, account, name, phone, birth, email, qq, sex, group_id, status, last_login_ip, login_count, create_time, update_time')->select()->toArray(); try {
return json([ $tid = $this->getCurrentTenantIdOrFail();
'code' => 200, $users = AdminUser::where('delete_time', null)
'msg' => '获取成功', ->where('tid', $tid)
'data' => [ ->field('id, tid, account, name, phone, birth, email, qq, sex, group_id, status, last_login_ip, login_count, create_time, update_time')
'list' => $users, ->select()
'total' => count($users) ->toArray();
] return json([
]); 'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $users,
'total' => count($users)
]
]);
} catch (\Throwable $e) {
return json([
'code' => 401,
'msg' => '获取用户失败:' . $e->getMessage(),
'data' => [
'list' => [],
'total' => 0
]
]);
}
} }
/** /**
@@ -37,15 +62,33 @@ class UserController extends BaseController
*/ */
public function getTenantUsers(int $tenantId) public function getTenantUsers(int $tenantId)
{ {
$users = AdminUser::where('delete_time', null)->where('tenant_id', $tenantId)->field('id, account, name, phone, birth, email, qq, sex, group_id, status, last_login_ip, login_count, create_time, update_time')->select()->toArray(); try {
return json([ $tid = $this->getCurrentTenantIdOrFail();
'code' => 200, if ($tenantId !== $tid) {
'msg' => '获取成功', return json([
'data' => [ 'code' => 403,
'list' => $users, 'msg' => '禁止跨租户查看用户'
'total' => count($users) ]);
] }
]); $users = AdminUser::where('delete_time', null)
->where('tid', $tid)
->field('id, tid, account, name, phone, birth, email, qq, sex, group_id, status, last_login_ip, login_count, create_time, update_time')
->select()
->toArray();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $users,
'total' => count($users)
]
]);
} catch (\Throwable $e) {
return json([
'code' => 401,
'msg' => '获取用户失败:' . $e->getMessage()
]);
}
} }
/** /**
@@ -54,9 +97,18 @@ class UserController extends BaseController
*/ */
public function getUserInfo(int $id) public function getUserInfo(int $id)
{ {
$tid = $this->getTenantId();
$user = AdminUser::where('id', $id) $user = AdminUser::where('id', $id)
->where('tid', $tid)
->where('delete_time', null)
->field('id, account, name, phone, email, birth, qq, sex, group_id, status, create_time, last_login_ip') ->field('id, account, name, phone, email, birth, qq, sex, group_id, status, create_time, last_login_ip')
->find(); ->find();
if (!$user) {
return json([
'code' => 404,
'msg' => '用户不存在或无权限访问'
]);
}
// 记录操作日志 // 记录操作日志
$this->logSuccess('用户管理', '获取用户信息', ['id' => $id]); $this->logSuccess('用户管理', '获取用户信息', ['id' => $id]);
@@ -75,10 +127,20 @@ class UserController extends BaseController
public function changePassword(int $id, string $password) public function changePassword(int $id, string $password)
{ {
try { try {
AdminUser::where('id', $id)->update([ $tid = $this->getCurrentTenantIdOrFail();
$affected = AdminUser::where('id', $id)
->where('tid', $tid)
->where('delete_time', null)
->update([
'password' => md5($password), 'password' => md5($password),
'update_time' => date('Y-m-d H:i:s') 'update_time' => date('Y-m-d H:i:s')
]); ]);
if (!$affected) {
return json([
'code' => 404,
'msg' => '用户不存在或无权限修改'
]);
}
// 记录操作日志 // 记录操作日志
$this->logSuccess('用户管理', '修改密码', ['id' => $id]); $this->logSuccess('用户管理', '修改密码', ['id' => $id]);
return json([ return json([
@@ -102,12 +164,21 @@ class UserController extends BaseController
public function addUser() public function addUser()
{ {
$data = request()->param(); $data = request()->param();
$tid = $this->getTenantId();
if ($tid <= 0) {
return json([
'code' => 401,
'msg' => '未获取到有效租户信息'
]);
}
$data['password'] = md5($data['password']); $data['password'] = md5($data['password']);
$data['create_time'] = date('Y-m-d H:i:s'); $data['create_time'] = date('Y-m-d H:i:s');
$data['update_time'] = $data['create_time']; $data['update_time'] = $data['create_time'];
$data['group_id'] = 2; $data['group_id'] = 2;
$data['tid'] = $tid;
$id = AdminUser::insertGetId($data); $id = AdminUser::insertGetId($data);
// 记录操作日志
$this->logSuccess('用户管理', '添加用户', ['data' => $data]); $this->logSuccess('用户管理', '添加用户', ['data' => $data]);
return json([ return json([
'code' => 200, 'code' => 200,
@@ -124,8 +195,25 @@ class UserController extends BaseController
{ {
$data = request()->param(); $data = request()->param();
unset($data['_t'], $data['id']); unset($data['_t'], $data['id']);
unset($data['tid']);
$tid = $this->getTenantId();
if ($tid <= 0) {
return json([
'code' => 401,
'msg' => '未获取到有效租户信息'
]);
}
$data['update_time'] = date('Y-m-d H:i:s'); $data['update_time'] = date('Y-m-d H:i:s');
AdminUser::where('id', $id)->update($data); $affected = AdminUser::where('id', $id)
->where('tid', $tid)
->where('delete_time', null)
->update($data);
if (!$affected) {
return json([
'code' => 404,
'msg' => '用户不存在或无权限编辑'
]);
}
$this->logSuccess('用户管理', '编辑用户', ['id' => $id]); $this->logSuccess('用户管理', '编辑用户', ['id' => $id]);
return json([ return json([
'code' => 200, 'code' => 200,
@@ -139,16 +227,29 @@ class UserController extends BaseController
*/ */
public function deleteUser(int $id) public function deleteUser(int $id)
{ {
$user = AdminUser::where('id', $id)->where('delete_time', null)->find(); $tid = $this->getTenantId();
if ($tid <= 0) {
return json([
'code' => 401,
'msg' => '未获取到有效租户信息'
]);
}
$user = AdminUser::where('id', $id)
->where('tid', $tid)
->where('delete_time', null)
->find();
if (!$user) { if (!$user) {
return json([ return json([
'code' => 404, 'code' => 404,
'msg' => '用户不存在已删除' 'msg' => '用户不存在已删除或无权限操作'
]); ]);
} }
AdminUser::where('id', $id)->update([ AdminUser::where('id', $id)
->where('tid', $tid)
->update([
'delete_time' => date('Y-m-d H:i:s') 'delete_time' => date('Y-m-d H:i:s')
]); ]);
+2 -1
View File
@@ -5,8 +5,9 @@ return [
// \think\middleware\CheckRequestCache::class, // \think\middleware\CheckRequestCache::class,
// 多语言加载 // 多语言加载
// \think\middleware\LoadLangPack::class, // \think\middleware\LoadLangPack::class,
// Session初始化 // 跨域支持
\app\common\middleware\AllowCrossDomain::class, \app\common\middleware\AllowCrossDomain::class,
// Session初始化
\think\middleware\SessionInit::class, \think\middleware\SessionInit::class,
]; ];
+2 -2
View File
@@ -2,5 +2,5 @@
use think\facade\Route; use think\facade\Route;
// 数据统计路由 // 数据统计路由
Route::get('contentstats', 'app\\admin\\controller\\AnalyticsController@getContentStats'); Route::get('contentstats', 'app\\admin\\controller\\Cms\\Analytics\\AnalyticsController@getContentStats');
Route::get('usersstats', 'app\\admin\\controller\\AnalyticsController@getUserStats'); Route::get('usersstats', 'app\\admin\\controller\\Cms\\Analytics\\AnalyticsController@getUserStats');
+17 -17
View File
@@ -2,22 +2,22 @@
use think\facade\Route; use think\facade\Route;
// 文章路由 // 文章路由
Route::get('articlesList', 'app\\admin\\controller\\Article\\ArticleController@getArticlesList'); Route::get('articlesList', 'app\\admin\\controller\\Cms\\Article\\ArticleController@getArticlesList');
Route::get('allarticles', 'app\\admin\\controller\\Article\\ArticleController@getAllArticles'); Route::get('allarticles', 'app\\admin\\controller\\Cms\\Article\\ArticleController@getAllArticles');
Route::get('articles/:id', 'app\\admin\\controller\\Article\\ArticleController@getArticle'); Route::get('articles/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@getArticle');
Route::post('createarticle', 'app\\admin\\controller\\Article\\ArticleController@createArticle'); Route::post('createarticle', 'app\\admin\\controller\\Cms\\Article\\ArticleController@createArticle');
Route::post('editarticle/:id', 'app\\admin\\controller\\Article\\ArticleController@editArticle'); Route::post('editarticle/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@editArticle');
Route::delete('deletearticle/:id', 'app\\admin\\controller\\Article\\ArticleController@deleteArticle'); Route::delete('deletearticle/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@deleteArticle');
Route::post('publisharticle/:id', 'app\\admin\\controller\\Article\\ArticleController@publishArticle'); Route::post('publisharticle/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@publishArticle');
Route::post('unPublisharticle/:id', 'app\\admin\\controller\\Article\\ArticleController@unPublishArticle'); Route::post('unPublisharticle/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@unPublishArticle');
Route::post('articleRecommend/:id', 'app\\admin\\controller\\Article\\ArticleController@articleRecommend'); Route::post('articleRecommend/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@articleRecommend');
Route::post('unArticleRecommend/:id', 'app\\admin\\controller\\Article\\ArticleController@unArticleRecommend'); Route::post('unArticleRecommend/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@unArticleRecommend');
Route::post('articleTop/:id', 'app\\admin\\controller\\Article\\ArticleController@articleTop'); Route::post('articleTop/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@articleTop');
Route::post('unArticleTop/:id', 'app\\admin\\controller\\Article\\ArticleController@unArticleTop'); Route::post('unArticleTop/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleController@unArticleTop');
// 文章分类路由 // 文章分类路由
Route::get('allcategories', 'app\\admin\\controller\\Article\\ArticleCategoryController@getAllArticleCategories'); Route::get('allcategories', 'app\\admin\\controller\\Cms\\Article\\ArticleCategoryController@getAllArticleCategories');
Route::get('categories', 'app\\admin\\controller\\Article\\ArticleCategoryController@getArticleCategories'); Route::get('categories', 'app\\admin\\controller\\Cms\\Article\\ArticleCategoryController@getArticleCategories');
Route::delete('categories/:id', 'app\\admin\\controller\\Article\\ArticleCategoryController@deleteCategory'); Route::delete('categories/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleCategoryController@deleteCategory');
Route::post('createCategory', 'app\\admin\\controller\\Article\\ArticleCategoryController@createCategory'); Route::post('createCategory', 'app\\admin\\controller\\Cms\\Article\\ArticleCategoryController@createCategory');
Route::post('editCategory/:id', 'app\\admin\\controller\\Article\\ArticleCategoryController@editCategory'); Route::post('editCategory/:id', 'app\\admin\\controller\\Cms\\Article\\ArticleCategoryController@editCategory');
+4 -4
View File
@@ -2,7 +2,7 @@
use think\facade\Route; use think\facade\Route;
// Banner路由 // Banner路由
Route::get('allbanners', 'app\\admin\\controller\\BannerController@getAllBanners'); Route::get('allbanners', 'app\\admin\\controller\\Cms\\Banner\\BannerController@getAllBanners');
Route::post('createbanner', 'app\\admin\\controller\\BannerController@createBanner'); Route::post('createbanner', 'app\\admin\\controller\\Cms\\Banner\\BannerController@createBanner');
Route::post('editbanner/:id', 'app\\admin\\controller\\BannerController@editBanner'); Route::post('editbanner/:id', 'app\\admin\\controller\\Cms\\Banner\\BannerController@editBanner');
Route::delete('deletebanner/:id', 'app\\admin\\controller\\BannerController@deleteBanner'); Route::delete('deletebanner/:id', 'app\\admin\\controller\\Cms\\Banner\\BannerController@deleteBanner');
+12
View File
@@ -11,5 +11,17 @@ Route::get('storage/:path', 'app\\admin\\controller\\StorageController@index')->
// 登录路由 // 登录路由
Route::post('loginInfo', 'app\\admin\\controller\\LoginController@loginInfo'); Route::post('loginInfo', 'app\\admin\\controller\\LoginController@loginInfo');
Route::post('login', 'app\\admin\\controller\\LoginController@login'); Route::post('login', 'app\\admin\\controller\\LoginController@login');
Route::post('sendLoginCode', 'app\\admin\\controller\\LoginController@sendLoginCode');
Route::post('loginBySms', 'app\\admin\\controller\\LoginController@loginBySms');
Route::post('sendRegisterCode', 'app\\admin\\controller\\LoginController@sendRegisterCode');
Route::post('sendResetCode', 'app\\admin\\controller\\LoginController@sendResetCode');
Route::post('register', 'app\\admin\\controller\\LoginController@register');
Route::post('resetPassword', 'app\\admin\\controller\\LoginController@resetPassword');
Route::post('logout', 'app\\admin\\controller\\LoginController@logout'); Route::post('logout', 'app\\admin\\controller\\LoginController@logout');
Route::get('user/info', 'app\\admin\\controller\\LoginController@userInfo'); Route::get('user/info', 'app\\admin\\controller\\LoginController@userInfo');
// 极验路由
Route::get('login/getGeetest3Infos', 'app\\admin\\controller\\LoginController@getGeetest3Infos');
Route::get('login/getGeetest4Infos', 'app\\admin\\controller\\LoginController@getGeetest4Infos');
Route::get('login/getOpenVerify', 'app\\admin\\controller\\LoginController@getOpenVerify');
+8
View File
@@ -0,0 +1,8 @@
<?php
use think\facade\Route;
// 需求路由
Route::get('demandList', 'app\\admin\\controller\\Cms\\Demand\\DemandController@getDemandList');
Route::post('addDemand', 'app\\admin\\controller\\Cms\\Demand\\DemandController@addDemand');
Route::post('editDemand/:id', 'app\\admin\\controller\\Cms\\Demand\\DemandController@editDemand');
Route::post('deleteDemand/:id', 'app\\admin\\controller\\Cms\\Demand\\DemandController@deleteDemand');
+22
View File
@@ -0,0 +1,22 @@
<?php
use think\facade\Route;
// 主域名池管理路由
Route::group('domain/pool', function () {
Route::get('index', 'app\admin\controller\Cms\Domain\DomainPoolController/index');
Route::get('getEnabledDomains', 'app\admin\controller\Cms\Domain\DomainPoolController/getEnabledDomains');
Route::post('create', 'app\admin\controller\Cms\Domain\DomainPoolController/create');
Route::post('update', 'app\admin\controller\Cms\Domain\DomainPoolController/update');
Route::delete('delete/:id', 'app\admin\controller\Cms\Domain\DomainPoolController/delete');
Route::post('toggleStatus', 'app\admin\controller\Cms\Domain\DomainPoolController/toggleStatus');
});
// 租户域名绑定路由
Route::group('domain/tenant', function () {
Route::get('index', 'app\admin\controller\Cms\Domain\TenantDomainController/index');
Route::get('myDomains', 'app\admin\controller\Cms\Domain\TenantDomainController/myDomains');
Route::post('apply', 'app\admin\controller\Cms\Domain\TenantDomainController/apply');
Route::post('audit', 'app\admin\controller\Cms\Domain\TenantDomainController/audit');
Route::post('toggleStatus', 'app\admin\controller\Cms\Domain\TenantDomainController/toggleStatus');
Route::delete('delete/:id', 'app\admin\controller\Cms\Domain\TenantDomainController/delete');
});
+15 -4
View File
@@ -3,9 +3,20 @@ use think\facade\Route;
// 组织机构管理路由 // 组织机构管理路由
Route::group('erp', function() { Route::group('erp', function() {
Route::get('organization', 'app\admin\controller\Erp\OrganizationController/getOrganization'); Route::get('getOrganization', 'app\admin\controller\Erp\OrganizationController/getOrganization');
Route::get('getOrganizationDetail/:id', 'app\admin\controller\Erp\OrganizationController/getOrganizationDetail'); Route::get('getOrganizationDetail/:id', 'app\admin\controller\Erp\OrganizationController/getOrganizationDetail');
Route::post('organization', 'app\admin\controller\Erp\OrganizationController/createOrganization'); Route::post('createOrganization', 'app\admin\controller\Erp\OrganizationController/createOrganization');
Route::post('organization/:id', 'app\admin\controller\Erp\OrganizationController/editOrganization'); Route::post('editOrganization/:id', 'app\admin\controller\Erp\OrganizationController/editOrganization');
Route::delete('organization/:id', 'app\admin\controller\Erp\OrganizationController/deleteOrganization'); Route::delete('deleteOrganization/:id', 'app\admin\controller\Erp\OrganizationController/deleteOrganization');
Route::get('getCompanys', 'app\admin\controller\Erp\OrganizationController/getCompanys');
Route::get('getDepartments', 'app\admin\controller\Erp\OrganizationController/getDepartments');
}); });
// 员工管理路由
Route::group('erp', function() {
Route::get('getEmployee', 'app\admin\controller\Erp\EmployeeController/getEmployee');
Route::get('getEmployeeDetail/:id', 'app\admin\controller\Erp\EmployeeController/getEmployeeDetail');
Route::post('createEmployee', 'app\admin\controller\Erp\EmployeeController/createEmployee');
Route::post('editEmployee/:id', 'app\admin\controller\Erp\EmployeeController/editEmployee');
Route::delete('deleteEmployee/:id', 'app\admin\controller\Erp\EmployeeController/deleteEmployee');
});
+17 -12
View File
@@ -2,16 +2,21 @@
use think\facade\Route; use think\facade\Route;
// 文件路由 // 文件路由
Route::get('usercate', 'app\\admin\\controller\\FileController@getUserCate'); Route::get('usercate', 'app\\admin\\controller\\System\\FileController@getUserCate');
Route::get('allfiles', 'app\\admin\\controller\\FileController@getAllFiles'); Route::get('allfiles', 'app\\admin\\controller\\System\\FileController@getAllFiles');
Route::get('catefiles/:id', 'app\\admin\\controller\\FileController@getCateFiles'); Route::get('catefiles/:id', 'app\\admin\\controller\\System\\FileController@getCateFiles');
Route::post('uploadfile', 'app\\admin\\controller\\FileController@uploadFile'); Route::post('uploadfile', 'app\\admin\\controller\\System\\FileController@uploadFile');
Route::post('updatefile/:id', 'app\\admin\\controller\\FileController@updateFile'); Route::post('uploadfiles', 'app\\admin\\controller\\System\\FileController@uploadFile');
Route::delete('deletefile/:id', 'app\\admin\\controller\\FileController@deleteFile'); Route::post('updatefile/:id', 'app\\admin\\controller\\System\\FileController@updateFile');
Route::get('movefile/:id', 'app\\admin\\controller\\FileController@moveFile'); Route::delete('deletefile/:id', 'app\\admin\\controller\\System\\FileController@deleteFile');
Route::post('createfilecate', 'app\\admin\\controller\\FileController@createFileCate'); Route::get('movefile/:id', 'app\\admin\\controller\\System\\FileController@moveFile');
Route::post('renamefilecate/:id', 'app\\admin\\controller\\FileController@renameFileCate'); Route::post('createfilecate', 'app\\admin\\controller\\System\\FileController@createFileCate');
Route::delete('deletefilecate/:id', 'app\\admin\\controller\\FileController@deleteFileCate'); Route::post('renamefilecate/:id', 'app\\admin\\controller\\System\\FileController@renameFileCate');
Route::delete('deletefilecate/:id', 'app\\admin\\controller\\System\\FileController@deleteFileCate');
Route::post('uploadavatar', 'app\\admin\\controller\\FileController@uploadAvatar'); Route::post('uploadavatar', 'app\\admin\\controller\\System\\FileController@uploadAvatar');
Route::post('uploadavatar/:id', 'app\\admin\\controller\\FileController@updateAvatar'); Route::post('uploadavatar/:id', 'app\\admin\\controller\\System\\FileController@updateAvatar');
Route::post('batchdeletefiles', 'app\\admin\\controller\\System\\FileController@batchDeleteFiles');
Route::post('batchDeleteFilesPermanently', 'app\\admin\\controller\\System\\FileController@batchDeleteFilesPermanently');
Route::post('batchMoveFiles', 'app\\admin\\controller\\System\\FileController@batchMoveFiles');
+10
View File
@@ -0,0 +1,10 @@
<?php
use think\facade\Route;
// 友情链接路由
Route::get('friendlinks', 'app\admin\controller\Cms\Friendlink\FriendlinkController@getList');
Route::get('friendlinks/all', 'app\admin\controller\Cms\Friendlink\FriendlinkController@getAll');
Route::post('friendlinks', 'app\admin\controller\Cms\Friendlink\FriendlinkController@add');
Route::put('friendlinks/:id', 'app\admin\controller\Cms\Friendlink\FriendlinkController@update');
Route::delete('friendlinks/:id', 'app\admin\controller\Cms\Friendlink\FriendlinkController@delete');
Route::post('friendlinks/batchdelete', 'app\admin\controller\Cms\Friendlink\FriendlinkController@batchDelete');
+4 -4
View File
@@ -2,7 +2,7 @@
use think\facade\Route; use think\facade\Route;
// 前端导航路由 // 前端导航路由
Route::get('frontmenus', 'app\\admin\\controller\\FrontMenuController@getFrontMenus'); Route::get('frontmenus', 'app\\admin\\controller\\Cms\\FrontMenu\\FrontMenuController@getFrontMenus');
Route::post('createfrontmenu', 'app\\admin\\controller\\FrontMenuController@createFrontMenu'); Route::post('createfrontmenu', 'app\\admin\\controller\\Cms\\FrontMenu\\FrontMenuController@createFrontMenu');
Route::post('editfrontmenu/:id', 'app\\admin\\controller\\FrontMenuController@editFrontMenu'); Route::post('editfrontmenu/:id', 'app\\admin\\controller\\Cms\\FrontMenu\\FrontMenuController@editFrontMenu');
Route::delete('deletefrontmenu/:id', 'app\\admin\\controller\\FrontMenuController@deleteFrontMenu'); Route::delete('deletefrontmenu/:id', 'app\\admin\\controller\\Cms\\FrontMenu\\FrontMenuController@deleteFrontMenu');
+1
View File
@@ -3,6 +3,7 @@ use think\facade\Route;
// 模块管理路由 // 模块管理路由
Route::get('modules/list', 'app\\admin\\controller\\ModulesController@getList'); Route::get('modules/list', 'app\\admin\\controller\\ModulesController@getList');
Route::get('modules/getTenantList', 'app\\admin\\controller\\ModulesController@getTenantList');
Route::get('modules/:id', 'app\\admin\\controller\\ModulesController@getDetail'); Route::get('modules/:id', 'app\\admin\\controller\\ModulesController@getDetail');
Route::post('modules', 'app\\admin\\controller\\ModulesController@add'); Route::post('modules', 'app\\admin\\controller\\ModulesController@add');
Route::put('modules/:id', 'app\\admin\\controller\\ModulesController@edit'); Route::put('modules/:id', 'app\\admin\\controller\\ModulesController@edit');
+4 -4
View File
@@ -2,7 +2,7 @@
use think\facade\Route; use think\facade\Route;
// 单页路由 // 单页路由
Route::get('allonepages', 'app\\admin\\controller\\OnePageController@getAllOnePages'); Route::get('allonepages', 'app\\admin\\controller\\Cms\\OnePage\\OnePageController@getAllOnePages');
Route::post('createonepage', 'app\\admin\\controller\\OnePageController@createOnePage'); Route::post('createonepage', 'app\\admin\\controller\\Cms\\OnePage\\OnePageController@createOnePage');
Route::post('editonepage/:id', 'app\\admin\\controller\\OnePageController@editOnePage'); Route::post('editonepage/:id', 'app\\admin\\controller\\Cms\\OnePage\\OnePageController@editOnePage');
Route::delete('deleteonepage/:id', 'app\\admin\\controller\\OnePageController@deleteOnePage'); Route::delete('deleteonepage/:id', 'app\\admin\\controller\\Cms\\OnePage\\OnePageController@deleteOnePage');
+14
View File
@@ -0,0 +1,14 @@
<?php
use think\facade\Route;
// 特色产品路由
Route::get('productsList', 'app\admin\controller\Cms\Products\ProductsController@productsList');
Route::post('addProducts', 'app\admin\controller\Cms\Products\ProductsController@addProducts');
Route::put('editProducts/:id', 'app\admin\controller\Cms\Products\ProductsController@editProducts');
Route::delete('deleteProducts/:id', 'app\admin\controller\Cms\Products\ProductsController@deleteProducts');
// 产品分类
Route::get('productsTypesList', 'app\admin\controller\Cms\Products\ProductsTypesController@productsTypesList');
Route::post('addProductsTypes', 'app\admin\controller\Cms\Products\ProductsTypesController@addProductsTypes');
Route::put('editProductsTypes/:id', 'app\admin\controller\Cms\Products\ProductsTypesController@editProductsTypes');
Route::delete('deleteProductsTypes/:id', 'app\admin\controller\Cms\Products\ProductsTypesController@deleteProductsTypes');
+5 -5
View File
@@ -2,8 +2,8 @@
use think\facade\Route; use think\facade\Route;
// 角色路由 // 角色路由
Route::get('allRoles', 'app\\admin\\controller\\RoleController@getAllRoles'); Route::get('allRoles', 'app\\admin\\controller\\System\\RoleController@getAllRoles');
Route::get('roles/:id', 'app\\admin\\controller\\RoleController@getRoleById'); Route::get('roles/:id', 'app\\admin\\controller\\System\\RoleController@getRoleById');
Route::post('roles', 'app\\admin\\controller\\RoleController@createRole'); Route::post('roles', 'app\\admin\\controller\\System\\RoleController@createRole');
Route::put('roles/:id', 'app\\admin\\controller\\RoleController@updateRole'); Route::put('roles/:id', 'app\\admin\\controller\\System\\RoleController@updateRole');
Route::delete('roles/:id', 'app\\admin\\controller\\RoleController@deleteRole'); Route::delete('roles/:id', 'app\\admin\\controller\\System\\RoleController@deleteRole');
+8
View File
@@ -0,0 +1,8 @@
<?php
use think\facade\Route;
// 特色服务路由
Route::get('servicesList', 'app\admin\controller\Cms\Services\ServicesController@servicesList');
Route::post('addServices', 'app\admin\controller\Cms\Services\ServicesController@addServices');
Route::put('editServices/:id', 'app\admin\controller\Cms\Services\ServicesController@editServices');
Route::delete('deleteServices/:id', 'app\admin\controller\Cms\Services\ServicesController@deleteServices');
+6
View File
@@ -4,5 +4,11 @@ use think\facade\Route;
// 站点设置路由 // 站点设置路由
Route::get('normalInfos', 'app\\admin\\controller\\SiteSettingsController@getNormalInfos'); Route::get('normalInfos', 'app\\admin\\controller\\SiteSettingsController@getNormalInfos');
Route::post('saveNormalInfos', 'app\\admin\\controller\\SiteSettingsController@saveNormalInfos'); Route::post('saveNormalInfos', 'app\\admin\\controller\\SiteSettingsController@saveNormalInfos');
Route::get('loginVerifyInfos', 'app\\admin\\controller\\SiteSettingsController@getVerifyInfos');
Route::post('saveloginVerifyInfos', 'app\\admin\\controller\\SiteSettingsController@saveVerifyInfos');
Route::get('legalInfos', 'app\\admin\\controller\\SiteSettingsController@getLegalInfos'); Route::get('legalInfos', 'app\\admin\\controller\\SiteSettingsController@getLegalInfos');
Route::post('saveLegalInfos', 'app\\admin\\controller\\SiteSettingsController@saveLegalInfos'); Route::post('saveLegalInfos', 'app\\admin\\controller\\SiteSettingsController@saveLegalInfos');
Route::get('companyInfos', 'app\\admin\\controller\\SiteSettingsController@getCompanyInfos');
Route::post('saveCompanyInfos', 'app\\admin\\controller\\SiteSettingsController@saveCompanyInfos');
Route::get('companySeo', 'app\\admin\\controller\\SiteSettingsController@getCompanySeo');
Route::post('saveCompanySeo', 'app\\admin\\controller\\SiteSettingsController@saveCompanySeo');
+33
View File
@@ -0,0 +1,33 @@
<?php
use think\facade\Route;
// 短信管理路由
Route::group('sms', function () {
// 获取短信网关配置
Route::get('info', 'app\\admin\\controller\\System\\SmsController@getSmsInfo');
// 编辑短信网关配置
Route::post('editinfo', 'app\\admin\\controller\\System\\SmsController@editSmsInfo');
// 发送测试短信(入队任务)
Route::post('sendtest', 'app\\admin\\controller\\System\\SmsController@sendTestSms');
// Android 网关轮询待发送任务
Route::post('gateway/poll', 'app\\admin\\controller\\System\\SmsController@gatewayPoll');
// 兼容:如果网关端只请求 poll(不带 gateway
Route::post('poll', 'app\\admin\\controller\\System\\SmsController@gatewayPoll');
Route::get('poll', 'app\\admin\\controller\\System\\SmsController@gatewayPoll');
// Android 网关上报短信内容(解析验证码)
Route::post('gateway/report', 'app\\admin\\controller\\System\\SmsController@gatewayReport');
// 兼容:如果网关端只请求 report(不带 gateway
Route::post('report', 'app\\admin\\controller\\System\\SmsController@gatewayReport');
// 管理员:短信任务列表(租户隔离)
Route::get('taskList', 'app\\admin\\controller\\System\\SmsController@getSmsTaskList');
// 管理员:编辑短信任务(租户隔离)
Route::post('taskEdit/:id', 'app\\admin\\controller\\System\\SmsController@editSmsTask');
});
+13
View File
@@ -0,0 +1,13 @@
<?php
use think\facade\Route;
// 租户管理路由
Route::group('tenant', function() {
Route::get('getTenant', 'app\admin\controller\Tenant\TenantController/getTenant');
Route::get('getTenantDetail/:id', 'app\admin\controller\Tenant\TenantController/getTenantDetail');
Route::post('createTenant', 'app\admin\controller\Tenant\TenantController/createTenant');
Route::post('editTenant/:id', 'app\admin\controller\Tenant\TenantController/editTenant');
Route::delete('deleteTenant/:id', 'app\admin\controller\Tenant\TenantController/deleteTenant');
Route::get('findTenantCode', 'app\admin\controller\Tenant\TenantController/findTenantCode');
});
+8
View File
@@ -0,0 +1,8 @@
<?php
use think\facade\Route;
// 模板管理路由
Route::get('theme', 'app\admin\controller\Cms\Theme\ThemeController@index');
Route::post('theme/switch', 'app\admin\controller\Cms\Theme\ThemeController@switch');
Route::get('theme/data', 'app\admin\controller\Cms\Theme\ThemeController@getData');
Route::post('theme/data', 'app\admin\controller\Cms\Theme\ThemeController@saveData');
+2 -2
View File
@@ -11,7 +11,7 @@ use think\facade\Cache;
use think\response\Json; use think\response\Json;
use app\service\JwtService; use app\service\JwtService;
use app\model\AdminUser; use app\model\System\AdminUser;
use app\model\System\SystemSiteSettings; use app\model\System\SystemSiteSettings;
class LoginController extends BaseController class LoginController extends BaseController
@@ -80,7 +80,7 @@ class LoginController extends BaseController
'id' => $user['id'], 'id' => $user['id'],
'account' => $user['account'], 'account' => $user['account'],
'name' => $user['name'], 'name' => $user['name'],
'tenant_id' => $user['tenant_id'], 'tid' => $user['tid'],
'group_id' => $user['group_id'] 'group_id' => $user['group_id']
]; ];
+1 -1
View File
@@ -10,7 +10,7 @@ use think\facade\Db;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use app\model\AdminUser; use app\model\System\AdminUser;
class UserController extends BaseController class UserController extends BaseController
{ {
+8
View File
@@ -0,0 +1,8 @@
<?php
use think\facade\Route;
// 模板相关路由
Route::get('theme', 'app\admin\controller\ThemeController@index');
Route::post('theme/switch', 'app\admin\controller\ThemeController@switch');
Route::get('theme/data', 'app\admin\controller\ThemeController@getData');
Route::post('theme/data', 'app\admin\controller\ThemeController@saveData');
+4 -30
View File
@@ -15,47 +15,21 @@ class AllowCrossDomain
{ {
$origin = $request->header('origin', ''); $origin = $request->header('origin', '');
$allowedDomains = [ // 允许所有来源(生产环境建议限制具体域名)
'http://localhost:3000', $header['Access-Control-Allow-Origin'] = $origin ?: '*';
'http://localhost:5000',
'http://localhost:8000',
'http://127.0.0.1:3000',
'http://127.0.0.1:5000',
'http://127.0.0.1:8000',
'http://localhost:5173',
'http://backapi.yunzer.cn',
'http://www.yunzer.cn',
'https://www.yunzer.cn',
'http://yunzer.cn',
'https://yunzer.cn',
'http://back.yunzer.cn',
'https://back.yunzer.cn',
'http://backend.yunzer.cn',
'https://backend.yunzer.cn',
];
if (in_array($origin, $allowedDomains)) {
$header['Access-Control-Allow-Origin'] = $origin;
} else {
if (!empty($origin)) {
$header['Access-Control-Allow-Origin'] = $origin;
} else {
$header['Access-Control-Allow-Origin'] = '*';
}
}
if ($request->method() === 'OPTIONS') { if ($request->method() === 'OPTIONS') {
$header['Access-Control-Allow-Credentials'] = 'true'; $header['Access-Control-Allow-Credentials'] = 'true';
$header['Access-Control-Max-Age'] = 1800; $header['Access-Control-Max-Age'] = 1800;
$header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS'; $header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS';
$header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With'; $header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, X-Current-Domain';
return response('', 200, $header); return response('', 200, $header);
} }
$header['Access-Control-Allow-Credentials'] = 'true'; $header['Access-Control-Allow-Credentials'] = 'true';
$header['Access-Control-Max-Age'] = 1800; $header['Access-Control-Max-Age'] = 1800;
$header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS'; $header['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS';
$header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With'; $header['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, X-Current-Domain';
return $next($request)->header($header); return $next($request)->header($header);
} }
+69
View File
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace app\common\middleware;
use think\facade\Db;
use think\Request;
/**
* 域名解析中间件
* 通过访问域名自动识别租户
*/
class DomainParse
{
public function handle(Request $request, \Closure $next)
{
$host = $request->host(true); // 获取完整域名,不带端口
// 你的后台/平台域名(排除,不走租户逻辑)
$adminDomains = ['back.yunzer.cn', 'api.yunzer.cn'];
$platformDomains = ['www.yunzer.cn', 'yunzer.cn'];
// 后台/平台/API域名,直接放行
if (in_array($host, $adminDomains) || in_array($host, $platformDomains)) {
return $next($request);
}
// 解析域名(兼容 yunzer.com.cn/dh2.fun
$domainParts = explode('.', $host);
$allowMainDomains = ['yunzer.com.cn', 'dh2.fun'];
$mainDomain = '';
foreach ($allowMainDomains as $domain) {
$domainLen = count(explode('.', $domain));
$currentLen = count($domainParts);
if ($currentLen > $domainLen) {
$matchMain = implode('.', array_slice($domainParts, -$domainLen));
if ($matchMain === $domain) {
$mainDomain = $matchMain;
break;
}
}
}
// 非租户主域名,放行(走API默认界面,可改为跳转到平台)
if (empty($mainDomain)) {
return $next($request);
}
// 查询租户域名绑定记录
$tenantDomain = Db::name('mete_tenant_domain')
->where('full_domain', $host)
->where('status', 1)
->find();
if ($tenantDomain) {
// 1. 写入租户ID到请求(核心,后续所有逻辑都能获取)
$request->tenantId = $tenantDomain['tid'];
$request->header('X-Tenant-Id', (string)$tenantDomain['tid']);
// 2. 额外:写入租户域名信息,方便模板使用
$request->tenantDomain = $host;
$request->tenantMainDomain = $mainDomain;
}
return $next($request);
}
}
+66 -1
View File
@@ -9,6 +9,7 @@ use think\App;
use think\exception\ValidateException; use think\exception\ValidateException;
use think\Validate; use think\Validate;
use think\Request; use think\Request;
use think\facade\Db;
// 在控制器方法中添加 // 在控制器方法中添加
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Origin: *');
@@ -44,6 +45,12 @@ abstract class BaseController
*/ */
protected $middleware = []; protected $middleware = [];
/**
* 当前租户ID
* @var int
*/
protected $tenantId = 0;
/** /**
* 构造方法 * 构造方法
* @access public * @access public
@@ -59,7 +66,65 @@ abstract class BaseController
} }
// 初始化 // 初始化
protected function initialize() {} protected function initialize()
{
// 自动检测并设置租户ID
$this->initTenantId();
}
/**
* 初始化租户ID
* 优先从 Referer 获取来源域名,否则使用当前访问域名
* 从 mete_tenant_domain 表查询对应的 tid
*/
protected function initTenantId(): void
{
// 优先从 Referer 获取来源域名(跨域调用场景)
$referer = $this->request->header('Referer');
if ($referer) {
$host = parse_url($referer, PHP_URL_HOST);
} else {
// 否则使用当前访问域名
$host = $this->request->host(true);
}
// 查询域名对应的租户ID
$tenantDomain = Db::name('mete_tenant_domain')
->where('full_domain', $host)
->where('status', 1)
->whereNull('delete_time')
->find();
if ($tenantDomain) {
$this->tenantId = (int)$tenantDomain['tid'];
}
}
/**
* 获取当前租户ID
* @return int
*/
protected function getTenantId(): int
{
return $this->tenantId;
}
/**
* 根据域名获取租户ID(静态方法,方便全局调用)
* @return int
*/
public static function getTenantIdByDomain(string $baseUrl): int
{
// 查询域名对应的租户ID
$tenantDomain = Db::name('mete_tenant_domain')
->where('full_domain', $baseUrl)
->where('status', 1)
->whereNull('delete_time')
->find();
return $tenantDomain ? (int)$tenantDomain['tid'] : 0;
}
/** /**
* 验证数据 * 验证数据
@@ -5,14 +5,14 @@ declare(strict_types=1);
namespace app\index\controller\Article; namespace app\index\controller\Article;
use app\index\BaseController; use app\index\BaseController;
use app\model\Articles;
use app\model\ArticlesCategory;
use Symfony\Component\VarDumper\VarDumper; use Symfony\Component\VarDumper\VarDumper;
use think\exception\ValidateException; use think\exception\ValidateException;
use think\facade\Request; use think\facade\Request;
use think\facade\Session; use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Cms\Articles;
use app\model\Cms\ArticlesCategory;
class ArticleController extends BaseController class ArticleController extends BaseController
{ {
@@ -102,6 +102,44 @@ class ArticleController extends BaseController
} }
} }
/**
* 获取文章分类列表(mete_articles_category:当前租户 tid、启用状态)
* 支持 baseUrl=租户前台域名,与 getCenterNews 一致,便于 api 域名跨域调用时解析 tid
*
* @return Json
*/
public function getArticleCategories(): Json
{
$baseUrl = $this->request->get('baseUrl', '');
if (!empty($baseUrl)) {
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
}
$tid = $this->getTenantId();
if (empty($tid)) {
return json([
'code' => 400,
'msg' => '无法识别租户信息',
'list' => [],
]);
}
$articleCategories = ArticlesCategory::where('delete_time', null)
->where('tid', $tid)
->where('status', 1)
->order('sort', 'asc')
->order('id', 'asc')
->select()
->toArray();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articleCategories,
]);
}
/** /**
* 游客文章阅读量函数 * 游客文章阅读量函数
* @return Json * @return Json
@@ -12,348 +12,196 @@ use think\facade\Session;
use think\response\Json; use think\response\Json;
use think\db\exception\DbException; use think\db\exception\DbException;
use app\model\Articles; use app\model\Cms\Articles;
use app\model\ArticlesCategory; use app\model\Cms\ArticlesCategory;
use tidy;
class NewsCenterController extends BaseController class NewsCenterController extends BaseController
{ {
/** /**
* 根据分类获取文章 * 根据域名获取新闻数据
* @param string $category 分类名称 *
* 返回字段:list 列表;total 当前租户下已发布新闻总数;count 当前筛选(全部或 cate)下的条数,翻页按 count 与 page_size。
*
* @return Json * @return Json
*/ */
public function getNewsByCategory(string $category): Json public function getCenterNews(): Json
{ {
// 查询分类 $baseUrl = (string) $this->request->param('baseUrl', '');
$categoryInfo = ArticlesCategory::where('name', $category)
->where('delete_time', null)
->find();
if (!$categoryInfo) { if ($baseUrl !== '') {
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
}
$tid = $this->getTenantId();
if (empty($tid)) {
return json([ return json([
'code' => 200, 'code' => 400,
'msg' => 'success', 'msg' => '无法识别租户信息',
'list' => [], 'list' => [],
]); ]);
} }
// 查询文章 // param 合并 GET,避免个别环境下仅 get 取不到查询串
$articles = Articles::published() $cateId = (int) $this->request->param('cate', 0);
->where('cate', $categoryInfo['id']) $listPage = (int) $this->request->param('page', 0);
->order('publish_date', 'desc') $pageSizeReq = (int) $this->request->param('page_size', 0);
->limit(4)
->select();
return json([ // 点击左侧分类:文章 cate 可能挂在子分类上,需包含「该分类 + 其下所有子分类」id
'code' => 200, $categoryIdsForFilter = [];
'msg' => 'success', if ($cateId > 0) {
'list' => $articles, $categoryIdsForFilter = $this->resolveCategoryBranchIds($cateId, $tid);
]); if ($categoryIdsForFilter === []) {
} $categoryIdsForFilter = [$cateId];
/**
* 获取企业新闻
* @return Json
*/
public function getCompanyNews(): Json
{
// 获取分页参数
$page = Request::param('page', 1);
$limit = Request::param('limit', 10);
$page = max(1, intval($page));
$limit = max(1, min(50, intval($limit))); // 限制每页最多50条
// 查询分类
$categoryInfo = ArticlesCategory::where('name', '企业新闻')
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit,
]);
}
// 查询总数
$total = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->count();
// 查询文章
$articles = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->order('top', 'desc')
->order('recommend', 'desc')
->order('sort', 'desc')
->order('publish_date', 'desc')
->page($page, $limit)
->select();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
'total' => $total,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 获取金蝶新闻
* @return Json
*/
public function getKingdeeNews(): Json
{
// 获取分页参数
$page = Request::param('page', 1);
$limit = Request::param('limit', 10);
$page = max(1, intval($page));
$limit = max(1, min(50, intval($limit))); // 限制每页最多50条
// 查询分类
$categoryInfo = ArticlesCategory::where('name', '金蝶新闻')
->where('delete_time', null)
->find();
if (!$categoryInfo) {
return json([
'code' => 200,
'msg' => 'success',
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit,
]);
}
// 查询总数
$total = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->count();
// 查询文章
$articles = Articles::where('cate', $categoryInfo['id'])
->where('delete_time', null)
->where('status', 2)
->order('top', 'desc')
->order('recommend', 'desc')
->order('sort', 'desc')
->order('publish_date', 'desc')
->page($page, $limit)
->select();
return json([
'code' => 200,
'msg' => 'success',
'list' => $articles,
'total' => $total,
'page' => $page,
'limit' => $limit,
]);
}
/**
* 获取技术中心子分类
* @return Json
*/
public function getTechnologyCategories(): Json
{
// 获取"技术中心"主分类
$parentCategory = ArticlesCategory::where('name', '技术中心')
->where('delete_time', null)
->find();
if (!$parentCategory) {
return json([
'code' => 200,
'msg' => 'success',
'data' => []
]);
}
// 查找所有子分类
$subCategories = ArticlesCategory::where('cid', $parentCategory['id'])
->where('delete_time', null)
->field('id,name,desc,sort,image')
->order('sort', 'desc')
->select();
return json([
'code' => 200,
'msg' => 'success',
'data' => $subCategories
]);
}
/**
* 获取技术中心
* @return Json
*/
public function getTechnologyCenter(): Json
{
// 1. 分页参数规范化
$page = max(1, intval(Request::param('page', 1)));
$limit = max(1, min(50, intval(Request::param('limit', 10))));
// 获取分类ID参数(可选)
$categoryId = Request::param('category_id', null);
// 2. 第一步:获取“技术中心”主分类的 id
$parentCategory = ArticlesCategory::where('name', '技术中心')
->where('delete_time', null)
->find();
// print_r($parentCategory['id']);
if (!$parentCategory) {
return $this->emptyResponse($page, $limit);
}
// 3. 第二步:查找 cid 等于主分类 id 的所有子分类 id
$subCategoryQuery = ArticlesCategory::where('cid', $parentCategory['id'])
->where('delete_time', null);
// print_r($subCategoryQuery->select());
// 如果指定了分类ID,则只查询该分类
if ($categoryId) {
$subCategoryQuery->where('id', $categoryId);
}
$subCategoryIds = $subCategoryQuery->column('id');
// print_r($subCategoryIds);
if (empty($subCategoryIds)) {
return $this->emptyResponse($page, $limit);
}
// 5. 第三步:查询 Articles 表,并限定字段
$articleQuery = Articles::whereIn('cate', $subCategoryIds)
->where('delete_time', null)
->where('status', 2);
// 获取总数
$total = (clone $articleQuery)->count();
// 获取列表:限定返回 id, title, desc, publish_date, image, likes, views
$articles = $articleQuery->field('id,title,desc,publish_date,image,likes,views,cate')
->order([
'top' => 'desc',
'recommend' => 'desc',
'sort' => 'desc',
'publish_date' => 'desc'
])
->page($page, $limit)
->select();
// 如果文章没有image,查询对应分类的image
$categoryImages = ArticlesCategory::whereIn('id', $subCategoryIds)->column('image', 'id');
foreach ($articles as &$article) {
if (empty($article['image']) && isset($categoryImages[$article['cate']])) {
$article['image'] = $categoryImages[$article['cate']];
} }
} }
$baseQuery = static function () use ($tid, $cateId, $categoryIdsForFilter) {
$q = Articles::published()
->where('tid', $tid)
->where('delete_time', null);
if ($cateId > 0 && $categoryIdsForFilter !== []) {
$q->whereIn('cate', $categoryIdsForFilter);
}
return $q;
};
// 当前租户下「全部」已发布新闻数(不受 cate 筛选影响)
$totalTenantNews = (int) Articles::published()
->where('tid', $tid)
->where('delete_time', null)
->count();
// 当前列表条件下的总数:tid +(可选)cate,用于翻页总页数 = ceil(count / page_size)
$countFiltered = (int) $baseQuery()->count();
$useListPagination = $listPage > 0 && $pageSizeReq > 0;
if ($useListPagination) {
// 新闻中心列表页:服务端分页;不要用查询参数名 page,避免与路由占位符冲突
$pageSizeReq = max(1, min($pageSizeReq, 50));
$listPage = max(1, $listPage);
$articles = $baseQuery()
->order('publish_date', 'desc')
->page($listPage, $pageSizeReq)
->select();
} else {
// 首页等:按 limit 取前 N 条(默认 8)
$limit = (int) $this->request->param('limit', 8);
if ($limit < 1) {
$limit = 8;
}
if ($limit > 200) {
$limit = 200;
}
$articles = $baseQuery()
->order('publish_date', 'desc')
->limit($limit)
->select();
}
// 处理图片:如果文章 image 为空,则取分类的 image
foreach ($articles as &$article) {
if (empty($article['image']) && !empty($article['cate'])) {
$category = ArticlesCategory::where('id', $article['cate'])
->where('delete_time', null)
->find();
if ($category && !empty($category['image'])) {
$article['image'] = $category['image'];
}
}
}
unset($article);
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => 'success', 'msg' => 'success',
'list' => $articles, 'list' => $articles,
'total' => $total, // 当前租户下新闻总条数(全部状态为已发布)
'page' => $page, 'total' => $totalTenantNews,
'limit' => $limit, // 当前筛选(全部或某分类)下的条数,列表翻页按 count 与 page_size 计算
'count' => $countFiltered,
]); ]);
} }
/** /**
* 统一空返回 * 解析分类筛选用的 id 列表:自身 + mete_articles_category 中 cid=该 id 的所有子孙(同 tid、未删除)
*/ */
private function emptyResponse($page, $limit): Json private function resolveCategoryBranchIds(int $rootId, int $tid): array
{ {
return json([ if ($rootId <= 0 || $tid <= 0) {
'code' => 200, return [];
'msg' => 'success', }
'list' => [],
'total' => 0, $root = ArticlesCategory::where('id', $rootId)
'page' => $page, ->where('tid', $tid)
'limit' => $limit, ->where('delete_time', null)
]); ->find();
if (!$root) {
return [$rootId];
}
$ids = [$rootId];
$queue = [$rootId];
while ($queue !== []) {
$pid = array_shift($queue);
$children = ArticlesCategory::where('cid', $pid)
->where('tid', $tid)
->where('delete_time', null)
->column('id');
foreach ($children as $id) {
$id = (int) $id;
if ($id > 0 && !in_array($id, $ids, true)) {
$ids[] = $id;
$queue[] = $id;
}
}
}
return $ids;
} }
/** /**
* 获取金蝶新闻详情 * 获取新闻详情
* @param int $id 文章ID
* @return Json
*/ */
public function getKingdeeNewsDetail(int $id): Json public function getNewsDetail(int $id): Json
{ {
$article = Articles::where('id', $id) try {
->where('delete_time', null) $baseUrl = $this->request->get('baseUrl', '');
->where('status', 2) if (!empty($baseUrl)) {
->field('id,title,cate,image,desc,author,content,publisher,publish_date,views,likes,is_trans,transurl') $this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
->find(); }
if (!$article) { $tid = $this->getTenantId();
return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]); $query = Articles::published()->where('id', $id);
if ($tid > 0) {
$query->where('tid', $tid);
}
$article = $query->find();
if (!$article) {
return json([
'code' => 404,
'msg' => '文章不存在',
'list' => [],
]);
}
return json([
'code' => 200,
'msg' => 'success',
'list' => $article,
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'msg' => '获取新闻详情失败:' . $e->getMessage(),
'list' => [],
]);
} }
$cate = (int) $article['cate'];
// 转换为数组再处理
$articleData = $article->toArray();
// $articleData['catename'] = $this->getCategoryName($cate);
// 获取相关文章
$articleData['relatedArticles'] = $this->getRelatedArticles($id, $cate);
// 获取上一篇下一篇
$articleData['nextPreviousArticles'] = $this->getNextPreviousArticles($id, $cate);
// 增加浏览量
Articles::where('id', $id)->inc('views')->update();
return json(['code' => 200, 'msg' => 'success', 'data' => $articleData]);
}
/**
* 获取企业新闻详情
*/
public function getCompanyNewsDetail(int $id): Json
{
$article = Articles::where('id', $id)
->where('delete_time', null)
->where('status', 2)
->find();
if (!$article) {
return json(['code' => 404, 'msg' => '文章不存在', 'data' => null]);
}
$cate = (int) $article['cate'];
// 转换为数组再处理
$articleData = $article->toArray();
$articleData['catename'] = $this->getCategoryName($cate);
// 获取相关文章
$articleData['relatedArticles'] = $this->getRelatedArticles($id, $cate);
// 获取上一篇下一篇
$articleData['nextPreviousArticles'] = $this->getNextPreviousArticles($id, $cate);
// 增加浏览量
Articles::where('id', $id)->inc('views')->update();
return json(['code' => 200, 'msg' => 'success', 'data' => $articleData]);
} }
/** /**
+65
View File
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace app\index\controller;
use app\index\BaseController;
use think\response\Json;
use think\facade\Db;
/**
* Banner 控制器
*/
class BannerController extends BaseController
{
/**
* 获取 Banner 列表
* @return Json
*/
public function getBanners()
{
$baseUrl = $this->request->get('baseUrl', '');
if (!empty($baseUrl)) {
$this->tenantId = BaseController::getTenantIdByDomain($baseUrl);
}
$tid = $this->getTenantId();
if (empty($tid)) {
return json([
'code' => 400,
'msg' => '无法识别租户信息',
'list' => [],
]);
}
// 查询该租户下的 Banner
$banners = Db::name('mete_apps_cms_banner')
->where('tid', $tid)
->whereNull('delete_time')
->order('sort', 'asc')
->order('id', 'desc')
->select()
->toArray();
// 处理图片路径
foreach ($banners as &$banner) {
if (!empty($banner['image'])) {
// 如果图片路径已经是完整 URL,直接返回
if (!preg_match('/^https?:\/\//', $banner['image'])) {
// 拼接完整 URL
$banner['image'] = $this->request->scheme() . '://' . $this->request->host() .
(strpos($banner['image'], '/') === 0 ? '' : '/') . $banner['image'];
}
}
}
return json([
'code' => 200,
'msg' => 'success',
'list' => $banners,
]);
}
}
+415 -10
View File
@@ -6,19 +6,368 @@ namespace app\index\controller;
use app\model\Banner; use app\model\Banner;
use app\index\BaseController; use app\index\BaseController;
use app\model\FrontMenu; use app\model\Cms\FrontMenu;
use app\model\OnePage; use app\model\Cms\OnePage;
use app\model\System\SystemSiteSetting;
use app\service\ThemeService;
use think\db\exception\DbException; use think\db\exception\DbException;
use think\facade\Env; use think\facade\Env;
use app\model\System\SystemSiteSettings; use think\facade\Request;
use app\model\Cms\TemplateSiteConfig;
use app\model\Cms\Friendlink;
use app\model\Cms\Services;
use app\model\Cms\Products;
use app\model\Tenant\Tenant;
use app\model\Cms\Articles;
use app\model\Cms\ArticlesCategory;
class Index extends BaseController class Index extends BaseController
{ {
private ThemeService $themeService;
public function __construct()
{
$this->themeService = new ThemeService();
}
public function index() public function index()
{ {
// 获取请求对象
$request = $this->request ?? Request::instance();
// 优先从请求对象获取中间件设置的租户ID(通过域名解析)
$tid = $request->tenantId ?? 0;
// 兼容:从header获取
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
// 调试:检查tid
if ($tid === 0) {
die('tenantId未获取到,请检查域名解析');
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
// 不使用软删除过滤
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径:/themes/{theme_key}/,优先使用index.php,其次index.html
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
if (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.php')) {
// 渲染PHP模板(直接include
return $this->renderPhpTemplate($themeBasePath . DIRECTORY_SEPARATOR . 'index.php', $themeUrlPath);
} elseif (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.html')) {
// 读取HTML模板内容
$content = file_get_contents($themeBasePath . DIRECTORY_SEPARATOR . 'index.html');
// 修复资源路径:将相对路径转换为绝对路径
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
// 模板不存在,返回默认
return view('index/index'); return view('index/index');
} }
/**
* 通用页面渲染
* 根据页面名称(如 about, blog, portfolio 等)渲染对应模板
* @param string $page 页面名称
* @return \think\response|\think\response\View
*/
public function page(string $page = 'index')
{
$request = $this->request ?? Request::instance();
// 获取租户ID
$tid = $request->tenantId ?? 0;
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
if ($tid === 0) {
die('tenantId未获取到,请检查域名解析');
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
// 安全检查:只允许字母、数字、中划线
$page = preg_replace('/[^a-zA-Z0-9\-]/', '', $page);
if (empty($page)) {
$page = 'index';
}
// 查找 PHP 模板(优先)或 HTML 模板
$templateFile = $themeBasePath . DIRECTORY_SEPARATOR . $page . '.php';
$templateHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . $page . '.html';
if (is_file($templateFile)) {
return $this->renderPhpTemplate($templateFile, $themeUrlPath);
} elseif (is_file($templateHtmlFile)) {
$content = file_get_contents($templateHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
// 模板不存在
return response('页面不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 文章详情页
* @param int $id 文章ID
* @return \think\response
*/
public function articleDetail(int $id = 0)
{
$request = $this->request ?? Request::instance();
// 获取租户ID
$tid = $request->tenantId ?? 0;
if ($tid == 0) {
$headerTid = $request->header('X-Tenant-Id');
$tid = $headerTid ? (int) $headerTid : 0;
}
if ($tid === 0) {
die('tenantId未获取到,请检查域名解析');
}
// 与 NewsCenterController::getNewsDetail 一致:已发布 + 当前租户(网页直出,非 JSON)
$templateVars = $this->buildArticleDetailTemplateVars($id, $tid);
if ($templateVars === null) {
return response('文章不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
}
// 获取租户选择的模板
$themeKey = 'default';
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->withoutField('delete_time')
->find();
$themeKey = $config['value'] ?? 'default';
}
// 模板路径
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
$themeUrlPath = '/themes/' . $themeKey . '/';
// 查找文章详情模板
$templateFile = $themeBasePath . DIRECTORY_SEPARATOR . 'article_detail.php';
$templateHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . 'article_detail.html';
$blogDetailsFile = $themeBasePath . DIRECTORY_SEPARATOR . 'blog-details.php';
$blogDetailsHtmlFile = $themeBasePath . DIRECTORY_SEPARATOR . 'blog-details.html';
// 优先使用 article_detail 模板
if (is_file($templateFile)) {
return $this->renderPhpTemplate($templateFile, $themeUrlPath, $templateVars);
} elseif (is_file($templateHtmlFile)) {
$content = file_get_contents($templateHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
} elseif (is_file($blogDetailsFile)) {
return $this->renderPhpTemplate($blogDetailsFile, $themeUrlPath, $templateVars);
} elseif (is_file($blogDetailsHtmlFile)) {
$content = file_get_contents($blogDetailsHtmlFile);
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
return response('文章不存在', 404, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 组装文章详情页模板变量(数据源与 getNewsDetail 一致,并限定租户 + 已发布)
*
* @return array<string, mixed>|null
*/
private function buildArticleDetailTemplateVars(int $id, int $tid): ?array
{
$article = Articles::published()
->where('id', $id)
->where('tid', $tid)
->find();
if (!$article) {
return null;
}
$row = $article->toArray();
$categoryName = '未分类';
if (!empty($row['cate'])) {
$cat = ArticlesCategory::where('id', $row['cate'])
->where('delete_time', null)
->find();
if ($cat) {
$categoryName = (string) $cat['name'];
}
}
$coverPath = $row['image'] ?? '';
if ($coverPath === '' && !empty($row['cate'])) {
$category = ArticlesCategory::where('id', $row['cate'])
->where('delete_time', null)
->find();
if ($category && !empty($category['image'])) {
$coverPath = (string) $category['image'];
}
}
$apiUrl = rtrim((string) (Env::get('app.api_url', '') ?: 'https://api.yunzer.cn'), '/');
$articleCoverUrl = $this->resolveArticleMediaUrl($coverPath, $apiUrl);
$descPlain = strip_tags((string) ($row['desc'] ?? ''));
$title = (string) ($row['title'] ?? '详情');
$descShort = $descPlain !== ''
? (function_exists('mb_substr') ? mb_substr($descPlain, 0, 160) : substr($descPlain, 0, 160))
: $title;
return [
'article' => $row,
'articleCoverUrl' => $articleCoverUrl,
'articleCategoryName' => $categoryName,
'pageTitle' => $title . ' - 新闻详情',
'pageDescription' => $descShort,
'pageKeywords' => $title,
];
}
/**
* 文章封面等资源 URL(与列表页逻辑一致:相对路径拼 API 域名)
*/
private function resolveArticleMediaUrl(string $path, string $apiUrl): string
{
if ($path === '') {
return '';
}
if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
return $path;
}
if (strpos($path, '/') === 0) {
return $apiUrl . $path;
}
return $apiUrl . '/' . $path;
}
/**
* 修复模板中的资源路径
* 将相对路径 (assets/, css/, js/, images/) 转换为绝对路径 (/themes/xxx/)
*/
private function fixTemplateAssets(string $content, string $themeUrlPath): string
{
// 需要修复的资源目录
$assetsDirs = ['assets', 'css', 'js', 'images', 'img', 'fonts', 'plugins', 'libs'];
foreach ($assetsDirs as $dir) {
// 匹配 src="assets/..." 或 href="css/..." 等相对路径
// 不匹配已以 / 开头的绝对路径
$content = preg_replace(
'/(src|href)=["\'](' . $dir . '\/[^"\']*)["\']/',
'$1="' . $themeUrlPath . '$2"',
$content
);
// 匹配 src='assets/...'
$content = preg_replace(
'/(src|href)=[\']([^\/][^\'\"]*\/[^\'\"]*)[\']/',
'$1="' . $themeUrlPath . '$2"',
$content
);
}
return $content;
}
/**
* 渲染PHP模板文件
*/
private function renderPhpTemplate(string $templateFile, string $themeUrlPath = '', array $templateVars = [])
{
// 定义模板基础URL常量,供模板使用
if (!defined('THEME_URL')) {
define('THEME_URL', $themeUrlPath);
}
ob_start();
// 获取URL参数
$getParams = Request::get();
extract($getParams, EXTR_OVERWRITE);
extract($templateVars, EXTR_OVERWRITE);
include $templateFile;
$content = ob_get_clean();
// 修复PHP模板输出中的资源路径
$content = $this->fixTemplateAssets($content, $themeUrlPath);
return response($content, 200, ['Content-Type' => 'text/html; charset=utf-8']);
}
/**
* 前端初始化接口 - 返回当前模板和填充数据
* @return \think\response\Json
*/
public function init()
{
// 优先从请求对象获取中间件设置的租户ID(通过域名解析)
$tid = $this->request->tenantId ?? 0;
// 兼容:URL参数传递的tid作为备用
$tid = $tid > 0 ? $tid : Request::param('tid', 0, 'int');
// 从TemplateSiteConfig获取配置(根据租户ID
$config = null;
if ($tid > 0) {
$config = TemplateSiteConfig::where('tid', $tid)
->where('key', 'current_theme')
->find();
}
$themeKey = $config['value'] ?? 'default';
// 模板路径:/themes/{theme_key}/,优先使用index.php,其次index.html
$themeBasePath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $themeKey;
if (is_file($themeBasePath . DIRECTORY_SEPARATOR . 'index.php')) {
$themePath = '/themes/' . $themeKey . '/index.php';
} else {
$themePath = '/themes/' . $themeKey . '/index.html';
}
return json([
'code' => 200,
'msg' => 'success',
'data' => [
'theme_key' => $themeKey,
'theme_path' => $themePath
]
]);
}
/** /**
* 获取日志列表 * 获取日志列表
*/ */
@@ -177,26 +526,82 @@ class Index extends BaseController
} }
/** /**
* 前端底部数据 * 获取首页综合数据
* @return \think\response\Json * @return \think\response\Json
*/ */
public function getFooterData() public function getHomeData()
{ {
$baseUrl = Request::param('baseUrl', '');
if (empty($baseUrl)) {
return json(['code' => 400, 'msg' => '缺少 baseUrl 参数']);
}
try { try {
$footerData = SystemSiteSettings::where('delete_time', null) // 通过域名获取租户ID
->field('label, value') $tid = BaseController::getTenantIdByDomain($baseUrl);
if (empty($tid)) {
return json([
'code' => 400,
'msg' => '无法识别租户信息',
'data' => []
]);
}
// 获取站点基础信息 (normalinfos)
$normalInfos = SystemSiteSetting::where('tid', $tid)
->field('sitename,logo,logow,ico,description,copyright,companyname,icp,companyintroduction')
->find();
// 获取特色服务列表
$servicesList = Services::where('delete_time', null)
->where('tid', $tid)
->order('sort', 'asc')
->field('id,title,desc,thumb,url')
->select() ->select()
->toArray(); ->toArray();
// 获取企业产品列表
$productsList = Products::where('delete_time', null)
->where('tid', $tid)
->order('sort', 'asc')
->field('id,title,desc,content,thumb,url')
->select()
->toArray();
// 获取友情链接列表
$friendlinkList = Friendlink::where('delete_time', null)
->where('tid', $tid)
->where('status', 1)
->order('sort', 'asc')
->field('id,link_name,link_url,link_logo')
->select()
->toArray();
// 获取联系方式
$contact = Tenant::where('id', $tid)
->where('status', 1)
->where('delete_time', null)
->field('contact_phone,contact_email,address,worktime')
->find();
// 合并返回
return json([ return json([
'code' => 200, 'code' => 200,
'msg' => 'success', 'msg' => 'success',
'data' => $footerData 'data' => [
'normal' => $normalInfos ?: (object) [],
'contact' => $contact ?: (object) [],
'services' => $servicesList,
'products' => $productsList,
'links' => $friendlinkList
]
]); ]);
} catch (DbException $e) { } catch (\Exception $e) {
return json([ return json([
'code' => 500, 'code' => 500,
'msg' => 'fail' . $e->getMessage(), 'msg' => '服务器错误' . $e->getMessage(),
'data' => null 'data' => null
]); ]);
} }
+1
View File
@@ -1,5 +1,6 @@
<?php <?php
return [ return [
\app\common\middleware\AllowCrossDomain::class, \app\common\middleware\AllowCrossDomain::class,
\app\common\middleware\DomainParse::class,
\think\middleware\SessionInit::class, \think\middleware\SessionInit::class,
]; ];
+22 -18
View File
@@ -5,30 +5,34 @@ use think\facade\Route;
Route::get('/', 'app\index\controller\Index@index'); Route::get('/', 'app\index\controller\Index@index');
Route::get('index/index', 'app\index\controller\Index@index'); Route::get('index/index', 'app\index\controller\Index@index');
// --- 前端底部数据路由 --- // --- 模板通用页面路由 ---
Route::get('article_detail/:id', 'app\index\controller\Index@articleDetail');
Route::get(':page', 'app\index\controller\Index@page')
->pattern(['page' => 'about|blog|blog-details|contact|portfolio|portfolio-details|service-details|services|team|articles']);
// --- 模板初始化接口 ---
Route::get('init', 'app\index\controller\Index@init');
// --- Banner 路由 ---
Route::get('getBanners', 'app\index\controller\BannerController@getBanners');
// --- 前端其他数据路由 ---
Route::get('footerdata', 'app\index\controller\Index@getFooterData'); Route::get('footerdata', 'app\index\controller\Index@getFooterData');
Route::get('companyInfos', 'app\index\controller\Index@getCompanyInfos');
Route::post('requirement', 'app\index\controller\Index@requirement');
Route::get('homeData', 'app\index\controller\Index@getHomeData');
// --- 文章详情路由 --- // --- 新闻中心列表路由 ---
Route::get('kingdeenews/detail/:id', 'app\index\controller\Article\NewsCenterController@getKingdeeNewsDetail'); Route::get('getCenterNews', 'app\index\controller\Article\NewsCenterController@getCenterNews');
Route::get('companynews/detail/:id', 'app\index\controller\Article\NewsCenterController@getCompanyNewsDetail'); Route::get('getNewsDetail/:id', 'app\index\controller\Article\NewsCenterController@getNewsDetail');
// --- 文章列表路由 ---
Route::get('kingdeenews$', 'app\index\controller\Article\NewsCenterController@getKingdeeNews');
Route::get('companynews$', 'app\index\controller\Article\NewsCenterController@getCompanyNews');
Route::get('technologyCenter$', 'app\index\controller\Article\NewsCenterController@getTechnologyCenter');
Route::get('technologyCategories$', 'app\index\controller\Article\NewsCenterController@getTechnologyCategories');
Route::get('newscentertop4', 'app\index\controller\Article\ArticleController@getNewsCenterTop4');
Route::get('newsbycategory/:category', 'app\index\controller\Article\NewsCenterController@getNewsByCategory');
// --- 文章互动路由 --- // --- 文章分类路由 ---
Route::get('getArticleCategories', 'app\index\controller\Article\ArticleController@getArticleCategories');
// --- 新闻中心互动路由 ---
Route::post('articleViews/:id', 'app\index\controller\Article\ArticleController@articleViews'); Route::post('articleViews/:id', 'app\index\controller\Article\ArticleController@articleViews');
Route::post('articleLikes/:id', 'app\index\controller\Article\ArticleController@articleLikes'); Route::post('articleLikes/:id', 'app\index\controller\Article\ArticleController@articleLikes');
Route::post('articleUnlikes/:id', 'app\index\controller\Article\ArticleController@articleUnlikes'); Route::post('articleUnlikes/:id', 'app\index\controller\Article\ArticleController@articleUnlikes');
// --- 前端导航与单页路由 ---
Route::get('headmenu', 'app\index\controller\Index@getHeadMenu');
Route::rule('onepage/:path', 'app\index\controller\Index@getOnePageByPath', 'GET')->pattern(['path' => '.*']);
// --- 日志相关路由 ---
Route::get('index/getLogs', 'app\index\controller\Index@getLogs');
Route::get('getLogs', 'app\index\controller\Index@getLogs');
+1
View File
@@ -15,6 +15,7 @@ class AdminModules extends Model
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'mid' => 'integer',
'name' => 'string', 'name' => 'string',
'code' => 'string', 'code' => 'string',
'path' => 'string', 'path' => 'string',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -36,6 +36,7 @@ class Articles extends Model
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'cate' => 'integer', 'cate' => 'integer',
'sort' => 'integer', 'sort' => 'integer',
'status' => 'integer', 'status' => 'integer',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -36,6 +36,11 @@ class ArticlesCategory extends Model
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'cid' => 'integer',
'tid' => 'integer',
'name' => 'string',
'image' => 'string',
'desc' => 'string',
'sort' => 'integer', 'sort' => 'integer',
'status' => 'integer', 'status' => 'integer',
'create_time' => 'datetime', 'create_time' => 'datetime',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -23,11 +23,12 @@ class Banner extends Model
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_banner'; protected $name = 'mete_apps_cms_banner';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'title' => 'string', 'title' => 'string',
'desc' => 'string', 'desc' => 'string',
'url' => 'string', 'url' => 'string',
+44
View File
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class Demand extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_demand';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'title' => 'string',
'desc' => 'string',
'applicant' => 'string',
'phone' => 'string',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -17,22 +17,22 @@ use think\model\concern\SoftDelete;
/** /**
* 文章分类模型 * 文章分类模型
*/ */
class AdminUser extends Model class DemandCategory extends Model
{ {
// 启用软删除 // 启用软删除
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_admin_user'; protected $name = 'mete_demand_category';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'sex' => 'integer', 'title' => 'string',
'group_id' => 'integer', 'desc' => 'string',
'login_count' => 'integer', 'applicant' => 'string',
'phone' => 'string',
'status' => 'integer', 'status' => 'integer',
'api_key_status' => 'integer',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
'delete_time' => 'datetime', 'delete_time' => 'datetime',
@@ -40,3 +40,4 @@ class AdminUser extends Model
} }
+44
View File
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class Friendlink extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_friendlink';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'link_name' => 'string',
'link_url' => 'string',
'link_logo' => 'string',
'description' => 'string',
'sort' => 'integer',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -23,16 +23,19 @@ class FrontMenu extends Model
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_front_menu'; protected $name = 'mete_apps_cms_front_menu';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'pid' => 'integer', 'pid' => 'integer',
'tid' => 'integer',
'title' => 'string', 'title' => 'string',
'image' => 'string',
'type' => 'integer', 'type' => 'integer',
'path' => 'string', 'path' => 'string',
'component_path' => 'string', 'component_path' => 'string',
'sort' => 'integer',
'desc' => 'string', 'desc' => 'string',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\Cms;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -23,11 +23,12 @@ class OnePage extends Model
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_onepage'; protected $name = 'mete_apps_cms_onepage';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'title' => 'string', 'title' => 'string',
'content' => 'string', 'content' => 'string',
'path' => 'string', 'path' => 'string',
+44
View File
@@ -0,0 +1,44 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 特色服务模型
*/
class Products extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_products';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'title' => 'string',
'desc' => 'string',
'content' => 'string',
'thumb' => 'string',
'url' => 'string',
'sort' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+42
View File
@@ -0,0 +1,42 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 特色服务模型
*/
class ProductsTypes extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_products_types';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'pid' => 'integer',
'title' => 'string',
'desc' => 'string',
'sort' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+43
View File
@@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 特色服务模型
*/
class Services extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_services';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'title' => 'string',
'desc' => 'string',
'thumb' => 'string',
'url' => 'string',
'sort' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 网站模板模型
*/
class TemplateSiteConfig extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_template_site_config';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'key' => 'string',
'value' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+40
View File
@@ -0,0 +1,40 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Cms;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 网站模板数据模型
*/
class TemplateThemeData extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_cms_template_theme_data';
// 字段类型转换
protected $type = [
'id' => 'integer',
'theme_key' => 'string',
'field_key' => 'string',
'field_value' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+51
View File
@@ -0,0 +1,51 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Erp;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 员工模型
*/
class Employee extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_apps_erp_employee';
// 字段类型转换
protected $type = [
'id' => 'integer',
'account' => 'string',
'password' => 'string',
'name' => 'string',
'gender' => 'integer',
'birthday' => 'date',
'affiliate_unit' => 'string',
'department' => 'string',
'position' => 'string',
'nation' => 'string',
'phone' => 'string',
'wechat' => 'string',
'email' => 'string',
'home_address' => 'string',
'account_status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+2 -1
View File
@@ -28,13 +28,14 @@ class Organization extends Model
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tenant_id' => 'integer', 'tid' => 'integer',
'org_name' => 'string', 'org_name' => 'string',
'org_code' => 'string', 'org_code' => 'string',
'parent_id' => 'integer', 'parent_id' => 'integer',
'sort' => 'integer', 'sort' => 'integer',
'leader_id' => 'integer', 'leader_id' => 'integer',
'remark' => 'string', 'remark' => 'string',
'is_company' => 'integer',
'status' => 'integer', 'status' => 'integer',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
+54
View File
@@ -0,0 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\System;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 文章分类模型
*/
class AdminUser extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_admin_user';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'account' => 'string',
'password' => 'string',
'name' => 'string',
'birth' => 'date',
'phone' => 'string',
'email' => 'string',
'qq' => 'string',
'sex' => 'integer',
'avatar' => 'string',
'group_id' => 'integer',
'login_count' => 'integer',
'last_login_ip' => 'string',
'status' => 'integer',
'api_key' => 'string',
'api_key_expire' => 'datetime',
'api_key_status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\System;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -28,6 +28,7 @@ class AdminUserGroup extends Model
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'name' => 'string', 'name' => 'string',
'status' => 'integer', 'status' => 'integer',
'rights' => 'string', 'rights' => 'string',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\System;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -23,11 +23,13 @@ class Files extends Model
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_files'; protected $name = 'mete_system_files';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'uid' => 'integer',
'name' => 'string', 'name' => 'string',
'type' => 'integer', 'type' => 'integer',
'cate' => 'integer', 'cate' => 'integer',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\System;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -23,11 +23,13 @@ class FilesCategory extends Model
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_files_category'; protected $name = 'mete_system_files_category';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'uid' => 'integer',
'name' => 'string', 'name' => 'string',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
@@ -9,7 +9,7 @@
// | Author: Liu21st <liu21st@gmail.com> // | Author: Liu21st <liu21st@gmail.com>
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model; namespace app\model\System;
use think\Model; use think\Model;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@@ -21,20 +21,28 @@ class OperationLog extends Model
{ {
// 启用软删除 // 启用软删除
use SoftDelete; use SoftDelete;
// 数据库表名 // 数据库表名
protected $name = 'mete_operation_log'; protected $name = 'mete_system_operation_log';
// 字段类型转换 // 字段类型转换
protected $type = [ protected $type = [
'id' => 'integer', 'id' => 'integer',
'tid' => 'integer',
'user_id' => 'integer', 'user_id' => 'integer',
'module' => 'string',
'action' => 'string',
'method' => 'string',
'url' => 'string',
'ip' => 'string',
'user_agent' => 'string',
'request_data' => 'string',
'response_data' => 'string',
'status' => 'integer', 'status' => 'integer',
'error_message' => 'string',
'execution_time' => 'float', 'execution_time' => 'float',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
'delete_time' => 'datetime', 'delete_time' => 'datetime',
]; ];
} }
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\model\System;
use think\Model;
/**
* 短信发送任务表(Android 网关轮询获取)
*/
class SmsTask extends Model
{
// 数据库表名
protected $name = 'mete_system_sms_tasks';
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'api_key' => 'string',
'phone' => 'string',
'content' => 'string',
'status' => 'integer',
'code' => 'string',
'report_raw' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\System;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 系统域名池模型
*/
class SystemDomainPool extends Model
{
// 数据库表名
protected $name = 'mete_system_domain_pool';
// 字段类型转换
protected $type = [
'id' => 'integer',
'main_domain' => 'string',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+43
View File
@@ -0,0 +1,43 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\System;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 站点设置模型
*/
class SystemSiteSetting extends Model
{
// 启用软删除
use SoftDelete;
// 数据库表名
protected $name = 'mete_system_site_setting';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'sitename' => 'string',
'logo' => 'string',
'logow' => 'string',
'ico' => 'string',
'companyintroduction' => 'string',
'description' => 'string',
'copyright' => 'string',
'companyname' => 'string',
'icp' => 'string',
'create_time' => 'datetime',
];
}
+1
View File
@@ -35,6 +35,7 @@ class SystemMenu extends Model
'icon' => 'string', 'icon' => 'string',
'sort' => 'integer', 'sort' => 'integer',
'status' => 'integer', 'status' => 'integer',
'is_visible' => 'integer',
'type' => 'integer', 'type' => 'integer',
'permission' => 'string', 'permission' => 'string',
'remark' => 'string', 'remark' => 'string',
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace app\model\Template;
use think\Model;
use think\model\concern\SoftDelete;
class TemplateSiteConfig extends Model
{
use SoftDelete;
protected $name = 'mete_template_site_config';
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'key' => 'string',
'value' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace app\model\Template;
use think\Model;
use think\model\concern\SoftDelete;
class TemplateThemeData extends Model
{
use SoftDelete;
protected $name = 'mete_template_theme_data';
protected $type = [
'id' => 'integer',
'theme_key' => 'string',
'field_key' => 'string',
'field_value' => 'string',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+5
View File
@@ -30,9 +30,14 @@ class Tenant extends Model
'contact_phone' => 'string', 'contact_phone' => 'string',
'contact_email' => 'string', 'contact_email' => 'string',
'address' => 'string', 'address' => 'string',
'worktime' => 'string',
'seoTitle' => 'string',
'seoKeywords' => 'string',
'seoDescription' => 'string',
'status' => 'integer', 'status' => 'integer',
'create_time' => 'datetime', 'create_time' => 'datetime',
'update_time' => 'datetime', 'update_time' => 'datetime',
'delete_time' => 'datetime',
'remark' => 'string', 'remark' => 'string',
]; ];
} }
+36
View File
@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: Liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace app\model\Tenant;
use think\Model;
/**
* 租户域名模型
*/
class TenantDomain extends Model
{
// 数据库表名
protected $name = 'mete_tenant_domain';
// 字段类型转换
protected $type = [
'id' => 'integer',
'tid' => 'integer',
'sub_domain' => 'string',
'main_domain' => 'string',
'full_domain' => 'string',
'status' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
];
}
+270
View File
@@ -0,0 +1,270 @@
<?php
declare(strict_types=1);
namespace app\service;
use think\facade\Db;
use think\facade\Config;
use app\model\Cms\TemplateSiteConfig;
/**
* 模板服务类
* 负责扫描 public/themes 目录,管理模板配置
*/
class ThemeService
{
private string $themesPath;
public function __construct()
{
$this->themesPath = root_path() . 'public' . DIRECTORY_SEPARATOR . 'themes';
}
/**
* 获取所有可用模板列表
* @return array
*/
public function getThemeList(): array
{
$themes = [];
$dirs = $this->scanThemeDirs();
foreach ($dirs as $dir) {
$config = $this->readThemeConfig($dir);
$preview = $this->getThemePreview($dir);
$themes[] = [
'key' => $dir,
'name' => $config['name'] ?? $dir,
'description' => $config['description'] ?? '',
'version' => $config['version'] ?? '1.0.0',
'author' => $config['author'] ?? '',
'preview' => $preview,
'path' => '/themes/' . $dir . '/index.html',
'fields' => $config['fields'] ?? [],
];
}
return $themes;
}
/**
* 扫描模板目录
* @return array
*/
private function scanThemeDirs(): array
{
$dirs = [];
if (!is_dir($this->themesPath)) {
return $dirs;
}
$items = scandir($this->themesPath);
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$fullPath = $this->themesPath . DIRECTORY_SEPARATOR . $item;
// 检查是否有 index.html 或 index.php
if (is_dir($fullPath) && (is_file($fullPath . DIRECTORY_SEPARATOR . 'index.html') || is_file($fullPath . DIRECTORY_SEPARATOR . 'index.php'))) {
$dirs[] = $item;
}
}
return $dirs;
}
/**
* 读取模板配置文件
* @param string $themeDir
* @return array
*/
private function readThemeConfig(string $themeDir): array
{
$configPath = $this->themesPath . DIRECTORY_SEPARATOR . $themeDir . DIRECTORY_SEPARATOR . 'config.json';
if (!is_file($configPath)) {
return [];
}
$content = file_get_contents($configPath);
$config = json_decode($content, true);
return $config ?? [];
}
/**
* 获取模板预览图
* @param string $themeDir
* @return string
*/
private function getThemePreview(string $themeDir): string
{
$previewPath = '/themes/' . $themeDir . '/preview.png';
// 如果 preview.png 不存在,使用默认占位图
$fullPath = $this->themesPath . DIRECTORY_SEPARATOR . $themeDir . DIRECTORY_SEPARATOR . 'preview.png';
if (!is_file($fullPath)) {
return 'https://picsum.photos/300/200?random=' . ord($themeDir[0]);
}
return $previewPath;
}
/**
* 获取当前激活的模板Key
* @param int $tid 租户ID
* @return string
*/
public function getCurrentTheme(int $tid)
{
// 直接通过 tid 查询对应的 value
$value = TemplateSiteConfig::where('tid', $tid)->value('value');
return $value;
}
/**
* 切换当前模板
* @param int $tid 租户ID
* @param string $themeKey
* @return bool
*/
public function switchTheme(int $tid, string $themeKey): bool
{
// 验证模板是否存在
$themes = $this->getThemeList();
$exists = false;
foreach ($themes as $theme) {
if ($theme['key'] === $themeKey) {
$exists = true;
break;
}
}
if (!$exists) {
return false;
}
try {
// 查找或创建配置记录
$where = [['key', '=', 'current_theme'], ['delete_time', '=', null]];
if ($tid > 0) {
$where[] = ['tid', '=', $tid];
}
$config = TemplateSiteConfig::where($where)
->find();
$now = date('Y-m-d H:i:s');
if ($config) {
TemplateSiteConfig::where('id', $config['id'])->update([
'value' => $themeKey,
'update_time' => $now
]);
} else {
TemplateSiteConfig::insert([
'tid' => $tid,
'key' => 'current_theme',
'value' => $themeKey,
'create_time' => $now,
'update_time' => $now
]);
}
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 获取模板数据(用于前端渲染)
* @param int $tid 租户ID
* @param string|null $themeKey
* @return array
*/
public function getThemeData(int $tid = 0, ?string $themeKey = null): array
{
$themeKey = $themeKey ?? $this->getCurrentTheme($tid);
try {
if ($tid > 0) {
$where[] = ['tid', '=', $tid];
}
$configData = TemplateSiteConfig::where($where)
->select()
->toArray();
$data = [];
foreach ($configData as $item) {
// 去掉 field_ 前缀
$fieldKey = substr($item['key'], 6);
$data[$fieldKey] = $item['value'];
}
return [
'theme_key' => $themeKey,
'theme_path' => '/themes/' . $themeKey . '/index.html',
'data' => $data
];
} catch (\Exception $e) {
return [
'theme_key' => $themeKey,
'theme_path' => '/themes/' . $themeKey . '/index.html',
'data' => []
];
}
}
/**
* 保存模板字段数据
* @param int $tid 租户ID
* @param string $themeKey
* @param string $fieldKey
* @param mixed $fieldValue
* @return bool
*/
public function saveThemeField(int $tid, string $themeKey, string $fieldKey, $fieldValue): bool
{
try {
// 使用 TemplateSiteConfig 表,key 格式为 field_xxx
$configKey = 'field_' . $fieldKey;
$where = [
['key', '=', $configKey],
['delete_time', '=', null]
];
if ($tid > 0) {
$where[] = ['tid', '=', $tid];
}
$existing = TemplateSiteConfig::where($where)
->find();
$value = is_array($fieldValue) ? json_encode($fieldValue, JSON_UNESCAPED_UNICODE) : $fieldValue;
$now = date('Y-m-d H:i:s');
if ($existing) {
TemplateSiteConfig::where('id', $existing['id'])
->update([
'value' => $value,
'update_time' => $now
]);
} else {
TemplateSiteConfig::insert([
'tid' => $tid,
'key' => $configKey,
'value' => $value,
'create_time' => $now,
'update_time' => $now
]);
}
return true;
} catch (\Exception $e) {
return false;
}
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace app\utils;
// 引入极验 SDK 的核心类
use geetest\GeetestLib;
use app\model\System\SystemSiteSettings;
class Geetest
{
/**
* 极验 3.0 ID KEY
* 优先从系统配置表 SystemSiteSettings 中读取 geetest3ID / geetest3KEY
*/
private $captchaId;
private $privateKey;
public function __construct()
{
// 默认值可以为空,避免硬编码
$this->captchaId = null;
$this->privateKey = null;
try {
// 从系统设置表中读取极验3.0配置
$settings = SystemSiteSettings::where('delete_time', null)
->whereIn('label', ['geetest3ID', 'geetest3KEY'])
->column('value', 'label');
if (!empty($settings['geetest3ID']) && !empty($settings['geetest3KEY'])) {
$this->captchaId = $settings['geetest3ID'];
$this->privateKey = $settings['geetest3KEY'];
}
} catch (\Throwable $e) {
// 读取配置失败时,不抛出致命错误,留给上层决定是否启用极验
error_log('加载极验配置失败: ' . $e->getMessage());
}
}
/**
* 初始化极验验证(前端获取验证参数)
* @return string JSON 字符串
*/
public function init()
{
$gtLib = new GeetestLib($this->captchaId, $this->privateKey);
// 用户唯一标识(可传用户ID、IP等,不能为空)
$userId = request()->ip();
// 预处理验证
$status = $gtLib->pre_process($userId);
// 保存状态到 session
session('gtserver', $status);
session('gt_user_id', $userId);
// 返回前端需要的验证参数
return $gtLib->get_response_str();
}
/**
* 验证前端提交的极验数据
* @param array $data 前端提交的 geetest_challenge/validate/seccode
* @return bool 验证结果
*/
public function verify(array $data)
{
$gtLib = new GeetestLib($this->captchaId, $this->privateKey);
$userId = session('gt_user_id');
$status = session('gtserver');
if ($status == 1) {
// 正常模式验证
$result = $gtLib->success_validate(
$data['geetest_challenge'],
$data['geetest_validate'],
$data['geetest_seccode'],
$userId
);
} else {
// 宕机模式验证
$result = $gtLib->fail_validate(
$data['geetest_challenge'],
$data['geetest_validate'],
$data['geetest_seccode']
);
}
return $result;
}
}
+2 -1
View File
@@ -34,7 +34,8 @@
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"app\\": "app" "app\\": "app",
"geetest\\": "extend/geetest/"
}, },
"psr-0": { "psr-0": {
"": "extend/" "": "extend/"
Generated
+86 -86
View File
@@ -608,6 +608,91 @@
}, },
"time": "2021-10-29T13:26:27+00:00" "time": "2021-10-29T13:26:27+00:00"
}, },
{
"name": "symfony/polyfill-mbstring",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-23T08:48:59+00:00"
},
{ {
"name": "topthink/framework", "name": "topthink/framework",
"version": "v8.1.4", "version": "v8.1.4",
@@ -1037,91 +1122,6 @@
], ],
"time": "2024-09-25T14:21:43+00:00" "time": "2024-09-25T14:21:43+00:00"
}, },
{
"name": "symfony/polyfill-mbstring",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-12-23T08:48:59+00:00"
},
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v6.4.32", "version": "v6.4.32",
@@ -1319,5 +1319,5 @@
"php": ">=8.0.0" "php": ">=8.0.0"
}, },
"platform-dev": {}, "platform-dev": {},
"plugin-api-version": "2.9.0" "plugin-api-version": "2.6.0"
} }
+2
View File
@@ -21,6 +21,8 @@ return [
// 应用映射(自动多应用模式有效) // 应用映射(自动多应用模式有效)
'app_map' => [], 'app_map' => [],
// 域名绑定(自动多应用模式有效) // 域名绑定(自动多应用模式有效)
// 多租户二级域名池场景下,不建议在这里写死具体主域名/子域名
// 否则后续新增租户域名需要反复改配置
'domain_bind' => [], 'domain_bind' => [],
// 禁止URL访问的应用列表(自动多应用模式有效) // 禁止URL访问的应用列表(自动多应用模式有效)
'deny_app_list' => [], 'deny_app_list' => [],
+126
View File
@@ -0,0 +1,126 @@
server
{
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
server_name dh2.fun ~^(?<subdomain>.+)\.dh2\.fun$;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/api.yunzer.cn/public;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/dh2.fun.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/dh2.fun/*.conf;
# 根目录的 *.html 和 *.php,交给 ThinkPHP 处理
location ~* ^/(.+\.(html|php))$ {
try_files $uri /index.php?$query_string;
}
# 所有 /themes/... 直接当静态文件返回(模板静态资源)
location ^~ /themes/ {
root /www/wwwroot/api.yunzer.cn/public;
try_files $uri $uri/ =404;
expires 12h;
access_log off;
}
# /template1..100/... 预览入口
location ~ ^/(template(?:[1-9]\d?|100))/(.*)$ {
alias /www/wwwroot/api.yunzer.cn/public/themes/$1/$2;
try_files $uri $uri/ =404;
expires 12h;
access_log off;
}
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#HTTP_TO_HTTPS_START
set $isRedcert 1;
if ($server_port != 443) {
set $isRedcert 2;
}
if ( $uri ~ /\.well-known/ ) {
set $isRedcert 1;
}
if ($isRedcert != 1) {
rewrite ^(/.*)$ https://$host$1 permanent;
}
#HTTP_TO_HTTPS_END
ssl_certificate /www/server/panel/vhost/cert/dh2.fun/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/dh2.fun/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
error_page 404 /404.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-82.conf;
#PHP-INFO-END
#REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效
include /www/server/panel/vhost/rewrite/dh2.fun.conf;
#REWRITE-END
# 禁止访问的敏感文件
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$
{
return 404;
}
# 禁止访问的敏感目录
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
# PHP 解析 + 传递子域名参数
location ~ [^/]\.php(/|$) {
include enable-php-82.conf;
fastcgi_param HTTP_SUBDOMAIN $subdomain;
fastcgi_param HTTP_MAIN_DOMAIN dh2.fun;
}
# TP路由重写
if (!-e $request_filename) {
# 将原始 URI 追加到 /index.php 后面,让 ThinkPHP 从 pathinfo 里拿到 :page
rewrite ^(.*)$ /index.php$1$is_args$args last;
}
access_log /www/wwwlogs/dh2.fun.log;
error_log /www/wwwlogs/dh2.fun.error.log;
}
+120
View File
@@ -0,0 +1,120 @@
server {
listen 80;
listen 443 ssl;
listen 443 quic;
http2 on;
# 捕获子域名变量 $subdomain
server_name yunzer.com.cn ~^(?<subdomain>.+)\.yunzer\.com\.cn$;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/api.yunzer.cn/public;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/yunzer.com.cn.conf;
#CERT-APPLY-CHECK--END
include /www/server/panel/vhost/nginx/extension/yunzer.com.cn/*.conf;
# --- SSL 配置 START ---
set $isRedcert 1;
if ($server_port != 443) {
set $isRedcert 2;
}
if ( $uri ~ /\.well-known/ ) {
set $isRedcert 1;
}
if ($isRedcert != 1) {
rewrite ^(/.*)$ https://$host$1 permanent;
}
ssl_certificate /www/server/panel/vhost/cert/yunzer.com.cn/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/yunzer.com.cn/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';
error_page 497 https://$host$request_uri;
# --- SSL 配置 END ---
#ERROR-PAGE-START
error_page 404 /404.html;
#ERROR-PAGE-END
# --- 安全与防盗链拦截 START ---
location ~* (\.user.ini|\.htaccess|\.htpasswd|\.env.*|\.project|\.bashrc|\.bash_profile|\.bash_logout|\.DS_Store|\.gitignore|\.gitattributes|LICENSE|README\.md|CLAUDE\.md|CHANGELOG\.md|CHANGELOG|CONTRIBUTING\.md|TODO\.md|FAQ\.md|composer\.json|composer\.lock|package(-lock)?\.json|yarn\.lock|pnpm-lock\.yaml|\.\w+~|\.swp|\.swo|\.bak(up)?|\.old|\.tmp|\.temp|\.log|\.sql(\.gz)?|docker-compose\.yml|docker\.env|Dockerfile|\.csproj|\.sln|Cargo\.toml|Cargo\.lock|go\.mod|go\.sum|phpunit\.xml|phpunit\.xml|pom\.xml|build\.gradl|pyproject\.toml|requirements\.txt|application(-\w+)?\.(ya?ml|properties))$ {
return 404;
}
location ~* /(\.git|\.svn|\.bzr|\.vscode|\.claude|\.idea|\.ssh|\.github|\.npm|\.yarn|\.pnpm|\.cache|\.husky|\.turbo|\.next|\.nuxt|node_modules|runtime)/ {
return 404;
}
location ~ \.well-known {
allow all;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
# --- 安全与防盗链拦截 END ---
# --- 静态资源与缓存 START ---
location ^~ /themes/ {
root /www/wwwroot/api.yunzer.cn/public;
try_files $uri $uri/ =404;
expires 12h;
access_log off;
}
location ~ ^/(template(?:[1-9]\d?|100))/(.*)$ {
alias /www/wwwroot/api.yunzer.cn/public/themes/$1/$2;
try_files $uri $uri/ =404;
expires 12h;
access_log off;
}
location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~* \.(js|css)$ {
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
# --- 静态资源与缓存 END ---
# --- 核心路由与 PHP 解析 START ---
# 1. 根目录的 *.html,交给 ThinkPHP 处理
location ~* ^/(.+\.html)$ {
try_files /themes/template3/$1 /index.php?$query_string;
}
# 2. 唯一且正确的 PHP 解析块(解决了直接下载的问题)
location ~ [^/]\.php(/|$) {
# 引入宝塔的 PHP FastCGI 配置,确保 PHP 正常执行
include enable-php-82.conf;
# 传递自定义子域名参数给 ThinkPHP
fastcgi_param HTTP_SUBDOMAIN $subdomain;
fastcgi_param HTTP_MAIN_DOMAIN yunzer.com.cn;
}
# 3. 统一入口(ThinkPHP 默认伪静态):先找真实文件,找不到再交给 index.php
location / {
# 当访问的路径没有对应静态文件时,将请求回退到 ThinkPHP 前端入口。
# 使用 /index.php$uri 让内部请求变成 /index.php/portfolio 这类形式,
# 从而使 ThinkPHP 能从 pathinfo 中拿到 portfolio,并匹配 Route::get(':page', ...)
# 保留 query_string(用于 ?page=n 之类分页参数)
try_files $uri $uri/ /index.php$uri$is_args$args;
}
# --- 核心路由与 PHP 解析 END ---
access_log /www/wwwlogs/yunzer.com.cn.log;
error_log /www/wwwlogs/yunzer.com.cn.error.log;
}
+10
View File
@@ -6,6 +6,16 @@
RewriteCond %{REQUEST_METHOD} OPTIONS RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L] RewriteRule ^(.*)$ $1 [R=204,L]
# 排除静态文件和特定PHP文件
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/themes/
RewriteCond %{REQUEST_URI} !^/static/
RewriteCond %{REQUEST_URI} !^/storage/
RewriteCond %{REQUEST_URI} !^/favicon.ico$
RewriteCond %{REQUEST_URI} !^/robots.txt$
RewriteRule ^(.*)$ index.php [L]
# 添加 CORS 头 # 添加 CORS 头
Header set Access-Control-Allow-Origin "http://localhost:5173" Header set Access-Control-Allow-Origin "http://localhost:5173"
Header set Access-Control-Allow-Credentials "true" Header set Access-Control-Allow-Credentials "true"

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