feat: 实现非对称加密关键接口

This commit is contained in:
kuaifan 2023-03-27 10:09:00 +08:00
parent ef7ed58a22
commit 7e98a78333
43 changed files with 724 additions and 394 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@
/public/.user.ini
/storage/*.key
/config/LICENSE
/config/PGP_*
/vendor
/build
/tmp

View File

@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
## [0.25.48]
### Performance
- 自动清空文件回收站
## [0.25.42]
### Performance

View File

@ -9889,12 +9889,12 @@
* Clones a request and overrides some of its parameters.
*
* @return static
* @param array $query The GET parameters
* @param array $request The POST parameters
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array $cookies The COOKIE parameters
* @param array $files The FILES parameters
* @param array $server The SERVER parameters
* @param array|null $query The GET parameters
* @param array|null $request The POST parameters
* @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array|null $cookies The COOKIE parameters
* @param array|null $files The FILES parameters
* @param array|null $server The SERVER parameters
* @return static
* @static
*/

View File

@ -534,7 +534,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/one 10. 获取单条消息
* @api {get} api/dialog/msg/one 11. 获取单条消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -563,7 +563,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/read 11. 已读聊天消息
* @api {get} api/dialog/msg/read 12. 已读聊天消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -614,7 +614,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/unread 12. 获取未读消息数据
* @api {get} api/dialog/msg/unread 13. 获取未读消息数据
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -655,7 +655,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendtext 13. 发送消息
* @api {post} api/dialog/msg/sendtext 14. 发送消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -676,7 +676,6 @@ class DialogController extends AbstractController
*/
public function msg__sendtext()
{
Base::checkClientVersion('0.19.0');
$user = User::auth();
//
if (!$user->bot) {
@ -691,11 +690,11 @@ class DialogController extends AbstractController
}
}
//
$dialog_id = Base::getPostInt('dialog_id');
$update_id = Base::getPostInt('update_id');
$reply_id = Base::getPostInt('reply_id');
$text = trim(Base::getPostValue('text'));
$silence = trim(Base::getPostValue('silence')) === 'yes';
$dialog_id = intval(Request::input('dialog_id'));
$update_id = intval(Request::input('update_id'));
$reply_id = intval(Request::input('reply_id'));
$text = trim(Request::input('text'));
$silence = trim(Request::input('silence')) === 'yes';
//
WebSocketDialog::checkDialog($dialog_id);
//
@ -745,7 +744,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendrecord 14. 发送语音
* @api {post} api/dialog/msg/sendrecord 15. 发送语音
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -765,15 +764,15 @@ class DialogController extends AbstractController
{
$user = User::auth();
//
$dialog_id = Base::getPostInt('dialog_id');
$reply_id = Base::getPostInt('reply_id');
$dialog_id = intval(Request::input('dialog_id'));
$reply_id = intval(Request::input('reply_id'));
//
WebSocketDialog::checkDialog($dialog_id);
//
$action = $reply_id > 0 ? "reply-$reply_id" : "";
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$base64 = Base::getPostValue('base64');
$duration = Base::getPostInt('duration');
$base64 = Request::input('base64');
$duration = intval(Request::input('duration'));
if ($duration < 600) {
return Base::retError('说话时间太短');
}
@ -792,7 +791,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendfile 15. 文件上传
* @api {post} api/dialog/msg/sendfile 16. 文件上传
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -814,16 +813,16 @@ class DialogController extends AbstractController
{
$user = User::auth();
//
$dialog_id = Base::getPostInt('dialog_id');
$reply_id = Base::getPostInt('reply_id');
$image_attachment = Base::getPostInt('image_attachment');
$dialog_id = intval(Request::input('dialog_id'));
$reply_id = intval(Request::input('reply_id'));
$image_attachment = intval(Request::input('image_attachment'));
//
$dialog = WebSocketDialog::checkDialog($dialog_id);
//
$action = $reply_id > 0 ? "reply-$reply_id" : "";
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename');
$image64 = Request::input('image64');
$fileName = Request::input('filename');
if ($image64) {
$data = Base::image64save([
"image64" => $image64,
@ -876,7 +875,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/sendfileid 16. 通过文件ID发送文件
* @api {get} api/dialog/msg/sendfileid 17. 通过文件ID发送文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -946,7 +945,7 @@ class DialogController extends AbstractController
}
/**
* @api {post} api/dialog/msg/sendanon 16. 发送匿名消息
* @api {post} api/dialog/msg/sendanon 18. 发送匿名消息
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -964,8 +963,8 @@ class DialogController extends AbstractController
{
User::auth();
//
$userid = Base::getPostInt('userid');
$text = trim(Base::getPostValue('text'));
$userid = intval(Request::input('userid'));
$text = trim(Request::input('text'));
//
$anonMessage = Base::settingFind('system', 'anon_message', 'open');
if ($anonMessage != 'open') {
@ -999,7 +998,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/readlist 17. 获取消息阅读情况
* @api {get} api/dialog/msg/readlist 19. 获取消息阅读情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1028,7 +1027,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/detail 18. 消息详情
* @api {get} api/dialog/msg/detail 20. 消息详情
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1076,7 +1075,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/download 19. 文件下载
* @api {get} api/dialog/msg/download 21. 文件下载
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1119,7 +1118,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/withdraw 20. 聊天消息撤回
* @api {get} api/dialog/msg/withdraw 22. 聊天消息撤回
*
* @apiDescription 消息撤回限制24小时内需要token身份
* @apiVersion 1.0.0
@ -1145,7 +1144,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/mark 21. 消息标记操作
* @api {get} api/dialog/msg/mark 23. 消息标记操作
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1212,7 +1211,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/silence 22. 消息免打扰
* @api {get} api/dialog/msg/silence 24. 消息免打扰
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1275,7 +1274,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/forward 23. 转发消息给
* @api {get} api/dialog/msg/forward 25. 转发消息给
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1312,7 +1311,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/emoji 24. emoji回复
* @api {get} api/dialog/msg/emoji 26. emoji回复
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1347,7 +1346,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/tag 25. 标注/取消标注
* @api {get} api/dialog/msg/tag 27. 标注/取消标注
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1376,7 +1375,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/todo 26. 设待办/取消待办
* @api {get} api/dialog/msg/todo 28. 设待办/取消待办
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1419,7 +1418,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/todolist 27. 获取消息待办情况
* @api {get} api/dialog/msg/todolist 29. 获取消息待办情况
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1449,7 +1448,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/msg/done 28. 完成待办
* @api {get} api/dialog/msg/done 30. 完成待办
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1496,7 +1495,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/add 29. 新增群组
* @api {get} api/dialog/group/add 31. 新增群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1558,7 +1557,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/edit 30. 修改群组
* @api {get} api/dialog/group/edit 32. 修改群组
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -1619,7 +1618,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/adduser 31. 添加群成员
* @api {get} api/dialog/group/adduser 33. 添加群成员
*
* @apiDescription 需要token身份
* - 有群主时:只有群主可以邀请
@ -1655,7 +1654,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/deluser 32. 移出(退出)群成员
* @api {get} api/dialog/group/deluser 34. 移出(退出)群成员
*
* @apiDescription 需要token身份
* - 只有群主、邀请人可以踢人
@ -1699,7 +1698,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/transfer 33. 转让群组
* @api {get} api/dialog/group/transfer 35. 转让群组
*
* @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散
@ -1743,7 +1742,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/disband 34. 解散群组
* @api {get} api/dialog/group/disband 36. 解散群组
*
* @apiDescription 需要token身份
* - 只有群主且是个人类型群可以解散
@ -1771,7 +1770,7 @@ class DialogController extends AbstractController
}
/**
* @api {get} api/dialog/group/searchuser 35. 搜索个人群(仅限管理员)
* @api {get} api/dialog/group/searchuser 37. 搜索个人群(仅限管理员)
*
* @apiDescription 需要token身份用于创建部门搜索个人群组
* @apiVersion 1.0.0

View File

@ -595,8 +595,8 @@ class FileController extends AbstractController
{
$user = User::auth();
//
$id = Base::getPostInt('id');
$content = Base::getPostValue('content');
$id = intval(Request::input('id'));
$content = Request::input('content');
//
$file = File::permissionFind($id, $user, 1);
//

View File

@ -1543,7 +1543,8 @@ class ProjectController extends AbstractController
public function task__add()
{
User::auth();
parse_str(Request::getContent(), $data);
//
$data = Request::input();
$project_id = intval($data['project_id']);
$column_id = $data['column_id'];
// 项目
@ -1663,7 +1664,7 @@ class ProjectController extends AbstractController
{
User::auth();
//
parse_str(Request::getContent(), $data);
$data = Request::input();
$task_id = intval($data['task_id']);
//
$task = ProjectTask::userTask($task_id, true, true, 2);
@ -1989,8 +1990,8 @@ class ProjectController extends AbstractController
{
User::auth();
//
$project_id = intval(Base::getContentValue('project_id'));
$flows = Base::getContentValue('flows');
$project_id = intval(Request::input('project_id'));
$flows = Request::input('flows');
//
if (!is_array($flows)) {
return Base::retError('参数错误');

View File

@ -137,14 +137,14 @@ class ReportController extends AbstractController
$user = User::auth();
//
$input = [
"id" => Base::getPostValue("id", 0),
"sign" => Base::getPostValue("sign"),
"title" => Base::getPostValue("title"),
"type" => Base::getPostValue("type"),
"content" => Base::getPostValue("content"),
"receive" => Base::getPostValue("receive"),
"id" => Request::input("id", 0),
"sign" => Request::input("sign"),
"title" => Request::input("title"),
"type" => Request::input("type"),
"content" => Request::input("content"),
"receive" => Request::input("receive"),
// 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1上一天同理。
"offset" => Base::getPostValue("offset", 0),
"offset" => Request::input("offset", 0),
];
$validator = Validator::make($input, [
'id' => 'numeric',

View File

@ -10,7 +10,6 @@ use App\Module\BillExport;
use App\Module\BillMultipleExport;
use App\Module\Doo;
use App\Module\Extranet;
use Arr;
use Carbon\Carbon;
use Guanguans\Notify\Factory;
use Guanguans\Notify\Messages\EmailMessage;
@ -439,7 +438,7 @@ class SystemController extends AbstractController
$type = trim(Request::input('type'));
if ($type == 'save') {
User::auth('admin');
$list = Base::getPostValue('list');
$list = Request::input('list');
$array = [];
if (empty($list) || !is_array($list)) {
return Base::retError('参数错误');
@ -488,7 +487,7 @@ class SystemController extends AbstractController
$type = trim(Request::input('type'));
if ($type == 'save') {
User::auth('admin');
$list = Base::getPostValue('list');
$list = Request::input('list');
$array = [];
if (empty($list) || !is_array($list)) {
return Base::retError('参数错误');
@ -514,7 +513,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/license 08. License
* @api {post} api/system/license 10. License
*
* @apiDescription 获取License信息、保存License限管理员
* @apiVersion 1.0.0
@ -536,7 +535,7 @@ class SystemController extends AbstractController
//
$type = trim(Request::input('type'));
if ($type == 'save') {
$license = Base::getPostValue('license');
$license = Request::input('license');
Doo::licenseSave($license);
}
//
@ -550,7 +549,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/info 10. 获取终端详细信息
* @api {get} api/system/get/info 11. 获取终端详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -579,7 +578,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ip 11. 获取IP地址
* @api {get} api/system/get/ip 12. 获取IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -594,7 +593,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/cnip 12. 是否中国IP地址
* @api {get} api/system/get/cnip 13. 是否中国IP地址
*
* @apiVersion 1.0.0
* @apiGroup system
@ -611,7 +610,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipgcj02 13. 获取IP地址经纬度
* @api {get} api/system/get/ipgcj02 14. 获取IP地址经纬度
*
* @apiVersion 1.0.0
* @apiGroup system
@ -628,7 +627,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/ipinfo 14. 获取IP地址详细信息
* @api {get} api/system/get/ipinfo 15. 获取IP地址详细信息
*
* @apiVersion 1.0.0
* @apiGroup system
@ -645,7 +644,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/imgupload 15. 上传图片
* @api {post} api/system/imgupload 16. 上传图片
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -679,8 +678,8 @@ class SystemController extends AbstractController
$scale = [$width, $height, $whcut];
}
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename'));
$image64 = trim(Request::input('image64'));
$fileName = trim(Request::input('filename'));
if ($image64) {
$data = Base::image64save([
"image64" => $image64,
@ -705,7 +704,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/imgview 16. 浏览图片空间
* @api {get} api/system/get/imgview 17. 浏览图片空间
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -801,7 +800,7 @@ class SystemController extends AbstractController
}
/**
* @api {post} api/system/fileupload 17. 上传文件
* @api {post} api/system/fileupload 18. 上传文件
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
@ -822,8 +821,8 @@ class SystemController extends AbstractController
return Base::retError('身份失效,等重新登录');
}
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
$image64 = trim(Base::getPostValue('image64'));
$fileName = trim(Base::getPostValue('filename'));
$image64 = trim(Request::input('image64'));
$fileName = trim(Request::input('filename'));
if ($image64) {
$data = Base::image64save([
"image64" => $image64,
@ -843,7 +842,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/showitem 18. 首页显示ITEM
* @api {get} api/system/get/showitem 19. 首页显示ITEM
*
* @apiDescription 用于判断首页是否显示pro、github、更新日志...
* @apiVersion 1.0.0
@ -875,7 +874,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/get/starthome 19. 启动首页设置信息
* @api {get} api/system/get/starthome 20. 启动首页设置信息
*
* @apiDescription 用于判断注册是否需要启动首页
* @apiVersion 1.0.0
@ -895,7 +894,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/email/check 20. 邮件发送测试(限管理员)
* @api {get} api/system/email/check 21. 邮件发送测试(限管理员)
*
* @apiDescription 测试配置邮箱是否能发送邮件
* @apiVersion 1.0.0
@ -941,7 +940,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/checkin/export 21. 导出签到数据(限管理员)
* @api {get} api/system/checkin/export 22. 导出签到数据(限管理员)
*
* @apiVersion 1.0.0
* @apiGroup system
@ -1108,7 +1107,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/checkin/down 22. 下载导出的签到数据
* @api {get} api/system/checkin/down 23. 下载导出的签到数据
*
* @apiVersion 1.0.0
* @apiGroup system
@ -1134,7 +1133,7 @@ class SystemController extends AbstractController
}
/**
* @api {get} api/system/version 23. 获取版本号
* @api {get} api/system/version 24. 获取版本号
*
* @apiVersion 1.0.0
* @apiGroup system

View File

@ -1527,7 +1527,7 @@ class UsersController extends AbstractController
return Base::retError('未开放修改权限,请联系管理员');
}
//
$list = Base::getPostValue('list');
$list = Request::input('list');
$array = [];
if (empty($list) || !is_array($list)) {
return Base::retError('参数错误');
@ -1618,4 +1618,46 @@ class UsersController extends AbstractController
}
return Base::retSuccess('success', $row);
}
/**
* @api {get} api/users/key/client 28. 客户端KEY
*
* @apiDescription 获取客户端KEY用于加密数据发送给服务端
* @apiVersion 1.0.0
* @apiGroup users
* @apiName key__client
*
* @apiParam {String} [client_id] 客户端ID希望不变的除非清除浏览器缓存或者卸载应用
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function key__client()
{
$clientId = (trim(Request::input('client_id')) ?: Base::generatePassword(6)) . Doo::userId();
//
$cacheKey = "KeyPair::" . $clientId;
if (Cache::has($cacheKey)) {
$cacheData = Base::json2array(Cache::get($cacheKey));
if ($cacheData['private_key']) {
return Base::retSuccess('success', [
'type' => 'pgp',
'id' => $clientId,
'key' => $cacheData['public_key'],
]);
}
}
//
$name = Doo::userEmail() ?: Base::generatePassword(6);
$email = Doo::userEmail() ?: 'aa@bb.cc';
$data = Doo::pgpGenerateKeyPair($name, $email, Base::generatePassword());
Cache::put("KeyPair::" . $clientId, Base::array2json($data), Carbon::now()->addQuarter());
//
return Base::retSuccess('success', [
'type' => 'pgp',
'id' => $clientId,
'key' => $data['public_key'],
]);
}
}

View File

@ -12,59 +12,8 @@ class VerifyCsrfToken extends Middleware
* @var array
*/
protected $except = [
// 上传图片
'api/system/imgupload/',
// 上传文件
'api/system/fileupload/',
// 保存任务优先级
'api/system/priority/',
// 保存创建项目列表模板
'api/system/column/template/',
// License 设置
'api/system/license/',
// 添加任务
'api/project/task/add/',
// 保存工作流
'api/project/flow/save/',
// 修改任务
'api/project/task/update/',
// 聊天发文本
'api/dialog/msg/sendtext/',
// 聊天发语音
'api/dialog/msg/sendrecord/',
// 聊天发文件
'api/dialog/msg/sendfile/',
// 聊天发匿名消息
'api/dialog/msg/sendanon/',
// 保存文件内容
'api/file/content/save/',
// 保存文件内容office
'api/file/content/office/',
// 保存文件内容(上传)
'api/file/content/upload/',
// 保存汇报
'api/report/store/',
// 签到设置
'api/users/checkin/save/',
// 签到上报
'api/public/checkin/report/',
// 接口部分
'api/*',
// 发布桌面端
'desktop/publish/',

View File

@ -4,9 +4,9 @@ namespace App\Http\Middleware;
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
use App\Module\Base;
use App\Module\Doo;
use Closure;
use Request;
class WebApi
{
@ -22,18 +22,41 @@ class WebApi
global $_A;
$_A = [];
if (Request::input('__Access-Control-Allow-Origin') || Request::header('__Access-Control-Allow-Origin')) {
header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:GET,POST,PUT,DELETE,OPTIONS');
header('Access-Control-Allow-Headers:Content-Type, platform, platform-channel, token, release, Access-Control-Allow-Origin');
Doo::load();
$encrypt = Doo::pgpParseStr($request->header('encrypt'));
if ($request->isMethod('post')) {
$version = $request->header('version');
if ($version && version_compare($version, '0.25.48', '<')) {
// 旧版本兼容 php://input
parse_str($request->getContent(), $content);
if ($content) {
$request->merge($content);
}
} elseif ($encrypt['encrypt_type'] === 'pgp' && $content = $request->input('encrypted')) {
// 新版本解密提交的内容
$content = Doo::pgpDecryptApi($content, $encrypt['encrypt_id']);
if ($content) {
$request->merge($content);
}
}
}
// 强制 https
$APP_SCHEME = env('APP_SCHEME', 'auto');
if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) {
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
}
Doo::load();
return $next($request);
$response = $next($request);
// 加密返回内容
if ($encrypt['client_type'] === 'pgp' && $content = $response->getContent()) {
$response->setContent(json_encode([
'encrypted' => Doo::pgpEncryptApi($content, $encrypt['client_key'])
]));
}
return $response;
}
}

View File

@ -29,7 +29,7 @@ use Request;
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|File newQuery()
* @method static \Illuminate\Database\Query\Builder|File onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|File onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|File query()
* @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value)
@ -46,8 +46,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|File withTrashed()
* @method static \Illuminate\Database\Query\Builder|File withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|File withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|File withoutTrashed()
* @mixin \Eloquent
*/
class File extends AbstractModel

View File

@ -20,7 +20,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newQuery()
* @method static \Illuminate\Database\Query\Builder|FileContent onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent query()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereContent($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereCreatedAt($value)
@ -31,8 +31,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereText($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|FileContent withTrashed()
* @method static \Illuminate\Database\Query\Builder|FileContent withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|FileContent withoutTrashed()
* @mixin \Eloquent
*/
class FileContent extends AbstractModel

View File

@ -28,17 +28,17 @@ use Request;
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read int $owner_userid
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectColumn> $projectColumn
* @property-read int|null $project_column_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectLog> $projectLog
* @property-read int|null $project_log_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectUser> $projectUser
* @property-read int|null $project_user_count
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Project newQuery()
* @method static \Illuminate\Database\Query\Builder|Project onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Project onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Project query()
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedUserid($value)
@ -52,8 +52,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value)
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|Project withTrashed()
* @method static \Illuminate\Database\Query\Builder|Project withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Project withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|Project withoutTrashed()
* @mixin \Eloquent
*/
class Project extends AbstractModel

View File

@ -20,11 +20,11 @@ use Request;
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \App\Models\Project|null $project
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTask[] $projectTask
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTask> $projectTask
* @property-read int|null $project_task_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectColumn onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereColor($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereCreatedAt($value)
@ -34,8 +34,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereProjectId($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereSort($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withoutTrashed()
* @mixin \Eloquent
*/
class ProjectColumn extends AbstractModel

View File

@ -12,7 +12,7 @@ use App\Module\Base;
* @property string|null $name 流程名称
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectFlowItem[] $projectFlowItem
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectFlowItem> $projectFlowItem
* @property-read int|null $project_flow_item_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery()

View File

@ -52,18 +52,18 @@ use Request;
* @property-read bool $today
* @property-read \App\Models\Project|null $project
* @property-read \App\Models\ProjectColumn|null $projectColumn
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskFile[] $taskFile
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskFile> $taskFile
* @property-read int|null $task_file_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskTag[] $taskTag
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskTag> $taskTag
* @property-read int|null $task_tag_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTaskUser[] $taskUser
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTaskUser> $taskUser
* @property-read int|null $task_user_count
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime')
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereArchivedFollow($value)
@ -92,8 +92,8 @@ use Request;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTask withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTask withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask withoutTrashed()
* @mixin \Eloquent
*/
class ProjectTask extends AbstractModel
@ -530,8 +530,6 @@ class ProjectTask extends AbstractModel
public function updateTask($data, &$updateMarking = [])
{
AbstractModel::transaction(function () use ($data, &$updateMarking) {
// 判断版本
Base::checkClientVersion('0.19.0');
// 主任务
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
// 工作流

View File

@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newQuery()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog query()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereDeletedAt($value)
@ -28,8 +28,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withTrashed()
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withoutTrashed()
* @mixin \Eloquent
*/
class ProjectTaskPushLog extends AbstractModel

View File

@ -23,10 +23,10 @@ use JetBrains\PhpStorm\Pure;
* @property int $userid
* @property string $content
* @property string $sign 汇报唯一标识
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ReportReceive[] $Receives
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ReportReceive> $Receives
* @property-read int|null $receives_count
* @property-read mixed $receives
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\User[] $receivesUser
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $receivesUser
* @property-read int|null $receives_user_count
* @property-read \App\Models\User|null $sendUser
* @method static Builder|Report newModelQuery()

View File

@ -17,7 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $deleted_at
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newQuery()
* @method static \Illuminate\Database\Query\Builder|TaskWorker onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker query()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereArgs($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereCreatedAt($value)
@ -27,8 +27,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereStartAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|TaskWorker withTrashed()
* @method static \Illuminate\Database\Query\Builder|TaskWorker withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withoutTrashed()
* @mixin \Eloquent
*/
class TaskWorker extends AbstractModel

View File

@ -24,11 +24,11 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\WebSocketDialogUser[] $dialogUser
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\WebSocketDialogUser> $dialogUser
* @property-read int|null $dialog_user_count
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereAvatar($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereCreatedAt($value)
@ -40,8 +40,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereOwnerId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withoutTrashed()
* @mixin \Eloquent
*/
class WebSocketDialog extends AbstractModel

View File

@ -39,7 +39,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
@ -61,8 +61,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withoutTrashed()
* @mixin \Eloquent
*/
class WebSocketDialogMsg extends AbstractModel

View File

@ -1933,60 +1933,6 @@ class Base
}
}
/**
* php://input 字符串解析到变量并获取指定值
* @param $key
* @return array
*/
public static function getContentsParse($key)
{
parse_str(Request::getContent(), $input);
if ($key) {
$input = $input[$key] ?? array();
}
return is_array($input) ? $input : array($input);
}
/**
* php://input 字符串解析到变量并获取指定值
* @param $key
* @param null $default
* @return mixed|null
*/
public static function getContentValue($key, $default = null)
{
global $_A;
if (!isset($_A["__static_input_content"])) {
parse_str(Request::getContent(), $input);
$_A["__static_input_content"] = $input;
}
return $_A["__static_input_content"][$key] ?? $default;
}
/**
* @param $key
* @param null $default
* @return array|mixed|string|null
*/
public static function getPostValue($key, $default = null)
{
$value = self::getContentValue($key, $default);
if (!isset($value)) {
$value = Request::post($key, $default);
}
return $value;
}
/**
* @param $key
* @param null $default
* @return int
*/
public static function getPostInt($key, $default = null)
{
return intval(self::getPostValue($key, $default));
}
/**
* 多维 array_values
* @param $array

View File

@ -4,12 +4,14 @@ namespace App\Module;
use App\Exceptions\ApiException;
use App\Models\User;
use Cache;
use Carbon\Carbon;
use FFI;
class Doo
{
private static $doo;
private static $passphrase = "LYHevk5n";
/**
* char转为字符串
@ -45,10 +47,21 @@ class Doo
char* md5s(char* text, char* password);
char* macs();
char* dooSN();
char* pgpGenerateKeyPair(char* name, char* email, char* passphrase);
char* pgpEncrypt(char* plainText, char* publicKey);
char* pgpDecrypt(char* cipherText, char* privateKey, char* passphrase);
EOF, "/usr/lib/doo/doo.so");
$token = $token ?: Base::headerOrInput('token');
$language = $language ?: Base::headerOrInput('language');
self::$doo->initialize("/var/www", $token, $language);
//
$priPath = config_path("PGP_PRIVATE");
$pubPath = config_path("PGP_PUBLIC");
if (!file_exists($priPath) || !file_exists($pubPath)) {
$data = self::pgpGenerateKeyPair("doo", "admin@admin.com", self::$passphrase);
file_put_contents($priPath, $data['private_key']);
file_put_contents($pubPath, $data['public_key']);
}
}
/**
@ -299,4 +312,103 @@ class Doo
{
return self::string(self::doo()->dooSN());
}
/**
* 生成PGP密钥对
* @param $name
* @param $email
* @param string $passphrase
* @return array
*/
public static function pgpGenerateKeyPair($name, $email, string $passphrase = ""): array
{
return Base::json2array(self::string(self::doo()->pgpGenerateKeyPair($name, $email, $passphrase)));
}
/**
* PGP加密
* @param $plaintext
* @param $publicKey
* @return string
*/
public static function pgpEncrypt($plaintext, $publicKey): string
{
if (strlen($publicKey) < 50) {
$keyCache = Base::json2array(Cache::get("KeyPair::" . $publicKey));
$publicKey = $keyCache['public_key'];
}
return self::string(self::doo()->pgpEncrypt($plaintext, $publicKey));
}
/**
* PGP解密
* @param $encryptedText
* @param $privateKey
* @param null $passphrase
* @return string
*/
public static function pgpDecrypt($encryptedText, $privateKey, $passphrase = null): string
{
if (strlen($privateKey) < 50) {
$keyCache = Base::json2array(Cache::get("KeyPair::" . $privateKey));
$privateKey = $keyCache['private_key'];
$passphrase = $keyCache['passphrase'];
}
return self::string(self::doo()->pgpDecrypt($encryptedText, $privateKey, $passphrase));
}
/**
* PGP加密API
* @param $plaintext
* @param $publicKey
* @return string
*/
public static function pgpEncryptApi($plaintext, $publicKey): string
{
$content = Base::array2json($plaintext);
$content = self::pgpEncrypt($content, $publicKey);
return preg_replace("/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/i", "", $content);
}
/**
* PGP解密API
* @param $encryptedText
* @param null $privateKey
* @param null $passphrase
* @return array
*/
public static function pgpDecryptApi($encryptedText, $privateKey, $passphrase = null): array
{
$content = "-----BEGIN PGP MESSAGE-----\n\n" . $encryptedText . "\n-----END PGP MESSAGE-----";
$content = self::pgpDecrypt($content, $privateKey, $passphrase);
return Base::json2array($content);
}
/**
* 解析PGP参数
* @param $string
* @return string[]
*/
public static function pgpParseStr($string): array
{
$array = [
'encrypt_type' => '',
'encrypt_id' => '',
'client_type' => '',
'client_key' => '',
];
$string = str_replace(";", "&", $string);
parse_str($string, $params);
foreach ($params as $key => $value) {
$key = strtolower(trim($key));
if ($key) {
$array[$key] = trim($value);
}
}
if ($array['client_type'] === 'pgp' && $array['client_key']) {
$array['client_key'] = str_replace(["-", "_", "$"], ["+", "/", "\n"], $array['client_key']);
$array['client_key'] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n" . $array['client_key'] . "\n-----END PGP PUBLIC KEY BLOCK-----";
}
return $array;
}
}

View File

@ -13,6 +13,8 @@
"ext-gd": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-gnupg": "*",
"ext-openssl": "*",
"ext-simplexml": "*",
"directorytree/ldaprecord-laravel": "^2.7",
"fideloper/proxy": "^4.4.1",

View File

@ -3,8 +3,12 @@ version: '3'
services:
php:
container_name: "dootask-php-${APP_ID}"
image: "kuaifan/php:swoole-8.0.rc7"
shm_size: "1024m"
image: "kuaifan/php:swoole-8.0.rc9"
shm_size: "2gb"
ulimits:
core:
soft: 0
hard: 0
volumes:
- ./docker/crontab/crontab.conf:/etc/supervisor/conf.d/crontab.conf
- ./docker/php/php.conf:/etc/supervisor/conf.d/php.conf

View File

@ -12,6 +12,7 @@
<!--style-->
<link rel="stylesheet" type="text/css" href="./css/iview.css">
<link rel="stylesheet" type="text/css" href="./css/loading.css">
<script src="./js/jsencrypt.min.js"></script>
<script src="./js/scroll-into-view.min.js"></script>
<script src="./config.js"></script>
</head>

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.25.42",
"version": "0.25.48",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",
@ -23,12 +23,13 @@
"axios": "^0.24.0",
"cross-env": "^7.0.3",
"css-loader": "^6.7.2",
"dexie": "^3.2.3",
"echarts": "^5.2.2",
"element-ui": "git+https://github.com/kuaifan/element.git#master",
"file-loader": "^6.2.0",
"inquirer": "^8.2.0",
"internal-ip": "^6.2.0",
"jquery": "^3.6.1",
"jquery": "^3.6.4",
"jspdf": "^2.5.1",
"le5le-store": "^1.0.7",
"less": "^4.1.2",
@ -38,6 +39,7 @@
"moment": "^2.29.1",
"node-sass": "^6.0.1",
"notification-koro1": "^1.1.1",
"openpgp": "git+https://github.com/kuaifan/openpgpjs.git#base64",
"photoswipe": "^5.2.8",
"postcss": "^8.4.5",
"quill": "^1.3.7",

File diff suppressed because one or more lines are too long

View File

@ -49,6 +49,7 @@ input[type="date"] {
src: url('./glyphicons-halflings-regular.eot');
src: url('./glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
url('./glyphicons-halflings-regular.woff') format('woff'),
url('./glyphicons-halflings-regular.woff2') format('woff2'),
url('./glyphicons-halflings-regular.ttf') format('truetype'),
url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
}

View File

@ -5,13 +5,13 @@
<meta name="description" content="APP接口文档">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="assets/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="assets/prism.css" rel="stylesheet" />
<link href="assets/main.css" rel="stylesheet" media="screen, print">
<link href="assets/favicon.ico" rel="icon" type="image/x-icon">
<link href="assets/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180">
<link href="assets/favicon-32x32.png" rel="icon" type="image/png" sizes="32x32">
<link href="assets/favicon-16x16.png"rel="icon" type="image/png" sizes="16x16">
<link href="assets/bootstrap.min.css?v=1679925462953" rel="stylesheet" media="screen">
<link href="assets/prism.css?v=1679925462953" rel="stylesheet" />
<link href="assets/main.css?v=1679925462953" rel="stylesheet" media="screen, print">
<link href="assets/favicon.ico?v=1679925462953" rel="icon" type="image/x-icon">
<link href="assets/apple-touch-icon.png?v=1679925462953" rel="apple-touch-icon" sizes="180x180">
<link href="assets/favicon-32x32.png?v=1679925462953" rel="icon" type="image/png" sizes="32x32">
<link href="assets/favicon-16x16.png?v=1679925462953" rel="icon" type="image/png" sizes="16x16">
</head>
<body class="container-fluid">
@ -306,7 +306,7 @@
{{#if optional}}
<span class="label optional">{{__ "optional"}}</span>
{{else}}
{{#if ../template.showRequiredLabels}}
{{#if ../../template.showRequiredLabels}}
<span class="label required">{{__ "required"}}</span>
{{/if}}
{{/if}}</td>
@ -928,6 +928,6 @@
</div>
</div>
<script src="assets/main.bundle.js"></script>
<script src="assets/main.bundle.js?v=1679925462953"></script>
</body>
</html>

2
public/js/jsencrypt.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -212,7 +212,7 @@ store.dispatch("init").then(action => {
$A.Notice = app.$Notice;
$A.Modal = app.$Modal;
if (action === "clearCacheSuccess") {
if (action === "handleClearCache") {
$A.messageSuccess("清除成功");
}
})

View File

@ -36,13 +36,26 @@ const localforage = require("localforage");
* 是否在数组里
* @param key
* @param array
* @param regular
* @returns {boolean|*}
*/
inArray(key, array) {
inArray(key, array, regular = false) {
if (!this.isArray(array)) {
return false;
}
return array.includes(key);
if (regular) {
return !!array.find(item => {
if (item && item.indexOf("*")) {
const rege = new RegExp("^" + item.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*') + "$", "g")
if (rege.test(key)) {
return true
}
}
return item == key
});
} else {
return array.includes(key);
}
},
/**

View File

@ -514,23 +514,23 @@ export default {
this.invite = $A.trim(this.invite)
//
if (!$A.isEmail(this.email)) {
$A.messageWarning("请输入正确的邮箱地址");
this.$refs.email.focus();
return;
$A.messageWarning("请输入正确的邮箱地址")
this.$refs.email.focus()
return
}
if (!this.password) {
$A.messageWarning("请输入密码");
this.$refs.password.focus();
return;
$A.messageWarning("请输入密码")
this.$refs.password.focus()
return
}
if (this.loginType == 'reg') {
if (this.password != this.password2) {
$A.messageWarning("确认密码输入不一致");
this.$refs.password2.focus();
return;
$A.messageWarning("确认密码输入不一致")
this.$refs.password2.focus()
return
}
}
this.loadIng++;
this.loadIng++
this.$store.dispatch("call", {
url: 'users/login',
data: {
@ -542,35 +542,39 @@ export default {
},
}).then(({data}) => {
$A.IDBSave("cacheLoginEmail", this.email)
this.codeNeed = false;
this.$store.dispatch("handleClearCache", data).then(this.goNext);
this.codeNeed = false
this.$store.dispatch("handleClearCache", data).then(this.goNext)
}).catch(({data, msg}) => {
if (data.code === 'email') {
this.loginType = 'login';
$A.modalWarning(msg);
this.loginType = 'login'
$A.modalWarning(msg)
} else {
$A.modalError(msg);
$A.modalError({
content: msg,
onOk: _ => {
this.$refs.code.focus()
}
})
}
if (data.code === 'need') {
this.reCode();
this.codeNeed = true;
this.$refs.code.focus();
this.reCode()
this.codeNeed = true
}
}).finally(_ => {
this.loadIng--;
});
this.loadIng--
})
})
},
goNext() {
this.loginJump = true;
const fromUrl = decodeURIComponent($A.getObject(this.$route.query, 'from'));
this.loginJump = true
const fromUrl = decodeURIComponent($A.getObject(this.$route.query, 'from'))
if (fromUrl) {
$A.IDBSet("clearCache", "login").then(_ => {
window.location.replace(fromUrl);
window.location.replace(fromUrl)
})
} else {
this.goForward({name: 'manage-dashboard'}, true);
this.goForward({name: 'manage-dashboard'}, true)
}
},

View File

@ -742,8 +742,7 @@ export default {
Store.set('updateNotification', null);
return;
case 'clearCache':
this.$store.dispatch("handleClearCache", null).then(async () => {
await $A.IDBSet("clearCache", "handle")
$A.IDBSet("clearCache", "handle").then(_ => {
$A.reloadUrl()
});
return;

View File

@ -166,7 +166,7 @@
@on-emoji="onEmoji"
@on-show-emoji-user="onShowEmojiUser">
<template #header>
<div v-if="(allMsgs.length === 0 && loadMsg) || prevId > 0" class="dialog-item loading">
<div v-if="(allMsgs.length === 0 && loadIng) || prevId > 0" class="dialog-item loading">
<div v-if="scrollOffset < 100" class="dialog-wrapper-loading"></div>
</div>
<div v-else-if="allMsgs.length === 0" class="dialog-item nothing">{{$L('暂无消息')}}</div>
@ -537,6 +537,7 @@ export default {
msgText: '',
msgNew: 0,
msgType: '',
loadIng: 0,
allMsgs: [],
tempMsgs: [],
@ -936,6 +937,19 @@ export default {
immediate: true
},
loadMsg: {
handler(load) {
if (load) {
this.loadIng++
} else {
setTimeout(_ => {
this.loadIng--
}, 300)
}
},
immediate: true
},
msgType() {
this.getMsgs({
dialog_id: this.dialogId,

View File

@ -138,8 +138,7 @@ export default {
toggleRoute(path) {
switch (path) {
case 'clearCache':
this.$store.dispatch("handleClearCache", null).then(async () => {
await $A.IDBSet("clearCache", "handle")
$A.IDBSet("clearCache", "handle").then(_ => {
$A.reloadUrl()
});
break;

View File

@ -1,6 +1,7 @@
import {Store} from 'le5le-store';
import * as openpgp from 'openpgp/lightweight';
import {languageType} from "../language";
import {$callData} from './utils'
import {$callData, $urlSafe} from './utils'
export default {
/**
@ -26,6 +27,7 @@ export default {
}
// 读取缓存
state.clientId = await $A.IDBString("clientId")
state.cacheServerUrl = await $A.IDBString("cacheServerUrl")
state.cacheUserBasic = await $A.IDBArray("cacheUserBasic")
state.cacheDialogs = (await $A.IDBArray("cacheDialogs")).map(item => Object.assign(item, {loading: false, extra_draft_has: item.extra_draft_content ? 1 : 0}))
@ -40,13 +42,19 @@ export default {
state.callAt = await $A.IDBArray("callAt")
state.cacheEmojis = await $A.IDBArray("cacheEmojis")
// 客户端ID
if (!state.clientId) {
state.clientId = $A.randomString(6)
await $A.IDBSet("clientId", state.clientId)
}
// 清理缓存
const clearCache = await $A.IDBString("clearCache")
if (clearCache) {
await $A.IDBRemove("clearCache")
await $A.IDBSet("callAt", state.callAt = [])
if (clearCache === "handle") {
action = "clearCacheSuccess"
await dispatch(action = "handleClearCache")
}
}
@ -77,7 +85,15 @@ export default {
}
state.themeIsDark = $A.dark.isDarkEnabled()
//
dispatch("call", {
url: "users/key/client",
data: {client_id: state.clientId},
encrypt: false,
}).then(({data}) => {
state.apiKeyData = data;
})
// 加载语言包
$A.loadScriptS([
`language/web/key.js`,
`language/web/${languageType}.js`,
@ -91,7 +107,7 @@ export default {
* 访问接口
* @param state
* @param dispatch
* @param params // {url,data,method,timeout,header,spinner,websocket, before,complete,success,error,after}
* @param params // {url,data,method,timeout,header,spinner,websocket,encrypt, before,complete,success,error,after}
* @returns {Promise<unknown>}
*/
call({state, dispatch}, params) {
@ -107,34 +123,68 @@ export default {
if ($A.isJson(params.header)) {
params.header = Object.assign(header, params.header)
} else {
params.header = header;
params.header = header
}
params.url = $A.apiUrl(params.url);
params.data = $A.date2string(params.data);
if (params.encrypt === undefined && $A.inArray(params.url, [
'users/login',
'users/editpass',
'users/operation',
'users/delete/account',
'dialog/msg/*',
], true)) {
params.encrypt = true
}
params.url = $A.apiUrl(params.url)
params.data = $A.date2string(params.data)
//
const cloneParams = $A.cloneJSON(params);
return new Promise(function (resolve, reject) {
const cloneParams = $A.cloneJSON(params)
return new Promise(async (resolve, reject) => {
// 加密传输
const encrypt = []
if (params.encrypt === true) {
// 有数据才加密
if (params.data) {
// PGP加密
if (state.apiKeyData.type === 'pgp') {
encrypt.push(`encrypt_type=${state.apiKeyData.type};encrypt_id=${state.apiKeyData.id}`)
params.method = "post" // 加密传输时强制使用post
params.data = {encrypted: await dispatch("pgpEncryptApi", params.data)}
}
}
encrypt.push("client_type=pgp;client_key=" + $urlSafe((await dispatch("pgpGetLocalKey")).publicKeyB64))
}
if (encrypt.length > 0) {
params.header.encrypt = encrypt.join(";")
}
// 数据转换
if (params.method === "post") {
params.data = JSON.stringify(params.data)
}
// Spinner
if (params.spinner === true || (typeof params.spinner === "number" && params.spinner > 0)) {
const {before, complete} = params;
const {before, complete} = params
params.before = () => {
dispatch("showSpinner", typeof params.spinner === "number" ? params.spinner : 0)
typeof before === "function" && before()
};
}
//
params.complete = () => {
dispatch("hiddenSpinner")
typeof complete === "function" && complete()
};
}
//
params.success = (result, status, xhr) => {
state.ajaxNetworkException = false;
if (!$A.isJson(result)) {
console.log(result, status, xhr);
reject({ret: -1, data: {}, msg: "Return error"})
return;
}
const {ret, data, msg} = result;
}
// 请求回调
params.success = async (result, status, xhr) => {
state.ajaxNetworkException = false
if (!$A.isJson(result)) {
console.log(result, status, xhr)
reject({ret: -1, data: {}, msg: "Return error"})
return
}
if (params.encrypt === true && result.encrypted) {
result = await dispatch("pgpDecryptApi", result.encrypted)
}
const {ret, data, msg} = result
if (ret === -1) {
state.userId = 0
if (params.skipAuthError !== true) {
@ -144,109 +194,110 @@ export default {
onOk: () => {
dispatch("logout")
}
});
})
reject(result)
return;
return
}
}
if (ret === -2 && params.checkNick !== false) {
// 需要昵称
dispatch("userEditInput", 'nickname').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject);
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置昵称!')})
});
return;
})
return
}
if (ret === -3 && params.checkTel !== false) {
// 需要联系电话
dispatch("userEditInput", 'tel').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject);
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置联系电话!')})
});
return;
})
return
}
if (ret === 1) {
resolve({data, msg});
resolve({data, msg})
} else {
reject({ret, data, msg: msg || "Unknown error"})
//
if (ret === -4001) {
dispatch("forgetProject", data.project_id);
dispatch("forgetProject", data.project_id)
} else if (ret === -4002) {
dispatch("forgetTask", data.task_id);
dispatch("forgetTask", data.task_id)
} else if (ret === -4003) {
dispatch("forgetDialog", data.dialog_id);
dispatch("forgetDialog", data.dialog_id)
}
}
};
}
params.error = (xhr, status) => {
const networkException = window.navigator.onLine === false || (status === 0 && xhr.readyState === 4);
const networkException = window.navigator.onLine === false || (status === 0 && xhr.readyState === 4)
if (params.checkNetwork !== false) {
state.ajaxNetworkException = networkException;
state.ajaxNetworkException = networkException
}
if (networkException) {
reject({ret: -1001, data: {}, msg: "Network exception"})
} else {
reject({ret: -1, data: {}, msg: "System error"})
}
};
//
if (params.websocket === true || params.ws === true) {
const apiWebsocket = $A.randomString(16);
}
// WebSocket
if (params.websocket === true) {
const apiWebsocket = $A.randomString(16)
const apiTimeout = setTimeout(() => {
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket);
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket)
if (WListener) {
WListener.complete();
WListener.error("timeout");
WListener.after();
WListener.complete()
WListener.error("timeout")
WListener.after()
}
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket);
}, params.timeout || 30000);
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket)
}, params.timeout || 30000)
state.ajaxWsListener.push({
apiWebsocket: apiWebsocket,
complete: typeof params.complete === "function" ? params.complete : () => { },
success: typeof params.success === "function" ? params.success : () => { },
error: typeof params.error === "function" ? params.error : () => { },
after: typeof params.after === "function" ? params.after : () => { },
});
})
//
params.complete = () => { };
params.success = () => { };
params.error = () => { };
params.after = () => { };
params.header['Api-Websocket'] = apiWebsocket;
params.complete = () => { }
params.success = () => { }
params.error = () => { }
params.after = () => { }
params.header['Api-Websocket'] = apiWebsocket
//
if (state.ajaxWsReady === false) {
state.ajaxWsReady = true;
state.ajaxWsReady = true
dispatch("websocketMsgListener", {
name: "apiWebsocket",
callback: (msg) => {
switch (msg.type) {
case 'apiWebsocket':
clearTimeout(apiTimeout);
const apiWebsocket = msg.apiWebsocket;
const apiSuccess = msg.apiSuccess;
const apiResult = msg.data;
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket);
clearTimeout(apiTimeout)
const apiWebsocket = msg.apiWebsocket
const apiSuccess = msg.apiSuccess
const apiResult = msg.data
const WListener = state.ajaxWsListener.find((item) => item.apiWebsocket == apiWebsocket)
if (WListener) {
WListener.complete();
WListener.complete()
if (apiSuccess) {
WListener.success(apiResult);
WListener.success(apiResult)
} else {
WListener.error(apiResult);
WListener.error(apiResult)
}
WListener.after();
WListener.after()
}
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket);
break;
state.ajaxWsListener = state.ajaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket)
break
}
}
});
})
}
}
$A.ajaxc(params);
//
$A.ajaxc(params)
})
},
@ -668,6 +719,7 @@ export default {
const cacheLoginEmail = await $A.IDBString("cacheLoginEmail");
const cacheFileSort = await $A.IDBJson("cacheFileSort");
await $A.IDBClear();
await $A.IDBSet("clientId", state.clientId);
await $A.IDBSet("cacheServerUrl", state.cacheServerUrl);
await $A.IDBSet("cacheProjectParameter", state.cacheProjectParameter);
await $A.IDBSet("cacheLoginEmail", cacheLoginEmail);
@ -3132,5 +3184,141 @@ export default {
state.ws.close();
state.ws = null;
}
},
/** *****************************************************************************************/
/** *************************************** pgp *********************************************/
/** *****************************************************************************************/
/**
* 创建密钥对
* @param state
* @returns {Promise<unknown>}
*/
pgpGenerate({state}) {
return new Promise(async resolve => {
const data = await openpgp.generateKey({
type: 'ecc',
curve: 'curve25519',
passphrase: state.clientId,
userIDs: [{name: 'doo', email: 'admin@admin.com'}],
})
data.publicKeyB64 = data.publicKey.replace(/\s*-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----\s*/g, '').replace(/\n+/g, '$')
resolve(data)
})
},
/**
* 获取密钥对不存在自动创建
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
pgpGetLocalKey({state, dispatch}) {
return new Promise(async resolve => {
// 已存在
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 避免重复生成
while (state.localKeyLock === true) {
await new Promise(r => setTimeout(r, 100));
}
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 生成密钥对
state.localKeyLock = true
state.localKeyPair = await dispatch("pgpGenerate")
state.localKeyLock = false
resolve(state.localKeyPair)
})
},
/**
* 加密
* @param state
* @param dispatch
* @param data {message:any, ?publicKey:string}
* @returns {Promise<unknown>}
*/
pgpEncrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {message: data}
}
const message = data.message || data.text
const publicKeyArmored = data.publicKey || data.key || (await dispatch("pgpGetLocalKey")).publicKey
const encryptionKeys = await openpgp.readKey({armoredKey: publicKeyArmored})
//
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({text: message}),
encryptionKeys,
})
resolve(encrypted)
})
},
/**
* 解密
* @param state
* @param dispatch
* @param data {encrypted:any, ?privateKey:string, ?passphrase:string}
* @returns {Promise<unknown>}
*/
pgpDecrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {encrypted: data}
}
const encrypted = data.encrypted || data.text
const privateKeyArmored = data.privateKey || data.key || (await dispatch("pgpGetLocalKey")).privateKey
const decryptionKeys = await openpgp.decryptKey({
privateKey: await openpgp.readPrivateKey({armoredKey: privateKeyArmored}),
passphrase: data.passphrase || state.clientId
})
//
const {data: decryptData} = await openpgp.decrypt({
message: await openpgp.readMessage({armoredMessage: encrypted}),
decryptionKeys
})
resolve(decryptData)
})
},
/**
* API加密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpEncryptApi({state, dispatch}, data) {
return new Promise(resolve => {
data = $A.jsonStringify(data)
dispatch("pgpEncrypt", {
message: data,
publicKey: state.apiKeyData.key
}).then(data => {
resolve(data.replace(/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/g, ''))
})
})
},
/**
* API解密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpDecryptApi({state, dispatch}, data) {
return new Promise(resolve => {
dispatch("pgpDecrypt", {
encrypted: "-----BEGIN PGP MESSAGE-----\n\n" + data + "\n-----END PGP MESSAGE-----"
}).then(data => {
resolve($A.jsonParse(data))
})
})
}
}

View File

@ -1,4 +1,7 @@
export default {
// 客户端ID希望不变的除非清除浏览器缓存或者卸载应用
clientId: "",
// 是否移动端(支持触摸)
supportTouch: "ontouchend" in document,
@ -170,4 +173,9 @@ export default {
// 表单布局
formLabelPosition: $A(window).width() > 576 ? 'right' : 'top',
formLabelWidth: $A(window).width() > 576 ? 'auto' : '',
// 加密相关
apiKeyData: {},
localKeyPair: {},
localKeyLock: false,
};

View File

@ -73,3 +73,14 @@ function __callData(key, requestData, state) {
export function $callData(key, requestData, state) {
return new __callData(key, requestData, state)
}
export function $urlSafe(value, encode = true) {
if (value) {
if (encode) {
value = String(value).replace(/\+/g, "-").replace(/\//g, "_")
} else {
value = String(value).replace(/-/g, "+").replace(/_/g, "/")
}
}
return value
}

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@
@endif
<link rel="stylesheet" type="text/css" href="{{ asset_main('css/iview.css') }}?v={{ $version }}">
<link rel="stylesheet" type="text/css" href="{{ asset_main('css/loading.css') }}?v={{ $version }}">
<script src="{{ asset_main('js/jsencrypt.min.js') }}?v={{ $version }}"></script>
<script src="{{ asset_main('js/scroll-into-view.min.js') }}?v={{ $version }}"></script>
<script>
window.csrfToken = {