diff --git a/README.md b/README.md index e392fa8a3ce3a62474b3c1103d7f2b64e11cac7e..afd4e0c0a589edcbdcf6c7cc8112a05d74c86236 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Webman官方文档
- ## 开源单用户独立版本快速开发框架 我们致力于提供一个卓越、简约且美观的开源单用户独立应用开发框架。秉持“取之于开源,回馈于开源”的理念,我们希望为开发者节省宝贵时间,让他们能够享受生活的每一刻——无论是休闲放松、自我提升、陪伴家人、健身锻炼,还是邂逅美好。 @@ -33,18 +32,22 @@ ### 基础架构 - 多租户体系:产品订阅、初始数据、独立数据源等企业级功能 - 权限管理:完整的RBAC权限控制系统 + ### 组织架构 - 用户管理:系统操作者配置中心 - 部门架构:树形组织管理,支持数据权限 - 岗位管理:职务体系配置 + ### 系统管理 - 菜单权限:菜单/操作/按钮权限精细控制 - 角色管理:菜单权限分配+数据范围划分 - 字典系统:常用数据标准化维护 + ### 监控审计 - 通知公告:系统消息发布平台 - 操作日志:完整记录正常与异常操作 - 登录审计:包含异常登录监控 + ### 设计哲学 大道至简 —— 我们坚持: @@ -53,39 +56,32 @@ - 架构清晰明了 - 无论是前端还是后端开发,都能享受**"快就完事了"**的编程体验。 - ## 环境需求 为了确保系统的正常运行,请确保您的服务器环境满足以下要求: -- PHP:版本需 >= 8.1,并开启以下扩展: -- mbstring:用于多字节字符串处理。 -- json:支持JSON数据的编码和解码。 -- pdo:提供统一的数据库访问接口。 -- openssl:用于加密和解密操作,保障数据传输安全。 -- redis:支持Redis缓存和数据存储。 -- pcntl:用于多进程控制。 -- Mysql:版本需 >= 5.7,作为系统的主要数据库存储数据。 -- Redis:版本需 >= 4.0,用于缓存数据,提高系统性能。 -- Git:版本需 >= 2.x,方便代码的版本管理和协作开发。 -- Composer:版本需 >= 2.x,用于PHP依赖管理。 - +- **PHP**:版本需 >= 8.1,并开启以下扩展: + - mbstring:用于多字节字符串处理。 + - json:支持JSON数据的编码和解码。 + - pdo:提供统一的数据库访问接口。 + - openssl:用于加密和解密操作,保障数据传输安全。 + - redis:支持Redis缓存和数据存储。 + - pcntl:用于多进程控制。 +- **Mysql**:版本需 >= 5.7,作为系统的主要数据库存储数据。 +- **Redis**:版本需 >= 4.0,用于缓存数据,提高系统性能。 +- **Git**:版本需 >= 2.x,方便代码的版本管理和协作开发。 +- **Composer**:版本需 >= 2.x,用于PHP依赖管理。 ## 体验地址 - [体验地址](https://admin.madong.tech) https://admin.madong.tech - 账号:admin - 密码:123456 -期待您的使用和反馈,我们将不断优化和完善系统,为您提供更优质的服务。 - +期待您的使用和反馈,我们将不断优化和完善系统,为您提供更优质的服务。 ## 官方社区 欢迎加入我们的官方社区交流互动
-
## 加入我们
-欢迎加入技术交流群,与核心开发者直接沟通(扫码添加作者微信进群),共同打造更强大的开源企业级平台!
+欢迎加入技术交流群,与核心开发者直接沟通(扫码添加作者微信进群),共同打造更强大的开源企业级平台!
\ No newline at end of file
diff --git a/server/app/admin/controller/LoginController.php b/server/app/admin/controller/LoginController.php
index 68f77afbcfbeecfb326c561c5994a8b44f05f194..9b8c68b8e7b0dc5fe9242de02e8c4b60eef7e345 100644
--- a/server/app/admin/controller/LoginController.php
+++ b/server/app/admin/controller/LoginController.php
@@ -18,6 +18,8 @@ use core\exception\handler\AdminException;
use core\jwt\JwtToken;
use core\utils\Json;
use core\captcha\Captcha;
+use core\utils\RSAService;
+use core\uuid\UUIDGenerator;
use support\Container;
use support\Request;
@@ -58,13 +60,12 @@ class LoginController extends Crud
{
try {
// 生成密钥对
- $cache = Container::make(CacheService::class,[]);
- // 使用 uniqid 增加唯一性
- $keyId = bin2hex(random_bytes(8)) . uniqid();
- $keys = $this->service->generateRSAKeys();
+ $cache = Container::make(CacheService::class, []);
+ $keyId = md5(UUIDGenerator::generate());
+ $keys = RSAService::generateKeys();
// 存储私钥到缓存,用于解密密码
- $cache->set("rsa_private_key:$keyId", $keys['private'], 60); // 5分钟过期
- return Json::success('ok', ['flag' => config('core.captcha.app.enable', false),'key_id'=>$keyId,'public_key'=>$keys['public']]);
+ $cache->set("rsa_private_key_$keyId", $keys['private'], 60); // 5分钟过期
+ return Json::success('ok', ['flag' => config('core.captcha.app.enable', false), 'key_id' => $keyId, 'public_key' => $keys['public']]);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
@@ -127,12 +128,10 @@ class LoginController extends Crud
$type = $request->input('type', 'admin');
$grantType = $request->input('grant_type', 'default');//refresh_token sms default 可以自行定义拓展登录方式
$keyId = $request->input('key_id', '');//获取公钥Id
+ /** @var SysAdminService $service */
$service = Container::make(SysAdminService::class);
$captcha = new Captcha();
-// if (config('tenant.enabled') && empty($tenantId)) {
-// throw new AdminException('请选择数据源!');
-// }
if (config('core.captcha.app.enable') && $grantType === 'default') {
if (!$captcha->check($uuid, $code)) {
@@ -150,7 +149,8 @@ class LoginController extends Crud
}
$username = $info->getData('user_name');
}
- $data = $service->login($username, $password, $type, $grantType, ['keyId'=> $keyId ?? '']);
+
+ $data = $service->login($username, $password, $type, $grantType, ['key_id' => $keyId ?? '']);
return Json::success('ok', $data);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
diff --git a/server/app/common/dao/system/SysAdminDao.php b/server/app/common/dao/system/SysAdminDao.php
index 89ce32b25810d7a772fd02b01eded53e84fa09cc..51d27def1dd9005b47e118378d96cff3eb346bc2 100644
--- a/server/app/common/dao/system/SysAdminDao.php
+++ b/server/app/common/dao/system/SysAdminDao.php
@@ -15,7 +15,6 @@ namespace app\common\dao\system;
use app\common\model\system\SysAdmin;
use InvalidArgumentException;
use core\abstract\BaseDao;
-use core\context\TenantContext;
class SysAdminDao extends BaseDao
{
@@ -28,11 +27,11 @@ class SysAdminDao extends BaseDao
/**
* 用户详情
*
- * @param $id
- * @param array|null $field
- * @param array|null $with
- * @param string $order
- * @param array|null $withoutScopes
+ * @param $id
+ * @param array|null $field
+ * @param array|null $with
+ * @param string $order
+ * @param array|null $withoutScopes
*
* @return SysAdmin|null
* @throws \Exception
@@ -90,17 +89,53 @@ class SysAdminDao extends BaseDao
*/
public function getList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): array
{
-// $where['enabled'] = 1;//显示禁用用户列表
+ // 初始化部门ID
+ $deptId = null;
+
+ // 遍历$where数组,查找并移除dept_id条件
+ foreach ($where as $index => $condition) {
+ // 条件可能是索引数组,第一个元素是字段名
+ if (is_array($condition) && count($condition) >= 2) {
+ $fieldName = $condition[0];
+ // 如果字段名是'dept_id'
+ if ($fieldName === 'dept_id') {
+ // 根据条件数组的长度判断操作符和值的位置
+ if (count($condition) === 2) {
+ // 条件格式为 ['dept_id', 值]
+ $deptId = $condition[1];
+ } elseif (count($condition) === 3) {
+ // 条件格式为 ['dept_id', '=', 值]
+ $deptId = $condition[2];
+ }
+ // 移除这个条件
+ unset($where[$index]);
+ // 因为我们只处理一个dept_id条件,所以找到后跳出循环
+ break;
+ }
+ }
+ }
+
+ // 重新索引数组,防止父类处理时出错
+ $where = array_values($where);
+
if (empty($with)) {
$with = ['depts', 'posts', 'casbin.roles'];
}
+
$query = parent::selectModel($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
+
+ // 如果存在deptId条件,则添加关联条件
+ if (!is_null($deptId)) {
+ $query->whereHas('depts', function ($q) use ($deptId) {
+ $q->where('id', $deptId); // 假设部门模型的主键是id
+ });
+ }
+
$total = $query->count();
$items = $query->get()->makeHidden(['password', 'backend_setting']);
return [$total, $items];
}
-
/**
* 获取用户列表-角色id
*
@@ -120,7 +155,7 @@ class SysAdminDao extends BaseDao
}
$where['enabled'] = 1;//有效用户
$where['is_super'] = 0;//非顶级管理员
- $query = $this->getModel()->with(['roles'])
+ $query = $this->getModel()->with(['roles'])
->whereHas('roles', function ($query) use ($roleId) {
$query->where('id', $roleId);
});
@@ -201,7 +236,7 @@ class SysAdminDao extends BaseDao
{
$result = $this->getModel()
->where('id', $id)
- ->with(['depts', 'posts', 'casbin.roles','roles'])
+ ->with(['depts', 'posts', 'casbin.roles', 'roles'])
->first()
->makeHidden(['password', 'backend_setting']);
diff --git a/server/app/common/dao/system/SysCrontabDao.php b/server/app/common/dao/system/SysCrontabDao.php
index dc2c7e7a9c84cec8e579c83782a6f2369f0504d4..ba3e314035497a396397ccf830cb1c6d214152eb 100644
--- a/server/app/common/dao/system/SysCrontabDao.php
+++ b/server/app/common/dao/system/SysCrontabDao.php
@@ -38,7 +38,7 @@ class SysCrontabDao extends BaseDao
* @return \Illuminate\Database\Eloquent\Collection|null
* @throws \Exception
*/
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false,?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
$result = parent::selectList($where, $field, $page, $limit, $order, [], $search,$withoutScopes);
diff --git a/server/app/common/dao/system/SysDeptDao.php b/server/app/common/dao/system/SysDeptDao.php
index 875fe6677c619e2ee1652772d11a0247e61ea94d..35264a7d5afeb31ae4ca5f3cde95761fd14bf552 100644
--- a/server/app/common/dao/system/SysDeptDao.php
+++ b/server/app/common/dao/system/SysDeptDao.php
@@ -26,7 +26,7 @@ class SysDeptDao extends BaseDao
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
return parent::selectList($where, $field, $page, $limit, $order, ['leader'], $search, $withoutScopes);
}
diff --git a/server/app/common/dao/system/SysMenuDao.php b/server/app/common/dao/system/SysMenuDao.php
index 541b2c0464471d7abc273a6ba828851df1a593a9..2b7a01c857fd1107797f3d4402c538bf7fb7e8de 100644
--- a/server/app/common/dao/system/SysMenuDao.php
+++ b/server/app/common/dao/system/SysMenuDao.php
@@ -29,7 +29,7 @@ class SysMenuDao extends BaseDao
return SysMenu::class;
}
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
$order='sort';
return parent::selectList($where, $field, $page, $limit, $order, [], $search, $withoutScopes);
diff --git a/server/app/common/dao/system/SysMessageDao.php b/server/app/common/dao/system/SysMessageDao.php
index 448c53ad7b30a9d767c37771dae58372d04d7a38..5921eab183517619ed63cca38b354b4b503a59b1 100644
--- a/server/app/common/dao/system/SysMessageDao.php
+++ b/server/app/common/dao/system/SysMessageDao.php
@@ -28,8 +28,7 @@ class SysMessageDao extends BaseDao
return parent::get($id, $field, ['sender'], $order, $withoutScopes);
}
-
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
return parent::selectList($where, $field, $page, $limit, 'created_at desc', ['sender'], $search, $withoutScopes);
}
diff --git a/server/app/common/dao/system/SysOperateLogDao.php b/server/app/common/dao/system/SysOperateLogDao.php
index 7e4bc9e22d27c57eb79c48a3a35f016ca185388e..01793f34bdaae0e391655c5f4a170e8485e0eb35 100644
--- a/server/app/common/dao/system/SysOperateLogDao.php
+++ b/server/app/common/dao/system/SysOperateLogDao.php
@@ -32,19 +32,19 @@ class SysOperateLogDao extends BaseDao
/**
* 操作日志列表
*
- * @param array $where
- * @param string $field
- * @param int $page
- * @param int $limit
- * @param string $order
- * @param array $with
- * @param bool $search
- * @param array|null $withoutScopes
+ * @param array $where
+ * @param string|array $field
+ * @param int $page
+ * @param int $limit
+ * @param string $order
+ * @param array $with
+ * @param bool $search
+ * @param array|null $withoutScopes
*
* @return \Illuminate\Database\Eloquent\Collection|null
* @throws \Exception
*/
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
//注意不要输出result 要不然深度递归会卡死
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes)->makeHidden(['result']);
diff --git a/server/app/common/dao/system/SysRecycleBinDao.php b/server/app/common/dao/system/SysRecycleBinDao.php
index 83177550dcdafd184a9009e0ff1c402f586a84b6..bc244b9f7e5eb154396ae809fa92b9b99e9faabc 100644
--- a/server/app/common/dao/system/SysRecycleBinDao.php
+++ b/server/app/common/dao/system/SysRecycleBinDao.php
@@ -29,7 +29,7 @@ class SysRecycleBinDao extends BaseDao
return SysRecycleBin::class;
}
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
return parent::selectList($where, $field, $page, $limit, $order, ['operate'], $search, $withoutScopes);
}
diff --git a/server/app/common/dao/system/SysRoleDao.php b/server/app/common/dao/system/SysRoleDao.php
index 3aca30db14135ae93d4ceb00ea0c97d3d4abcd64..7cc69d3ae8019d525c344af22032b93b18d9a403 100644
--- a/server/app/common/dao/system/SysRoleDao.php
+++ b/server/app/common/dao/system/SysRoleDao.php
@@ -146,7 +146,7 @@ class SysRoleDao extends BaseDao
* @return \Illuminate\Database\Eloquent\Collection|null
* @throws \Exception
*/
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
$with = ['casbin'];
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
diff --git a/server/app/common/dao/system/SysUploadDao.php b/server/app/common/dao/system/SysUploadDao.php
index 3b2eb17f41b1119130c19e26bf383b27111bfa55..bb799ccbf5c9b90a1e4179ee15c8a882ee1ce39d 100644
--- a/server/app/common/dao/system/SysUploadDao.php
+++ b/server/app/common/dao/system/SysUploadDao.php
@@ -29,7 +29,7 @@ class SysUploadDao extends BaseDao
return SysUpload::class;
}
- public function selectList(array $where, string $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
+ public function selectList(array $where, string|array $field = '*', int $page = 0, int $limit = 0, string $order = '', array $with = [], bool $search = false, ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Collection
{
//重写追加两个关联模型
return parent::selectList($where, $field, $page, $limit, $order, ['createds', 'updateds'], $search, $withoutScopes);
diff --git a/server/app/common/services/system/SysAdminService.php b/server/app/common/services/system/SysAdminService.php
index 81a4e71e8a3933259b870298f31aede7e40d65e2..73c69343d38638988c22d8df5d5e7254b88551b3 100644
--- a/server/app/common/services/system/SysAdminService.php
+++ b/server/app/common/services/system/SysAdminService.php
@@ -20,6 +20,8 @@ use core\casbin\Permission;
use core\enum\system\PolicyPrefix;
use core\exception\handler\AdminException;
use core\jwt\JwtToken;
+use core\utils\RSAService;
+use phpseclib3\Crypt\RSA;
use support\Container;
use Webman\Event\Event;
@@ -236,20 +238,20 @@ class SysAdminService extends BaseService
/**
* 用户登录
*
- * @param string $username
- * @param string $password
- * @param string $type
- * @param string $grantType
- * @param string|int $tenantId
+ * @param string $username
+ * @param string $password
+ * @param string $type
+ * @param string $grantType
+ * @param array $params
*
* @return array
- * @throws \Exception
+ * @throws \core\exception\handler\AdminException
*/
public function login(string $username, string $password = '', string $type = 'admin', string $grantType = 'default', array $params = []): array
{
$adminInfo = $this->getAdminByName($username);
$this->validateAdminStatus($adminInfo);
- $decryptedPassword = $this->validateRsaKeys($params['keyId'], $password);
+ $decryptedPassword = $this->validateRsaKeys($params['key_id'], $password);
$this->validatePassword($adminInfo, $decryptedPassword, $grantType);
[$userInfo, $token] = $this->generateTokenData($adminInfo, $type);
$this->emitLoginSuccessEvent(array_merge($userInfo, $token), $tenant?->id ?? null);
@@ -294,7 +296,6 @@ class SysAdminService extends BaseService
}
}
-
/**
* token生成
*
@@ -483,28 +484,25 @@ class SysAdminService extends BaseService
preg_match($pattern, $url, $matches);
return $matches[1] ?? '';
}
+
/**
* 校验密钥
+ *
* @param $keyId
* @param $encryptedPassword
+ *
* @return string
* @throws AdminException
*/
private function validateRsaKeys($keyId, $encryptedPassword): string
{
- $cache = Container::make(CacheService::class,[]);
- $privateKey = $cache->get("rsa_private_key:$keyId");
+ $cache = Container::make(CacheService::class, []);
+ $privateKey = $cache->get("rsa_private_key_$keyId");
if (!$privateKey) {
throw new AdminException('私钥不存在或已过期,请刷新页面重试');
}
- $privateKeyResource = openssl_pkey_get_private($privateKey);
- $decrypted = '';
- $encryptedData = base64_decode($encryptedPassword);
- if (openssl_private_decrypt($encryptedData, $decrypted, $privateKeyResource)) {
- $cache->delete("rsa_private_key:$keyId"); // 删除私钥,防止泄露
- return $decrypted;
- } else {
- throw new AdminException('解密失败!');
- }
+ return RSAService::decrypt($encryptedPassword, $privateKey);
}
}
+
+
diff --git a/server/composer.json b/server/composer.json
index 32ca147bcf10bfe254a5b96fba6133ec3351124b..261fca2fc14a8748c834f0dd4fb44ba95c2c7abf 100644
--- a/server/composer.json
+++ b/server/composer.json
@@ -73,7 +73,8 @@
"topthink/think-template": "^3.0",
"ext-gd": "*",
"php-di/php-di": "^7.0",
- "casbin/casbin": "^4.0"
+ "casbin/casbin": "^4.0",
+ "phpseclib/phpseclib": "^3.0"
},
"suggest": {
"ext-event": "For better performance. "
diff --git a/server/composer.lock b/server/composer.lock
index 7f41469a9b8c25fba963a3da71d369ba996d427e..9616d999963e71d1693e50e3ef75be43116b09d4 100644
--- a/server/composer.lock
+++ b/server/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f817c0f2a5ba81575320f7454197f6df",
+ "content-hash": "57d030f5acb60a4099e32eebfb29f6be",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@@ -3594,6 +3594,123 @@
],
"time": "2025-03-10T09:33:39+00:00"
},
+ {
+ "name": "paragonie/constant_time_encoding",
+ "version": "v3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/constant_time_encoding.git",
+ "reference": "df1e7fde177501eee2037dd159cf04f5f301a512"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512",
+ "reference": "df1e7fde177501eee2037dd159cf04f5f301a512",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9",
+ "vimeo/psalm": "^4|^5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "ParagonIE\\ConstantTime\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com",
+ "role": "Maintainer"
+ },
+ {
+ "name": "Steve 'Sc00bz' Thomas",
+ "email": "steve@tobtu.com",
+ "homepage": "https://www.tobtu.com",
+ "role": "Original Developer"
+ }
+ ],
+ "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
+ "keywords": [
+ "base16",
+ "base32",
+ "base32_decode",
+ "base32_encode",
+ "base64",
+ "base64_decode",
+ "base64_encode",
+ "bin2hex",
+ "encoding",
+ "hex",
+ "hex2bin",
+ "rfc4648"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/constant_time_encoding/issues",
+ "source": "https://github.com/paragonie/constant_time_encoding"
+ },
+ "time": "2024-05-08T12:36:18+00:00"
+ },
+ {
+ "name": "paragonie/random_compat",
+ "version": "v9.99.100",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">= 7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*",
+ "vimeo/psalm": "^1"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "polyfill",
+ "pseudorandom",
+ "random"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "time": "2020-10-15T08:29:30+00:00"
+ },
{
"name": "php-di/invoker",
"version": "2.3.6",
@@ -3984,6 +4101,116 @@
],
"time": "2024-07-20T21:41:07+00:00"
},
+ {
+ "name": "phpseclib/phpseclib",
+ "version": "3.0.46",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpseclib/phpseclib.git",
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/constant_time_encoding": "^1|^2|^3",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+ "php": ">=5.6.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*"
+ },
+ "suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
+ "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+ "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+ "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+ "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "phpseclib/bootstrap.php"
+ ],
+ "psr-4": {
+ "phpseclib3\\": "phpseclib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jim Wigginton",
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Patrick Monnerat",
+ "email": "pm@datasphere.ch",
+ "role": "Developer"
+ },
+ {
+ "name": "Andreas Fischer",
+ "email": "bantu@phpbb.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Hans-Jürgen Petrich",
+ "email": "petrich@tronic-media.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+ "homepage": "http://phpseclib.sourceforge.net",
+ "keywords": [
+ "BigInteger",
+ "aes",
+ "asn.1",
+ "asn1",
+ "blowfish",
+ "crypto",
+ "cryptography",
+ "encryption",
+ "rsa",
+ "security",
+ "sftp",
+ "signature",
+ "signing",
+ "ssh",
+ "twofish",
+ "x.509",
+ "x509"
+ ],
+ "support": {
+ "issues": "https://github.com/phpseclib/phpseclib/issues",
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/terrafrost",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/phpseclib",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-06-26T16:29:55+00:00"
+ },
{
"name": "psr/cache",
"version": "3.0.0",
@@ -8177,7 +8404,7 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
- "stability-flags": {},
+ "stability-flags": [],
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
@@ -8186,6 +8413,6 @@
"ext-pdo": "*",
"ext-gd": "*"
},
- "platform-dev": {},
+ "platform-dev": [],
"plugin-api-version": "2.6.0"
}
diff --git a/server/core/abstract/BaseDao.php b/server/core/abstract/BaseDao.php
index f72c5e8a39df18a46fdab2868f5c499aae2e0e7d..c45cee2ccd270035ff2bf53039e5ee39ed45d069 100644
--- a/server/core/abstract/BaseDao.php
+++ b/server/core/abstract/BaseDao.php
@@ -388,15 +388,15 @@ abstract class BaseDao
* 获取一条数据
*
* @param $id
- * @param string|array|null $field
+ * @param array|null $field
* @param array|null $with
- * @param string $order
+ * @param string $order
* @param array|null $withoutScopes
*
* @return Model|null
- * @throws Exception
+ * @throws \Exception
*/
- public function get($id, string|array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model
+ public function get($id, ?array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model
{
$where = is_array($id) ? $id : [$this->getPk() => $id];
$query = $this->getModel()->query();
diff --git a/server/core/utils/RSAService.php b/server/core/utils/RSAService.php
new file mode 100644
index 0000000000000000000000000000000000000000..9bbb14ed845a30c7b3ed03eab67fdf50fc9b6b83
--- /dev/null
+++ b/server/core/utils/RSAService.php
@@ -0,0 +1,132 @@
+toString('PKCS1');
+ $publicPem = $privateKey->getPublicKey()->toString('PKCS8');
+
+ return [
+ 'private' => $privatePem,
+ 'public' => $publicPem,
+ ];
+ }
+
+ /**
+ * 签名
+ *
+ * @param string $data
+ * @param string $privatePem
+ *
+ * @return string
+ */
+ public static function sign(string $data, string $privatePem): string
+ {
+ $privateKey = PublicKeyLoader::loadPrivateKey($privatePem)
+ ->withHash('sha256')
+ ->withPadding(RSA::SIGNATURE_PKCS1);
+
+ return $privateKey->sign($data);
+ }
+
+ /**
+ * 验证签名
+ *
+ * @param string $data
+ * @param string $signature
+ * @param string $publicPem
+ *
+ * @return bool
+ */
+ public static function verify(string $data, string $signature, string $publicPem): bool
+ {
+ $publicKey = PublicKeyLoader::load($publicPem)
+ ->withHash('sha256')
+ ->withPadding(RSA::SIGNATURE_PKCS1);
+
+ return $publicKey->verify($data, $signature);
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param string $data
+ * @param string $publicPem
+ *
+ * @return string
+ */
+ public static function encrypt(string $data, string $publicPem): string
+ {
+ $publicKey = PublicKeyLoader::load($publicPem)->withPadding(RSA::ENCRYPTION_PKCS1);
+ return base64_encode($publicKey->encrypt($data));
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param string $cipherBase64
+ * @param string $privatePem
+ *
+ * @return string
+ */
+ public static function decrypt(string $cipherBase64, string $privatePem): string
+ {
+ $privateKey = PublicKeyLoader::loadPrivateKey($privatePem)
+ ->withPadding(RSA::ENCRYPTION_PKCS1);
+
+ $cipher = base64_decode($cipherBase64);
+ return $privateKey->decrypt($cipher);
+ }
+
+
+ /**
+ * 公钥掩码显示
+ *
+ * @param string $pem
+ * @param int $keep
+ *
+ * @return string
+ */
+ public static function maskKey(string $pem, int $keep = 20): string
+ {
+ $clean = str_replace(
+ ["\r", "\n", "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----"],
+ '',
+ $pem
+ );
+ $len = strlen($clean);
+ return substr($clean, 0, $keep) . '...' . substr($clean, -$keep);
+ }
+}
diff --git a/server/core/uuid/UUIDGenerator.php b/server/core/uuid/UUIDGenerator.php
index d77f91029fb0ca30b513ae866bf04e1a0cefdc1c..5f1c314ad501d6be84ad3011b9e26fcc7007a6c1 100644
--- a/server/core/uuid/UUIDGenerator.php
+++ b/server/core/uuid/UUIDGenerator.php
@@ -29,7 +29,7 @@ class UUIDGenerator
* @return string|null
* @throws \Exception
*/
- public static function generate(string $format = 'uuid', int $length = 36)
+ public static function generate(string $format = 'uuid', int $length = 36): ?string
{
if ($format === 'uuid') {
return self::generateUUID();
diff --git a/server/plugin/install/app/functions.php b/server/plugin/install/app/functions.php
index f1a4eefc69b8c03173fdf7f08b0106c24680823b..3986b9ab29c8dcf9cc30f93b42ec0bbaa01e2f06 100644
--- a/server/plugin/install/app/functions.php
+++ b/server/plugin/install/app/functions.php
@@ -130,35 +130,35 @@ function generateEnvTemplate(): string
# Database Configuration
DB_CONNECTION=mysql
- DB_HOST = ~db_host~
- DB_PORT = ~db_port~
- DB_DATABASE= ~db_name~
- DB_USERNAME= ~db_user~
- DB_PASSWORD= ~db_pwd~
- DB_PREFIX = ~db_prefix~
+ DB_HOST=~db_host~
+ DB_PORT=~db_port~
+ DB_DATABASE=~db_name~
+ DB_USERNAME=~db_user~
+ DB_PASSWORD=~db_pwd~
+ DB_PREFIX=~db_prefix~
# Redis Configuration
- REDIS_HOST= ~redis_host~
- REDIS_PORT= ~redis_port~
- REDIS_PASSWORD= ~redis_pwd~
+ REDIS_HOST=~redis_host~
+ REDIS_PORT=~redis_port~
+ REDIS_PASSWORD=~redis_pwd~
REDIS_DB=0
# Queue Redis Configuration
- QUEUE_REDIS_HOST= ~redis_host~
- QUEUE_REDIS_PORT= ~redis_port~
- QUEUE_REDIS_PASSWORD= ~redis_pwd~
+ QUEUE_REDIS_HOST=~redis_host~
+ QUEUE_REDIS_PORT=~redis_port~
+ QUEUE_REDIS_PASSWORD=~redis_pwd~
QUEUE_REDIS_DB=0
QUEUE_REDIS_PREFIX=queue
# Cache Configuration
- CACHE_CUSTOM_REDIS_HOST= ~redis_host~
- CACHE_CUSTOM_REDIS_PORT= ~redis_port~
- CACHE_CUSTOM_REDIS_PASSWORD= ~redis_pwd~
+ CACHE_CUSTOM_REDIS_HOST=~redis_host~
+ CACHE_CUSTOM_REDIS_PORT=~redis_port~
+ CACHE_CUSTOM_REDIS_PASSWORD=~redis_pwd~
CACHE_CUSTOM_REDIS_DB=0
CACHE_CUSTOM_REDIS_PREFIX=cache_custom
# Feature Toggles
- APP_TASK_ENABLED=false # Timer switch
+ APP_TASK_ENABLED=false # Timer switch
APP_TENANT_ENABLED=true # Tenant mode switch
APP_TENANT_AUTO_SELECT_FIRST=false # Tenant auto
CAPTCHA_ENABLED=false # Captcha switch
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 510e4777e6aeda26f3c7b68a311c52c14b49c685..9d82a9b4ac7a457f624e9a60d92c842f70256392 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -2317,6 +2317,9 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+ jsencrypt@3.5.4:
+ resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
+
jsesc@3.1.0:
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
engines: {node: '>=6'}
@@ -6113,6 +6116,8 @@ snapshots:
js-tokens@9.0.1: {}
+ jsencrypt@3.5.4: {}
+
jsesc@3.1.0: {}
json5@2.2.3: {}
diff --git a/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue b/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue
index e263d30c83bf6c9c781715feebf6435d7cacd14e..ec0b53239df2d83eea6cb03a27dc2cd5cf15731b 100644
--- a/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue
+++ b/web/src/components/common/effects/layouts/widgets/preferences/preferences-drawer.vue
@@ -58,6 +58,8 @@ import {
import { SystemUserApi } from '#/api/system/user';
import { SaveOutlined } from '@ant-design/icons-vue';
+import { message as antMessage } from 'ant-design-vue';
+
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
const message = globalShareState.getMessage();
@@ -225,7 +227,12 @@ async function handleCopy() {
async function handleUserPreferences(){
const {theme}= preferences;
const api=new SystemUserApi();
- await api.preferences({theme});
+ try {
+ await api.preferences({theme});
+ antMessage.success($t('preferences.savePreferencesSuccess'));
+ } catch (error) {
+ antMessage.error($t('preferences.savePreferencesFailed'));
+ }
}
diff --git a/web/src/locale/effects/langs/en-US/preferences.json b/web/src/locale/effects/langs/en-US/preferences.json
index db83a97a446fc3bac024c9e844a07a17660bcfa4..e31364eda87bddac35349f62579bb1ac3fdc92f8 100644
--- a/web/src/locale/effects/langs/en-US/preferences.json
+++ b/web/src/locale/effects/langs/en-US/preferences.json
@@ -29,6 +29,8 @@
"plain": "Plain",
"rounded": "Rounded",
"save":"Save",
+ "savePreferencesSuccess": "Preferences saved successfully",
+ "savePreferencesFailed": "Failed to save preferences",
"copyPreferences": "Copy Preferences",
"copyPreferencesSuccessTitle": "Copy successful",
"copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
diff --git a/web/src/locale/effects/langs/zh-CN/preferences.json b/web/src/locale/effects/langs/zh-CN/preferences.json
index 21aa4a7be5402bc7a55dc5a8a7cab4414d17c200..ae437f47c014f9951af25985b8ef4cea8c641ee1 100644
--- a/web/src/locale/effects/langs/zh-CN/preferences.json
+++ b/web/src/locale/effects/langs/zh-CN/preferences.json
@@ -30,6 +30,8 @@
"rounded": "圆润",
"copyPreferences": "复制偏好设置",
"save":"保存",
+ "savePreferencesSuccess": "偏好设置保存成功",
+ "savePreferencesFailed": "偏好设置保存失败",
"copyPreferencesSuccessTitle": "复制成功",
"copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
"clearAndLogout": "清空缓存 & 退出登录",