信息发布

This commit is contained in:
2025-11-24 16:34:56 +08:00
commit d5491511f7
138 changed files with 17218 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,33 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\AgreementService;
class AgreementController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new AgreementService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\BannerService;
class BannerController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new BannerService();
}
/**
* 轮播图模块
* @return \support\Response
*/
public function getBannerModule()
{
try {
$raw = $this->service->getBannerModule();
$data = $this->transformData($raw, method_exists($this, 'indexFormat')
? [$this, 'indexFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
}
@@ -0,0 +1,51 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostCategoryService;
use support\Request;
class PostCategoryController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostCategoryService();
}
/**
* 分类树
* @param Request $request
* @return \support\Response
*/
public function tree(Request $request)
{
try {
$params = $request->all();
$result = $this->service->getList($params);
return $this->success($result, '获取分类列表');
} catch (\Throwable $e) {
return $this->error($e->getMessage().$e->getFile().$e->getLine());
}
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostCommentService;
class PostCommentController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostCommentService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\model\PostCategory;
use app\common\model\PostMedia;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostService;
class PostController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
if (isset($data['id']))
$data['media'] = PostMedia::where('post_id', $data['id'])->get();
if (isset($data['category_id']))
$data['category_name'] = PostCategory::where('id', $data['category_id'])->value('name')??'';
return $data;
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function showFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
if (isset($data['id']))
$data['media'] = PostMedia::where('post_id', $data['id'])->get();
return $data;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostMediaService;
class PostMediaController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostMediaService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\admin\service\RoleService;
use app\common\traits\crud\CrudControllerTrait;
use support\Request;
class RoleController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new RoleService();
}
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
public function menuResource(Request $request)
{
try {
// $data = $this->validate($this->service->getValidator(), $request->post(), 'allocation');
$data = $request->post();
$res = $this->service->menuResource($request, $data);
return $this->success($res);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\admin\service\SystemsService;
class SystemsController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new SystemsService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
}
+196
View File
@@ -0,0 +1,196 @@
<?php
namespace app\admin\controller;
use app\common\controller\BaseController;
use app\common\model\Corp;
use app\common\service\OssService;
use app\common\traits\crud\CrudControllerTrait;
use app\admin\service\UserService;
use app\crm\model\Customer;
use support\Request;
class UserController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new UserService();
}
/**
* 添加
*
* @param Request $request 请求对象
* @return mixed 返回更新成功的数据或信息
*/
public function store(Request $request)
{
try {
$data = $request->post();
if(mb_strlen($data['mobile']) < 11){
return $this->error('手机号错误');
}
$res = $this->service->serviceCreateUser($request, $data);
return $this->success([], '添加成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 更新
*
* @param Request $request 请求对象
* @return mixed 返回更新成功的数据或信息
*/
public function update(Request $request)
{
try {
$data = $request->post();
$res = $this->service->serviceUpdateUser($request, $data);
return $this->success([], '更新成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 修改密码
* @param Request $request
* @return \support\Response
* @throws \Random\RandomException
*/
public function savePassword(Request $request)
{
try {
$data = $request->all();
if(empty($data['password'])){
return $this->error('密码不能为空');
}
$result = $this->service->savePassword($request,$data);;
return $this->success($result, '编辑成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 更新
*
* @param Request $request 请求对象
* @return mixed 返回更新成功的数据或信息
*/
public function edit(Request $request)
{
try {
$data = $request->post();
$data['id'] = $data['id']??$request->user->uid;
$res = $this->service->serviceUpdate($request, $data);
return $this->success([], '更新成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if(isset($data['records'])){
return $data;
}
$data['sex_txt'] = '';
if (isset($data['sex']) && $data['sex'] == 1){
$data['sex_txt'] = '男';
}elseif(isset($data['sex']) && $data['sex'] == 2){
$data['sex_txt'] = '女';
}
$ossservice = new OssService();
if(isset($data['avatar']) && !empty($data['avatar']) && strpos($data['avatar'], 'http') === false){
//$data['avatar_pull'] = $ossservice->generatePrivateUrl($data['avatar']);
$data['avatar_pull'] = '/proxy/ossurl?file='.$data['avatar'];
}else{
$data['avatar_pull'] = $data['avatar'];
}
switch ($data['status']){
case 1:
$data['status_txt'] = '正常';
break;
case 2:
$data['status_txt'] = '离职';
break;
default:
$data['status_txt'] = '';
break;
}
$data['role_ids'] = [];
$data['system_ids'] = [];
if (isset($data['roles']) && count($data['roles']) > 0) {
foreach ($data['roles'] as $role) {
$data['role_ids'][] = $role['id'];
}
}
if (isset($data['systems']) && count($data['systems']) > 0) {
foreach ($data['systems'] as $system) {
$data['system_ids'][] = $system['id'];
}
}
return $data;
}
/**
* 授权
*
* @param Request $request
* @return mixed
*/
public function authorize(Request $request)
{
try {
$data = $request->post();
$res = $this->service->authorize($request, $data);
return $this->success([], '分配成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 直接授权
*
* @param Request $request
* @return mixed
*/
public function updateResources(Request $request)
{
try {
$data = $request->post();
$res = $this->service->updateResources($request, $data);
return $this->success([], '分配成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function updateSign(Request $request)
{
try {
$data = $request->post();
$this->service->serviceUpdateSign($request, $data);
return $this->success([], '签名成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
}
+94
View File
@@ -0,0 +1,94 @@
<?php
namespace app\admin\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\User;
use support\Db;
class UserLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
public function updateRoleAndSystem(array $data): bool
{
$roleIds = $data['role_ids'] ?? [];
$systemIds = $data['system_ids'] ?? [];
$userId = $data['user_id'] ?? 0;
Db::beginTransaction();
try {
Db::table('user_roles')->where('user_id', $userId)->delete();
if (count($roleIds) > 0) {
$insertDataRole = [];
foreach ($roleIds as $roleId) {
$insertDataRole[] = [
'role_id' => $roleId,
'user_id' => $userId
];
}
$res = Db::table('user_roles')->insert($insertDataRole);
var_dump($res);
}
Db::table('user_systems')->where('user_id', $userId)->delete();
if (count($systemIds) > 0) {
$insertDataSystem = [];
foreach ($systemIds as $systemId) {
$insertDataSystem[] = [
'system_id' => $systemId,
'user_id' => $userId
];
}
Db::table('user_systems')->insert($insertDataSystem);
}
Db::commit();
return true;
} catch (\Throwable $exception) {
Db::rollBack();
return false;
}
}
public function updateUserResources(array $data): bool
{
$userId = $data['user_id'] ?? 0;
$resources = $data['resources'] ?? [];
if (empty($userId)) {
return false;
}
Db::beginTransaction();
try {
// 删除原有资源关联
Db::table('user_resources')->where('user_id', $userId)->delete();
// 批量插入新角色资源关联
if (!empty($resources)) {
$insertData = [];
foreach ($resources as $resourceId) {
$insertData[] = [
'user_id' => $userId,
'resource_id' => $resourceId
];
}
Db::table('user_resources')->insert($insertData);
}
Db::commit();
return true;
} catch (\Throwable $exception) {
Db::rollBack();
return false;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\admin\service;
use app\common\logic\RoleLogic;
use app\common\validate\RoleValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Role;
use support\Db;
class RoleService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Role::class;
/**
* 验证器
*/
protected $validator = RoleValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取角色资源
*/
public function menuResource($request, $data)
{
return (new RoleLogic)->updateRoleResources($data);
}
public function getList()
{
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
return $this->logicListAll($config);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace app\admin\service;
use app\common\validate\SystemsValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Systems;
class SystemsService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Systems::class;
/**
* 验证器
*/
protected $validator = SystemsValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
namespace app\admin\service;
use app\admin\logic\UserLogic;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\User;
use app\common\validate\UserValidate;
use support\exception\BusinessException;
use support\Request;
class UserService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
/**
* 验证器
*/
protected $validator = UserValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/** 更新 */
public function serviceCreateUser(Request $request, array $data = [])
{
$config = $this->buildQueryConfig($request, 'create');
if(isset($data['avatar']) && empty($data['avatar'])){
$data['avatar'] = 'bsh_datax/user/20251021/a4ee815405a1506abeec1af0f738f70f_68f6e76514b66.jpg';
}
if(empty($data['password'])){
$data['password'] = password_hash('12345678', PASSWORD_DEFAULT);
}else{
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
$created = $this->logicCreate($data);
return $this->postProcess('Create', $data);
}
/** 更新 */
public function serviceUpdateUser(Request $request, array $data = [])
{
$data = $this->preProcess('Update', $data, $request);
$config = $this->buildQueryConfig($request, 'update');
$updated = $this->logicUpdateBy($config, $data);
return $this->postProcess('Update', $data);
}
/** 更新 */
public function serviceUpdate(Request $request, array $data = [])
{
unset($data['roles']);
unset($data['avatar_pull']);
$data = $this->preProcess('Update', $data, $request);
$config = $this->buildQueryConfig($request, 'edit');
$updated = $this->logicUpdateBy($config, $data);
return $this->postProcess('Update', $data);
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
break;
case 'index':
// $config['where']['status'] = 1;
$config['order']['id'] = 'desc';
// 关键字
if (isset($p['keyword']) && $p['keyword']) {
$k = '%' . $p['keyword'] . '%';
$config['where']['name'] = ['like', $k];
}
if (isset($p['status']) && $p['status']) {
$config['where']['status'] = $p['status'];
}
if (isset($p['user_id']) && $p['user_id']) {
$config['where']['user_id'] = $p['user_id'];
}
if (isset($p['id']) && $p['id']) {
$config['where']['id'] = $p['id'];
}
if (isset($p['corp_id']) && $p['corp_id']) {
$config['where']['corp_id'] = $p['corp_id'];
}
$config['fields'] = ['id','sn', 'name', 'email', 'mobile', 'avatar', 'nickname', 'is_admin', 'status', 'created_at'];
$config['with']['roles'][] = 'id';
$config['with']['roles'][] = 'name';
$config['with']['systems'][] = 'id';
$config['with']['systems'][] = 'name';
break;
case 'show':
$config['where']['id'] = $p['id'];
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
case 'savePassword':
$config['where']['id'] = $p['id'];
break;
case 'edit':
case 'updateSign':
$config['where']['id'] = $request->user->uid;
break;
}
return $config;
}
/**
* 授权
*
* @param Request $request
* @param array $data
* @return bool
*/
public function authorize(Request $request, array $data = [])
{
return (new UserLogic())->updateRoleAndSystem($data);
}
/**
* 更新用户资源权限
*
* @param Request $request
* @param array $data
* @return bool
*/
public function updateResources(Request $request, array $data = [])
{
return (new UserLogic())->updateUserResources($data);
}
/**
* 修改用户密码
*/
public function savePassword(Request $request, array $data)
{
$config = $this->buildQueryConfig($request, 'savePassword');
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$user = $this->logicUpdateBy($config,$data);
return $user;
}
public function serviceUpdateSign(Request $request, array $data)
{
$config = $this->buildQueryConfig($request, 'updateSign');
$user = $this->logicUpdateBy($config,$data);
return $user;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\AgreementService;
class AgreementController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new AgreementService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace app\common\controller;
use app\common\service\AuthService;
use app\common\validate\UserValidate;
use support\Redis;
use support\Request;
class AuthController extends BaseController
{
/**
* 不需要头信息的方法
*/
protected $noNeedHeaderData = ['feishuAuth'];
protected $noNeedLogin = ['feishuScope', 'login', 'register', 'feishuAuth'];
// 调试
public function feishuAuth(Request $request)
{
try {
$code = $request->all();
return $this->success($code);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function feishuScope(Request $request)
{
try {
$result = (new AuthService())->feishuAuthorize($request->corp_abbr, $request->system_code);
return $this->success($result);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function login(Request $request)
{
try {
$data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_login');
$result = (new AuthService())->login($data);
return $this->success($result, '登录成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function register(Request $request)
{
try {
$data = $this->validate(new UserValidate(), $this->requestData($request), $request->login_type.'_register');
$result = (new AuthService())->register($data);
return $this->success($result, '注册成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function refresh(Request $request)
{
try {
$data = $this->requestData($request);
if (!$data['refresh_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
$result = (new AuthService())->refreshToken($data['refresh_token'], $data['system_code']);
return $this->success($result, '刷新成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage(), 401);
}
}
// public function perm(Request $request)
// {
// try {
// $data = $this->requestData($request);
// if (!$data['access_token'] || !$data['system_code']) return $this->error('参数缺失', 422);
// $result = (new AuthService())->logout($data['access_token'], $data['system_code']);
// return $this->success($result, '已登出');
// } catch (\Throwable $e) {}
// }
// public function verify(Request $request)
// {
// try {
// $token = $request->post('access_token') ?: $request->header('authorization');
// $systemCode = $request->post('system_code');
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
// $payload = (new AuthService())->verifyToken($token, $systemCode);
// return $this->success($payload, '验证成功');
// } catch (\Throwable $e) {
// return $this->error($e->getMessage() ?: '无效或过期的token', 401);
// }
// }
// public function logout(Request $request)
// {
// $token = $request->post('access_token') ?: $request->header('Authorization');
// $systemCode = $request->post('system_code') ?: $request->header('system_code');
// (new AuthService())->logout($token, $systemCode);
// return $this->success([], '已登出');
// }
// public function blockToken(Request $request)
// {
// $token = $request->post('token');
// $systemCode = $request->post('system_code');
// if (!$token || !$systemCode) return $this->error('参数缺失', 422);
// (new AuthService())->blockToken($token, $systemCode);
// return $this->success([], '已强制停用该token');
// }
}
@@ -0,0 +1,33 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\BannerService;
class BannerController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new BannerService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace app\common\controller;
use support\exception\BusinessException;
use app\common\traits\ResponseTrait;
use app\common\traits\DataTransformer;
use think\Validate;
use support\Request;
class BaseController
{
/**
* 响应处理、数据格式化处理
*/
use ResponseTrait, DataTransformer;
/**
* 验证器
* @var Validate
*/
protected mixed $validator = null;
/**
* @var Model
*/
protected $model = null;
/**
* 不登录不鉴权
* @var array
*/
protected $noNeedLogin = [];
/**
* 登录不鉴权
* @var array
*/
protected $noNeedAuth = [];
/**
* 不用头部数据
* @var array
*/
protected $noNeedHeaderData = [];
/**
* 数据限制
* null 不做限制,任何管理员都可以查看该表的所有数据
* auth 管理员能看到自己以及自己的子管理员插入的数据
* personal 管理员只能看到自己插入的数据
* @var string
*/
protected $dataLimit = null;
/**
* 数据限制字段
*/
protected $dataLimitField = 'admin_id';
/**
* 验证数据
*
* @param Validate $validator 验证器实例
* @param array $data 待验证的数据
* @param string $scene 验证场景
* @return array 验证后的数据
* @throws BusinessException 验证失败时抛出业务异常
*/
public function validate(Validate $validator, array $data, string $scene = ''): array
{
// 如果指定了验证场景,并且验证器支持场景方法,则设置验证场景
if ($scene && method_exists($validator, 'scene')) {
$validator->scene($scene);
}
// 执行数据验证
if (!$validator->check($data)) {
// 验证失败,抛出业务异常,异常信息为验证器返回的错误信息
throw new BusinessException($validator->getError());
}
// 验证成功,返回原始数据
return $data;
}
/**
* 参数处理
*/
public function requestData(Request $request)
{
return $request->all() + $request->basic_data;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostCategoryService;
class PostCategoryController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostCategoryService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
@@ -0,0 +1,44 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\model\User;
use app\common\service\OssService;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostCommentService;
class PostCommentController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostCommentService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
if (isset($data['user_id'])){
$ossservice = new OssService();
$data['user'] = User::where('id', $data['user_id'])->select(['name', 'avatar', 'openid'])->first();
// $data['user']['avatar'] = $data['user']['avatar'];
if(!str_contains($data['user']['avatar'], 'http') && !empty($data['user']['avatar'])){
$data['user']['avatar_pull'] = $ossservice->generatePrivateUrl($data['user']['avatar']);
}
}
return $data;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\model\PostMedia;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostService;
class PostController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function indexFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
if (isset($data['id']))
$data['media'] = PostMedia::where('post_id', $data['id'])->get();
return $data;
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function showFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
if (isset($data['id']))
$data['media'] = PostMedia::where('post_id', $data['id'])->get();
return $data;
}
}
@@ -0,0 +1,33 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\PostMediaService;
class PostMediaController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new PostMediaService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
@@ -0,0 +1,90 @@
<?php
namespace app\common\controller;
use app\admin\service\RoleService;
use app\common\service\BrandSeriesService;
use app\common\service\BrandService;
use app\common\service\SysAccountService;
use app\common\service\SysCurrencyService;
use app\common\service\SysDictItemService;
use app\common\service\SysDictService;
use app\common\service\SysPortService;
use app\common\service\VehColorService;
use app\common\service\VehModificationService;
use support\Request;
use app\common\service\CorpService;
use app\common\service\CorpFeishuAppService;
use app\common\service\SystemsService;
use app\common\service\UserService;
use support\Cache;
use app\common\service\PermService;
use app\common\service\UserGroupService;
class PresetController extends BaseController
{
protected $noNeedLogin = ['systems'];
protected $noNeedHeaderData = ['systems'];
public function systems()
{
try {
$result = (new SystemsService())->getList();
return $this->success($result, '获取系统列表');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function users()
{
try {
$result = (new UserService())->getList();
return $this->success($result, '获取用户列表');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function roles()
{
try {
$result = (new RoleService)->getList();
return $this->success($result, '获取角色列表');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function cacheClear()
{
try {
Cache::clear();
return $this->success([], '操作成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function menus(Request $request)
{
try {
$uid = $request->user->uid;
$systemCode = $request->system_code;
$permService = new PermService();
$menuTree = $permService->getMenuTree($uid, $systemCode);
$permissionCodes = $permService->getPermissionCodes($uid, $systemCode);
$result = [
'menu' => $menuTree,
'permissions' => $permissionCodes
];
return $this->success($result, '获取菜单权限');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
}
@@ -0,0 +1,82 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\ResourceService;
use support\Request;
class ResourceController extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new ResourceService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
protected function allFormat($data)
{
if (isset($data['created_at']))
$data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
if (isset($data['updated_at']))
$data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
return $data;
}
/**
* 资源树形结构
*/
public function listTree(Request $request)
{
try {
$raw = $this->service->serviceListAll($request);
$data = $this->transformData($raw, method_exists($this, 'allFormat')
? [$this, 'allFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 菜单
*/
public function menuList(Request $request)
{
try {
$raw = $this->service->serviceMenuList($request);
$data = $this->transformData($raw, method_exists($this, 'allFormat')
? [$this, 'allFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 菜单
*/
public function menuTreeList(Request $request)
{
try {
$raw = $this->service->serviceMenuTreeList($request);
$data = $this->transformData($raw, method_exists($this, 'allFormat')
? [$this, 'allFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage().$e->getFile().$e->getLine());
}
}
}
+299
View File
@@ -0,0 +1,299 @@
<?php
namespace app\common\controller;
use app\common\service\OssService;
use app\common\service\UploadedFilesService;
use app\common\service\UploadService;
use app\common\traits\ResponseTrait;
use support\Log;
use support\Request;
use support\exception\BusinessException;
use Shopwwi\WebmanFilesystem\Facade\Storage;
class UploadController
{
use ResponseTrait;
protected $noNeedLogin = ['proxy'];
protected $noNeedHeaderData = ['proxy'];
public function upload(Request $request)
{
try {
$scene = $request->input('scene', 'image');
$file = $request->file('file');
if ($file && $file->isValid()) {
$fileName = md5($file->getUploadName() . time()).'.'.$file->getUploadExtension();
$pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.$fileName;
$fullPathUrl = public_path() . $pathUrl;
$file->move($fullPathUrl);
$data = [
'domain' => getenv('MAIN_DOMAIN'),
'file_url' => $pathUrl,
'full_url' => getenv('MAIN_DOMAIN') . $pathUrl,
'scene' => $scene,
];
// 记录文件信息到数据库
try {
$uploadedFilesService = new UploadedFilesService();
$partition = $scene;
$category = $request->input('category', 'general');
$fileInfo = [
'file_name' => basename($fileName),
'file_path' => $pathUrl,
'file_size' => $file->getSize(),
'file_type' => $file->getUploadMimeType(),
'original_name' => $file->getUploadName(),
];
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
$data['file_id'] = $recordResult['id'];
$data['partition'] = $partition;
$data['category'] = $category;
} catch (\Throwable $e) {
// 记录失败不影响上传结果,只记录日志
Log::error('记录文件信息失败: ' . $e->getMessage());
}
return $this->success($data, 'upload success');
}
return $this->error('file not found');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
// 上传单文件附件
public function uploadFile(Request $request)
{
try {
$scene = $request->input('scene');
$file = $request->file('file');
if (!$file || !$file->isValid()) {
return $this->error('上传失败');
}
if (empty($scene)) {
// return $this->error('上传场景不能为空');
$sceneUrl = '';
}else{
$sceneUrl = $scene . '/';
}
$originalName = $file->getUploadName();
$result = Storage::adapter('oss')
->path('bsh_datax/' . $sceneUrl . date('Ymd'))
->size(1024*1024*100)
->upload($file);
$ossservice = new OssService();
$data = [
'original_name' => $originalName,
'file_name' => $result->file_name,
'file_url' => $ossservice->generatePrivateUrl($result->file_name)
];
// 记录文件信息到数据库
$category = $request->input('category', 'general');
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
$data = array_merge($data, $res);
return $this->success($data);
}catch (\Throwable $e){
return $this->error($e->getMessage());
}
}
// 上传单文件图片
public function uploadImage(Request $request)
{
try {
$scene = $request->input('scene');
$file = $request->file('image');
if (!$file || !$file->isValid()) {
return $this->error('上传失败');
}
if (empty($scene)) {
// return $this->error('上传场景不能为空');
$sceneUrl = '';
}else{
$sceneUrl = $scene . '/';
}
$result = Storage::adapter('oss')
->path('bsh_datax/' . $sceneUrl . date('Ymd'))
->size(1024*1024*100)
->extYes(['image/jpeg','image/png'])
->extNo(['image/gif'])
->upload($file);
$ossservice = new OssService();
$data = [
'href' => $result->file_name,
'alt' => '',
'url' => $ossservice->generatePrivateUrl($result->file_name)
];
// 记录文件信息到数据库
$category = $request->input('category', 'general');
$res = $this->recordFileInfo($result,$request,$file,$scene,$category);
$data = array_merge($data, $res);
return json(['errno' => 0, 'data' => $data]);
// return $this->success($data);
} catch (\Throwable $e) {
return json(['errno' => 1, 'message' => $e->getMessage()]);
// return $this->error($e->getMessage());
}
}
public function recordFileInfo($result, $request, $file, $scene, $category)
{
try {
$uploadedFilesService = new UploadedFilesService();
$partition = $scene;
$fileInfo = [
'file_name' => basename($result->file_name),
'file_path' => $result->file_name, // OSS存储路径
'file_size' => $file->getSize(),
'file_type' => $file->getUploadMimeType(),
'original_name' => $file->getUploadName(),
];
$partition = !empty($partition)?$partition:'other';
$category = !empty($category)?$category:'other';
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, $request, $partition, $category);
$data = [];
$data['file_id'] = $recordResult['id'];
$data['partition'] = $partition;
$data['category'] = $category;
return $data;
} catch (\Throwable $e) {
// 记录失败不影响上传结果,只记录日志
Log::error('记录文件信息失败: ' . $e->getMessage());
return [];
}
}
/**
* 获取文件信息
*
* @param Request $request
* @return \support\Response
*/
public function getFileInfo(Request $request)
{
try {
$fileId = $request->input('id');
if (!$fileId) {
return $this->error('文件ID不能为空');
}
$uploadedFilesService = new UploadedFilesService();
$fileInfo = $uploadedFilesService->getFileInfo($fileId);
if (!$fileInfo) {
return $this->error('文件不存在');
}
return $this->success($fileInfo);
} catch (\Throwable $e) {
return $this->error('获取文件信息失败: ' . $e->getMessage());
}
}
/**
* 删除文件
*
* @param Request $request
* @return \support\Response
*/
public function deleteFile(Request $request)
{
try {
$fileId = $request->input('id');
$path = $request->input('path');
$uploadedFilesService = new UploadedFilesService();
if(!empty($path) && empty($fileId)){
$where = ['file_path' => $path];
$file = $uploadedFilesService->getFile($where);
$fileId = $file->id;
}elseif(empty($path) && !empty($fileId)){
$where = ['id' => $fileId];
$file = $uploadedFilesService->getFile($where);
$path = $file->file_path;
}
if (!$fileId) {
return $this->error('文件ID与路径不能为空');
}
// 删除 OSS 上的文件
$ossService = new OssService();
$ossService->deleteFile($path);
$result = $uploadedFilesService->deleteFile($fileId);
if ($result) {
return $this->success([], '文件删除成功');
} else {
return $this->error('文件删除失败');
}
} catch (\Throwable $e) {
return $this->error('文件删除失败: ' . $e->getMessage());
}
}
/**
* 获取分区配置
*
* @param Request $request
* @return \support\Response
*/
public function getPartitionConfig(Request $request)
{
try {
$uploadedFilesService = new UploadedFilesService();
$config = $uploadedFilesService->getPartitionConfig();
return $this->success($config);
} catch (\Throwable $e) {
return $this->error('获取分区配置失败: ' . $e->getMessage());
}
}
public function proxy(Request $request)
{
$filePath = $request->get('file'); // 原OSS文件路径
try {
// 生成签名URL
$signedUrl = (new \app\common\service\OssService())->generatePrivateUrl($filePath);
// 获取图片数据并返回
$imageData = file_get_contents($signedUrl);
// 根据文件类型设置Content-Type
$mime = mime_content_type('data://application/octet-stream;base64,'.base64_encode($imageData));
return response($imageData, 200, [
'Content-Type' => $mime,
'Cache-Control' => 'public, max-age=600' // 客户端缓存10分钟
]);
} catch (\Exception $e) {
return response('Image not found '.$e->getMessage(), 404);
}
}
}
+193
View File
@@ -0,0 +1,193 @@
<?php
namespace app\common\controller;
use app\common\controller\BaseController;
use app\common\service\OssService;
use app\common\traits\crud\CrudControllerTrait;
use app\common\service\UserService;
use app\common\service\WechatService;
use support\Request;
class UserController extends BaseController
{
protected $noNeedLogin = ['getQrUrl', 'getUserInfo', 'bindUser'];
protected $noNeedHeaderData = ['getQrUrl', 'getUserInfo', 'bindUser'];
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new UserService();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
/**
* 获取微信二维码
*/
public function getQrUrl()
{
try {
$result = $this->service->makeQrUrl();
return $this->success($result);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
/**
* 获取用户信息
*/
public function getUserInfo(Request $request)
{
try {
$params = $request->all();
$result = $this->service->userInfo($params['code'], $params['state']);
unset($result['password']);
if (!isset($result['avatar'])){
return $this->success($result);
}
$ossservice = new OssService();
$result['avatar_pull'] = $result['avatar'];
if(strpos($result['avatar'], 'http') === false && !empty($result['avatar'])){
$result['avatar_pull'] = $ossservice->generatePrivateUrl($result['avatar']);
}
return $this->success($result);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
/**
* 用户信息(登录后调用)
*/
public function info(Request $request)
{
try {
$uid = $request->user->uid;
$result = (new UserService())->getUserInfo($uid);
return $this->success($result, '获取用户信息');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 绑定用户
*/
public function bindUser(Request $request)
{
try {
$params = $request->all();
$result = $this->service->bindUser($params);
unset($result['password']);
return $this->success($result);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
/**
* 修改密码
* @param Request $request
* @return \support\Response
* @throws \Random\RandomException
*/
public function savePassword(Request $request)
{
try {
$uid = $request->user->uid;
$data = $request->all();
$result = (new UserService())->savePassword($uid,$data);;
return $this->success($result, '编辑成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
public function wechatLogin($request)
{
try {
$params = $request->all();
$result = $this->service->wechatLogin($params['code'], $params['state']);
return $this->success($result);
} catch (\Throwable $th) {
return $this->error($th->getMessage());
}
}
public function wechatLogin1($request)
{
$loginCode = $request->post('login_code');
$phoneCode = $request->post('phone_code');
if (!$loginCode || !$phoneCode) {
return json(['code'=>0, 'msg'=>'缺少参数']);
}
// 1) code2Session
$session = $this->code2Session($loginCode);
if (!isset($session['openid'])) {
return json(['code'=>0, 'msg'=>'登录失败', 'data'=>$session]);
}
$openid = $session['openid'];
// 2) access_token
$tokenData = $this->getAccessToken();
$token = $tokenData['access_token'];
// 3) 换手机号
$phoneData = $this->getPhoneNumber($token, $phoneCode);
if (!isset($phoneData['phone_info']['phoneNumber'])) {
return json(['code'=>0, 'msg'=>'获取手机号失败', 'data'=>$phoneData]);
}
$phone = $phoneData['phone_info']['phoneNumber'];
// 4) 写入数据库或登录逻辑
// 比如自动注册
$user = User::firstOrCreate(['openid'=>$openid], [
'phone' => $phone
]);
return json([
'code' => 1,
'msg' => '登录成功',
'data' => [
'openid' => $openid,
'phone' => $phone,
'token' => md5($openid.time()), // 你自己的登录 token
]
]);
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\Agreement;
class AgreementLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Agreement::class;
}
+572
View File
@@ -0,0 +1,572 @@
<?php
namespace app\common\logic;
use app\common\model\Corp;
use app\common\model\CorpFeishuApp;
use app\common\model\Resource;
use app\common\model\UserTokenLog;
use app\common\model\Systems;
use app\common\model\User;
use app\common\model\UserSn;
use app\common\utils\Feishu;
use app\common\utils\Jwt;
use support\Redis;
use support\exception\BusinessException;
use support\Log;
class AuthLogic
{
/**
* 账号密码登录
*/
public function uPwdLogin(array $data): array
{
$user = $this->validateUser($data['account'], $data['password']);
$system = $this->validateSystem($data['system_code']);
$this->validateSystemPermission($user, $system);
return $this->generateTokensAndReturn($user, $system, $data);
}
/**
* 飞书登录
* 如果用户不存在则新增用户信息
*/
public function feishuLogin(array $data): array
{
$feishuUserInfo = $this->getFeishuUserInfo($data);
$user = $this->getOrCreateUser($feishuUserInfo);
$system = $this->validateSystem($data['system_code']);
if ($user->isLogin) {
$this->validateSystemPermission($user, $system);
}
return $this->generateTokensAndReturn($user, $system, $data);
}
/**
* 注册
*/
public function register(array $data)
{
try {
// 获取可用用户编号
$sn = $this->getAvailableUserSn();
$data['sn'] = $sn->user_sn;
// 对密码进行哈希处理
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
// 处理账户
$data = $data + normalizeAccount($data['account']);
// 创建用户记录
$user = $this->createUser($data);
// 更新用户编号的用户 ID
$this->updateUserId($user->id, $sn->user_sn);
$system = $this->validateSystem($data['system_code']);
return $this->generateTokensAndReturn($user, $system, $data);
} catch (\Throwable $e) {
throw $e;
}
}
/**
* 查找或创建飞书用户
*/
private function getOrCreateUser(array $feishuUserInfo)
{
try {
$phone = $this->sanitizePhone($feishuUserInfo['mobile'] ?? '');
$email = $feishuUserInfo['enterprise_email'];
// $user = User::where('corp_id', $feishuUserInfo['corp_id'])
// ->where(function($query) use ($phone, $email) {
// $query->where('phone', $phone)
// ->orWhere('email', $email);
// })
// ->first();
$userByPhone = User::where('corp_id', $feishuUserInfo['corp_id'])
->where('phone', $phone)
->where('phone', '!=', '')
->first();
$userByEmail = User::where('corp_id', $feishuUserInfo['corp_id'])
->where('email', $email)
->where('email', '!=', '')
->first();
$user = $userByPhone ?: $userByEmail;
if (!$user) {
// 获取编号
$userSn = $this->getAvailableUserSn();
// 创建用户
$user = User::create([
'name' => $feishuUserInfo['name'],
'avatar' => $feishuUserInfo['avatar_middle'],
'phone' => $this->sanitizePhone($feishuUserInfo['mobile'] ?? ''),
'email' => $feishuUserInfo['enterprise_email'],
'union_id' => $feishuUserInfo['union_id'],
'user_id' => $feishuUserInfo['user_id'],
'open_id' => $feishuUserInfo['open_id'],
'tenant_key' => $feishuUserInfo['tenant_key'],
'corp_id' => $feishuUserInfo['corp_id'],
'password' => password_hash($userSn->user_sn, PASSWORD_DEFAULT)
]);
// 更新使用
$this->updateUserId($user->id, $userSn->user_sn);
// 更新编号
if (!$user->update(['sn'=>$userSn->user_sn])) {
throw new BusinessException('编号绑定失败');
}
// 是否登陆
$user->isLogin = false;
$options = [
'corp_id' => $user->corp_id,
'user_id' => $user->id
];
\Webman\RedisQueue\Redis::send('perm-agt', $options);
} else {
$updated = [
'union_id' => $feishuUserInfo['union_id'],
'user_id' => $feishuUserInfo['user_id'],
'open_id' => $feishuUserInfo['open_id'],
'tenant_key' => $feishuUserInfo['tenant_key']
];
if ($phone && $phone == $user->phone) {
$updated['phone'] = $phone;
}
if ($phone && $phone == $user->phone) {
$updated['email'] = $email;
}
// 更新飞书信息
$user->update($updated);
// 是否登陆
$user->isLogin = true;
}
return $user;
} catch (\Throwable $e) {
throw $e;
}
}
/**
* 过滤和格式化手机号
*/
private function sanitizePhone($mobile)
{
return isset($mobile) ? str_replace('+86', '', $mobile) : '';
}
/**
* 生成并记录token,返回包含token的响应数据
*/
private function generateTokensAndReturn(User $user, Systems $system, array $data): array
{
$payload = $this->buildJwtPayload($user, $system);
$token = $this->generateAccessToken($payload);
$refreshToken = $this->generateRefreshToken($user->id, $data['system_code']);
$homeUrl = $this->getHome($system->id);
// 记录 token 到 Redis 和 MySQL
$this->recordUserToken($user->id, $token, $data['system_code']);
return [
'access_token' => $token,
'refresh_token' => $refreshToken,
'expires_in' => config('jwt.jwt_expiration'),
'system_code' => $data['system_code'],
'login_type' => $data['login_type'],
'home_url' => $homeUrl,
// 'payload' => $payload,
];
}
/**
* 获取飞书用户信息
*/
private function getFeishuUserInfo(array $data)
{
$certificate = $this->feishuAppInfo($data['corp_abbr'], $data['system_code']);
$feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']);
$userAccessToken = $feishu->userAccessToken($data['feishu_code'], $certificate['r_url']);
$userInfo = $feishu->userInfo($userAccessToken['access_token']);
if ($userInfo['code'] != 0) {
throw new BusinessException('授权失败,请重试');
}
return $userInfo['data'] + $data + $certificate;
}
/**
* 更新用户编号
*/
private function updateUserId($userId, $userSn)
{
$updated = UserSn::where('user_sn', $userSn)
->update(['user_id' => $userId]);
if ($updated === 0) {
throw new BusinessException('用户编号不存在或更新失败');
}
return $updated;
}
/**
* 记录用户token到 Redis 和 MySQL
*/
protected function recordUserToken(int $userId, string $token, string $systemCode)
{
$request = request();
$ip = $request ? $request->getRealIp() : '';
$ua = $request ? $request->header('user-agent', '') : '';
$now = time();
$expireAt = $now + config('jwt.jwt_expiration');
// Redis Set 用户token集合
Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token);
Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration'));
// Redis Hash token元信息
$meta = [
'system_code' => $systemCode,
'login_ip' => $ip,
'user_agent' => $ua,
'login_time' => $now,
'expired_at' => $expireAt,
];
Redis::hMSet("user_token_meta:{$token}", $meta);
Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration'));
// 可选:MySQL 持久化
try {
UserTokenLog::create([
'user_id' => $userId,
'token' => $token,
'system_code'=> $systemCode,
'login_ip' => $ip,
'user_agent' => $ua,
'login_time' => $now,
'expired_at' => $expireAt,
'is_valid' => 1,
]);
} catch (\Throwable $e) {
Log::error('Token log write failed: '.$e->getMessage());
}
}
/**
* 校验token有效性
*/
public function verifyToken(string $token, string $systemCode): array
{
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
$payload = (new Jwt())->verifyToken($token);
// 校验黑名单
if (Redis::has("token_blacklist:{$token}")) {
throw new BusinessException('token已失效', 403);
}
// 强制校验aud(系统隔离)
if ($systemCode && $payload['aud'] != $systemCode && $payload['aud'] != ($payload['system_code'] ?? '')) {
throw new BusinessException('token不属于当前系统', 403);
}
return (array)$payload;
}
/**
* 获取飞书应用信息
*/
public function feishuAppInfo(string $corpAbbr, string $systemCode)
{
$corpInfo = $this->validateCorp($corpAbbr);
$systemInfo = $this->validateSystem($systemCode);
$fsAppInfo = $this->validateFeishuApp($corpInfo->id, $systemInfo->id);
return [
'app_id' => $fsAppInfo->app_id,
'app_secret' => $fsAppInfo->app_secret,
'r_url' => $fsAppInfo->r_url,
'scope' => $fsAppInfo->scopes,
'state' => $fsAppInfo->state,
'abbr' => $corpAbbr,
'corp_id' => $corpInfo->id,
'system_id' => $systemInfo->id
];
}
/**
* 验证用户信息
*/
private function validateUser(string $account, string $password)
{
// $user = User::byAccount($account)->first();
$user = User::byAccount($account)->first();
if (!$user || !password_verify($password, $user->password)) {
throw new BusinessException('账号或密码错误', 401);
}
if ($user->status != 1) {
throw new BusinessException('账号已禁用', 403);
}
return $user;
}
/**
* 验证系统信息
*/
private function validateSystem(string $systemCode)
{
$system = Systems::where('code', $systemCode)->first();
if (!$system) {
throw new BusinessException('系统不存在', 403);
}
return $system;
}
/**
* 验证公司信息
*/
private function validateCorp(string $corpAbbr)
{
$corp = Corp::where('abbr', $corpAbbr)->first();
if (!$corp) {
throw new BusinessException('公司不存在', 403);
}
return $corp;
}
/**
* 验证飞书应用信息
*/
private function validateFeishuApp(int $corpId, int $systemId)
{
$feishuAppInfo = CorpFeishuApp::where('corp_id', $corpId)->where('system_id', $systemId)->first();
if (!$feishuAppInfo) {
throw new BusinessException('飞书应用不存在', 403);
}
return $feishuAppInfo;
}
/**
* 验证用户系统权限
*/
private function validateSystemPermission(User $user, Systems $system)
{
if ($system->is_role) {
$systemIds = $user->systems->pluck('id')->toArray();
if (!in_array($system->id, $systemIds)) {
throw new BusinessException('无该系统权限', 403);
}
}
}
/**
* 获取可用的用户编号
*
* @return \app\common\model\UserSn 用户编号模型实例
* @throws BusinessException 当未找到适用编号时抛出
*/
private function getAvailableUserSn(): \app\common\model\UserSn
{
$userSnModel = new UserSn();
$sn = $userSnModel->where('user_id', '=', 0)->inRandomOrder()->first();
if (!$sn) {
Log::error('用户注册时未找到适用编号');
throw new BusinessException('未找到适用编号');
}
return $sn;
}
/**
* 创建用户记录
*
* @param array $data 包含用户信息的数组
* @return \app\common\model\User 用户模型实例
*/
private function createUser(array $data): \app\common\model\User
{
try {
return User::create($data);
} catch (\Throwable $e) {
Log::error('用户注册时创建用户记录失败', [
'data' => $data,
'error' => $e->getMessage()
]);
throw new BusinessException('用户注册失败,请稍后重试', 500);
}
}
/**
* JWT payload 构建
* 可扩展 payload 字段
*/
private function buildJwtPayload(User $user, ?Systems $system = null): array
{
$now = time();
$exp = $now + config('jwt.jwt_expiration');
$system_ids = $user->systems ? $user->systems->pluck('id')->toArray() : [];
$role_ids = $user->roles ? $user->roles->pluck('id')->toArray() : [];
return [
'sub' => 'user_' . $user->id,
'aud' => $system->code,
'iat' => $now,
'nbf' => $now,
'exp' => $exp,
'typ' => 'access',
'uid' => $user->id,
'uname' => $user->name,
'role_ids' => $role_ids,
'system_ids' => $system_ids
];
}
/**
* JWT签发
*/
private function generateAccessToken(array $payload)
{
return (new Jwt())->generateToken($payload);
}
/**
* 刷新令牌生成
*/
public function generateRefreshToken(int $userId, string $systemCode)
{
$refreshToken = bin2hex(random_bytes(32));
// user_id+system_code只允许一个refresh_token生效
Redis::set("refresh_token:{$userId}:{$systemCode}", $refreshToken, config('jwt.jwt_refresh_token_expiration'));
// 建立refresh_token到user_id+system_code映射,便于校验
Redis::set("refresh_token_map:{$refreshToken}:{$systemCode}", $userId, config('jwt.jwt_refresh_token_expiration'));
return $refreshToken;
}
/**
* 刷新token
* @param string $refreshToken
* @param string|null $systemCode 可选,指定系统
* @return array
*/
public function refreshToken(string $refreshToken, ?string $systemCode = null): array
{
if (!$systemCode) throw new BusinessException('缺少system_code', 422);
// 校验refresh_token
$userId = Redis::get("refresh_token_map:{$refreshToken}:{$systemCode}");
if (!$userId) {
throw new BusinessException('refresh_token无效或已过期', 401);
}
$validToken = Redis::get("refresh_token:{$userId}:{$systemCode}");
if ($validToken !== $refreshToken) {
throw new BusinessException('refresh_token无效或已过期', 401);
}
$user = User::find($userId);
if (!$user || $user->status != 1) {
throw new BusinessException('用户无效', 403);
}
$system = $this->validateSystem($systemCode);
$this->validateSystemPermission($user, $system);
$payload = $this->buildJwtPayload($user, $system);
$token = $this->generateAccessToken($payload);
$refreshToken = $this->generateRefreshToken($user->id, $systemCode);
$this->recordUserToken($user->id, $token, $systemCode);
return [
'access_token' => $token,
'refresh_token' => $refreshToken,
'expires_in' => config('jwt.jwt_expiration'),
// 'payload' => $payload,
];
}
/**
* 登出/失效token
*/
public function logout(?string $token, ?string $systemCode = null)
{
if ($token) {
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
$payload = null;
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
$userId = $payload['uid'] ?? null;
$aud = $payload['aud'] ?? null;
$sc = $systemCode ?? $aud;
if ($userId && $sc) {
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
}
Redis::del("user_token_meta:{$token}");
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
}
}
/**
* 强制拉黑token
*/
public function blockToken(string $token, ?string $systemCode = null)
{
if (!$token) {
throw new BusinessException('token不能为空', 422);
}
Redis::set("token_blacklist:{$token}", 1, config('jwt.jwt_expiration'));
$payload = null;
try { $payload = (new Jwt())->verifyToken($token); } catch (\Throwable $e) {}
$userId = $payload['uid'] ?? null;
$aud = $payload['aud'] ?? null;
$sc = $systemCode ?? $aud;
if ($userId && $sc) {
Redis::sRem("user_tokens:{$userId}:{$sc}", $token);
}
Redis::del("user_token_meta:{$token}");
UserTokenLog::where('token', $token)->update(['is_valid'=>0]);
}
public function getHome($systemId)
{
$info = Resource::where('system_id', $systemId)
->where('is_home', 1)
->first();
if (!$info) {
return '/dashboard/console';
}
// 递归拼接 URL
$url = $this->buildParentUrl($info);
return $url ?: '/dashboard/console';
}
/**
* 递归构建父级 URL
* @param Resource $resource
* @return string
*/
protected function buildParentUrl(Resource $resource)
{
$url = $resource->url ?? '';
// 如果存在父级,则递归拼接
if ($resource->parent_id != 0) {
$parent = Resource::find($resource->parent_id);
if ($parent) {
$parentUrl = $this->buildParentUrl($parent);
$url = $parentUrl . ($url ? '/' . ltrim($url, '/') : '');
}
}
return $url;
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\Banner;
class BannerLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Banner::class;
}
+144
View File
@@ -0,0 +1,144 @@
<?php
namespace app\common\logic;
use app\common\model\User;
use app\common\model\Resource;
use app\common\model\Systems;
class PermLogic
{
/**
* 获取用户所有资源(合并角色+直接分配),并加入公共菜单,排序按sort字段
*/
public function getUserResources(int $userId, string $systemCode)
{
$system = Systems::where('code', $systemCode)->first();
// 获取用户及其关联角色资源和直接分配的资源
$user = User::with(['roles.resources', 'resources'])->find($userId);
if (!$user) {
return collect();
}
$all = collect();
// 遍历用户的所有角色,合并每个角色所拥有的资源
foreach ($user->roles as $role) {
$all = $all->merge(
// 过滤当前角色的资源列表
$role->resources->filter(function ($resource) use($system) {
// 资源过滤条件:
// 1. !$resource->no_auth:资源需要权限认证(排除公共资源)
// 2. 系统匹配条件:
// - $system->id == $resource->system_id:资源属于当前系统
// - $resource->system_id == 0:资源为全局公共资源(所有系统可用)
return !$resource->no_auth && ($system->id == $resource->system_id || $resource->system_id == 0);
})
);
}
// 合并用户直接分配的资源,排除不需要权限认证的资源
$all = $all->merge(
$user->resources->filter(function ($resource) {
// 只保留需要权限认证的资源(或公共资源)
return !$resource->no_auth;
})
);
// 加入公共菜单,公共菜单不管是否授权,所有人都能访问
$publicResources = Resource::where('no_auth', 1)
->orderBy('sort', 'asc') // 按 sort 字段升序排序
->get();
// 合并所有公共资源
$all = $all->merge($publicResources);
// 返回唯一的资源列表,并按 id 排序
// 使用 sortBy('sort') 来确保最终结果根据 'sort' 字段排序
return $all->unique('id')->values()->sortBy('sort');
}
/**
* 构建目录/菜单树
*
* @param \Illuminate\Support\Collection $resources 全部资源集合
* @param int $parentId 当前父级资源 ID
* @param string $parentPath 父级完整路径(用于递归)
* @return array
*/
public function buildTree($resources, int $parentId = 0, string $parentPath = ''): array
{
$tree = [];
foreach ($resources as $node) {
if ($node->parent_id !== $parentId) continue;
// 拼接完整路径
$currentPath = $parentPath;
if ($currentPath && substr($currentPath, -1) === '/') {
$currentPath = rtrim($currentPath, '/');
}
if (!empty($node->url)) {
if (!$currentPath) {
$currentPath = $node->url;
} else {
$url = ltrim($node->url, '/');
$currentPath = $currentPath . '/' . $url;
}
}
// 子节点递归
$children = [];
if ($node->type === 'directory') {
$children = $this->buildTree($resources, $node->id, $currentPath);
}
// 权限
$authList = $resources
->filter(fn($r) => $r->type === 'permission' && $r->parent_id === $node->id)
->map(fn($r) => [
'id' => $r->id,
'title' => $r->name,
'auth_mark' => $r->code,
])->values()->all();
// 判断是否有子级菜单(非权限)
$hasChildrenMenu = collect($resources)->where('parent_id', $node->id)
->whereIn('type', ['directory', 'menu'])->isNotEmpty();
$componentValue = $hasChildrenMenu ? '/index/index' : $currentPath;
$tree[] = [
'id' => $node->id,
'name' => $node->name,
'path' => $node->url,
'component' => $componentValue,
'meta' => [
'title' => $node->name,
'icon' => $node->icon,
'keepAlive' => $node->keep_alive == 1 ? true : false,
'authList' => $authList,
'openType' => $node->open_type,
'isHide' => $node->is_hide == 0 ? false : true,
'isHideTab' => $node->is_hide_tab == 0 ? false : true
],
'children' => $children,
];
}
return $tree;
}
/**
* 获取用户所有权限code
*/
public function getPermissionCodes(int $userId, string $systemCode): array
{
$resources = $this->getUserResources($userId, $systemCode);
return $resources
->filter(fn($res) => $res->type === 'permission')
->pluck('code')
->unique()
->values()
->all();
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\PostCategory;
class PostCategoryLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostCategory::class;
public function getList($where)
{
return PostCategory::where('status', 1)->where($where)->orderBy('sort','desc')->get();
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\PostComment;
class PostCommentLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostComment::class;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\Post;
class PostLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Post::class;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\PostMedia;
class PostMediaLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostMedia::class;
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\Resource;
use support\Request;
class ResourceLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Resource::class;
public function logicListAllResources(Request $request, array $config)
{
$data = $this->logicListAll($config);
if ($request->get('role_id')) {
$selected = (new RoleLogic())->getRoleResources($request->get('role_id'));
} else if ($request->get('user_id')) {
$selected = (new RoleLogic())->getUserResources($request->get('user_id'));
} else {
$selected = [];
}
foreach ($data as &$item) {
$item['selected'] = in_array($item['id'], $selected) ? true : false;
}
return $data;
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace app\common\logic;
use support\Db;
class RoleLogic
{
/**
* 更新角色资源权限
*
* @param array $data 包含 role_id 和 resources 的数据
* @return bool 操作是否成功
*/
public function updateRoleResources(array $data): bool
{
$roleId = $data['role_id'] ?? 0;
$resources = $data['resources'] ?? [];
if (empty($roleId)) {
return false;
}
Db::beginTransaction();
try {
// 删除原有角色资源关联
Db::table('role_resources')->where('role_id', $roleId)->delete();
// 批量插入新角色资源关联
if (!empty($resources)) {
$insertData = [];
foreach ($resources as $resourceId) {
$insertData[] = [
'role_id' => $roleId,
'resource_id' => $resourceId
];
}
Db::table('role_resources')->insert($insertData);
}
Db::commit();
return true;
} catch (\Throwable $exception) {
Db::rollBack();
return false;
}
}
/**
* 获取角色资源
*
* @param int $roleId 角色ID
* @return array 资源ID列表
*/
public function getRoleResources(int $roleId): array
{
if (empty($roleId)) {
return [];
}
return Db::table('role_resources')
->where('role_id', $roleId)
->pluck('resource_id')
->toArray();
}
/**
* 获取用户资源
*
* @param int $userId 用户ID
* @return array 资源ID列表
*/
public function getUserResources(int $userId): array
{
if (empty($userId)) {
return [];
}
return Db::table('user_resources')
->where('user_id', $userId)
->pluck('resource_id')
->toArray();
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\Systems;
class SystemsLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Systems::class;
public function getList()
{
return Systems::where('status', 1)->orderBy('sort', 'asc')->get(['id', 'name', 'code', 'type', 'type']);
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\UploadedFiles;
class UploadedFilesLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UploadedFiles::class;
/**
* 根据分区获取文件列表
*
* @param string $partition
* @param int $limit
* @param int $page
* @return mixed
*/
public function getFilesByPartition(string $partition, int $limit = 15, int $page = 1)
{
$config = [
'model' => $this->modelClass,
'where' => ['partition' => $partition],
'order' => ['created_at' => 'desc', 'id' => 'desc']
];
return $this->logicList($config, $limit, $page);
}
/**
* 根据分类获取文件列表
*
* @param string $category
* @param int $limit
* @param int $page
* @return mixed
*/
public function getFilesByCategory(string $category, int $limit = 15, int $page = 1)
{
$config = [
'model' => $this->modelClass,
'where' => ['category' => $category],
'order' => ['created_at' => 'desc', 'id' => 'desc']
];
return $this->logicList($config, $limit, $page);
}
/**
* 根据文件类型获取文件列表
*
* @param string $fileType
* @param int $limit
* @param int $page
* @return mixed
*/
public function getFilesByType(string $fileType, int $limit = 15, int $page = 1)
{
$config = [
'model' => $this->modelClass,
'where' => ['file_type' => $fileType],
'order' => ['created_at' => 'desc', 'id' => 'desc']
];
return $this->logicList($config, $limit, $page);
}
/**
* 搜索文件
*
* @param string $keyword
* @param int $limit
* @param int $page
* @return mixed
*/
public function searchFiles(string $keyword, int $limit = 15, int $page = 1)
{
$config = [
'model' => $this->modelClass,
'where' => [
'or' => [
['file_name', 'like', '%' . $keyword . '%'],
['original_name', 'like', '%' . $keyword . '%'],
['category', 'like', '%' . $keyword . '%']
]
],
'order' => ['created_at' => 'desc', 'id' => 'desc']
];
return $this->logicList($config, $limit, $page);
}
/**
* 获取文件统计信息
*
* @return array
*/
public function getFileStatistics(): array
{
$model = new $this->modelClass();
$totalFiles = $model->count();
$totalSize = $model->sum('file_size');
$partitionStats = $model->selectRaw('partition, COUNT(*) as count, SUM(file_size) as size')
->groupBy('partition')
->get()
->toArray();
$categoryStats = $model->selectRaw('category, COUNT(*) as count, SUM(file_size) as size')
->groupBy('category')
->get()
->toArray();
$typeStats = $model->selectRaw('file_type, COUNT(*) as count, SUM(file_size) as size')
->groupBy('file_type')
->get()
->toArray();
return [
'total_files' => $totalFiles,
'total_size' => $totalSize,
'partition_stats' => $partitionStats,
'category_stats' => $categoryStats,
'type_stats' => $typeStats
];
}
/**
* 清理过期文件
*
* @param int $days 保留天数
* @return int 清理的文件数量
*/
public function cleanExpiredFiles(int $days = 30): int
{
$expiredTime = time() - ($days * 24 * 60 * 60);
$config = [
'model' => $this->modelClass,
'where' => [
['created_at', '<', $expiredTime]
]
];
$files = $this->logicListAll($config);
$count = 0;
foreach ($files as $file) {
// 删除物理文件
$fullPath = public_path() . $file->file_path;
if (file_exists($fullPath)) {
unlink($fullPath);
}
// 删除数据库记录
$this->logicDeleteBy(['where' => ['id' => $file->id]]);
$count++;
}
return $count;
}
}
+207
View File
@@ -0,0 +1,207 @@
<?php
namespace app\common\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\common\model\User;
use app\common\service\WechatService;
use support\exception\BusinessException;
class UserLogic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
/**
* 构建 JWT 令牌有效载荷
*
* @param User $user
* @return array
*/
public function buildJwtPayload(User $user): array
{
$now = time();
return [
'sub' => 'user_' . $user->id,
'aud' => 'bsh.yw.hnzhwlkj.cn',
'iat' => $now,
'nbf' => $now,
'exp' => $now + config('jwt.jwt_expiration'),
'typ' => 'access',
'user_id' => $user->id,
'name' => $user->name,
'meta' => $user->toArray(),
'uid' => $user->id,
];
}
/**
* 用户信息
*/
public function userInfo(int $uid)
{
$user = User::find($uid);
if (!$user) {
throw new BusinessException('账户不存在', 404);
}
if ($user->status != 1) {
throw new BusinessException('账号已禁用', 403);
}
return $user;
}
/**
* 检测用户是否存在
*
* @param string $openid
* @param string $unionid
* @return User|bool
*/
public function checkUser(string $openid, string $unionid): User|bool
{
$uinfo = User::where('status', 1)
->where('openid', $openid)
->where('unionid', $unionid)
->first();
return $uinfo === null ? false : $uinfo;
}
/**
* 绑定用户
*
* @param array $data
* @return User|bool
*/
public function bindUser(array $data): User|bool
{
$uinfo = User::where('status', 1)
->where('name', $data['name'])
->where('mobile', $data['mobile'])
->where('sn', $data['sn'])
->first();
if ($uinfo !== null) {
$uinfo->update([
'openid' => $data['openid'],
'unionid' => $data['unionid'],
'nickname' => $data['nickname'],
'avatar' => $data['avatar'],
'password' => password_hash($data['password'], PASSWORD_DEFAULT)
]);
return $uinfo;
}
return false;
}
/**
* 创建用户
*
* @param array $data
* @return User
*/
public function createUser(array $data): User
{
return User::create($data);
}
/**
* 获取微信access_token
*
* @param string $code
* @param string $state
* @return array
*/
public function getAccessToken(string $code, string $state): array
{
$wechat = new WechatService();
return $wechat->wechatWebAccessToken($code, $state);
}
/**
* 获取微信用户信息
*
* @param array $token
* @return array
*/
public function getWechatUserInfo(array $token)
{
$wechat = new WechatService();
return $wechat->wechatWebUserInfo($token);
}
/**
* 获取用户列表
*/
public function getList()
{
$list = User::where('status', 1)->orderBy('id', 'desc')->get();
$arr = [];
foreach ($list->toArray() as $item) {
$arr[] = [
'name' => $item['name'],
'id' => $item['id'],
];
}
return $arr;
}
/**
* 修改密码
*/
public function savePassword(int $uid, array $data)
{
$password = password_hash($data['password_new'], PASSWORD_DEFAULT);
$res = User::where('id',$uid)->update(['password' => $password]);
return $res;
}
//---------------------------------------------小程序------------------------------------------------------------
/**
* 获取微信小程序access_token
*
* @param string $code
* @param string $state
* @return array
*/
public function getMiniAccessToken(): array
{
$wechat = new WechatService();
return $wechat->getMiniAccessToken();
}
/**
* 获取 openid / session_key
*
* @param string $code
* @param string $state
* @return array
*/
public function miniCode2Session($loginCode): array
{
$wechat = new WechatService();
return $wechat->miniCode2Session($loginCode);
}
/**
* 获取 openid / session_key
*
* @param string $code
* @param string $state
* @return array
*/
public function getMiniPhoneNumber($access_token, $phone_code): array
{
$wechat = new WechatService();
return $wechat->getMiniPhoneNumber($access_token, $phone_code);
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class Agreement extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'agreement';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'title', 'type', 'content', 'sort', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class Banner extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'banner';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = [];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class BannerModule extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'banner_module';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = [];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class Post extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'post';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'user_id', 'category_id', 'content', 'location_name', 'latitude', 'longitude', 'status', 'sort', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class PostCategory extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'post_category';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'parent_id', 'name', 'level', 'path', 'sort', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class PostComment extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'post_comment';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'post_id', 'user_id', 'parent_id', 'root_id', 'content', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class PostMedia extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'post_media';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'post_id', 'media_type', 'url', 'sort', 'updated_at', 'created_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class Resource extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'resources';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'system_id', 'name', 'desc', 'type', 'code', 'parent_id', 'url', 'icon', 'open_type', 'is_hide', 'is_hide_tab', 'no_auth', 'is_home', 'instance', 'sort', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace app\common\model;
use support\Model;
/**
* roles 角色表
* @property integer $id 角色ID(主键)
* @property string $name 角色名称
* @property string $desc 角色描述
* @property string $created_at
* @property string $updated_at
*/
class Role extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'roles';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
protected $fillable = [
'system_id',
'name',
'desc',
'sort',
'status'
];
// 角色下所有用户
public function users()
{
return $this->belongsToMany(User::class, 'user_roles', 'role_id', 'user_id');
}
// 角色拥有的资源(权限点、菜单)
public function resources()
{
return $this->belongsToMany(Resource::class, 'role_resources', 'role_id', 'resource_id');
}
}
+49
View File
@@ -0,0 +1,49 @@
<?php
namespace app\common\model;
use support\Model;
class Systems extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'systems';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'code', 'name', 'type', 'desc', 'icon', 'domain_url', 'is_role', 'sort', 'status', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace app\common\model;
use support\Model;
class UploadedFiles extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'uploaded_files';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = [
'id',
'file_name',
'file_path',
'file_size',
'file_type',
'category',
'original_name',
'extension',
'partition',
'upload_ip',
'user_agent',
'created_at',
'updated_at'
];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace app\common\model;
use support\Model;
class User extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = ['id', 'password', 'name', 'email', 'mobile', 'avatar', 'sn', 'status', 'is_admin', 'nickname', 'openid', 'unionid', 'created_at', 'updated_at'];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
// 用户所有角色
public function roles()
{
return $this->belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id');
}
// 用户直接拥有的资源(用于特殊补权)
public function resources()
{
return $this->belongsToMany(Resource::class, 'user_resources', 'user_id', 'resource_id')->orderBy('sort', 'asc');
}
// 用户可访问的系统
public function systems()
{
return $this->belongsToMany(Systems::class, 'user_systems', 'user_id', 'system_id');
}
// 账号查询的作用域
public function scopeByAccount($query, $account)
{
return $query->where('sn', $account)
->orWhere('mobile', $account)
->orWhere('email', $account);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
namespace app\common\service;
use app\common\validate\AgreementValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Agreement;
use support\Request;
class AgreementService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Agreement::class;
/**
* 验证器
*/
protected $validator = AgreementValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'show':
if (isset($p['type']) && !empty($p['type'])) {
$config['where']['type'] = $p['type'];
}
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace app\common\service;
use app\common\logic\AuthLogic;
use app\common\model\User;
use app\common\utils\Feishu;
use support\Db;
class AuthService
{
/**
* 飞书授权
*/
public function feishuAuthorize(string $corpAbbr, string $systemCcode)
{
$logic = new AuthLogic();
$certificate = $logic->feishuAppInfo($corpAbbr, $systemCcode);
$feishu = new Feishu($certificate['app_id'], $certificate['app_secret'], $certificate['abbr']);
return ['auth_url' => $feishu->authorize($certificate)];
}
/**
* 登录,支持多类型
*/
public function login(array $data): array
{
$loginType = $data['login_type'] ?? 'upwd';
$logic = new AuthLogic();
return match ($loginType) {
default => $logic->uPwdLogin($data),
};
}
/**
* 用户注册
*/
public function register(array $data)
{
$logic = new AuthLogic();
try {
Db::beginTransaction();
$result = $logic->register($data);
Db::commit();
return $result;
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
/**
* 校验token
*/
public function verifyToken(string $token, string $systemCode): array
{
return (new AuthLogic())->verifyToken($token, $systemCode);
}
/**
* 刷新token
* @param string $refreshToken
* @param string|null $systemCode
*/
public function refreshToken(string $refreshToken, string $systemCode): array
{
return (new AuthLogic())->refreshToken($refreshToken, $systemCode);
}
/**
* 登出(失效token)
*/
public function logout(?string $token, string $systemCode): void
{
(new AuthLogic())->logout($token, $systemCode);
}
/**
* 强制拉黑token
*/
public function blockToken(string $token, string $systemCode): void
{
(new AuthLogic())->blockToken($token, $systemCode);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace app\common\service;
use app\common\model\BannerModule;
use app\common\validate\BannerValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Banner;
class BannerService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Banner::class;
/**
* 验证器
*/
protected $validator = BannerValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
public function getBannerModule(){
return BannerModule::where('status','1')->get();
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace app\common\service;
use app\common\model\Corp;
use app\common\model\Systems;
use support\Redis;
use support\exception\BusinessException;
class BasicDataService
{
// 定义Redis键名前缀常量
const LOGIN_TYPE_KEY = 'bsh_login_types';
const SYSTEM_CODE_KEY = 'bsh_system_codes';
/**
* 检查并缓存独立的基础参数
*/
public function checkAndCacheBaseParams($loginType, $systemCode)
{
if (!$loginType && !$systemCode) {
throw new BusinessException('header缺少必要参数');
}
// 检查并缓存登录类型
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
$this->cacheLoginTypes();
if (!$this->checkParamInCache(self::LOGIN_TYPE_KEY, $loginType)) {
throw new BusinessException('无效的登录方式');
}
}
// 检查并缓存系统码
if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) {
$this->cacheSystemCodes();
if (!$this->checkParamInCache(self::SYSTEM_CODE_KEY, $systemCode)) {
throw new BusinessException('无效的系统码');
}
}
return true;
}
/**
* 检查参数是否在Redis集合中
*/
private function checkParamInCache($cacheKey, $param)
{
return Redis::sIsMember($cacheKey, $param);
}
/**
* 缓存所有登录类型到集合
*/
private function cacheLoginTypes()
{
$cacheKey = self::LOGIN_TYPE_KEY;
// 如果集合已存在,直接返回
if (Redis::exists($cacheKey)) {
return;
}
$loginTypes = $this->getAllLoginTypes();
if (empty($loginTypes)) {
throw new BusinessException('未获取到登录类型数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$loginTypes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 缓存所有系统码到集合
*/
private function cacheSystemCodes()
{
$cacheKey = self::SYSTEM_CODE_KEY;
// 如果集合已存在,直接返回
if (Redis::exists($cacheKey)) {
return;
}
$systemCodes = $this->getAllSystemCodes();
if (empty($systemCodes)) {
throw new BusinessException('未获取到系统码数据');
}
// 批量添加值到集合
Redis::sAdd($cacheKey, ...$systemCodes);
Redis::expire($cacheKey, 3600); // 1小时有效期
}
/**
* 获取所有有效的登录类型
*/
private function getAllLoginTypes()
{
return ['upwd', 'feishu', 'qrcode']; // 示例数据
}
/**
* 获取所有有效的系统码
*/
private function getAllSystemCodes()
{
return Systems::pluck('code')->toArray();
}
/**
* 获取所有有效的公司标识
*/
private function getAllCorpAbbrs()
{
return Corp::pluck('abbr')->toArray();
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
namespace app\common\service;
use OSS\OssClient;
use OSS\Core\OssException;
use support\exception\BusinessException;
class OssService
{
private $accessKeyId;
private $accessKeySecret;
private $bucket;
private $endpoint;
private $domain;
private $ossClient;
public function __construct()
{
$this->accessKeyId = getenv('OSS_ACCESSID');
$this->accessKeySecret = getenv('OSS_ACCESSSECRET');
$this->bucket = getenv('OSS_BUCKET');
$this->endpoint = getenv('OSS_ENDPOINT');
$this->domain = getenv('OSS_DOMAIN');
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
}
/**
* 上传图片到 OSS
* @param string $filePath 本地文件路径
* @param string $objectKey OSS上存储的对象名称
* @return string 图片的 URL
*/
public function uploadImage($filePath, $objectKey)
{
try {
// 上传文件到 OSS
$this->ossClient->uploadFile($this->bucket, $objectKey, $filePath);
// 构造图片的私有 URL
$url = $this->generatePrivateUrl($objectKey);
return $url;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Error: ' . $th->getMessage());
}
}
/**
* 生成图片的私有访问 URL
* @param string $objectKey
* @return string
*/
public function generatePrivateUrl($objectKey, $expireTime = 3600)
{
// 生成 OSS 私有 URL(有效期为 3600 秒)
$originalUrl = $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime);
// // 使用正则表达式替换
$originalUrl = preg_replace(
'#^https?://[^/]+#',
'https://static.hnzhwlkj.cn',
$originalUrl
);
return $originalUrl;
}
/**
* 删除 OSS 上的文件
* @param string $objectKey OSS上存储的对象名称
* @return bool 删除是否成功
*/
public function deleteFile($objectKey)
{
try {
// 删除 OSS 上的文件
$this->ossClient->deleteObject($this->bucket, $objectKey);
return true;
} catch (OssException $e) {
// 错误处理
throw new \RuntimeException('OSS Delete Error: ' . $e->getMessage());
} catch (\Throwable $th) {
// 错误处理
throw new BusinessException('Delete Error: ' . $th->getMessage());
}
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\common\service;
use app\common\logic\PermLogic;
use support\Cache;
class PermService
{
protected $logic;
public function __construct()
{
$this->logic = (new PermLogic());
}
// 用户资源缓存key
protected static function cacheKey(int $userId, string $systemCode): string
{
return "user_resources_{$userId}_{$systemCode}";
}
/**
* 获取用户所有资源(合并角色+直接分配),结果缓存
*/
public function getUserResources(int $userId, string $systemCode)
{
$cacheKey = self::cacheKey($userId, $systemCode);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
// 调用 Logic 层获取资源
$resources = $this->logic->getUserResources($userId, $systemCode);
// 缓存结果
Cache::set($cacheKey, $resources, 3600);
return $resources;
}
/**
* 构建目录/菜单树,节点包含权限与打开方式
*/
public function getMenuTree(int $userId, string $systemCode): array
{
// 调用 Logic 层处理菜单构建
$resources = $this->getUserResources($userId, $systemCode);
return $this->logic->buildTree($resources);
}
/**
* 获取用户所有权限code
*/
public function getPermissionCodes(int $userId, string $systemCode): array
{
return $this->logic->getPermissionCodes($userId, $systemCode);
}
/**
* 清除资源相关缓存
*/
public function clearCache(int $userId, string $systemCode): void
{
Cache::delete(self::cacheKey($userId, $systemCode));
}
}
@@ -0,0 +1,89 @@
<?php
namespace app\common\service;
use app\common\logic\PostCategoryLogic;
use app\common\model\NavItems;
use app\common\validate\PostCategoryValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostCategory;
use support\Request;
class PostCategoryService
{
use CrudServiceTrait;
protected $logic;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostCategory::class;
/**
* 验证器
*/
protected $validator = PostCategoryValidate::class;
public function __construct()
{
$this->logic = (new PostCategoryLogic());
}
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取系统list
*/
public function getList($params)
{
$where = [];
if(isset($params['parent_id']) && $params['parent_id']) {
$where['parent_id'] = $params['parent_id'];
}else{
$where['parent_id'] = 0;
}
$result = $this->logic->getList($where)->toArray();
foreach ($result as $key => $value) {
$where = [];
$where['parent_id'] = $value['id'];
$result[$key]['children'] = $this->logic->getList($where)->toArray();
foreach ( $result[$key]['children'] as $k => $v) {
$where = [];
$where['parent_id'] = $v['id'];
$result[$key]['children'][$k]['children'] = $this->logic->getList($where)->toArray();
}
}
return $result;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$brand_id = $data['parent_id'] ?? 0;
if($brand_id == 0) {
$data['level'] = 1;
$data['path'] = '';
return $data;
}
$parent = PostCategory::where('id', $data['parent_id'])->first();
$parent['path'] = !empty($parent['path'])?:'/';
$data['level'] = $parent['level']+1;
$data['path'] = $parent['path'].$parent['id'].'/';
if($data['level'] > 3) {
throw new \Exception("分类层级不能超过三层");
}
return $data;
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
namespace app\common\service;
use app\common\validate\PostCommentValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostComment;
use support\Redis;
use support\Request;
class PostCommentService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostComment::class;
/**
* 验证器
*/
protected $validator = PostCommentValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['id'] = 'desc';
// 帖子
if (isset($p['post_id']) && !empty($p['post_id'])) {
$config['where']['post_id'] = $p['post_id'];
}
// 用户
if (isset($p['user_id']) && !empty($p['user_id'])) {
$config['where']['user_id'] = $p['user_id'];
}
// 只查询根评论
if (isset($p['parent_id']) && $p['parent_id'] == 0) {
$config['where']['parent_id'] = 0;
}
// 根评论ID
if (isset($p['parent_id']) && !empty($p['parent_id'])) {
$config['where']['root_id'] = $p['parent_id'];
}
// 根评论ID
if (isset($p['status']) && !empty($p['status'])) {
$config['where']['status'] = $p['status'];
}
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
$data['root_id'] = 0;
if(!empty($data['parent_id'])){
$data['root_id'] = PostComment::where('id', $data['parent_id'])->value('root_id');
}
return $data;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace app\common\service;
use app\common\validate\PostMediaValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\PostMedia;
class PostMediaService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = PostMedia::class;
/**
* 验证器
*/
protected $validator = PostMediaValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
+113
View File
@@ -0,0 +1,113 @@
<?php
namespace app\common\service;
use app\common\model\PostMedia;
use app\common\validate\PostValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Post;
use support\Redis;
use support\Request;
class PostService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Post::class;
/**
* 验证器
*/
protected $validator = PostValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 添加前置处理
*/
public function beforeServiceStore($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
$media = serialize($data['media']);
redis::set('PostMedia_'.$data['user_id'],$media,30);
unset($data['media']);
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceStore($res)
{
$media = redis::get('PostMedia_'.$res['user_id']);
$media = unserialize($media);
foreach ($media as $v) {
PostMedia::create([
'post_id' => $res->id,
'media_type' => $this->getMediaTypeByUrl($v['url']),
'url' => $v['url'],
]);
}
}
/**
* 添加前置处理
*/
public function beforeServiceUpdate($data, Request $request)
{
$data['user_id'] = $request->user->uid;;
$media = serialize($data['media']);
redis::set('PostMedia_'.$data['user_id'],$media,30);
unset($data['media']);
return $data;
}
/**
* 添加后置处理
*/
public function afterServiceUpdate($res)
{
PostMedia::where('post_id', $res->id)->delete();
$media = redis::get('PostMedia_'.$res['user_id']);
$media = unserialize($media);
foreach ($media as $v) {
PostMedia::create([
'post_id' => $res->id,
'media_type' => $this->getMediaTypeByUrl($v['url']),
'url' => $v['url'],
]);
}
}
function getMediaTypeByUrl(string $url): int
{
// 提取后缀
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
// 图片类型
$imageExt = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
// 视频类型
$videoExt = ['mp4', 'mov', 'avi', 'wmv', 'flv', 'mkv'];
if (in_array($ext, $imageExt)) {
return 1; // 图片
}
if (in_array($ext, $videoExt)) {
return 2; // 视频
}
return 0; // 未知类型
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace app\common\service;
use app\common\logic\ResourceLogic;
use app\common\validate\ResourceValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Resource;
use app\common\utils\Tree;
use support\Request;
class ResourceService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Resource::class;
/**
* 验证器
*/
protected $validator = ResourceValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuList':
$config['where']['status'] = 1;
$config['where']['type'] = ['!=', 'permission'];
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'menuTreeList':
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
public function serviceMenuList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuList');
$result = $this->logicListAll($config);
return $this->postProcess('MenuList', $result);
}
public function serviceMenuTreeList(Request $request)
{
$config = $this->buildQueryConfig($request, 'menuTreeList');
$result = (new ResourceLogic())->logicListAllResources($request, $config);
return $this->postProcess('MenuTreeList', $result);
}
protected function afterServiceDefault($data)
{
$data = Tree::buildMenuTreeWithReference($data->toArray());
return $data;
}
protected function afterServiceMenuList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
protected function afterServiceMenuTreeList($data)
{
$data = Tree::buildMenuTree($data->toArray());
return $data;
}
public function beforeServiceStore($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
public function beforeServiceUpdate($data, Request $request)
{
$data['parent_id'] = $data['parent_id'] != '' ? $data['parent_id'] : 0;
$data['system_id'] = $data['system_id'] != '' ? $data['system_id'] : 0;
return $data;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\common\service;
use app\common\logic\RoleLogic;
use app\common\validate\RoleValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\Role;
use support\Db;
class RoleService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = Role::class;
/**
* 验证器
*/
protected $validator = RoleValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取角色资源
*/
public function menuResource($request, $data)
{
return (new RoleLogic)->updateRoleResources($data);
}
public function getList()
{
$config['where']['status'] = 1;
$config['order']['sort'] = 'asc';
$config['order']['id'] = 'asc';
return $this->logicListAll($config);
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace app\common\service;
use app\common\logic\SystemsLogic;
class SystemsService
{
protected $logic;
public function __construct()
{
$this->logic = (new SystemsLogic());
}
/**
* 获取系统list
*/
public function getList()
{
return $this->logic->getList();
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
namespace app\common\service;
use support\exception\BusinessException;
use support\Log;
use support\Request;
class UploadService
{
/**
* 从服务器本地路径上传文件到 OSS
*
* @param Request $request
* - path: 本地文件绝对路径
* - scene: 业务分区/场景
* - category: 文件分类(可选)
* - original_name: 原始文件名(可选)
*/
public function uploadLocalFile($param)
{
try {
$filePath = $param['path'];
$scene = $param['scene'];
$category = $param['category']??'general';
$providedOriginalName = $param['original_name']??'';
if (empty($filePath)) {
Log::error('本地文件路径不能为空',$param);
throw new BusinessException('本地文件路径不能为空');
}
if (!file_exists($filePath) || !is_readable($filePath)) {
Log::error('本地文件不存在或不可读',$param);
throw new BusinessException('本地文件不存在或不可读');
}
if (empty($scene)) {
Log::error('上传场景不能为空',$param);
throw new BusinessException('上传场景不能为空');
}
$basename = basename($filePath);
$originalName = $providedOriginalName ?: $basename;
$extensionFromName = pathinfo($originalName, PATHINFO_EXTENSION);
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
$extension = $extensionFromName ?: $extensionFromPath;
$mime = mime_content_type($filePath) ?: 'application/octet-stream';
$size = filesize($filePath) ?: 0;
$sceneUrl = $scene ? ($scene . '/') : '';
$dir = 'x_server/' . $sceneUrl . date('Ymd') . '/';
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
// 若提供的名称没有扩展名或扩展名不一致,则以推断扩展名为准
$safeBaseName = pathinfo($safeBaseName, PATHINFO_FILENAME) . '.' . $extension;
} elseif (!$extension && !pathinfo($safeBaseName, PATHINFO_EXTENSION)) {
// 实在没有扩展名信息时,保持原名
}
// 重名检测:同一目录、同名且同类型则在文件名后追加序号
$uploadedFilesService = new UploadedFilesService();
$candidateName = $safeBaseName;
$nameBody = pathinfo($safeBaseName, PATHINFO_FILENAME);
$nameExt = pathinfo($safeBaseName, PATHINFO_EXTENSION);
$counter = 1;
while (true) {
$candidatePath = $dir . $candidateName;
$existing = $uploadedFilesService->findFile(['file_path' => $candidatePath]);
if (!$existing) {
break;
}
// 若存在同路径同名,且类型也相同,则追加序号
if (isset($existing->file_type) && strtolower($existing->file_type) === strtolower($mime)) {
$suffix = ' (' . $counter . ')';
$candidateName = $nameBody . $suffix . ($nameExt !== '' ? ('.' . $nameExt) : '');
$counter++;
continue;
}
// 类型不同则允许同名,结束循环
break;
}
$objectKey = $dir . $candidateName;
$ossService = new OssService();
$ossService->uploadImage($filePath, $objectKey);
$privateUrl = $ossService->generatePrivateUrl($objectKey);
// 记录文件信息到数据库
$uploadedFilesService = new UploadedFilesService();
$fileInfo = [
'file_name' => basename($objectKey),
'file_path' => $objectKey,
'file_size' => $size,
'file_type' => $mime,
'original_name' => $originalName,
];
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, null, $scene, $category);
$data = [
'original_name' => $originalName,
'file_name' => $objectKey,
'file_url' => $privateUrl,
'file_id' => $recordResult['id'] ?? null,
'partition' => $scene,
'category' => $category,
];
return $data;
} catch (\Throwable $e) {
Log::error($e->getMessage(),$param);
throw new BusinessException($e->getMessage());
}
}
/**
* 删除 OSS 上的文件
*
* @param array $param
* - object_key: OSS 对象键名-oss路径(必需)
* - file_id: 数据库文件记录ID(可选,用于删除数据库记录)
* @return bool 删除是否成功
*
* 示例
* $uploadService = new UploadService();
*
* 只删除 OSS 文件
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/documents/20241201/abc123.pdf'
* ]);
*
* 同时删除 OSS 文件和数据库记录
* $uploadService->deleteOssFile([
* 'object_key' => 'vehicle_album/images/20241201/abc123.jpg',
* 'file_id' => 123
* ]);
*/
public function deleteOssFile($objectKey,$fileId = null)
{
try {
if (empty($objectKey)) {
Log::error('OSS对象键名不能为空', $objectKey);
throw new BusinessException('OSS对象键名不能为空');
}
$uploadedFilesService = new UploadedFilesService();
// 如果提供了文件ID,同时删除数据库记录
if ($fileId) {
$uploadedFilesService->deleteFile($fileId);
}else{
$uploadedFilesService = new UploadedFilesService();
if (empty($fileId)) {
$where = ['file_path' => $objectKey];
$file = $uploadedFilesService->getFile($where);
$fileId = $file->id;
}
if(!empty($fileId)){
$uploadedFilesService->deleteFile($fileId);
}
}
// 删除 OSS 上的文件
$ossService = new OssService();
$ossService->deleteFile($objectKey);
Log::info('OSS文件删除成功', ['object_key' => $objectKey, 'file_id' => $fileId]);
return true;
} catch (\Throwable $e) {
Log::error('删除OSS文件失败: ' . $e->getMessage());
throw new BusinessException('删除OSS文件失败: ' . $e->getMessage());
}
}
}
+238
View File
@@ -0,0 +1,238 @@
<?php
namespace app\common\service;
use app\common\validate\UploadedFilesValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\UploadedFiles;
use support\Request;
use support\exception\BusinessException;
class UploadedFilesService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = UploadedFiles::class;
/**
* 文件上传分区配置
*/
protected array $uploadPartitions = [
'image' => [
'path' => 'images',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
'extensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
],
'document' => [
'path' => 'documents',
'max_size' => 10 * 1024 * 1024, // 10MB
'allowed_types' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
'extensions' => ['pdf', 'doc', 'docx']
],
'audio' => [
'path' => 'audios',
'max_size' => 20 * 1024 * 1024, // 20MB
'allowed_types' => ['audio/mp3', 'audio/wav', 'audio/ogg'],
'extensions' => ['mp3', 'wav', 'ogg']
],
'other' => [
'path' => 'others',
'max_size' => 5 * 1024 * 1024, // 5MB
'allowed_types' => [],
'extensions' => []
]
];
/**
* 记录文件信息到数据库
*
* @param array $fileInfo 文件信息
* @param Request|null $request 请求对象(可选)
* @param string $partition 分区名称
* @param string $category 文件分类
* @return array
* @throws BusinessException
*/
public function recordFileInfo(array $fileInfo, ?Request $request = null, string $partition = 'other', string $category = 'general'): array
{
// 验证必需的文件信息
$requiredFields = ['file_name', 'file_path', 'file_size', 'file_type'];
foreach ($requiredFields as $field) {
if (!isset($fileInfo[$field])) {
throw new BusinessException("缺少必需的文件信息字段: {$field}");
}
}
// 获取文件扩展名
$fileExtension = pathinfo($fileInfo['file_name'], PATHINFO_EXTENSION);
// 记录到数据库
$uploadIp = $request ? $request->getRealIp() : '0.0.0.0';
$userAgent = $request ? $request->header('User-Agent', '') : '';
$fileData = [
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
'partition' => $partition,
'upload_ip' => $uploadIp,
'user_agent' => $userAgent,
];
$uploadedFile = $this->logicCreate($fileData);
return [
'id' => $uploadedFile->id,
'file_name' => $fileInfo['file_name'],
'file_path' => $fileInfo['file_path'],
'file_url' => getenv('MAIN_DOMAIN') . $fileInfo['file_path'],
'file_size' => $fileInfo['file_size'],
'file_type' => $fileInfo['file_type'],
'category' => $category,
'partition' => $partition,
'original_name' => $fileInfo['original_name'] ?? '',
'extension' => $fileExtension,
];
}
/**
* 获取文件信息
*
* @param int $fileId
* @return array|null
*/
public function getFileInfo(int $fileId): ?array
{
$file = $this->logicShow(['where' => ['id' => $fileId]]);
if (!$file) {
return null;
}
return [
'id' => $file->id,
'file_name' => $file->file_name,
'file_path' => $file->file_path,
'file_url' => getenv('MAIN_DOMAIN') . $file->file_path,
'file_size' => $file->file_size,
'file_type' => $file->file_type,
'category' => $file->category,
'partition' => $file->partition ?? 'other',
'original_name' => $file->original_name ?? '',
'extension' => $file->extension ?? '',
'created_at' => $file->created_at,
];
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function getFile(array $where)
{
$config['model'] = $this->modelClass;
$config['where'] = $where;
$file = $this->logicShow($config);
if (!$file) {
return false;
}
return $file;
}
/**
* 获取文件
*
* @param string $path
* @return bool|\Illuminate\Database\Eloquent\Model
*/
public function findFile(array $where)
{
$file = UploadedFiles::where(['file_path' => $where['file_path']]);
if (!$file) {
return false;
}
return $file;
}
/**
* 删除文件
*
* @param int $fileId
* @return bool
*/
public function deleteFile(int $fileId): bool
{
$config['model'] = $this->modelClass;
$config['where'] = ['id' => $fileId];
$file = $this->logicShow($config);
if (!$file) {
return false;
}
// 删除数据库记录
$config =[
'model' => $this->modelClass,
'where' => ['id' => $fileId],
];
return $this->logicDeleteBy($config);
}
/**
* 获取分区配置
*
* @return array
*/
public function getPartitionConfig(): array
{
return $this->uploadPartitions;
}
/**
* 生成文件名
*
* @param $file
* @return string
*/
protected function generateFileName($file): string
{
$extension = $file->getUploadExtension();
$timestamp = time();
$random = substr(md5(uniqid()), 0, 8);
return $timestamp . '_' . $random . '.' . $extension;
}
/**
* 格式化文件大小
*
* @param int $bytes
* @return string
*/
protected function formatFileSize(int $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, 2) . ' ' . $units[$pow];
}
}
+229
View File
@@ -0,0 +1,229 @@
<?php
namespace app\common\service;
use app\common\logic\AuthLogic;
use app\common\model\Corp;
use app\common\validate\UserValidate;
use app\common\traits\crud\CrudServiceTrait;
use app\common\model\User;
use app\common\logic\UserLogic;
use app\common\utils\Jwt;
use support\exception\BusinessException;
class UserService
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = User::class;
protected $logic;
public function __construct()
{
$this->logic = (new UserLogic());
}
/**
* 验证器
*/
protected $validator = UserValidate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
/**
* 获取微信二维码
*/
public function makeQrUrl()
{
$wechat = new WechatService();
return $wechat->makeQrUrl();
}
/**
* 获取用户信息
*/
public function userInfo(string $code, string $state)
{
$logic = new UserLogic();
$accessToken = $logic->getAccessToken($code, $state);
// $accessToken['openid'] = 'op9DT6xBzVF05dypmQ2JsM5DP6yw';
// $accessToken['unionid'] = 'o04nT6-St_2MZR6VRduYEwiicmiA';
$userInfo = $logic->checkUser($accessToken['openid'], $accessToken['unionid']);
if ($userInfo === false) {
return [
'is_bind' => false,
'access_token' => $accessToken['access_token'],
'openid' => $accessToken['openid'],
'unionid' => $accessToken['unionid'],
];
}
$jwt = new Jwt();
$token = $jwt->generateToken($logic->buildJwtPayload($userInfo));
$refreshToken = (new AuthLogic())->generateRefreshToken($userInfo->id, 'board');
return array_merge($userInfo->toArray(), ['access_token' => $token, 'is_bind' => true, 'refresh_token'=> $refreshToken, 'expires_in' => config('jwt.jwt_expiration')]);
}
/**
* 获取用户信息(包括菜单和权限)
*/
public function getUserInfo(int $uid)
{
$user = $this->logic->userInfo($uid);
$ossservice = new OssService();
$user['avatar_pull'] = $user->avatar;
if(strpos($user->avatar, 'http') === false && !empty($user->avatar)){
//$user['avatar_pull'] = $ossservice->generatePrivateUrl($user->avatar);
$user['avatar_pull'] = '/proxy/ossurl?file='.$user->avatar;
}
return [
'user' => [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar,
'avatar_pull' => $user['avatar_pull'],
'mobile' => $user->mobile,
'email' => $user->email,
'nickname' => $user->nickname,
'status' => $user->status,
'roles' => $user->roles->pluck('name'),
'sn' => $user->sn,
],
// 'menu' => $menuTree,
// 'permissions' => $permissionCodes
];
}
/**
* 绑定用户
*
* @param array $data
* @return array
*/
public function bindUser(array $data)
{
$logic = new UserLogic();
$wechat = [
'access_token' => $data['access_token'],
'openid' => $data['openid'],
];
$wechatUserInfo = $logic->getWechatUserInfo($wechat);
var_dump($wechatUserInfo);
$uData = [
'nickname' => $wechatUserInfo['nickname'],
'avatar' => $wechatUserInfo['headimgurl'],
'unionid' => $wechatUserInfo['unionid'],
'openid' => $wechatUserInfo['openid'],
'name' => $data['name'],
'mobile' => $data['mobile'],
'sn' => $data['sn'],
'password' => $data['password'],
];
$userinfo = $logic->bindUser($uData);
if ($userinfo === false) {
throw new BusinessException('未查到用户信息');
}
$jwt = new Jwt();
$refreshToken = (new AuthLogic())->generateRefreshToken($userinfo->id, 'board');
return array_merge($userinfo->toArray(), [
'access_token' => $jwt->generateToken($logic->buildJwtPayload($userinfo)),
'is_bind' => true,
'refresh_token'=> $refreshToken,
'expires_in' => config('jwt.jwt_expiration')
]);
}
/**
* 获取用户列表
*/
public function getList()
{
return $this->logic->getList();
}
/**
* 修改用户密码
*/
public function savePassword(int $uid, array $data)
{
// 对密码进行哈希处理
$password = $data['password'];
$user = User::find($uid);
if (!password_verify($password, $user->password)) {
throw new BusinessException('密码错误');
}
$user = $this->logic->savePassword($uid,$data);
return $user;
}
/**
* 获取微信小程序用户信息
*/
public function wechatLogin(string $loginCode, string $phoneCode)
{
$logic = new UserLogic();
//$accessToken = $logic->getAccessToken($code, $state);
$session = $logic->miniCode2Session($loginCode);
if (!isset($session['openid'])) {
return json(['code'=>0, 'msg'=>'登录失败', 'data'=>$session]);
}
$openid = $session['openid'];
$tokenData = $logic->getMiniAccessToken();
$access_token = $tokenData['access_token'];
// 换手机号
$phoneData = $logic->getMiniPhoneNumber($access_token, $phoneCode);
if (!isset($phoneData['phone_info']['phoneNumber'])) {
return json(['code'=>0, 'msg'=>'获取手机号失败', 'data'=>$phoneData]);
}
$phone = $phoneData['phone_info']['phoneNumber'];
$user = User::firstOrCreate(['phone' => $phone], [
'openid' => $openid
]);
// $userInfo = $logic->checkUser($accessToken['openid'], $accessToken['unionid']);
//
// if ($userInfo === false) {
// return [
// 'is_bind' => false,
// 'access_token' => $accessToken['access_token'],
// 'openid' => $accessToken['openid'],
// 'unionid' => $accessToken['unionid'],
// ];
// }
$jwt = new Jwt();
$JwtPayload = $logic->buildJwtPayload($user);
$JwtPayload['exp'] = $JwtPayload['exp'] + (86400 * 30);
$token = $jwt->generateToken($JwtPayload);
$refreshToken = (new AuthLogic())->generateRefreshToken($user->id, 'info');
return array_merge($user->toArray(), ['access_token' => $token, 'is_bind' => true, 'refresh_token'=> $refreshToken, 'expires_in' => config('jwt.jwt_expiration')]);
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
namespace app\common\service;
use app\common\model\User;
use EasyWeChat\OpenPlatform\Application;
use support\Redis;
use GuzzleHttp\Client;
use support\exception\BusinessException;
class WechatService
{
protected array $web;
protected array $login;
protected $app;
public function __construct()
{
$cfg = config('wechat');
$this->web = $cfg['web'];
$this->mini = $cfg['mini'];
$this->login = $cfg['login'];
}
/**
* 获取微信二维码
*/
public function makeQrUrl()
{
try {
$state = bin2hex(random_bytes(16));
$ttl = (int)($this->login['state_ttl'] ?? 300);
// 记录 state
Redis::setEx("wx:login:state:$state", $ttl, json_encode([
'redirect' => $this->web['redirect'],
'ts' => time(),
], JSON_UNESCAPED_UNICODE));
// 组装跳转地址
$qrUrl = sprintf(
'https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect',
$this->web['app_id'],
urlencode($this->web['redirect']),
'code',
'snsapi_login',
$state,
);
$data = [
'state' => $state,
'qr_url' => $qrUrl
];
return $data;
} catch (\Throwable $th) {
// var_dump($th->getFile().'|'.$th->getLine().'|'.$th->getMessage());
throw new BusinessException('获取微信二维码失败');
}
}
/**
* 获取微信access_token
*/
public function wechatWebAccessToken(string $code, string $state): array
{
try {
// 校验 state
$stateJson = Redis::get("wx:login:state:$state");
if (!$stateJson) {
throw new \RuntimeException('state 已过期或无效');
}
// 拼接地址
$tokenUrl = sprintf(
'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code',
$this->web['app_id'],
$this->web['app_secret'],
$code
);
// code 换 token
$token = $this->httpGetJson($tokenUrl);
if (!isset($token['access_token'], $token['openid'])) {
throw new \RuntimeException('换取 access_token 失败:'. json_encode($token, JSON_UNESCAPED_UNICODE));
}
return $token;
} catch (\Throwable $th) {
throw new BusinessException('获取 access_token 失败');
}
}
/**
* 获取微信用户信息
*
* @param array $token
* @return array
*/
public function wechatWebUserInfo(array $accessToken): array
{
try {
// 拉取用户信息
return $this->httpGetJson(sprintf(
'https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s',
$accessToken['access_token'],
$accessToken['openid']
));
} catch (\Throwable $th) {
// var_dump($th->getFile().'|'.$th->getLine().'|'.$th->getMessage());
throw new BusinessException('获取用户信息失败');
}
}
/**
* tools - httpGetJson
*/
protected function httpGetJson(string $url): array
{
$client = new Client([
'verify' => false, // 禁用 SSL 验证
'timeout' => 10,
]);
$json = $client->get($url)->getBody()->getContents();
return json_decode($json, true) ?: [];
}
//----------------------------小程序登录----------------------------------------
/**
* 获取 openid / session_key
* @param $code
* @return mixed
*/
public function miniCode2Session($code)
{
$appid = $this->mini['app_id'];
$secret = $this->mini['app_secret'];
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$secret&js_code=$code&grant_type=authorization_code";
return json_decode(file_get_contents($url), true);
}
/**
* 获取 access_token
* @return mixed
*/
public function getMiniAccessToken()
{
$appid = $this->mini['app_id'];
$secret = $this->mini['app_secret'];
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret";
return json_decode(file_get_contents($url), true);
}
/**
* 调用微信接口取手机号
* @param $access_token
* @param $phone_code
* @return mixed
*/
public function getMiniPhoneNumber($access_token, $phone_code)
{
$url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={$access_token}";
$data = json_encode([
'code' => $phone_code
]);
$options = [
'http' => [
'header' => "Content-type: application/json",
'method' => 'POST',
'content' => $data,
],
];
return json_decode(file_get_contents($url, false, stream_context_create($options)), true);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace app\common\traits;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* 数据转换器 Trait
* 用于将原始数据(如模型、集合、分页等)统一转换为数组格式,
* 并支持字段级与结果级的灵活后置加工。
*/
trait DataTransformer
{
/**
* 核心转换方法
*
* @param mixed $rawData 原始数据:Model、Collection、Paginator、数组等
* @param array $opts 可选配置:
* - 'fieldFormatter' => callable 字段名格式化回调,如 fn($f)=>snake_case($f)
* - 'postFormatter' => callable 结果级加工回调,接收整个数组并返回
*
* @return array 转换并加工后的纯 PHP 数组
*/
protected function transformData(
mixed $rawData,
?callable $postFormatter = null,
?callable $fieldFormatter = null
): array {
try {
$fieldFormatter = $fieldFormatter ?: fn($field) => $field;
if ($rawData instanceof LengthAwarePaginator) {
$arr = $this->formatPaginated($rawData, $postFormatter, $fieldFormatter);
} elseif ($rawData instanceof Model) {
$arr = $this->formatModel($rawData, $fieldFormatter);
} elseif ($rawData instanceof Collection) {
$arr = $this->formatCollection($rawData, $fieldFormatter);
} elseif (is_array($rawData)) {
$arr = $this->formatArray($rawData, $fieldFormatter);
} else {
$arr = (array)$rawData;
}
if (is_callable($postFormatter)) {
$arr = $postFormatter($arr);
}
return $arr;
} catch (\Throwable $e) {
return [
'error' => true,
'message' => $e->getMessage(),
// 'message' => $e->getMessage()."\n".$e->getLine()."\n".$e->getFile(),
'code' => $e->getCode(),
];
}
}
/**
* 格式化分页数据:转换每条记录
*
* @param LengthAwarePaginator $p
* @param callable $fmt
* @return array{records: array, total: int, current: int, size: int}
*/
protected function formatPaginated(
LengthAwarePaginator $p,
?callable $postFormatter,
callable $fmt
): array {
$records = $p->getCollection()
->map(fn($item) => $this->transformData($item, $postFormatter, $fmt))
->toArray();
return [
'records' => $records,
'total' => $p->total(),
'current' => $p->currentPage(),
'size' => $p->perPage(),
];
}
/**
* 格式化 Eloquent 模型:主表属性 + 关联递归
*
* @param Model $model
* @param callable $fmt
* @return array
*/
protected function formatModel(
Model $model,
callable $fmt
): array {
$out = [];
// 1) 主表字段
foreach ($model->getAttributes() as $key => $value) {
$out[$fmt($key)] = $value;
}
// 2) 关联递归
foreach ($model->getRelations() as $rel => $relData) {
if ($relData instanceof Model) {
$out[$rel] = $this->formatModel($relData, $fmt);
} elseif ($relData instanceof Collection) {
$out[$rel] = $relData
->map(fn($item) => $this->transformData($item, null, $fmt))
->toArray();
} else {
$out[$rel] = $relData;
}
}
return $out;
}
/**
* 格式化集合数据:对每个元素递归调用 transformData
*
* @param Collection $c
* @param callable $fmt
* @return array
*/
protected function formatCollection(
Collection $c,
callable $fmt
): array {
return $c
->map(fn($item) => $this->transformData($item, null, $fmt))
->toArray();
}
/**
* 格式化普通数组:递归处理可能出现的模型或集合
*
* @param array $arr
* @param callable $fmt
* @return array
*/
protected function formatArray(
array $arr,
callable $fmt
): array {
return array_map(function($item) use ($fmt) {
if (is_array($item)
|| $item instanceof Model
|| $item instanceof Collection
|| $item instanceof LengthAwarePaginator
) {
return $this->transformData($item, null, $fmt);
}
return $item;
}, $arr);
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace app\common\traits;
use support\Response;
trait ResponseTrait
{
/**
* 返回成功响应
*
* @param mixed $data 响应数据
* @param string $message 响应消息,默认“请求成功”
* @param int $code 内部状态码,默认 0 表示成功
* @param int $httpStatus HTTP 状态码,默认 200
* @param array $header 响应头,默认 JSON 格式
* @return Response
*/
public function success(
$data = [],
string $message = '请求成功',
string $status = 'success',
int $code = 200,
int $httpStatus = 200,
array $header = ['Content-Type' => 'application/json']
): Response {
return $this->result($status, $code, $httpStatus, $message, $data, $header);
}
/**
* 返回错误响应
*
* @param string $message 响应错误消息,默认“请求失败”
* @param int $code 内部状态码,默认 1 表示错误
* @param int $httpStatus HTTP 状态码,默认 400
* @param mixed $data 可选的错误数据
* @param array $header 响应头,默认 JSON 格式
* @return Response
*/
public function error(
string $message = '请求失败',
int $code = 400,
int $httpStatus = 400,
$data = [],
array $header = ['Content-Type' => 'application/json']
): Response {
return $this->result('fail', $code, $httpStatus, $message, $data, $header);
}
/**
* 统一响应格式方法
*
* @param string $status 响应状态,例如 "success" 或 "fail"
* @param int $code 内部状态码
* @param int $httpStatus HTTP 状态码
* @param string $message 响应消息
* @param mixed $data 响应数据
* @param array $header 响应头
* @return Response
*/
protected function result(
string $status,
int $code,
int $httpStatus,
string $message,
$data,
array $header
): Response {
$body = [
'status' => $status,
'code' => $code,
'message' => $message,
'data' => $data,
];
return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header);
}
}
@@ -0,0 +1,148 @@
<?php
namespace app\common\traits\crud;
use support\Request;
/**
* CRUD 操作控制器 Trait
* 提供通用的增删改查控制器方法,通过调用 service 层实现业务逻辑解耦。
*/
trait CrudControllerTrait
{
/**
* 获取列表
*
* @param Request $request 请求对象
* @return mixed 返回分页数据及总数
*/
public function index(Request $request)
{
try {
$raw = $this->service->serviceList($request);
$data = $this->transformData($raw, method_exists($this, 'indexFormat')
? [$this, 'indexFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 获取详情
*
* @param Request $request 请求对象
* @param int|string $id 资源 ID
* @return mixed 返回资源详情数据
*/
public function show(Request $request)
{
try {
$raw = $this->service->serviceShow($request);
$data = $this->transformData($raw, method_exists($this, 'showFormat')
? [$this, 'showFormat']
: null);
return $this->success($data);
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 创建
*
* @param Request $request 请求对象
* @return mixed 返回创建成功的数据或信息
*/
public function store(Request $request)
{
try {
$data = $this->validate($this->service->getValidator(), $request->post(), 'store');
$res = $this->service->serviceStore($request, $data);
return $this->success($res, '创建成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 更新
*
* @param Request $request 请求对象
* @return mixed 返回更新成功的数据或信息
*/
public function update(Request $request)
{
try {
$data = $this->validate($this->service->getValidator(), $request->post(), 'update');
$res = $this->service->serviceUpdate($request, $data);
return $this->success([], '更新成功');
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
/**
* 删除
*
* @param Request $request 请求对象
* @return mixed 返回删除操作结果
*/
public function destroy(Request $request)
{
try {
$count = $this->service->serviceDestroy($request);
return $this->success([], "成功删除了 {$count} 条");
} catch (\Throwable $e) {
return $this->error($e->getMessage());
}
}
// ----------------------------------------------------------------
// 以下三个钩子方法,**在使用此 Trait 的 Controller** 中实现
// ----------------------------------------------------------------
/**
* 字段名格式化:例如全部下划线
*
* @param string $field
* @return string
*/
// protected function fieldFormatter(string $field): string
// {
// return snake_case($field);
// }
/**
* 字段级后置加工规则
*
* @return array<string,callable>
* 键为 “点号嵌套”或 “通配符” 字段路径,
* 值为 fn($value, $row)=>mixed
*/
// protected function getFieldTransforms(): array
// {
// return [
// 'name' => fn($v,$row)=>strtoupper($v),
// 'orders.*.total' => fn($v,$row)=>number_format($v,2),
// ];
// }
/**
* 结果级后置加工:接收整个数组,可任意增删改
*
* @param array $data
* @return array
*/
// protected function postFormat(array $data): array
// {
// // 例如重命名 records 为 items
// if (isset($data['records'])) {
// $data['items'] = $data['records'];
// unset($data['records']);
// }
// return $data;
// }
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace app\common\traits\crud;
use app\common\traits\crud\CrudUtil;
/**
* Trait CrudLogicTrait
*
* 提供最基础的 CRUD 和复杂查询方法,基于 CrudUtil。
* 使用此 Trait 的类,须定义:
* protected string $modelClass = \App\common\model\YourModel::class;
*/
trait CrudLogicTrait
{
/**
* 分页列表
*/
public function logicList(array $config, int $limit, int $page)
{
$config['model'] = $this->modelClass;
return CrudUtil::paginate(CrudUtil::buildQuery($config), $limit, $page);
}
/**
* 全量列表
*/
public function logicListAll(array $config)
{
$config['model'] = $this->modelClass;
return CrudUtil::getAll(CrudUtil::buildQuery($config));
}
/**
* 查询单条
*/
public function logicShow(array $config)
{
return CrudUtil::findOneBy(CrudUtil::buildQuery($config));
}
/**
* 创建(可带 dataLimit)
*/
public function logicCreate(array $data, array $dataLimit = [])
{
return CrudUtil::create($this->modelClass, $data, $dataLimit);
}
/**
* 任意条件更新(可带 dataLimit)
*/
public function logicUpdateBy(array $config, array $data)
{
return CrudUtil::updateBy(CrudUtil::buildQuery($config), $data);
}
/**
* 任意条件删除(可带 dataLimit)
*/
public function logicDeleteBy(array $config)
{
return CrudUtil::deleteBy(CrudUtil::buildQuery($config));
}
/**
* 复杂查询回调
*/
public function logicComplex(callable $callback)
{
return CrudUtil::executeComplex($this->modelClass, $callback);
}
/**
* 根据主键查询
*/
public function findByPk(int $id, array $with = [])
{
return CrudUtil::findByPk($this->modelClass, $id, $with);
}
}
+202
View File
@@ -0,0 +1,202 @@
<?php
namespace app\common\traits\crud;
use support\exception\BusinessException;
use support\Request;
/**
* Trait CrudServiceTrait
*
* 提供标准的增删改查 Service 接口,依赖 CrudLogicTrait。
* 使用此 Trait 的 Service 类,须:
* use CrudLogicTrait, CrudServiceTrait;
* protected string $modelClass = \App\common\model\YourModel::class;
*/
trait CrudServiceTrait
{
use CrudLogicTrait;
/** 具体 Service 里定义:protected string $modelClass = YourModel::class; */
/**
* 获取验证器
*/
public function getValidator()
{
return $this->validator;
}
/** 分页列表 */
public function serviceList(Request $request, string $scene = 'index')
{
$result = $this->cqyProcess(ucfirst($scene), $request);
if ($result === false) {
$config = $this->buildQueryConfig($request, $scene);
$limit = (int)$request->get('size', 15);
$page = (int)$request->get('current', 1);
$result = $this->logicList($config, $limit, $page);
}
return $this->postProcess(ucfirst($scene), $result);
}
/** 全量列表 */
public function serviceListAll(Request $request, string $scene = 'default')
{
$result = $this->cqyProcess(ucfirst($scene), $request);
if ($result === false) {
$config = $this->buildQueryConfig($request, $scene);
$result = $this->logicListAll($config);
}
return $this->postProcess(ucfirst($scene), $result);
}
/** 查看单条 */
public function serviceShow(Request $request, string $scene = 'show')
{
$config = $this->buildQueryConfig($request, $scene);
$item = $this->logicShow($config);
return $this->postProcess(ucfirst($scene), $item);
}
/** 创建 */
public function serviceStore(Request $request, array $data = [], string $scene = 'store')
{
$data = $this->preProcess(ucfirst($scene), $data, $request);
$dl = $this->getDataLimitFromRequest($request);
$created = $this->logicCreate($data, $dl);
return $this->postProcess(ucfirst($scene), $created);
}
/** 更新 */
public function serviceUpdate(Request $request, array $data = [], string $scene = 'update')
{
$data = $this->preProcess(ucfirst($scene), $data, $request);
$config = $this->buildQueryConfig($request, $scene);
$updated = $this->logicUpdateBy($config, $data);
return $this->postProcess(ucfirst($scene), $data);
}
/** 删除 */
public function serviceDestroy(Request $request, string $scene = 'destroy')
{
$config = $this->buildQueryConfig($request, $scene);
$deleted = $this->logicDeleteBy($config);
return $this->postProcess(ucfirst($scene), $deleted);
}
/** ----------------- 公共辅助方法 ----------------- */
/**
* 构造 CrudUtil 查询配置
*/
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
{
$p = $request->all();
$config = [];
$config['model'] = $this->modelClass;
switch ($scene) {
case 'default':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'index':
$config['where']['status'] = 1;
$config['order']['sort'] = 'desc';
$config['order']['id'] = 'desc';
break;
case 'show':
$config['where']['id'] = $p['id'];
$config['where']['status'] = 1;
break;
case 'store':
break;
case 'update':
$config['where']['id'] = $p['id'];
break;
case 'destroy':
$config['where']['id'] = $p['id'];
break;
}
return $config;
}
/** 解析 withFields 字符串 "rel:field,field;..." */
protected function parseWithFields(string $str): array
{
if (!$str) {
return [];
}
$out = [];
foreach (explode(';', $str) as $pair) {
[$rel, $flds] = explode(':', $pair);
$out[$rel] = explode(',', $flds);
}
return $out;
}
/** 从请求读取 dataLimitType, dataLimitField */
protected function getDataLimitFromRequest(Request $req): array
{
$type = $req->get('dataLimitType');
if (!$type) {
return [];
}
return [
'type' => $type,
'field' => $req->get('dataLimitField', 'admin_id'),
];
}
/**
* $data 前置处理
*
* @param array $data
* @param Request $request
* @return array
*/
protected function preProcess(string $op, mixed $data, Request $request): mixed
{
$hook = "beforeService{$op}";
return method_exists($this, $hook)
? $this->{$hook}($data, $request)
: $data;
}
/**
* 根据操作类型,调用对应的后置钩子
*
* @param string $op 操作:List, Show, Store, Update, Destroy
* @param mixed $data 原始返回值
* @return mixed 处理后的返回值
*/
protected function postProcess(string $op, mixed $data): mixed
{
$hook = "afterService{$op}";
if (method_exists($this, $hook)) {
$args = func_get_args();
array_shift($args);
array_shift($args);
$params = array_merge([$data], $args);
return call_user_func_array([$this, $hook], $params);
}
return $data;
}
/**
* 复杂/自定义查询
*
* @param string $op 场景
* @param mixed $data 原始返回值
* @return mixed 处理后的返回值
*/
protected function cqyProcess(string $op, Request $request): mixed
{
$hook = "complexQuery{$op}";
return method_exists($this, $hook)
? $this->{$hook}($request)
: false;
}
}
+808
View File
@@ -0,0 +1,808 @@
<?php
namespace app\common\traits\crud;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use support\exception\BusinessException;
use support\Db;
/**
* 构建查询工具类
* | 方法 | 说明 |
* | ------------------------------- | --------------------- |
* | `buildQuery($config)` | 构建动态查询(关联加载/字段/过滤/排序) |
* | `paginate($q,$per,$page)` | 分页返回 Paginator |
* | `getAll($q)` | 不分页直接 `get()` |
* | `findByPk($model,$id)` | 根据主键查询单条,找不到抛 404 |
* | `findOneBy($model,$where)` | 根据任意条件查询单条,找不到抛 404 |
* | `deleteByPk($model,$ids)` | 根据主键删除(可批量),返回删除条数 |
* | `deleteBy($model,$where)` | 根据任意条件删除,返回删除条数 |
* | `updateByPk($model,$id,$data)` | 根据主键更新,找不到抛 404 |
* | `updateBy($model,$where,$data)` | 根据任意条件更新,返回影响行数 |
*/
class CrudUtil
{
/**
* 构建查询,支持:
* - 本表字段过滤 where
* - 关联加载 with
* - 关联表字段选择 withFields
* - 关联表字段过滤 where with dot-notation
* - 排序 order
* - 本表字段选择 fields
*
* $config = [
* 'model' => User::class,
* 'where' => [
* 'status' => 1,
* 'name' => ['like','%张%'],
* 'roles.name' => '管理员', // 关联表字段过滤
* 'posts.created_at'=> ['>','2025-01-01'],
* ],
* 'order' => ['id'=>'desc'],
* 'fields' => ['id','name','email'],
* 'with' => ['roles','posts'],
* 'withFields' => [
* 'roles' => ['id','name'], // 关联表字段选择
* 'posts' => ['id','title','user_id'],
* ],
* 'dataLimit' => [
* 'type' => 'personal|same_role|role_tree|relation', // personal:个人数据,same_role:同角色,role_tree:角色树,relation:从属关联数据
* 'userId' => 1,
* 'roleId' => 2,
* 'userField' => 'user_id',
* 'roleField' => 'role_id'
* ]
* ];
*/
public static function buildQuery(array $config): Builder
{
/** @var Model $model */
$model = new $config['model'];
$q = $model->newQuery();
// eager-load:支持 ['rel','rel2'] 和 ['rel'=>fields[], ...]
if (! empty($config['with'])) {
foreach ($config['with'] as $rel => $fields) {
if (is_int($rel)) {
// 普通关联
$q->with($fields);
} else {
// 带字段筛选的关联
$q->with([$rel => function ($qr) use ($fields) {
$key = $qr->getModel()->getKeyName();
if (! in_array($key, $fields, true)) {
$fields[] = $key;
}
$qr->select($fields);
}]);
}
}
}
// 表字段
if (!empty($config['fields'])) {
$q->select($config['fields']);
}
// where
if (!empty($config['where'])) {
foreach ($config['where'] as $col => $val) {
if (strpos($col, '.') !== false) {
// 多层支持
$segments = explode('.', $col);
$leaf = array_pop($segments);
self::applyNestedWhere($q, $segments, $leaf, $val);
} else {
self::applyWhere($q, $col, $val);
}
}
}
// or where
if (!empty($config['orWhere'])) {
$q->where(function($q) use($config) {
foreach ($config['orWhere'] as $cond) {
[$col,$op,$val] = $cond;
$q->orWhere($col,$op,$val);
}
});
}
// 排序
if (!empty($config['order'])) {
foreach ($config['order'] as $col => $dir) {
$q->orderBy($col, strtolower($dir) === 'asc' ? 'asc' : 'desc');
}
}
// 数据权限过滤
if (!empty($config['dataLimit'])) {
self::applyDataLimit($q, $config['dataLimit']);
}
return $q;
}
/**
* 分页查询
*
* @param Builder $query
* @param int $perPage
* @param int $page
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
public static function paginate(Builder $q, int $perPage, int $page)
{
return $q->paginate($perPage, ['*'], 'page', $page);
}
/**
* 非分页查询
*
* @param Builder $query
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getAll(Builder $q)
{
return $q->get();
}
/**
* 执行复杂查询——仅在显式调用时生效
*
* @param string $modelClass 模型类
* @param callable $callback function(Builder $q) { ... }
* @return mixed 由 callback 返回的结果
*/
public static function executeComplex(string $modelClass, callable $cb)
{
/** @var Model $model */
$model = new $modelClass;
return $cb($model->newQuery());
}
/**
* 主键单条查询
*
* @param string $modelClass
* @param mixed $id
* @param array $with
* @return Model
* @throws BusinessException
*/
public static function findByPk(string $modelClass, $id, array $with = []): Model
{
$m = $modelClass::with($with)->find($id);
if (!$m) {
throw new BusinessException("记录 ID={$id} 不存在", 404);
}
return $m;
}
/**
* 任意条件单条查询,支持 dataLimit
*
* @param Builder $query
* @return Model
* @throws BusinessException
*/
public static function findOneBy(Builder $q): Model
{
$model = $q->first();
if (!$model) {
throw new BusinessException("记录不存在", 404);
}
return $model;
}
/**
* 创建,支持 dataLimit
*
* @param string $modelClass
* @param array $data
* @param array $dataLimit
* @return Model
* @throws BusinessException
*/
public static function create(
string $modelClass,
array $data,
array $dataLimit = []
): Model {
// dataLimit 检查/注入
if (!empty($dataLimit)) {
self::applyDataLimitOnMutate($data, $dataLimit);
}
return $modelClass::create($data);
}
/**
* 主键更新,支持 dataLimit
*
* @param string $modelClass
* @param mixed $id
* @param array $data
* @param array $dataLimit
* @return Model
* @throws BusinessException
*/
public static function updateByPk(
string $modelClass,
$id,
array $data,
array $dataLimit = []
): Model {
/** @var Model $m */
$m = $modelClass::find($id);
if (!$m) {
throw new BusinessException("记录 ID={$id} 不存在", 404);
}
// dataLimit 校验
if (!empty($dataLimit)) {
self::applyDataLimitOnMutate($m, $dataLimit, $data);
}
$m->fill($data)->save();
return $m;
}
/**
* 非主键条件更新(批量),支持 dataLimit
*
* @param Builder $query
* @param array $data
* @return int 受影响行数
*/
public static function updateBy(Builder $q, array $data): int
{
return $q->update($data);
}
/**
* 主键删除(支持批量)
*
* @param string $modelClass
* @param array $ids
* @return int 删除条数
*/
public static function deleteByPk(string $modelClass, array $ids): int
{
return $modelClass::destroy($ids);
}
/**
* 非主键条件删除,支持 dataLimit
*
* @param Builder $query
* @return int 删除条数
*/
public static function deleteBy(Builder $q): int
{
return $q->delete();
}
//=================== Internal Methods ===================//
/**
* 支持多层的 whereHas:$relations=['roles','permissions'],$leaf='name'
*/
protected static function applyNestedWhere(Builder $q, array $relations, string $leaf, $val): void
{
$rel = array_shift($relations);
$q->whereHas($rel, function (Builder $qr) use ($relations, $leaf, $val) {
if ($relations) {
// 还有更深层,继续递归
self::applyNestedWhere($qr, $relations, $leaf, $val);
} else {
// 最后一层,使用普通 applyWhere
self::applyWhere($qr, $leaf, $val);
}
});
}
/**
* 单字段 where 支持:
* - 多条件数组 [ [op1,val1], [op2,val2], … ]
* - 单条件 ['op', val]
* - 直接值
*
* @param Builder $q
* @param string $col
* @param mixed $val
* @return void
*/
protected static function applyWhere(Builder $q, string $col, $val): void
{
// 多条件数组
if (is_array($val) && isset($val[0]) && is_array($val[0])) {
foreach ($val as $pair) {
if (is_array($pair) && count($pair) === 2) {
self::applySingleWhere($q, $col, $pair[0], $pair[1]);
}
}
}
// 单条件
elseif (is_array($val) && count($val) === 2) {
self::applySingleWhere($q, $col, $val[0], $val[1]);
}
// 直接等值
else {
$q->where($col, $val);
}
}
/**
* 执行条件 where
*/
protected static function applySingleWhere(Builder $q, string $col, string $op, $v): void
{
switch (strtolower($op)) {
case 'like':
$q->where($col, 'like', $v);
break;
case 'in':
$q->whereIn($col, (array)$v);
break;
case 'not in':
$q->whereNotIn($col, (array)$v);
break;
case 'null':
$q->whereNull($col);
break;
case 'not null':
$q->whereNotNull($col);
break;
case 'between':
$q->whereBetween($col, (array)$v);
break;
default:
$q->where($col, $op, $v);
}
}
// ================== Data Limit ==================
/**
* 应用数据权限过滤到查询(buildQuery 场景)
*/
protected static function applyDataLimit(Builder $q, array $dataLimit): void
{
$type = $dataLimit['type'] ?? 'personal';
$userId = $dataLimit['userId'] ?? 0;
$roleId = $dataLimit['roleId'] ?? 0;
$userField = $dataLimit['userField'] ?? 'user_id';
$roleField = $dataLimit['roleField'] ?? 'role_id';
self::applyQueryPermission($q, $type, $userId, $roleId, $userField, $roleField);
}
/**
* 应用数据权限到"创建/更新"操作
*
* - $target 可能是 array|Model 对象
* - $newData 仅在 updateByPk 场景时传入,用以校验变更归属
*/
protected static function applyDataLimitOnMutate(&$target, array $dataLimit, array $newData = []): void
{
$type = $dataLimit['type'] ?? 'personal';
$userId = $dataLimit['userId'] ?? 0;
$roleId = $dataLimit['roleId'] ?? 0;
$userField = $dataLimit['userField'] ?? 'user_id';
$roleField = $dataLimit['roleField'] ?? 'role_id';
self::applyMutationPermission($target, $type, $userId, $roleId, $newData, $userField, $roleField);
}
/**
* 应用数据权限过滤到查询
*
* @param Builder $query 查询构造器
* @param string $type 权限类型:superadmin, personal, same_role, role_tree, relation
* @param int $currentUserId 当前用户ID
* @param int $currentRoleId 当前角色ID
* @param string $userField 用户ID字段名
* @param string $roleField 角色ID字段名
*/
public static function applyQueryPermission(
Builder $query,
string $type,
int $currentUserId = 0,
int $currentRoleId = 0,
string $userField = 'user_id',
string $roleField = 'role_id'
): void {
// 超管不需要过滤
if (self::isSuperAdmin($currentUserId)) {
return;
}
switch ($type) {
case 'personal':
// 个人权限:只能访问自己的数据
$query->where($userField, $currentUserId);
break;
case 'same_role':
// 同角色权限:访问同角色的数据
$userIds = self::getSameRoleUserIds($currentRoleId);
$query->whereIn($userField, $userIds);
break;
case 'role_tree':
// 角色树权限:访问自己角色及子角色的数据
$userIds = self::getRoleTreeUserIds($currentRoleId);
$query->whereIn($userField, $userIds);
break;
case 'relation':
// 从属关系权限:访问自己及下属的数据
$userIds = self::getRelationUserIds($currentUserId);
$query->whereIn($userField, $userIds);
break;
default:
// 默认个人权限
$query->where($userField, $currentUserId);
}
}
/**
* 应用数据权限到创建/更新操作
*
* @param mixed $target 创建时为数组,更新时为Model对象
* @param string $type 权限类型
* @param int $currentUserId 当前用户ID
* @param int $currentRoleId 当前角色ID
* @param array $newData 更新时的数据
* @param string $userField 用户ID字段名
* @param string $roleField 角色ID字段名
*/
public static function applyMutationPermission(
&$target,
string $type,
int $currentUserId,
int $currentRoleId,
array $newData = [],
string $userField = 'user_id',
string $roleField = 'role_id'
): void {
// 超管不需要检查
if (self::isSuperAdmin($currentUserId)) {
return;
}
switch ($type) {
case 'personal':
self::handlePersonalPermission($target, $newData, $userField, $currentUserId);
break;
case 'same_role':
self::handleSameRolePermission($target, $newData, $userField, $roleField, $currentRoleId);
break;
case 'role_tree':
self::handleRoleTreePermission($target, $newData, $userField, $roleField, $currentRoleId);
break;
case 'relation':
self::handleRelationPermission($target, $newData, $userField, $currentUserId);
break;
default:
throw new BusinessException("未定义的数据权限类型: $type", 403);
}
}
/**
* 获取同角色的用户ID列表
*
* @param int $roleId 角色ID
* @return array 用户ID列表
*/
public static function getSameRoleUserIds(int $roleId): array
{
return Db::table('user_roles')
->where('role_id', $roleId)
->pluck('user_id')
->toArray();
}
/**
* 获取角色树下的所有用户ID列表
*
* @param int $roleId 当前角色ID
* @return array 用户ID列表
*/
public static function getRoleTreeUserIds(int $roleId): array
{
// 获取角色树中的所有角色ID
$roleIds = self::getRoleTreeIds($roleId);
// 获取这些角色下的所有用户ID
return Db::table('user_roles')
->whereIn('role_id', $roleIds)
->pluck('user_id')
->toArray();
}
/**
* 获取从属关系下的用户ID列表(包括自己和下属)
*
* @param int $userId 当前用户ID
* @return array 用户ID列表
*/
public static function getRelationUserIds(int $userId): array
{
// 获取所有下属用户ID(递归)
$subordinateIds = self::getSubordinateIds($userId);
// 返回自己和所有下属的ID
return array_merge([$userId], $subordinateIds);
}
/**
* 获取直接下属的用户ID列表
*
* @param int $userId 用户ID
* @return array 直接下属用户ID列表
*/
public static function getDirectSubordinateIds(int $userId): array
{
return Db::table('user')
->where('report_to', $userId)
->pluck('id')
->toArray();
}
/**
* 递归获取所有下属用户ID列表
*
* @param int $userId 用户ID
* @return array 所有下属用户ID列表
*/
private static function getSubordinateIds(int $userId): array
{
$subordinates = self::getDirectSubordinateIds($userId);
$allSubordinates = $subordinates;
foreach ($subordinates as $subordinateId) {
$allSubordinates = array_merge($allSubordinates, self::getSubordinateIds($subordinateId));
}
return $allSubordinates;
}
/**
* 处理个人权限
*/
private static function handlePersonalPermission(
&$target,
array $newData,
string $userField,
int $currentUserId
): void {
if (is_array($target)) {
// 创建时强制设置用户ID
$target[$userField] = $currentUserId;
} else {
// 更新时校验原始记录
if ($target->{$userField} !== $currentUserId) {
throw new BusinessException("无权操作该记录", 403);
}
// 禁止变更归属字段
if (isset($newData[$userField]) && $newData[$userField] !== $currentUserId) {
throw new BusinessException("无权变更归属字段", 403);
}
}
}
/**
* 处理同角色权限
*/
private static function handleSameRolePermission(
&$target,
array $newData,
string $userField,
string $roleField,
int $currentRoleId
): void {
// 获取同角色用户ID列表
$sameRoleUserIds = self::getSameRoleUserIds($currentRoleId);
if (is_array($target)) {
// 创建时设置归属用户并校验
if (isset($target[$userField])) {
if (!in_array($target[$userField], $sameRoleUserIds)) {
throw new BusinessException("无权为指定用户创建数据", 403);
}
} else {
$target[$userField] = $currentRoleId;
}
} else {
// 更新时校验原始记录
if (!in_array($target->{$userField}, $sameRoleUserIds)) {
throw new BusinessException("无权操作该记录", 403);
}
// 禁止变更归属字段到无权限的用户
if (isset($newData[$userField]) && !in_array($newData[$userField], $sameRoleUserIds)) {
throw new BusinessException("无权变更归属字段", 403);
}
}
}
/**
* 处理角色树权限
*/
private static function handleRoleTreePermission(
&$target,
array $newData,
string $userField,
string $roleField,
int $currentRoleId
): void {
// 获取角色树用户ID列表
$roleTreeUserIds = self::getRoleTreeUserIds($currentRoleId);
if (is_array($target)) {
// 创建时校验用户是否在允许范围内
if (isset($target[$userField])) {
if (!in_array($target[$userField], $roleTreeUserIds)) {
throw new BusinessException("无权为指定用户创建数据", 403);
}
} else {
$target[$userField] = $currentRoleId;
}
} else {
// 更新时校验原始记录用户
if (!in_array($target->{$userField}, $roleTreeUserIds)) {
throw new BusinessException("无权操作该记录", 403);
}
// 校验新用户是否在允许范围内
if (isset($newData[$userField])) {
if (!in_array($newData[$userField], $roleTreeUserIds)) {
throw new BusinessException("无权变更用户字段", 403);
}
}
}
}
/**
* 处理从属关系权限
*/
private static function handleRelationPermission(
&$target,
array $newData,
string $userField,
int $currentUserId
): void {
// 获取从属关系用户ID列表
$relationUserIds = self::getRelationUserIds($currentUserId);
if (is_array($target)) {
// 创建时校验用户是否在允许范围内
if (isset($target[$userField])) {
if (!in_array($target[$userField], $relationUserIds)) {
throw new BusinessException("无权为指定用户创建数据", 403);
}
} else {
$target[$userField] = $currentUserId;
}
} else {
// 更新时校验原始记录用户
if (!in_array($target->{$userField}, $relationUserIds)) {
throw new BusinessException("无权操作该记录", 403);
}
// 校验新用户是否在允许范围内
if (isset($newData[$userField])) {
if (!in_array($newData[$userField], $relationUserIds)) {
throw new BusinessException("无权变更用户字段", 403);
}
}
}
}
/**
* 获取角色树ID(当前角色及其所有子角色)
* 从数据库中动态获取角色树关系
*/
private static function getRoleTreeIds(int $roleId): array
{
static $roleTreeCache = [];
if (isset($roleTreeCache[$roleId])) {
return $roleTreeCache[$roleId];
}
// 从数据库中递归获取角色树
$ids = [$roleId];
$queue = [$roleId];
while (!empty($queue)) {
$current = array_shift($queue);
// 查找当前角色的所有子角色
$childRoles = Db::table('roles')
->where('parent_id', $current)
->where('status', 1) // 只获取启用的角色
->pluck('id')
->toArray();
foreach ($childRoles as $childId) {
if (!in_array($childId, $ids)) {
$ids[] = $childId;
$queue[] = $childId;
}
}
}
$roleTreeCache[$roleId] = $ids;
return $ids;
}
/**
* 检查是否为超管
*
* @param int $userId 用户ID
* @return bool 是否为超管
*/
public static function isSuperAdmin(int $userId): bool
{
// 检查用户是否关联了超级管理员角色
// 设定角色ID为1的是超级管理员角色
$superAdminRoleId = 1;
return Db::table('user_roles')
->where('user_id', $userId)
->where('role_id', $superAdminRoleId)
->exists();
}
/**
* 获取用户的角色ID
*
* @param int $userId 用户ID
* @return int 角色ID
*/
public static function getUserIdRoleId(int $userId): int
{
// 从 user_roles 表中获取当前用户的角色ID
$roleId = Db::table('user_roles')
->where('user_id', $userId)
->value('role_id');
return $roleId ?: 0;
}
/**
* 获取用户的所有角色ID(包括继承的角色)
*
* @param int $userId 用户ID
* @return array 角色ID列表
*/
public static function getUserAllRoleIds(int $userId): array
{
// 获取用户直接关联的角色
$directRoleIds = Db::table('user_roles')
->where('user_id', $userId)
->pluck('role_id')
->toArray();
// 获取所有继承的角色
$allRoleIds = [];
foreach ($directRoleIds as $roleId) {
$allRoleIds = array_merge($allRoleIds, self::getRoleTreeIds($roleId));
}
// 去重并返回
return array_unique($allRoleIds);
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace app\common\utils;
use Firebase\JWT\JWT as FirebaseJWT;
use Firebase\JWT\Key;
use support\exception\BusinessException;
class Jwt
{
protected $secretKey;
protected $algorithm;
protected $issuer;
protected $expiration;
public function __construct()
{
$this->secretKey = config('jwt.jwt_secret_key');
$this->algorithm = config('jwt.jwt_algorithm');
$this->issuer = config('jwt.jwt_issuer');
$this->expiration = config('jwt.jwt_expiration');
}
/**
* 生成 JWT 令牌
*
* @param array $payload
* @return string
* @throws BusinessException
*/
public function generateToken(array $payload): string
{
$defaultPayload = [
// // —— 注册 Claim ——
'iss' => $this->issuer, // 签发者
// 'sub' => 'user_1001', // 面向的用户
// 'aud' => 'api.gateway.example.com', // 接收方
// 'iat' => time(), // 签发时间
// 'nbf' => time(), // 生效时间
// 'exp' => time() + $this->expiration, // 1 小时后过期
'jti' => bin2hex(random_bytes(16)), // 唯一 ID
// // —— 自定义 Claim ——
// 'uid' => 1001, // 业务用户 ID
// 'roles' => ['admin', 'editor'], // 用户角色
// 'meta' => ['region' => 'CN', 'tier' => 5], // 任意自定义数据
];
$payload = array_merge($defaultPayload, $payload);
try {
return FirebaseJWT::encode($payload, $this->secretKey, $this->algorithm);
} catch (\Exception $e) {
throw new BusinessException("令牌生成失败:" . $e->getMessage(), 500);
}
}
/**
* 验证 JWT 令牌
*
* @param string $token
* @return object
* @throws BusinessException
*/
public function verifyToken(string $token)
{
try {
return FirebaseJWT::decode($token, new Key($this->secretKey, $this->algorithm));
} catch (\Firebase\JWT\ExpiredException $e) {
throw new BusinessException('令牌已过期', 401);
} catch (\Firebase\JWT\SignatureInvalidException $e) {
throw new BusinessException('无效的令牌签名', 401);
} catch (\Firebase\JWT\BeforeValidException $e) {
throw new BusinessException('令牌尚未生效', 401);
} catch (\Exception $e) {
throw new BusinessException('令牌验证失败:' . $e->getMessage(), 400);
}
}
public function describe()
{
/* payload
1. 注册 Claim 详解
Claim 名称 含义 类型 建议取值
iss Issuer,发行者,标识该 JWT 的签发主体 string or URI 你的服务域名或项目标识,比如 https://api.example.com
sub Subject,主题,标识该 JWT 所面向的用户 string or int 用户在你系统里的唯一 ID
aud Audience,受众,标识该 JWT 的接收方 string or array 接收该令牌的服务标识,比如 ["service-a","service-b"]
exp Expiration Time,过期时间,Unix 时间戳,到达此时间后不可用 int time() + 3600(1 小时后过期)
nbf Not Before,生效时间,Unix 时间戳,表示在此时间之前不可用 int time() 或者 time() + 60(1 分钟后生效)
iat Issued At,签发时间,Unix 时间戳 int time()
jti JWT ID,JWT 的唯一标识符,用于去重或一次性令牌 string uniqid('', true) 或者 UUID
注意:exp、nbf、iat 必须是整数(秒级时间戳),否则库会抛类型异常或验证失败。
2. 自定义 Claim
除了上面这些“注册”字段,你可以任意添加,比如:
php
$payload['uid'] = 123; // 业务用户 ID
$payload['roles'] = ['admin','user']; // 权限数组
$payload['feature'] = 'beta'; // 标记某个功能开关
这些字段在你的应用程序里可以自由取用。
*/
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace app\common\utils;
use support\Db;
class TableInspector
{
/**
* 获取某个表的所有字段信息
*/
public static function getColumns(string $table, ?string $database = null): array
{
$database = $database ?: Db::connection()->getDatabaseName();
$columns = Db::select("
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE,
COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION
", [$database, $table]);
return array_map(function ($col) {
return [
'name' => $col->COLUMN_NAME,
'type' => $col->DATA_TYPE,
'column_type' => $col->COLUMN_TYPE,
'nullable' => $col->IS_NULLABLE === 'YES',
'default' => $col->COLUMN_DEFAULT,
'comment' => $col->COLUMN_COMMENT,
'extra' => $col->EXTRA,
];
}, $columns);
}
/**
* 获取字段名列表
*/
public static function getColumnNames(string $table, ?string $database = null): array
{
return array_column(self::getColumns($table, $database), 'name');
}
/**
* 获取字段注释 map:字段名 => 注释
*/
public static function getColumnComments(string $table, ?string $database = null): array
{
$columns = self::getColumns($table, $database);
$map = [];
foreach ($columns as $col) {
$map[$col['name']] = $col['comment'];
}
return $map;
}
}
+505
View File
@@ -0,0 +1,505 @@
<?php
namespace app\common\utils;
class Tree
{
// /**
// * 递归构建树形结构
// *
// * @param array $data 数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @param int $level 当前层级
// * @return array
// */
// public static function build(array $data, int $parentId = 0, string $pidField = 'parent_id', string $idField = 'id', int $level = 0): array
// {
// $tree = [];
// foreach ($data as $item) {
// if ($item[$pidField] == $parentId) {
// $item['level'] = $level;
// $children = static::build($data, $item[$idField], $pidField, $idField, $level + 1);
// if (!empty($children)) {
// $item['children'] = $children;
// }
// $tree[] = $item;
// }
// }
// return $tree;
// }
// /**
// * 使用引用方式构建树形结构(性能更好)
// *
// * @param array $data 数据列表
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @return array
// */
// public static function buildWithReference(array $data, string $pidField = 'parent_id', string $idField = 'id'): array
// {
// $items = [];
// $tree = [];
// // 建立以id为索引的数组
// foreach ($data as $item) {
// $item['level'] = 0;
// $items[$item[$idField]] = $item;
// }
// // 构建树形结构
// foreach ($items as $id => $item) {
// if ($item[$pidField] == 0) {
// $tree[] = &$items[$id];
// } else {
// if (isset($items[$item[$pidField]])) {
// $items[$id]['level'] = $items[$item[$pidField]]['level'] + 1;
// if (!isset($items[$item[$pidField]]['children'])) {
// $items[$item[$pidField]]['children'] = [];
// }
// $items[$item[$pidField]]['children'][] = &$items[$id];
// }
// }
// }
// return $tree;
// }
// /**
// * 将树形结构扁平化为列表(带层级前缀)
// *
// * @param array $tree 树形结构数据
// * @param string $prefix 前缀字符
// * @param string $field 显示字段名
// * @param int $level 层级
// * @return array
// */
// public static function flattenWithPrefix(array $tree, string $prefix = '--', string $field = 'name', int $level = 0): array
// {
// $result = [];
// foreach ($tree as $item) {
// $item[$field . '_with_prefix'] = str_repeat($prefix, $item['level']) . $item[$field];
// $result[] = $item;
// if (isset($item['children']) && !empty($item['children'])) {
// $children = static::flattenWithPrefix($item['children'], $prefix, $field, $item['level'] + 1);
// $result = array_merge($result, $children);
// }
// }
// return $result;
// }
// /**
// * 根据父ID查找所有子节点(包括子孙节点)
// *
// * @param array $data 数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @return array
// */
// public static function findChildren(array $data, int $parentId, string $pidField = 'parent_id', string $idField = 'id'): array
// {
// $children = [];
// foreach ($data as $item) {
// if ($item[$pidField] == $parentId) {
// $children[] = $item;
// $grandChildren = static::findChildren($data, $item[$idField], $pidField, $idField);
// $children = array_merge($children, $grandChildren);
// }
// }
// return $children;
// }
// /**
// * 获取指定节点的所有父级节点
// *
// * @param array $data 数据列表
// * @param int $id 当前节点ID
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @return array
// */
// public static function getParents(array $data, int $id, string $pidField = 'parent_id', string $idField = 'id'): array
// {
// $parents = [];
// foreach ($data as $item) {
// if ($item[$idField] == $id) {
// if ($item[$pidField] != 0) {
// $parents[] = $item;
// $parentParents = static::getParents($data, $item[$pidField], $pidField, $idField);
// $parents = array_merge($parents, $parentParents);
// }
// break;
// }
// }
// return $parents;
// }
// /**
// * 获取指定节点的子孙节点ID列表
// *
// * @param array $data 数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @return array
// */
// public static function getChildrenIds(array $data, int $parentId, string $pidField = 'parent_id', string $idField = 'id'): array
// {
// $ids = [];
// $children = static::findChildren($data, $parentId, $pidField, $idField);
// foreach ($children as $child) {
// $ids[] = $child[$idField];
// }
// return $ids;
// }
// /**
// * 检查是否有子节点
// *
// * @param array $data 数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @return bool
// */
// public static function hasChildren(array $data, int $parentId, string $pidField = 'parent_id'): bool
// {
// foreach ($data as $item) {
// if ($item[$pidField] == $parentId) {
// return true;
// }
// }
// return false;
// }
// /**
// * 构建带有权限列表的资源树(权限不显示在树结构中)
// *
// * @param array $data 资源数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @param string $typeField 类型字段名
// * @param string $permissionType 权限类型标识
// * @param int $level 当前层级
// * @return array
// */
// public static function buildWithAuthList(
// array $data,
// int $parentId = 0,
// string $pidField = 'parent_id',
// string $idField = 'id',
// string $typeField = 'type',
// string $permissionType = 'permission',
// int $level = 0
// ): array {
// $tree = [];
// foreach ($data as $item) {
// // 只处理指定父级的非权限节点
// if ($item[$pidField] == $parentId && $item[$typeField] != $permissionType) {
// $item['level'] = $level;
// // 递归处理子节点(非权限类型)
// $children = static::buildWithAuthList($data, $item[$idField], $pidField, $idField, $typeField, $permissionType, $level + 1);
// if (!empty($children)) {
// $item['children'] = $children;
// }
// // 收集当前节点下的所有权限
// $item['auth_list'] = static::collectDirectPermissions($data, $item[$idField], $pidField, $typeField, $permissionType);
// $tree[] = $item;
// }
// }
// return $tree;
// }
// /**
// * 收集指定节点下的直接权限项(只收集直接子节点中的权限)
// *
// * @param array $data 数据列表
// * @param int $parentId 父级ID
// * @param string $pidField 父级字段名
// * @param string $typeField 类型字段名
// * @param string $permissionType 权限类型标识
// * @return array
// */
// public static function collectDirectPermissions1(
// array $data,
// int $parentId,
// string $pidField = 'parent_id',
// string $typeField = 'type',
// string $permissionType = 'permission'
// ): array {
// $permissions = [];
// foreach ($data as $item) {
// // 收集直接子节点中的权限类型
// if ($item[$pidField] == $parentId && $item[$typeField] == $permissionType) {
// $permissions[] = $item;
// }
// }
// return $permissions;
// }
// /**
// * 使用引用方式构建带有权限列表的资源树(权限不显示在树结构中)
// *
// * @param array $data 资源数据列表
// * @param string $pidField 父级字段名
// * @param string $idField ID字段名
// * @param string $typeField 类型字段名
// * @param string $permissionType 权限类型标识
// * @return array
// */
// public static function buildWithAuthListAndReference(
// array $data,
// string $pidField = 'parent_id',
// string $idField = 'id',
// string $typeField = 'type',
// string $permissionType = 'permission'
// ): array {
// $items = [];
// $tree = [];
// // 建立以id为索引的数组(只包含非权限节点)
// foreach ($data as $item) {
// if ($item[$typeField] != $permissionType) {
// $item['level'] = 0;
// $items[$item[$idField]] = $item;
// }
// }
// // 构建树形结构
// foreach ($items as $id => $item) {
// if ($item[$pidField] == 0) {
// $tree[] = &$items[$id];
// } else {
// if (isset($items[$item[$pidField]])) {
// $items[$id]['level'] = $items[$item[$pidField]]['level'] + 1;
// if (!isset($items[$item[$pidField]]['children'])) {
// $items[$item[$pidField]]['children'] = [];
// }
// $items[$item[$pidField]]['children'][] = &$items[$id];
// }
// }
// }
// // 为每个节点添加权限列表
// foreach ($items as &$item) {
// $item['auth_list'] = static::collectDirectPermissions($data, $item[$idField], $pidField, $typeField, $permissionType);
// }
// return $tree;
// }
// 8.13 add
/**
* 构建前端需要的菜单树结构
*
* @param array $data 服务端菜单数据
* @param int $parentId 父级ID
* @return array
*/
public static function buildMenuTree(array $data, int $parentId = 0): array
{
$tree = [];
foreach ($data as $item) {
// 只处理非权限类型的节点
if ($item['parent_id'] == $parentId && $item['type'] != 'permission') {
// 转换数据结构以匹配前端需要的格式
$menuItem = [
'id' => $item['id'],
'path' => $item['url'] ?? '',
'name' => $item['name'],
'component' => $item['url'] ?? '',
'meta' => [
'title' => $item['name'],
'icon' => $item['icon'] ?? '',
'isHide' => !empty($item['is_hide']),
'isHideTab' => !empty($item['is_hide_tab']),
'keepAlive' => !empty($item['keep_alive']),
]
];
// 收集当前节点下的所有权限
$permissions = static::collectDirectPermissions($data, $item['id']);
if (!empty($permissions)) {
$authList = [];
foreach ($permissions as $permission) {
$authList[] = [
'id' => $permission['id'],
'title' => $permission['name'] ?? '',
'authMark' => $permission['code'] ?? '',
'selected' => isset($permission['selected']) && $permission['selected'] ? true : false
];
}
$menuItem['meta']['authList'] = $authList;
}
// 将服务端额外字段放入meta中
$extraFields = static::getExtraFields($item);
if (!empty($extraFields)) {
$menuItem['meta'] = array_merge($menuItem['meta'], $extraFields);
}
// 递归处理子菜单
$children = static::buildMenuTree($data, $item['id']);
if (!empty($children)) {
$menuItem['children'] = $children;
}
$tree[] = $menuItem;
}
}
return $tree;
}
/**
* 使用引用方式构建前端需要的菜单树结构(性能更好)
*
* @param array $data 服务端菜单数据
* @return array
*/
public static function buildMenuTreeWithReference(array $data): array
{
$items = [];
$tree = [];
// 过滤出菜单类型的节点并转换数据结构
$menuData = array_filter($data, function($item) {
return ($item['type'] ?? '') != 'permission';
});
// 建立以id为索引的数组并转换数据结构
foreach ($menuData as $item) {
$items[$item['id']] = [
'id' => $item['id'],
'path' => $item['url'] ?? '',
'name' => $item['name'],
'component' => $item['url'] ?? '',
'meta' => [
'title' => $item['name'],
'icon' => $item['icon'] ?? '',
'isHide' => !empty($item['is_hide']),
'isHideTab' => !empty($item['is_hide_tab']),
'keepAlive' => !empty($item['keep_alive']),
]
];
// 将服务端额外字段放入meta中
$extraFields = static::getExtraFields($item);
if (!empty($extraFields)) {
$items[$item['id']]['meta'] = array_merge($items[$item['id']]['meta'], $extraFields);
}
}
// 构建树形结构
foreach ($menuData as $item) {
if ($item['parent_id'] == 0) {
$tree[] = &$items[$item['id']];
} else {
if (isset($items[$item['parent_id']])) {
if (!isset($items[$item['parent_id']]['children'])) {
$items[$item['parent_id']]['children'] = [];
}
$items[$item['parent_id']]['children'][] = &$items[$item['id']];
}
}
}
// 为每个节点添加权限列表
foreach ($items as &$item) {
$parentId = $item['id'];
$permissions = static::collectDirectPermissions($data, $parentId);
if (!empty($permissions)) {
$authList = [];
foreach ($permissions as $permission) {
$authList[] = [
'id' => $permission['id'],
'title' => $permission['name'] ?? '',
'authMark' => $permission['code'] ?? ''
];
}
$item['meta']['authList'] = $authList;
}
}
return $tree;
}
/**
* 收集指定节点下的直接权限项(只收集直接子节点中的权限)
*
* @param array $data 数据列表
* @param int $parentId 父级ID
* @return array
*/
public static function collectDirectPermissions(array $data, int $parentId): array
{
$permissions = [];
foreach ($data as $item) {
// 收集直接子节点中的权限类型
if ($item['parent_id'] == $parentId && ($item['type'] ?? '') == 'permission') {
$permissions[] = $item;
}
}
return $permissions;
}
/**
* 获取需要放入meta的额外字段
*
* @param array $item 菜单项
* @return array
*/
public static function getExtraFields(array $item): array
{
$extraFields = [];
$standardFields = [
'id', 'name', 'url', 'icon', 'is_hide', 'is_hide_tab', 'keep_alive', 'level'
];
foreach ($item as $key => $value) {
// 如果不是标准字段且不是auth_list,则放入extraFields
if (!in_array($key, $standardFields) && $key !== 'auth_list') {
// 转换下划线命名到驼峰命名
$camelKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
$extraFields[$camelKey] = $value;
}
}
return $extraFields;
}
}
+194
View File
@@ -0,0 +1,194 @@
<?php
namespace app\common\utils\scaffold;
use Symfony\Component\Filesystem\Filesystem;
use app\common\utils\TableInspector;
class Generate
{
// 配置项
protected $options = [];
public function __construct(array $options = [])
{
if (empty($options)) {
return;
}
$this->options = $options;
}
public function doGenerate()
{
$fs = new Filesystem();
$templateDir = __DIR__ . '/templates/';
$resultStr = '';
if (!empty($this->options['controller'])) {
$controllerPath = __DIR__ . "/../../../{$this->options['controller']['module']}/controller/".$this->rName($this->options['controller']['name'])."Controller.php";
$fs->mkdir(__DIR__ . "/../../../{$this->options['controller']['module']}/controller");
$this->deleteFileIfExists($controllerPath);
$ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath, 'controller');
$resultStr .= $ControllerStub;
}
if (!empty($this->options['service'])) {
$servicePath = __DIR__ . "/../../../{$this->options['service']['module']}/service/".$this->rName($this->options['service']['name'])."Service.php";
$fs->mkdir(__DIR__ . "/../../../{$this->options['service']['module']}/service");
$this->deleteFileIfExists($servicePath);
$ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath, 'service');
$resultStr .= $ServiceStub;
}
if (!empty($this->options['logic'])) {
$logicPath = __DIR__ . "/../../../{$this->options['logic']['module']}/logic/".$this->rName($this->options['logic']['name'])."Logic.php";
$fs->mkdir(__DIR__ . "/../../../{$this->options['logic']['module']}/logic");
$this->deleteFileIfExists($logicPath);
$LogicStub = $this->generateFile($templateDir, 'LogicStub.stub', $logicPath, 'logic');
$resultStr .= $LogicStub;
}
if (!empty($this->options['model'])) {
$modelPath = __DIR__ . "/../../../{$this->options['model']['module']}/model/".$this->rName($this->options['model']['name']).".php";
$fs->mkdir(__DIR__ . "/../../../{$this->options['model']['module']}/model");
$this->deleteFileIfExists($modelPath);
$ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath, 'model');
$resultStr .= $ModelStub;
}
if (!empty($this->options['validate'])) {
$validatePath = __DIR__ . "/../../../{$this->options['validate']['module']}/validate/".$this->rName($this->options['validate']['name'])."Validate.php";
$fs->mkdir(__DIR__ . "/../../../{$this->options['validate']['module']}/validate");
$this->deleteFileIfExists($validatePath);
$ValidatorStub = $this->generateFile($templateDir, 'ValidateStub.stub', $validatePath, 'validate');
$resultStr .= $ValidatorStub;
}
return $resultStr ? $resultStr . "<info>文件生成成功!</info> <br/>" : '未生成任何文件';
}
/**
* 根据模板生成文件
*
* @param string $templateDir 模板目录
* @param string $templateFile 模板文件名
* @param string $outputPath 输出文件路径
*/
protected function generateFile($templateDir, $templateFile, $outputPath, $fileType)
{
$templatePath = $templateDir . $templateFile;
if (!file_exists($templatePath)) {
echo "<error>模板文件 {$templateFile} 不存在!</error> \n";
return;
}
$content = file_get_contents($templatePath);
switch ($fileType) {
case 'controller':
$content = str_replace('{{controllerModule}}', $this->options['controller']['module'], $content);
$content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content);
$content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content);
$content = str_replace('{{controllerName}}', $this->rName($this->options['controller']['name']), $content);
break;
case 'service':
$content = str_replace('{{serviceModule}}', $this->options['service']['module'], $content);
$content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content);
$content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content);
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
$content = str_replace('{{serviceName}}', $this->rName($this->options['service']['name']), $content);
break;
case 'logic':
$content = str_replace('{{logicModule}}', $this->options['logic']['module'], $content);
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
$content = str_replace('{{logicName}}', $this->rName($this->options['logic']['name']), $content);
break;
case 'model':
$content = str_replace('{{modelModule}}', $this->options['model']['module'], $content);
$content = str_replace('{{modelName}}', $this->rName($this->options['model']['name']), $content);
$content = str_replace('{{table}}', $this->options['table'], $content);
$content = str_replace('{{fillable}}', $this->fillableFields(), $content);
break;
case 'validate':
$content = str_replace('{{validateModule}}', $this->options['validate']['module'], $content);
$content = str_replace('{{validateName}}', $this->rName($this->options['validate']['name']), $content);
break;
}
file_put_contents($outputPath, $content);
return "生成文件: <info>{$outputPath}</info> <br/>";
}
/**
* 设置字段
*
* @param array $fields 字段数组
*/
public function fillableFields()
{
$tableName = $this->options['table'];
$fields = TableInspector::getColumnNames($tableName);
$str = '';
foreach ($fields as $key => $val) {
if ($key < (count($fields)-1)) {
$str .= "'{$val}', ";
} else {
$str .= "'{$val}'";
}
}
$fields = trim($str, '"');
return $fields;
}
/**
* 如果文件存在,删除它
*
* @param string $path 文件路径
*/
protected function deleteFileIfExists($path)
{
if (file_exists($path)) {
// "文件: <comment>{$path}</comment> 已存在,核对后手动删除。\n";
// return;
unlink($path);
echo "删除已存在文件: <comment>{$path}</comment> \n";
}
}
/**
* 设置名称
*/
public function rName($name = '')
{
return ucfirst(camelize($name));
}
}
/**
* 下划线转驼峰
* 思路:
* step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符
* step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符.
*/
function camelize($uncamelized_words, $separator='_') {
$uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator );
}
/**
* 驼峰命名转下划线命名
* 思路:
* 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写
*/
function uncamelize($camelCaps, $separator='_') {
return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
}
@@ -0,0 +1,33 @@
<?php
namespace app\{{controllerModule}}\controller;
use app\common\controller\BaseController;
use app\common\traits\crud\CrudControllerTrait;
use app\{{serviceModule}}\service\{{serviceName}}Service;
class {{controllerName}}Controller extends BaseController
{
use CrudControllerTrait;
protected $service;
public function __construct()
{
$this->service = new {{serviceName}}Service();
}
/**
* 列表页数据格式化
*
* @param array $data
* @return array
*/
// protected function indexFormat($data)
// {
// if (isset($data['created_at']))
// $data['created_at'] = date('Y-m-d H:i:s', $data['created_at']);
// if (isset($data['updated_at']))
// $data['updated_at'] = date('Y-m-d H:i:s', $data['updated_at']);
// return $data;
// }
}
@@ -0,0 +1,17 @@
<?php
namespace app\{{logicModule}}\logic;
use app\common\traits\crud\CrudLogicTrait;
use app\{{modelModule}}\model\{{modelName}};
class {{logicName}}Logic
{
use CrudLogicTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = {{modelName}}::class;
}
@@ -0,0 +1,49 @@
<?php
namespace app\{{modelModule}}\model;
use support\Model;
class {{modelName}} extends Model
{
/**
* The connection name for the model.
*
* @var string|null
*/
protected $connection = 'mysql';
/**
* The table associated with the model.
*
* @var string
*/
protected $table = '{{table}}';
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = 'id';
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = true;
protected $dateFormat = 'U';
/**
* 允许自动填充模型字段
*/
protected $fillable = [{{fillable}}];
/**
* 不允许自动填充模型字段
*/
protected $guarded = [];
}
@@ -0,0 +1,31 @@
<?php
namespace app\{{serviceModule}}\service;
use app\{{validateModule}}\validate\{{validateName}}Validate;
use app\common\traits\crud\CrudServiceTrait;
use app\{{modelModule}}\model\{{modelName}};
class {{serviceName}}Service
{
use CrudServiceTrait;
/**
* 指定当前要操作的模型
*/
protected string $modelClass = {{modelName}}::class;
/**
* 验证器
*/
protected $validator = {{validateName}}Validate::class;
/**
* 获取验证器
*/
public function getValidator()
{
return new $this->validator;
}
}
@@ -0,0 +1,29 @@
<?php
namespace app\{{validateModule}}\validate;
use think\Validate;
class {{validateName}}Validate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class AgreementValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class BannerValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class ConfigValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class NavCategoriesValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class NavItemsValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class PostCategoryValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class PostCommentValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class PostMediaValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class PostValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class ResourceValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace app\common\validate;
use think\Validate;
class RoleValidate extends Validate
{
protected $rule = [
'id' => 'require|integer',
'system_id' => 'require|integer',
'name' => 'require|max:64',
'desc' => 'max:128',
'sort' => 'integer',
'status' => 'in:0,1',
];
protected $message = [
'id.require' => '角色ID不能为空',
'system_id.require' => '系统ID不能为空',
'name.require' => '角色名称不能为空',
'name.max' => '角色名称不能超过64个字符',
'desc.max' => '描述不能超过128个字符',
'sort.integer' => '排序为数字',
'status.in' => '状态值必须为0或1',
];
protected $scene = [
'store' => ['name', 'desc', 'sort'],
'update' => ['id', 'name', 'desc', 'sort', 'status'],
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class SystemsValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace app\common\validate;
use think\Validate;
class UserValidate extends Validate
{
// 规则
protected $rule = [
];
// 提示
protected $message = [
];
// 场景
protected $scene = [
'store' => [
],
'update' => [
]
];
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace app\controller;
use support\Request;
use app\common\utils\scaffold\Generate;
use support\Redis;
class IndexController
{
protected $noNeedLogin = ['createResource'];
protected $noNeedHeaderData = ['createResource'];
public function index(Request $request)
{
return '<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">hi~</div>';
}
/**
* 生成资源
*/
public function createResource()
{
$options = [
'controller' => [
'module' => 'common',
'name' => 'agreement'
],
'model' => [
'module' => 'common',
'name' => 'agreement'
],
'service' => [
'module' => 'common',
'name' => 'agreement'
],
'logic' => [
'module' => 'common',
'name' => 'agreement'
],
'validate' => [
'module' => 'common',
'name' => 'agreement'
],
'table' => 'agreement'
];
$result = (new Generate($options))->doGenerate();
return $result;
}
public function test()
{
$result = Redis::set('test', 'test');
var_dump(Redis::get('test'));
return 'test';
}
}
+4
View File
@@ -0,0 +1,4 @@
<?php
/**
* Here is your custom functions.
*/
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AccessControl implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
// 如果是options请求则返回一个空响应,否则继续向洋葱芯穿越,并得到一个响应
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
// 给响应添加跨域相关的http头
$response->withHeaders([
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => $request->header('origin', '*'),
'Access-Control-Allow-Methods' => $request->header('access-control-request-method', '*'),
'Access-Control-Allow-Headers' => $request->header('access-control-request-headers', '*'),
]);
return $response;
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace app\middleware;
use app\common\utils\Jwt;
use ReflectionClass;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class AuthCheck implements MiddlewareInterface
{
public function process(Request $request, callable $handler) : Response
{
$controller = new ReflectionClass($request->controller);
$properties = $controller->getDefaultProperties();
$noNeedLogin = $properties['noNeedLogin'] ?? [];
$noNeedAuth = $properties['noNeedAuth'] ?? [];
if (in_array($request->action, $noNeedLogin)) {
// 白名单
return $handler($request);
} else {
try {
$authHeader = $request->header('Fdesk-Auth-Token');
if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) {
$body = [
'status' => 'fail',
'code' => 401,
'message' => '未授权',
'data' => [],
];
return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']);
}
$token = $matches[1];
$request->user = (new Jwt())->verifyToken($token);
return $handler($request);
} catch (\Throwable $e) {
$body = [
'status' => 'fail',
'code' => 401,
'message' => $e->getMessage(),
'data' => [],
];
return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']);
}
}
return $handler($request);
}
}

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