diff --git a/niucloud/core/addon/AddonLoader.php b/niucloud/core/addon/AddonLoader.php new file mode 100644 index 000000000..e95634bc5 --- /dev/null +++ b/niucloud/core/addon/AddonLoader.php @@ -0,0 +1,40 @@ +remember($cache_name, function () { + $list = Db::name("addon")->column("key"); + return $list; + }); + } + + /** + * 获取插件目录 + * @param string $addon + * @return string + */ + protected function getAddonPath(string $addon) + { + return root_path() . 'addon' . DIRECTORY_SEPARATOR. $addon. DIRECTORY_SEPARATOR; + + } + + /** + * 获取系统整体app目录 + * @return string + */ + protected function getAppPath() + { + return root_path(). "app". DIRECTORY_SEPARATOR; + } + /** + * 获取插件对应app目录 + * @param string $addon + * @return string + */ + protected function getAddonAppPath(string $addon) + { + return $this->getAddonPath($addon). "app". DIRECTORY_SEPARATOR; + } + + /** + *获取系统enum path + */ + protected function getEnumPath() + { + return root_path(). "app". DIRECTORY_SEPARATOR. "enum". DIRECTORY_SEPARATOR;; + } + + /** + *获取插件对应的enum目录 + * @param string $addon + */ + protected function getAddonEnumPath(string $addon) + { + return $this->getAddonPath($addon). "app". DIRECTORY_SEPARATOR. "enum". DIRECTORY_SEPARATOR; + } + + /** + *获取插件对应的config目录 + * @param string $addon + */ + protected function getAddonConfigPath(string $addon) + { + return $this->getAddonPath($addon). "config". DIRECTORY_SEPARATOR; + } + + /** + * 加载文件数据 + * @param $files + * @return array + */ + protected function loadFiles($files) + { + $default_sort = 100000; + $files_data = []; + if (!empty($files)) { + foreach ($files as $file) { + $config = include $file; + if (!empty($config)) { + if (isset($config[ 'file_sort' ])) { + $sort = $config[ 'file_sort' ]; + unset($config[ 'file_sort' ]); + $sort = $sort * 10; + while (array_key_exists($sort, $files_data)) { + $sort++; + } + $files_data[ $sort ] = $config; + } else { + $files_data[ $default_sort ] = $config; + $default_sort++; + } + } + } + } + ksort($files_data); + return $files_data; + } + + /** + * 加载 + * @return mixed + */ + abstract public function load(array $data); +} diff --git a/niucloud/core/addon/Event.php b/niucloud/core/addon/Event.php new file mode 100644 index 000000000..8695e9ffa --- /dev/null +++ b/niucloud/core/addon/Event.php @@ -0,0 +1,36 @@ +getLocalAddons(); + $event_files = []; + + foreach ($addons as $k => $v) + { + $event_path = $this->getAddonAppPath($v)."event.php"; + if(is_file($event_path)) + { + $event_files[] = $event_path; + } + } + $files_data = $this->loadFiles($event_files); + + $files_data[1] = $data; + + $events = []; + foreach ($files_data as $file_data) { + $events = empty($events) ? $file_data : array_merge2($events, $file_data); + } + return $events; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/Lang.php b/niucloud/core/addon/Lang.php new file mode 100644 index 000000000..f3742b835 --- /dev/null +++ b/niucloud/core/addon/Lang.php @@ -0,0 +1,52 @@ +getLocalAddons(); + $system_lang_path = $this->getAppPath()."lang". DIRECTORY_SEPARATOR. $data['lang_type']. DIRECTORY_SEPARATOR; + $lang_files = [ + $system_lang_path. "api.php", + $system_lang_path. "enum.php", + $system_lang_path. "validate.php", + ]; + + + foreach ($addons as $k => $v) + { + $lang_path = $this->getAddonAppPath($v)."lang". DIRECTORY_SEPARATOR. $data['lang_type']. DIRECTORY_SEPARATOR; + + $api_path = $lang_path."api.php"; + $enum_path = $lang_path."enum.php"; + $validate_path = $lang_path."validate.php"; + if(is_file($api_path)) + { + $lang_files[] = $api_path; + + } + if(is_file($enum_path)) + { + $lang_files[] = $enum_path; + } + if(is_file($validate_path)) + { + $lang_files[] = $validate_path; + } + } + $files_data = $this->loadFiles($lang_files); + $lang = []; + foreach ($files_data as $file_data) { + $lang = empty($lang) ? $file_data : array_merge($lang, $file_data); + } + return $lang; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/Menu.php b/niucloud/core/addon/Menu.php new file mode 100644 index 000000000..658799f28 --- /dev/null +++ b/niucloud/core/addon/Menu.php @@ -0,0 +1,22 @@ +getAddonEnumPath($data['addon'])."menu".DIRECTORY_SEPARATOR. $data['app_type']. ".php"; + if(is_file($menu_path)) + { + return include $menu_path; + } + return []; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/Notice.php b/niucloud/core/addon/Notice.php new file mode 100644 index 000000000..8b5b5e474 --- /dev/null +++ b/niucloud/core/addon/Notice.php @@ -0,0 +1,39 @@ +getEnumPath(). "notice". DIRECTORY_SEPARATOR. $data['type']. ".php"; + if(is_file($system_path)) + { + $template_files[] = $system_path; + } + $addons = $this->getLocalAddons(); + foreach ($addons as $k => $v) + { + $template_path = $this->getAddonEnumPath($v). "notice". DIRECTORY_SEPARATOR. $data['type']. ".php"; + if(is_file($template_path)) + { + $template_files[] = $template_path; + } + } + $template_files_data = $this->loadFiles($template_files); + + $template_data_array = []; + foreach ($template_files_data as $file_data) + { + $template_data_array = empty($template_data_array) ? $file_data : array_merge($template_data_array, $file_data); + } + return $template_data_array; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/Route.php b/niucloud/core/addon/Route.php new file mode 100644 index 000000000..a2e6f489a --- /dev/null +++ b/niucloud/core/addon/Route.php @@ -0,0 +1,27 @@ +getLocalAddons(); + + foreach ($addons as $k => $v) + { + $route_path = $this->getAddonAppPath($v). DIRECTORY_SEPARATOR. $data['app_type']. DIRECTORY_SEPARATOR. "route.php"; + if(is_file($route_path)) + { + include $route_path; + } + } + return true; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/UniappComponent.php b/niucloud/core/addon/UniappComponent.php new file mode 100644 index 000000000..56b593b5b --- /dev/null +++ b/niucloud/core/addon/UniappComponent.php @@ -0,0 +1,33 @@ +getLocalAddons(); + $components_files = []; + foreach ($addons as $k => $v) + { + $components_path = $this->getAddonEnumPath($v). "diy". DIRECTORY_SEPARATOR. "components.php"; + if(is_file($components_path)) + { + $components_files[] = $components_path; + } + } + $components_files_data = $this->loadFiles($components_files); + $components = $data; + foreach ($components_files_data as $file_data) + { + $components = empty($components) ? $file_data : array_merge2($components, $file_data); + } + return $components; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/UniappLink.php b/niucloud/core/addon/UniappLink.php new file mode 100644 index 000000000..e8fab4bdb --- /dev/null +++ b/niucloud/core/addon/UniappLink.php @@ -0,0 +1,37 @@ +getLocalAddons(); + $link_files = []; + foreach ($addons as $k => $v) + { + $link_path = $this->getAddonEnumPath($v). "diy". DIRECTORY_SEPARATOR. "links.php"; + if(is_file($link_path)) + { + $link_files[] = $link_path; + } + } + $link_files_data = $this->loadFiles($link_files); + $links = $data; + foreach ($link_files_data as $file_data) + { + if(empty($links)) + { + $links = $file_data; + }else + $links = array_merge($links, $file_data); + } + return $links; + } +} \ No newline at end of file diff --git a/niucloud/core/addon/UniappPages.php b/niucloud/core/addon/UniappPages.php new file mode 100644 index 000000000..48969c46f --- /dev/null +++ b/niucloud/core/addon/UniappPages.php @@ -0,0 +1,37 @@ +getLocalAddons(); + $page_files = []; + foreach ($addons as $k => $v) + { + $page_path = $this->getAddonEnumPath($v). "diy". DIRECTORY_SEPARATOR. "pages.php"; + if(is_file($page_path)) + { + $page_files[] = $page_path; + } + } + $page_files_data = $this->loadFiles($page_files); + $pages = $data; + foreach ($page_files_data as $file_data) + { + if(empty($pages)) + { + $pages = $file_data; + }else + $pages = array_merge($pages, $file_data); + } + return $pages; + } +} \ No newline at end of file diff --git a/niucloud/core/base/BaseAdminController.php b/niucloud/core/base/BaseAdminController.php new file mode 100644 index 000000000..575f96a9d --- /dev/null +++ b/niucloud/core/base/BaseAdminController.php @@ -0,0 +1,29 @@ +app_type = $this->request->appType(); + $this->site_id = $this->request->siteId(); + $this->username = $this->request->username(); + $this->uid = $this->request->uid(); + } +} \ No newline at end of file diff --git a/niucloud/core/base/BaseApiController.php b/niucloud/core/base/BaseApiController.php new file mode 100644 index 000000000..882ae9418 --- /dev/null +++ b/niucloud/core/base/BaseApiController.php @@ -0,0 +1,29 @@ +site_id = $this->request->siteId(); + $this->member_id = $this->request->memberId(); + $this->channel = $this->request->getChannel(); + } +} \ No newline at end of file diff --git a/niucloud/core/base/BaseController.php b/niucloud/core/base/BaseController.php new file mode 100644 index 000000000..9adcda687 --- /dev/null +++ b/niucloud/core/base/BaseController.php @@ -0,0 +1,97 @@ +app = $app; + $this->request = $this->app->request; + // 控制器初始化 + $this->initialize(); + } + + // 初始化 + protected function initialize() + {} + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @return array|string|true + * @throws ValidateException + */ + protected function validate(array $data, $validate, array $message = [], bool $batch = false) + { + if (is_array($validate)) { + $v = new Validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + $class = str_contains($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + $v = new $class(); + if (!empty($scene)) { + $v->scene($scene); + } + } + + $v->message($message); + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + return $v->failException(true)->check($data); + } + + + + +} diff --git a/niucloud/core/base/BaseCoreService.php b/niucloud/core/base/BaseCoreService.php new file mode 100644 index 000000000..eeac17329 --- /dev/null +++ b/niucloud/core/base/BaseCoreService.php @@ -0,0 +1,27 @@ +fire(...$arguments); + } + + /** + * 运行消息队列 + * @param Job $job + * @param $data + */ + public function fire(Job $job, $data): void + { + try { + $action = $data['do'] ?? 'doJob';//任务名 + $infoData = $data['data'] ?? [];//数据 + $errorCount = $data['errorCount'] ?? 0;//执行任务错误的最大重试次数 + $this->runJob($action, $job, $infoData, $errorCount); + } catch (\Throwable $e) { + $job->delete(); + } + } + + + + /** + * 执行队列 + * @param string $action + * @param Job $job + * @param array $infoData + * @param int $errorCount + */ + protected function runJob(string $action, Job $job, array $infoData, int $errorCount = 3) + { + + $action = method_exists($this, $action) ? $action : 'handle'; + if (!method_exists($this, $action)) { + $job->delete(); + } + + if ($this->{$action}(...$infoData)) { + //删除任务 + $job->delete(); + } else { + if ($job->attempts() >= $errorCount && $errorCount) { + //删除任务 + $job->delete(); + } else { + //再次放入队列 + $job->release(); + } + } + + } +} diff --git a/niucloud/core/base/BaseModel.php b/niucloud/core/base/BaseModel.php new file mode 100644 index 000000000..955e0b210 --- /dev/null +++ b/niucloud/core/base/BaseModel.php @@ -0,0 +1,25 @@ +request = request(); + } + /** + * 分页列表参数(页码和每页多少条) + * @return mixed + */ + public function getPageParam(){ + + $page = request()->params([ + ['page', 1], + ['limit', 15] + ]); + validate(Page::class) + ->check($page); + return $page; + } + + /** + * 分页列表 + * @param array $where + * @param string $field + * @param string $order + * @param int $page + * @param int $limit + * @param null $with //数组可以是数组 function($query) use ($with){$query->with($with);} + * @param null $each //闭包匿名函数 function($item, $key){$item['nickname'] = 'think';return $item;} + * @return mixed + */ + public function getPageList(Model $model, array $where, string $field = '*', string $order = '', array $append = [], $with = null, $each = null){ + $page_params = $this->getPageParam(); + $page = $page_params['page']; + $limit = $page_params['limit']; + + $list = $model->where($where)->when($append, function($query) use ($append){ + $query->append($append); + })->when($with, function ($query) use($with){ + $query->with($with); + })->field($field)->order($order)->paginate([ + 'list_rows' => $limit, + 'page' => $page, + ]); + if(!empty($each)){ + $list = $list->each($each); + } + return $list->toArray(); + } + + /** + * 分页数据查询,传入model(查询后结果) + * @param $model BaseModel + * @param $each + * @return mixed + */ + public function pageQuery($model, $each = null) + { + $page_params = $this->getPageParam(); + $page = $page_params['page']; + $limit = $page_params['limit']; + $list = $model->paginate([ + 'list_rows' => $limit, + 'page' => $page, + ]); + if(!empty($each)){ + $list = $list->each($each); + } + return $list->toArray(); + } + + /** + * 分页视图列表查询 + * @param Model $model + * @param array $where + * @param string $field + * @param string $order + * @param array $append + * @param null $with + * @param null $each + * @return array + * @throws \think\db\exception\DbException + */ + public function getPageViewList(Model $model, array $where, string $field = '*', string $order = '', array $append = [], $with = null, $each = null){ + $page_params = $this->getPageParam(); + $page = $page_params['page']; + $limit = $page_params['limit']; + + $list = $model->where($where)->when($append, function($query) use ($append){ + $query->append($append); + })->when($with, function ($query) use($with){ + $query->withJoin($with); + })->field($field)->order($order)->paginate([ + 'list_rows' => $limit, + 'page' => $page, + ]); + if(!empty($each)){ + $list = $list->each($each); + } + return $list->toArray(); + } + +} \ No newline at end of file diff --git a/niucloud/core/exception/AddonException.php b/niucloud/core/exception/AddonException.php new file mode 100644 index 000000000..59634410e --- /dev/null +++ b/niucloud/core/exception/AddonException.php @@ -0,0 +1,17 @@ +job($class)->secs($secs); + if (is_array($action)) { + $queue->data(...$action); + } else if (is_string($action)) { + $queue->do($action)->data(...$data); + } + if ($queue_name) { + $queue->setQueueName($queue_name); + } else if (static::queueName()) { + $queue->setQueueName(static::queueName()); + } + return $queue->push(); + } else { + $class_name = '\\' . $class; + $res = new $class_name(); + if (is_array($action)) { + return $res->doJob(...$action); + } else { + return $res->$action(...$data); + } + + } + } + +} diff --git a/niucloud/core/loader/Loader.php b/niucloud/core/loader/Loader.php new file mode 100644 index 000000000..d9959d71e --- /dev/null +++ b/niucloud/core/loader/Loader.php @@ -0,0 +1,100 @@ +name = $name; + } + $this->config = $config; + } + + /** + * 获取默认驱动 + * @return mixed + */ + abstract protected function getDefault(); + /** + * 获取实例 + * @param string $type + * @return object|\think\DbManager + * @throws \Exception + */ + public function create(string $type){ + $class = $this->getClass($type); + return self::createFacade($class, [ + $this->name, + $this->config, + $this->config_file + ], true); + } + + /** + * 获取类 + * @param string $type + * @return mixed|string + * @throws \Exception + */ + public function getClass(string $type){ + $class = config($this->config_name.'.drivers.'.$type.'.driver'); + if (class_exists($class)) { + return $class; + }else{ + if ($this->namespace || str_contains($type, '\\')) { + $class = str_contains($type, '\\') ? $type : $this->namespace . ucfirst(strtolower($type)); + if (class_exists($class)) { + return $class; + } + } + } + throw new \Exception("Driver [$type] not supported."); + } + + + public function getLoader(){ + + if(empty($this->class)){ + $this->name = $this->name ?: $this->getDefault(); + if (!$this->name) { + throw new \Exception(sprintf( + 'could not find driver [%s].', static::class + )); + } + $this->class = $this->create($this->name); + } + return $this->class; + } + /** + * 动态调用 + * @param $method + * @param $arguments + * @return mixed + */ + public function __call($method, $arguments) + { + return $this->getLoader()->{$method}(...$arguments); + } + +} \ No newline at end of file diff --git a/niucloud/core/loader/Storage.php b/niucloud/core/loader/Storage.php new file mode 100644 index 000000000..ba7583eab --- /dev/null +++ b/niucloud/core/loader/Storage.php @@ -0,0 +1,67 @@ +name = $name; + $this->config_file = $config_file; + $this->initialize($config); + } + /** + * 设置错误信息 + * @param string|null $error + * @return bool + */ + protected function setError(?string $error = null) + { + $this->error = $error ?: '未知错误'; + return false; + } + + /** + * 获取错误信息 + * @return string + */ + public function getError() + { + $error = $this->error; + $this->error = null; + return $error; + } + + /** + * 初始化 + * @param array $config + * @return mixed + */ + abstract protected function initialize(array $config); + +} diff --git a/niucloud/core/pay/Alipay.php b/niucloud/core/pay/Alipay.php new file mode 100644 index 000000000..332347e3a --- /dev/null +++ b/niucloud/core/pay/Alipay.php @@ -0,0 +1,299 @@ +config = $this->payConfig($config, 'alipay'); + Pay::config($this->config); + } + + public function mp(array $params){ + + } + /** + * 网页支付 + * @param array $params + * @return ResponseInterface + */ + public function web(array $params) + { + return $this->returnUrl(Pay::alipay()->web([ + 'out_trade_no' => $params['out_trade_no'], + 'total_amount' => $params['money'], + 'subject' => $params['boby'], + ])); + } + + /** + * 手机网页支付 + * @param array $params + * @return ResponseInterface + */ + public function wap(array $params) + { + return $this->returnUrl(Pay::alipay()->wap([ + 'out_trade_no' => $params['out_trade_no'], + 'total_amount' => $params['money'], + 'subject' => $params['boby'], + 'quit_url' => $params['quit_url'] ?? '',//用户付款中途退出返回商户网站的地址, 一般是商品详情页或购物车页 + '_method' => 'get', + ])); + } + + /** + * app支付 + * @param $params + * @return mixed|ResponseInterface + */ + public function app(array $params) + { + return $this->returnUrl(Pay::alipay()->app([ + 'out_trade_no' => $params['out_trade_no'], + 'total_amount' => $params['money'], + 'subject' => $params['boby'],//用户付款中途退出返回商户网站的地址, 一般是商品详情页或购物车页 + ])); + } + + /** + * 小程序支付 + * @param $params + * @return mixed|ResponseInterface + */ + public function mini(array $params) + { + return Pay::alipay()->mini([ + 'out_trade_no' => $params['out_trade_no'], + 'total_amount' => $params['money'], + 'subject' => $params['boby'], + 'buyer_id' => $params['buyer_id'],//买家支付宝用户ID 注:交易的买家与卖家不能相同。 + ]); + } + + /** + * 付款码支付 + * @param $params + * @return mixed|Collection + */ + public function pos(array $params) + { + return Pay::alipay()->pos([ + 'out_trade_no' => $params['out_trade_no'], + 'auth_code' => $params['auth_code'],//付授权码。 当面付场景传买家的付款码(25~30开头的长度为16~24位的数字,实际字符串长度以开发者获取的付款码长度为准)或者刷脸标识串(fp开头的35位字符串)。 + 'total_amount' => $params['money'], + 'subject' => $params['boby'], + ]); + } + + /** + * 扫码支付 + * @param $params + * @return mixed|Collection + */ + public function scan(array $params) + { + return Pay::alipay()->scan([ + 'out_trade_no' => $params['out_trade_no'], + 'total_amount' => $params['money'], + 'subject' => $params['boby'], + ]); + } + + /** + * 转账 + * @param $params + * @return mixed|Collection + */ + public function transfer(array $params) + { + + $result = $this->returnFormat(Pay::alipay()->transfer([ + 'out_biz_no' => $params['transfer_no'], + 'trans_amount' => $params['money'], + 'product_code' => $params['product_code'] ?: 'TRANS_ACCOUNT_NO_PWD',//业务产品码,单笔无密转账到支付宝账户固定为 : TRANS_ACCOUNT_NO_PWD; 收发现金红包固定为 : STD_RED_PACKET; + 'biz_scene' => $params['scene'] ?: 'DIRECT_TRANSFER',//描述特定的业务场景,可传的参数如下:DIRECT_TRANSFER:单笔无密转账到支付宝,B2C现金红包;PERSONAL_COLLECTION:C2C现金红包-领红包 + 'payee_info' => [//收款方信息 + 'identity' => $params['to_no'],//参与方的唯一标识 + 'identity_type' => $params['to_type'] ?: 'ALIPAY_LOGON_ID',//参与方的标识类型,目前支持如下类型:1、ALIPAY_USER_ID 支付宝的会员ID2、ALIPAY_LOGON_ID:支付宝登录号,支持邮箱和手机号格式3、ALIPAY_OPEN_ID:支付宝openid + 'name' => $params['to_name'],//参与方真实姓名,如果非空,将校验收款支付宝账号姓名一致性。当identity_type=ALIPAY_LOGON_ID时,本字段必填。 + ], + ])); + if(!empty($result['msg']) && $result['msg'] != 'Success'){ + throw new PayException($result['sub_msg']); + + }else{ + if($result['status'] == 'SUCCESS'){ + $result = array( + 'batch_id' => $result['pay_fund_order_id'] + ); + }else if($result['status'] == 'FAIL' && !empty($result['fail_reason'])){ + throw new PayException($result['fail_reason']); + } + } + return $result; + } + + /** + * 支付关闭 + * @param $out_trade_no + * @return void + */ + public function close(string|int $out_trade_no){ + $result = $this->returnFormat(Pay::alipay()->close([ + 'out_trade_no' => $out_trade_no, + ])); + //todo 支付宝关闭异步回调 + if(!empty($result['msg']) && $result['msg'] == 'Success'){ + return true; + }else{ + return false; + } + } + + /** + * 退款 + * @param $out_trade_no + * @param $money + * @return array|MessageInterface|Collection|null + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $params){ + $out_trade_no = $params['out_trade_no']; + $money = $params['money']; +// $total = $params['total']; +// $refund_no = $params['refund_no']; + $result = Pay::alipay()->refund([ + 'out_trade_no' => $out_trade_no, + 'refund_amount' => $money, + ]); + return $result; + } + + + /** + * 支部异步回调 + * @param $out_trade_no + * @return void + */ + public function notify(Callable $callback){ + try{ + $result = Pay::alipay()->callback(); + //通过返回的值 + if(!empty($result)){//成功 + //todo 这儿需要具体设计 + $temp_data = array( + 'mchid' => $result['seller_id'], + 'trade_no' => $result['trade_no'], + 'result' => $result + ); + $callback_result = $callback($result['out_trade_no'], $temp_data); + if(is_bool($callback_result) && $callback_result){ + return Pay::alipay()->success(); + } + } + return $this->fail(); + } catch (\Throwable $e) { + return $this->fail(); + } + + + } + + /** + * 查询普通支付订单 + * @param $out_trade_no + * @return void + */ + public function getOrder(array $params = []){ + $out_trade_no = $params['out_trade_no']; + $order = [ + 'out_trade_no' => $out_trade_no, + ]; + $result = $this->returnFormat(Pay::alipay()->find($order)); + if(!empty($result['msg']) && $result['msg'] == 'Success'){ + return [ + 'status' => OnlinePayEnum::getAliPayStatus($result['trade_status']) + ]; + }else{ + if(!empty($result['sub_code']) && $result['sub_code'] == 'ACQ.ACQ.SYSTEM_ERROR'){ + throw new PayException($result['msg']); + }else{ + return []; + } + } + } + + /** + * 查询退款单据 + * @param $out_trade_no + * @param $refund_no + * @return void + */ + public function getRefund(string $out_trade_no, ?string $refund_no){ + $order = [ + 'out_trade_no' => $out_trade_no, + 'out_request_no' => $refund_no, + '_type' => 'refund', + ]; + + $result = $this->returnFormat(Pay::alipay()->find($order)); + return $result; + } + + /** + * 获取转账订单 + * @param $transfer_no + * @return void + */ + public function getTransfer(string $transfer_no){ + $order = [ + 'out_biz_no' => $transfer_no, + '_type' => 'transfer' + ]; + $result = $this->returnFormat(Pay::alipay()->find($order)); + return $result; + } + + public function fail(){ + return 'fail'; + } + + public function returnUrl($params){ + return ['url' => $params->getHeader('Location')[0]]; + } +} \ No newline at end of file diff --git a/niucloud/core/pay/BasePay.php b/niucloud/core/pay/BasePay.php new file mode 100644 index 000000000..f30090381 --- /dev/null +++ b/niucloud/core/pay/BasePay.php @@ -0,0 +1,169 @@ + [ + 'enable' => true, + 'file' => runtime_path() . 'paylog'.DIRECTORY_SEPARATOR.date('Ym').DIRECTORY_SEPARATOR.date('d').'.log', + 'level' => env('app_debug') ? 'debug' : 'info', // 建议生产环境等级调整为 info,开发环境为 debug + 'type' => 'single', // optional, 可选 daily. + 'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天 + ], + 'http' => [ // optional + 'timeout' => 5.0, + ] + ], + [ + $type => [ + 'default' => $config + ] + ] + + + ); + } + + public function returnFormat($param){ + if($param instanceof \Psr\Http\Message\MessageInterface){ + return $param->getBody()->getContents(); + }else if($param instanceof \Yansongda\Supports\Collection){ + return $param->all(); + }else{ + return $param; + } + } + + + + +} \ No newline at end of file diff --git a/niucloud/core/pay/PayLoader.php b/niucloud/core/pay/PayLoader.php new file mode 100644 index 000000000..8d6da06e4 --- /dev/null +++ b/niucloud/core/pay/PayLoader.php @@ -0,0 +1,41 @@ +config = $config; + $config['mch_secret_cert'] = url_to_path($config['mch_secret_cert'] ?? ''); + $config['mch_public_cert_path'] = url_to_path($config['mch_public_cert_path'] ?? ''); + Pay::config($this->payConfig($config, 'wechat')); + } + + + /** + * 公众号支付 + * @param array $params + * @return mixed|Collection + */ + public function mp(array $params){ + return $this->returnFormat(Pay::wechat()->mp([ + 'out_trade_no' => $params['out_trade_no'], + 'description' => $params['boby'], + 'amount' => [ + 'total' => $params['money'], + ], + 'payer' => [ + 'openid' => $params['openid'], + ], + ])); + } + + + /** + * 手机网页支付 + * @param $params + * @return mixed + */ + public function wap(array $params) + { + $order = [ + 'out_trade_no' => $params['out_trade_no'], + 'description' => $params['boby'], + 'amount' => [ + 'total' => $params['money'], + ], + 'scene_info' => [ + 'payer_client_ip' => request()->ip(), + 'h5_info' => [ + 'type' => 'Wap', + ] + ], + ]; + //这儿有些特殊, 默认情况下,H5 支付所使用的 appid 是微信公众号的 appid,即配置文件中的 mp_app_id 参数,如果想使用关联的小程序的 appid,则只需要在调用参数中增加 ['_type' => 'mini'] 即可 + if(!empty($order['type'])){ + $order['_type'] = 'mini'; // 注意这一行 + } + return $this->returnFormat(Pay::wechat()->wap($order)); + } + + public function web(array $params){ + + } + /** + * app支付 + * @param $params + * @return mixed|ResponseInterface + */ + public function app(array $params) + { + return $this->returnFormat(Pay::wechat()->app([ + 'out_trade_no' => $params['out_trade_no'], + 'description' => $params['boby'], + 'amount' => [ + 'total' => $params['money'], + ], + ])); + } + + /** + * 小程序支付 + * @param $params + * @return mixed|ResponseInterface + */ + public function mini(array $params) + { + return $this->returnFormat(Pay::wechat()->mini([ + 'out_trade_no' => $params['out_trade_no'], + 'description' => $params['boby'], + 'amount' => [ + 'total' => $params['money'], + 'currency' => 'CNY',//一般是人民币 + ], + 'payer' => [ + 'openid' => $params['openid'], + ] + ])); + } + + /** + * 付款码支付 + * @param $params + * @return mixed|Collection + */ + public function pos(array $params) + { + //todo 需要自定义通过plugin来侧载开发 + $app = Factory::payment([ + 'app_id' => $this->config['appid'], //应用id + 'mch_id' => $this->config["mch_id"] ?? '', //商户号 + 'key' => $this->config["pay_v2_signkey"] ?? '', // API 密钥 todo 注意: 是v2密钥 是v2密钥 是v2密钥 + 'response_type' => 'array', + 'log' => [ + 'level' => 'debug', + 'permission' => 0777, + 'file' => 'runtime/log/wechat/easywechat.logs', + ], + 'sandbox' => false, // 设置为 false 或注释则关闭沙箱模式 + ]); + $data = [ + 'body' => $params['boby'], + 'out_trade_no' => $params['out_trade_no'], + 'total_fee' => $params['money'], + 'auth_code' => $params["auth_code"],//传入的付款码 + ]; + $result = $app->base->pay($data);//没有注释路由,调用没有问题 + return $this->returnFormat($result); + } + + /** + * 扫码支付 + * @param $params + * @return mixed|Collection + */ + public function scan(array $params) + { + return $this->returnFormat(Pay::wechat()->scan([ + 'out_trade_no' => $params['out_trade_no'], + 'description' => $params['boby'], + 'amount' => [ + 'total' => $params['money'], + ], + ])); + } + + /** + * 转账(微信的转账是很多笔的) + * @param $params + * @return mixed|Collection + */ + public function transfer(array $params) + { + //这儿的批次信息可能是这儿生成的,但依然需要记录 + $order = [ + 'out_batch_no' => time().'',// + 'batch_name' => $params['remark'], + 'batch_remark' => $params['remark'], + ]; + $transfer_list = $params['transfer_list']; + //单笔转账 + if(empty($transfer_list)){ + $transfer_list = array( + [ + 'transfer_no' => $params['transfer_no'].'1', + 'money' => (int)$params['money'], + 'remark' => $params['remark'], + 'openid' => $params['to_no'] + ] + ); + } + $total_amount = 0; + $total_num = 0; + + foreach($transfer_list as $v){ + $item_transfer = [ + 'out_detail_no' => time().'1', + 'transfer_amount' => (int)$v['money'], + 'transfer_remark' => $v['remark'], + 'openid' => $v['openid'], + ]; + $total_amount += (int)$v['money']; + $total_num++; + if(!empty($v['user_name'])){ + $item_transfer['user_name'] = $v['user_name'];// 明文传参即可,sdk 会自动加密 + } + $order['transfer_detail_list'][] = $item_transfer; + } + $order['total_amount'] = (int)$total_amount; + $order['total_num'] = (int)$total_num; + $result = $this->returnFormat(Pay::wechat()->transfer($order)); + + if(!empty($result['code'])){ +// if($result['code'] == 'PARAM_ERROR'){ +// throw new PayException(); +// }else if($result['code'] == 'INVALID_REQUEST'){ +// throw new PayException(); +// } + if($result['code'] == 'INVALID_REQUEST'){ + throw new PayException(700010); + } + throw new PayException($result['message']); + } + return ['batch_id' => $result['batch_id'], 'out_batch_no' => $result['out_batch_no']]; + } + + /** + * 支付关闭 + * @param $out_trade_no + * @return void + */ + public function close(string|int $out_trade_no){ + $result = Pay::wechat()->close([ + 'out_trade_no' => $out_trade_no, + ]); + return $this->returnFormat($result); + } + + /** + * 退款 + * @param $out_trade_no + * @param $money + * @return array|MessageInterface|Collection|null + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function refund(array $params){ + $out_trade_no = $params['out_trade_no']; + $money = $params['money']; + $total = $params['total']; + $refund_no = $params['refund_no']; + $result = Pay::wechat()->refund([ + 'out_trade_no' => $out_trade_no, + 'out_refund_no' => $refund_no, + 'amount' => [ + 'refund' => $money, + 'total' => $total, + 'currency' => 'CNY', + ], + ]); + return $this->returnFormat($result); + } + + + /** + * 异步回调 + * @param $out_trade_no + * @return void + */ + public function notify(Callable $callback){ + try{ + $result = $this->returnFormat(Pay::wechat()->callback()); + if($result['event_type'] == 'TRANSACTION.SUCCESS'){ + $pay_trade_data = $result['resource']['ciphertext']; + $temp_params = [ + 'trade_no' => $pay_trade_data['transaction_id'], + 'mch_id' => $pay_trade_data['mchid'] + ]; + $callback_result = $callback($pay_trade_data['out_trade_no'], $temp_params); + if(is_bool($callback_result) && $callback_result){ + return Pay::wechat()->success(); + } + } + return $this->fail(); + + } catch (\Throwable $e) { +// throw new PayException($e->getMessage()); + return $this->fail($e->getMessage()); + } + } + + /** + * 查询普通支付订单 + * @param string $out_trade_no + * @param string $transaction_id + * @return array|MessageInterface|Collection|null + * @throws ContainerException + * @throws InvalidParamsException + * @throws ServiceNotFoundException + */ + public function getOrder(array $params = []){ + + $out_trade_no = $params['out_trade_no']; + $transaction_id = $params['transaction_id']; + $order = [ + + ]; + if(!empty($out_trade_no)){ + $order['out_trade_no'] = $out_trade_no; + } + if(!empty($transaction_id)){ + $order['transaction_id'] = $transaction_id; + } + $result = Pay::wechat()->find($order); + if(empty($result)) + return $result; + $result = $this->returnFormat($result); + return [ + 'status' => OnlinePayEnum::getWechatPayStatus($result['trade_state']), + ]; + } + + /** + * 查询退款单据 + * @param $out_trade_no + * @param $refund_no + * @return void + */ + public function getRefund(?string $out_trade_no, ?string $refund_no = ''){ + $order = [ + '_type' => 'refund', + 'out_refund_no' => $refund_no + ]; + $result = Pay::wechat()->find($order); + return $this->returnFormat($result); + } + + /** + * 获取转账订单(todo 切勿调用) + * @param $transfer_no + * @return void + */ + public function getTransfer(string $transfer_no){ + + + $params = [ + 'out_batch_no' => $transfer_no, + ]; + + $allPlugins = Pay::wechat()->mergeCommonPlugins([QueryOutBatchNoPlugin::class]); + + $result = Pay::wechat()->pay($allPlugins, $params); + return $this->returnFormat($result); + } + + + public function fail($message = ''){ + $response = [ + 'code' => 'FAIL', + 'message' => $message ?: '失败', + ]; + return response($response, 400, [], 'json'); + } +} \ No newline at end of file diff --git a/niucloud/core/sms/Aliyun.php b/niucloud/core/sms/Aliyun.php new file mode 100644 index 000000000..bb11b5b2f --- /dev/null +++ b/niucloud/core/sms/Aliyun.php @@ -0,0 +1,98 @@ +app_key = $config['app_key'] ?? ''; + $this->secret_key = $config['secret_key'] ?? ''; + $this->sign = $config['sign'] ?? ''; + } + + + /** + * 发送短信 + * @param string $mobile + * @param string $template_id + * @param array $data + * @return array|false + */ + public function send(string $mobile, string $template_id, array $data = []) + { + try { + AlibabaCloud::accessKeyClient($this->app_key, $this->secret_key) + ->regionId('cn-hangzhou') + ->asDefaultClient(); + + $result = AlibabaCloud::rpcRequest() + ->product('Dysmsapi') + ->host('dysmsapi.aliyuncs.com') + ->version('2017-05-25') + ->action('SendSms') + ->method('POST') + ->debug(false) + ->options([ + 'query' => [ + 'PhoneNumbers' => $mobile, + 'SignName' => $this->sign, + 'TemplateCode' => $template_id, + 'TemplateParam' => $data, + ], + ]) + ->request(); + + $res = $result->toArray(); + if (isset($res['Code']) && $res['Code'] == 'OK') { + return $res; + } + $message = $res['Message'] ?? $res; + throw new NoticeException($message); + } catch( Exception $e) { + throw new NoticeException($e->getMessage()); + } + } + + public function open() + { + } + + public function modify(string $sign = null, string $phone, string $code) + { + } + + public function info() + { + } + + public function temps(int $page = 0, int $limit = 10, int $type = 1) + { + } + + public function apply(string $title, string $content, int $type) + { + } + + public function applys(int $tempType, int $page, int $limit) + { + } + + public function record($record_id) + { + } +} \ No newline at end of file diff --git a/niucloud/core/sms/BaseSms.php b/niucloud/core/sms/BaseSms.php new file mode 100644 index 000000000..1372ff7dc --- /dev/null +++ b/niucloud/core/sms/BaseSms.php @@ -0,0 +1,88 @@ +secret_id = $config['secret_id'] ?? ''; + $this->secret_key = $config['secret_key'] ?? ''; + $this->sign = $config['sign'] ?? ''; + $this->app_id = $config['app_id'] ?? ''; + } + + + + + /** + * 发送短信 + * @return bool|mixed + */ + public function send(string $mobile, string $template_id, array $data = []) + { + try { + $cred = new Credential($this->secret_id, $this->secret_key); + $httpProfile = new HttpProfile(); + $httpProfile->setEndpoint("sms.tencentcloudapi.com"); + + $clientProfile = new ClientProfile(); + $clientProfile->setHttpProfile($httpProfile); + + $client = new SmsClient($cred, 'ap-guangzhou', $clientProfile); + $params = [ + 'PhoneNumberSet' => ['+86' . $mobile], + 'TemplateID' => $template_id, + 'Sign' => $this->sign, + 'TemplateParamSet' => $data, + 'SmsSdkAppid' => $this->app_id, + ]; + $req = new SendSmsRequest(); + $req->fromJsonString(json_encode($params)); + $resp = json_decode($client->SendSms($req)->toJsonString(), true); + if (isset($resp['SendStatusSet']) && $resp['SendStatusSet'][0]['Code'] == 'Ok') { + return $resp; + } else { + $message = $res['SendStatusSet'][0]['Message'] ?? json_encode($resp); + throw new CommonException( $message); + } + } catch( Exception $e) { + throw new NoticeException($e->getMessage()); + } + } + + + public function open() + { + } + + public function modify(string $sign = null, string $phone, string $code) + { + } + + public function info() + { + } + + public function temps(int $page = 0, int $limit = 10, int $type = 1) + { + } + + public function apply(string $title, string $content, int $type) + { + } + + public function applys(int $tempType, int $page, int $limit) + { + } + + public function record($record_id) + { + } +} \ No newline at end of file diff --git a/niucloud/core/template/BaseTemplate.php b/niucloud/core/template/BaseTemplate.php new file mode 100644 index 000000000..e4ce05369 --- /dev/null +++ b/niucloud/core/template/BaseTemplate.php @@ -0,0 +1,62 @@ +site_id = $config['site_id'] ?? ''; + + } + + /** + * 实例化订阅消息业务 + * @return \EasyWeChat\MiniProgram\SubscribeMessage\Client + */ + public function template(){ + return CoreWeappService::app($this->site_id)->subscribe_message; + } + /** + * 消息发送 + * @param string $templateId + * @param array $data + * @return mixed|void + */ + public function send(array $data){ + return $this->template()->send([ + 'template_id' => $data['template_id'], + 'touser' => $data['openid'], + 'page' => $data['page'], + 'data' => $data['data'], + ]); + } + + /** + * 添加模板消息 + * @param array $data + * @return mixed|void + */ + public function add(array $data){ + return $this->template()->addTemplate($data['tid'], $data['kidList'], $data['sceneDesc']); + } + + /** + * 删除 + * @param array $data + * @return mixed|void + */ + public function delete(array $data){ + return $this->template()->deleteTemplate($data['template_id']); + } + + /** + * 获取 + * @return mixed|void + */ + public function get(){ + + } +} \ No newline at end of file diff --git a/niucloud/core/template/Wechat.php b/niucloud/core/template/Wechat.php new file mode 100644 index 000000000..9dfac299e --- /dev/null +++ b/niucloud/core/template/Wechat.php @@ -0,0 +1,86 @@ +site_id = $config['site_id'] ?? ''; + + } + + /** + * 实例化模板消息业务 + * @return \EasyWeChat\OfficialAccount\TemplateMessage\Client + * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException + */ + public function template(){ + return CoreWechatService::app($this->site_id)->template_message; + } + /** + * 消息发送 + * @param string $templateId + * @param array $data + * @return mixed|void + */ + public function send(array $data){ + $openid = $data['openid']; + $template_id = $data['template_id']; + $template_data = $data['data']; + $first = $data['first']; + $remark = $data['remark']; + $url = $data['url']; + $miniprogram = $data['miniprogram']; + + if(!empty($first)) $template_data['first'] = $first; + if(!empty($remark)) $template_data['remark'] = $remark; + return $this->template()->send([ + 'touser' => $openid, + 'template_id' => $template_id, + 'url' => $url, + 'miniprogram' => $miniprogram, + 'data' => $template_data, + ]); + } + + /** + * 添加模板消息 + * @param string $shortId + * @return mixed|void + */ + public function add(array $data){ + return $this->template()->addTemplate($data['shortId']); + } + + /** + * 删除 + * @param string $templateId + * @return mixed|void + */ + public function delete(array $data){ + return $this->template()->deletePrivateTemplate($data['templateId']); + } + + /** + * 获取 + * @return mixed|void + */ + public function get(){ + + } +} \ No newline at end of file diff --git a/niucloud/core/upload/Aliyun.php b/niucloud/core/upload/Aliyun.php new file mode 100644 index 000000000..6c9220d86 --- /dev/null +++ b/niucloud/core/upload/Aliyun.php @@ -0,0 +1,94 @@ +config['access_key']; + $access_key_secret = $this->config['secret_key']; + + $endpoint = $this->config['endpoint'];// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。 + $oss_client = new OssClient($access_key_id, $access_key_secret, $endpoint, $is_cname); + return $oss_client; + } + + /** + * 执行上传 + * @param $save_dir (保存路径) + * @return bool|mixed + */ + public function upload(string $dir) + { + $this->validate(); + $bucket = $this->config['bucket']; + try { + $this->client()->uploadFile( + $bucket, + $this->getFullPath(), + $this->getRealPath() + ); + return true; + } catch (OssException $e) { + throw new UploadFileException($e->getMessage()); + } + + } + + /** + * Notes: 抓取远程资源 + * @param $url + * @param null $key + * @return mixed|void + */ + public function fetch(string $url, ?string $key = null) + { + $bucket = $this->config['bucket']; + try { + $content = file_get_contents($url); + $this->client()->putObject( + $bucket, + $key, + $content + ); + return true; + } catch (OssException $e) { + throw new UploadFileException($e->getMessage()); + } + + } + + /** + * 删除文件 + * @param $file_name + * @return bool|mixed + */ + public function delete(string $file_name) + { + $bucket = $this->config['bucket']; + try { + $this->client()->deleteObject($bucket, $file_name); + return true; + } catch (OssException $e) { + throw new UploadFileException($e->getMessage()); + } + + } + + +} diff --git a/niucloud/core/upload/BaseUpload.php b/niucloud/core/upload/BaseUpload.php new file mode 100644 index 000000000..7a382787f --- /dev/null +++ b/niucloud/core/upload/BaseUpload.php @@ -0,0 +1,234 @@ +config = $config; + } + + /** + * 附件上传 + * @param $save_dir + * @return mixed + */ + abstract protected function upload(string $dir); + + /** + * 抓取远程附件 + * @param $url + * @param $key + * @return mixed + */ + abstract protected function fetch(string $url, ?string $key); + + /** + * 附件删除 + * @param $fileName + * @return mixed + */ + abstract protected function delete(string $file_name); + + + + /** + * 读取文件 + * @param $name + */ + public function read(string $name, bool $is_rename = true){ + $this->name = $name; + $this->file = request()->file($name); + if(empty($this->file)) + throw new UploadFileException(100012); + $this->file_info = [ + 'name' => $this->file->getOriginalName(),//文件原始名称 + 'mime' => $this->file->getOriginalMime(),//上传文件类型信息 + 'real_path' => $this->file->getRealPath(),//上传文件真实路径 + 'ext' => $this->file->getOriginalExtension(),//上传文件后缀 + 'size' => $this->file->getSize(),//上传文件大小 + ]; + if($is_rename){ + $this->file_name = $this->createFileName(); + }else{ + $this->file_name = $this->file_info['name']; + } + + } + + /** + * 设置文件类型 + * @param string $type + * @return $this + */ + public function setType(string $type){ + $this->type = $type; + return $this; + } + + /** + * 校验文件是否合法 + */ + public function check(){ + + } + + /** + * 生成新的文件名 + * @return string + */ + public function createFileName(string $key = '', string $ext = ''){ + //DIRECTORY_SEPARATOR 常量 + if(empty($key)){ + return time().md5($this->file_info['real_path']).'.'.$this->file_info['ext']; + }else{ + return time().md5($key).'.'.$ext; + } + + } + + /** + * 获取原始附件信息 + * @return mixed + */ + public function getFileInfo(){ + return $this->file_info; + } + + /** + * 获取上传文件的真实完整路径 + * @return mixed + */ + public function getRealPath(){ + return $this->file_info['real_path']; + } + /** + * 获取生成的文件完整地址 + * @return mixed + */ + public function getFullPath(string $dir = ''){ + return $this->full_path ?: $this->concatFullPath($dir); + } + + /** + * 合并路径和文件名 + */ + public function concatFullPath(string $dir = ''){ + $this->full_path = implode('/', array_filter([$dir, $this->getFileName()])); + return $this->full_path; + } + + /** + * 获取文件名 + * @return mixed + */ + public function getFileName() + { + return $this->file_name; + } + public function getUrl(string $path = ''){ + $path = !empty($path) ? $path : $this->getFullPath(); + $domain = $this->config['domain'] ?? ''; + $domain = empty($domain) ? '' : $domain.'/'; + return $domain.$path; + } + + + public function setValidate(array $validate = []){ + $this->validate = $validate ?: config('upload.rules')[$this->type] ?? []; + return $this; + } + + /** + * 根据上传文件的类型来校验文件是否符合配置 + * @param int $site_id + * @param string $file + * @param string $att_type + * @return void + */ + public function validate() + { + if (empty($this->file)) + throw new UploadFileException('UPLOAD_FAIL'); + + $config['file_ext'] = $this->validate['ext'] ?? []; + $config['file_mime'] = $this->validate['mime'] ?? []; + $config['file_size'] = $this->validate['size'] ?? 0; + $rule = []; + $file_size = $config['file_size'] ?? 0; + if ($file_size > 0) { + $rule[] = 'fileSize:' . $file_size; + } + //验证上传文件类型 + $file_mime = $config['file_mime'] ?? []; + $file_ext = $config['file_ext'] ?? []; + if (!empty($file_ext)) { + $rule[] = 'fileExt:' . implode(',', $file_ext); + } +// $image_config = $config['image'] ?? []; +// if (!empty($image_config)) { +// $image_width = $image_config['width'] ?? 0; +// $image_height = $image_config['height'] ?? 0; +// $image_type = $image_config['type'] ?? []; +// $image_rule = ''; +// if ($image_width > 0 && $image_height > 0) { +// $image_rule = 'image:' . $image_width . ',' . $image_height; +// } +// if (!empty($image_type)) { +// if (empty($image_rule)) { +// $image_rule = 'image:'; +// } else { +// $image_rule .= ','; +// } +// $image_rule .= implode(',', $image_type); +// } +// if (!empty($image_type)) { +// $rule[] = $image_rule; +// } +// } + if (!empty($rule)) { + if (!in_array($this->file->getOriginalMime(), $file_mime)) { + throw new UploadFileException('UPLOAD_TYPE_NOT_SUPPORT'); + } + validate([$this->name => implode('|', $rule)])->check([$this->name => $this->file]); + } + + } +} diff --git a/niucloud/core/upload/Local.php b/niucloud/core/upload/Local.php new file mode 100644 index 000000000..258ce7c9d --- /dev/null +++ b/niucloud/core/upload/Local.php @@ -0,0 +1,89 @@ +validate(); + mkdirs($dir); + $this->file->move($dir, $this->file_name); + //错误一般是已经被抛出了 + return true; + } + + + /** + * 远程获取图片 + * @param $url + * @param $key + * @return true + */ + public function fetch(string $url, ?string $key) + { + try { + mkdirs($key); + $content = @file_get_contents($url); + if (!empty($content)) { + file_put_contents($key, $content); +// $fp = fopen($key, "w"); +// fwrite($fp, $content); +// fclose($fp); + }else{ + throw new UploadFileException(203006); + } + return true; + } catch ( Exception $e ) { + throw new UploadFileException($e->getMessage()); + } + } + + /** + * base64转图片 + * @param string $content + * @param string|null $key + * @return void + */ + public function base64(string $content, ?string $key){ + + mkdirs($key); + file_put_contents(url_to_path($key), base64_decode($content)); + return true; + } + + /** + * 删除本地附件 + * @param $file_name + * @return bool|mixed + */ + public function delete(string $file_name) + { + $file_path = url_to_path($file_name); + if (!file_exists($file_path)) { + return true; +// throw new UploadFileException(100013); + } + return unlink($file_path); + } +} \ No newline at end of file diff --git a/niucloud/core/upload/Qcloud.php b/niucloud/core/upload/Qcloud.php new file mode 100644 index 000000000..75d1cd4eb --- /dev/null +++ b/niucloud/core/upload/Qcloud.php @@ -0,0 +1,111 @@ +config['access_key']; //替换为用户的 secretId,请登录访问管理控制台进行查看和管理,https://console.tencentcloud.com/cam/capi + $secret_key = $this->config['secret_key']; //替换为用户的 secretKey,请登录访问管理控制台进行查看和管理,https://console.tencentcloud.com/cam/capi + $region = $this->config['region']; //替换为用户的 region,已创建桶归属的region可以在控制台查看,https://console.tencentcloud.com/cos5/bucket + + return new Client( + array( + 'region' => $region, +// 'schema' => 'https', //协议头部,默认为http + 'credentials' => array( + 'secretId' => $secret_id, + 'secretKey' => $secret_key) + ) + ); + } + + + /** + * 执行上传 + * @param $save_dir (保存路径) + * @return bool|mixed + */ + public function upload(string $dir) + { + $this->validate(); + $bucket = $this->config['bucket']; + try { + $result = $this->client()->putObject(array( + 'Bucket' => $bucket, //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.tencentcloud.com/cos5/bucket + 'Key' => $this->getFullPath(), + 'Body' => fopen($this->getRealPath(), 'rb'), + )); + // 请求成功 + return true; + } catch ( Exception $e ) { + throw new UploadFileException($e->getMessage()); + } + } + + /** + * notes: 抓取远程资源(最大支持上传5G文件) + * @param $url + * @param null $key + * @return mixed|void + */ + public function fetch(string $url, ?string $key = null) + { + + $bucket = $this->config['bucket']; + try { + $result = $this->client()->putObject(array( + 'Bucket' => $bucket, //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.tencentcloud.com/cos5/bucket + 'Key' => $key, + 'Body' => fopen($url, 'rb'), + )); + // 请求成功 + return true; + } catch ( Exception $e ) { + throw new UploadFileException($e->getMessage()); + } + } + + /** + * 删除一个简单对象 + * @param $file_name + * @return bool|mixed + */ + public function delete(string $file_name) + { + $bucket = $this->config['bucket']; + try { + $this->client()->deleteObject(array( + 'Bucket' => $bucket, + 'Key' => $file_name + )); + return true; + } catch ( Exception $e ) { + throw new UploadFileException($e->getMessage()); + } + } + +} diff --git a/niucloud/core/upload/Qiniu.php b/niucloud/core/upload/Qiniu.php new file mode 100644 index 000000000..6e5abea9f --- /dev/null +++ b/niucloud/core/upload/Qiniu.php @@ -0,0 +1,96 @@ +config['access_key']; + $secret_key = $this->config['secret_key']; + return new Auth($access_key, $secret_key); + } + public function upload(string $dir) + { + $this->validate(); + $bucket = $this->config['bucket']; + //todo 这儿可以定义凭证的过期时间 + $up_token = $this->auth()->uploadToken($bucket); + // 初始化 UploadManager 对象并进行文件的上传。 + $upload_mgr = new UploadManager(); + list($ret, $err) = $upload_mgr->putFile($up_token, $this->getFullPath(), $this->getRealPath()); + if ($err !== null) + throw new UploadFileException($err->message()); + return true; + } + + /** + * 抓取网络资源到空间 + * @param $url + * @param $key + * @return true + * @throws Exception + */ + public function fetch(string $url, ?string $key = null) + { + $bucket = $this->config['bucket']; + $auth = $this->auth(); + if(!str_contains($url, 'http://') && !str_contains($url, 'https://')){ + $token = $auth->uploadToken($bucket); + $upload_mgr = new UploadManager(); + list($ret, $err) = $upload_mgr->putFile($token, $key, $url); + }else{ + //抓取网络资源到空间 + $bucket_manager = new BucketManager($auth); + list($ret, $err) = $bucket_manager->fetch($url, $bucket, $key);//不指定key时,以文件内容的hash作为文件名 + } + + if ($err !== null) + throw new UploadFileException($err->message()); + return true; + } + + /** + * 删除空间中的文件 + * @param $file_name + * @return bool|mixed + */ + public function delete(string $file_name) + { + $bucket = $this->config['bucket']; + $auth = $this->auth(); + $config = new Config(); + $bucket_manager = new BucketManager($auth, $config); + $err = $bucket_manager->delete($bucket, $file_name); + if ($err !== null) + throw new UploadFileException($err->message()); + return true; + } +} \ No newline at end of file diff --git a/niucloud/core/upload/UploadLoader.php b/niucloud/core/upload/UploadLoader.php new file mode 100644 index 000000000..f7fc963e5 --- /dev/null +++ b/niucloud/core/upload/UploadLoader.php @@ -0,0 +1,41 @@ +path = $path; + $this->config = $init; + $this->unique_key = $unique_key; + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + public function loadConfig() : array + { + $files_data = $this->loadFiles(); + if (!empty($files_data)) { + foreach ($files_data as $data) { + if ($this->unique_key) { + $this->config = $this->config + $data; + } else { + $this->config = array_merge($this->config, $data); + } + } + } + + return $this->config; + } + + /** + * 加载返回所有文件数据 + */ + public function loadFiles() + { + $this->parseFiles($this->path); + $default_sort = 100000; + $files_data = []; + if (!empty($this->files)) { + foreach ($this->files as $file) { + $config = include $file; + if (!empty($config)) { + if (isset($config[ 'file_sort' ])) { + $sort = $config[ 'file_sort' ]; + unset($config[ 'file_sort' ]); + $sort = $sort * 10; + while (array_key_exists($sort, $files_data)) { + $sort++; + } + $files_data[ $sort ] = $config; + } else { + $files_data[ $default_sort ] = $config; + $default_sort++; + } + } + } + } + ksort($files_data); + return $files_data; + } + + /** + * 整理所有文件 + * @param string $path + */ + protected function parseFiles(string $path) + { + $files = scandir($path); + //先加载系统(system),然后加载非插件,最后按照插件安装顺序进行加载 + foreach ($files as $file) { + if ($file != '.' && $file != '..') { + if (is_dir($path . DIRECTORY_SEPARATOR . $file)) { + $this->parseFiles($path . DIRECTORY_SEPARATOR . $file); + } else { + $this->files[] = $path . DIRECTORY_SEPARATOR . $file; + } + } + } + } + +} diff --git a/niucloud/core/util/Queue.php b/niucloud/core/util/Queue.php new file mode 100644 index 000000000..1c929a9f4 --- /dev/null +++ b/niucloud/core/util/Queue.php @@ -0,0 +1,230 @@ +error = $error ?: '未知错误'; + return false; + } + + /** + * 获取错误信息 + * @return string + */ + public function getError() + { + $error = $this->error; + $this->error = null; + return $error; + } + + /** + * 任务执行 + * @var string + */ + protected $do = 'doJob'; + + /** + * 默认任务执行方法名 + * @var string + */ + protected $defaultDo; + + /** + * 任务类名 + * @var string + */ + protected $job; + + /** + * 错误次数 + * @var int + */ + protected $errorCount = 3; + + /** + * 数据 + * @var array|string + */ + protected $data; + + /** + * 队列名 + * @var null + */ + protected $queueName = null; + + /** + * 延迟执行秒数 + * @var int + */ + protected $secs = 0; + + /** + * 记录日志 + * @var string|callable|array + */ + protected $log; + + /** + * @var array + */ + protected $rules = ['do', 'data', 'errorCount', 'job', 'secs', 'log']; + + /** + * @var static + */ + protected static $instance; + + /** + * Queue constructor. + */ + protected function __construct() + { + $this->defaultDo = $this->do; + } + + /** + * @return static + */ + public static function instance() + { + if (is_null(self::$instance)) { + self::$instance = new static(); + } + return self::$instance; + } + + /** + * @param string $queueName + * @return $this + */ + public function setQueueName(string $queueName) + { + $this->queueName = $queueName; + return $this; + } + + /** + * 放入消息队列 + * @param array|null $data + * @return mixed + */ + public function push(?array $data = null) + { + if (!$this->job) { + return $this->setError('需要执行的队列类必须存在'); + } + $jodValue = $this->getValues($data); + //todo 队列扩展策略调度, + $res = ThinkQueue::{$this->action()}(...$jodValue); + if (!$res) { + $res = ThinkQueue::{$this->action()}(...$jodValue); + if (!$res) { + Log::error('加入队列失败,参数:' . json_encode($this->getValues($data))); + } + } + $this->clean(); + return $res; + } + + /** + * 清除数据 + */ + public function clean() + { + $this->secs = 0; + $this->data = []; + $this->log = null; + $this->queueName = null; + $this->errorCount = 3; + $this->do = $this->defaultDo; + } + + /** + * 获取任务方式 + * @return string + */ + protected function action() + { + return $this->secs ? 'later' : 'push'; + } + + /** + * 获取参数 + * @param $data + * @return array + */ + protected function getValues($data) + { + $jobData['data'] = $data ?: $this->data; + $jobData['do'] = $this->do; + $jobData['errorCount'] = $this->errorCount; + $jobData['log'] = $this->log; + if ($this->do != $this->defaultDo) { + $this->job .= '@' . Config::get('queue.prefix', 'eb_') . $this->do; + } + if ($this->secs) { + return [$this->secs, $this->job, $jobData, $this->queueName]; + } else { + return [$this->job, $jobData, $this->queueName]; + } + } + + /** + * @param $name + * @param $arguments + * @return $this + */ + public function __call($name, $arguments) + { + if (in_array($name, $this->rules)) { + if ($name === 'data') { + $this->{$name} = $arguments; + } else { + $this->{$name} = $arguments[0] ?? null; + } + return $this; + } else { + throw new \RuntimeException('Method does not exist' . __CLASS__ . '->' . $name . '()'); + } + } +} diff --git a/niucloud/core/util/SqlUtil.php b/niucloud/core/util/SqlUtil.php new file mode 100644 index 000000000..9e0ee671b --- /dev/null +++ b/niucloud/core/util/SqlUtil.php @@ -0,0 +1,101 @@ +getSqlQuery($sql_data); + $query_count = count($sql_query); + for ($i = 0; $i < $query_count; $i++) { + $sql = trim($sql_query[ $i ]); + $is_write = false; + if (strstr($sql, 'CREATE TABLE')) { + $match_item = preg_match('/CREATE TABLE [`]?(\\w+)[`]?/is', $sql, $match_data); + $is_write = true; + } elseif (strstr($sql, 'ALTER TABLE')) { + $match_item = preg_match('/ALTER TABLE [`]?(\\w+)[`]?/is', $sql, $match_data); + + } elseif (strstr($sql, 'INSERT INTO')) { + $match_item = preg_match('/INSERT INTO [`]?(\\w+)[`]?/is', $sql, $match_data); + } else { + $match_item = 0; + } + if ($match_item > 0) { + try { + $table_name = $match_data[ 1 ]; + $new_table_name = $dbprefix . $table_name; + $sql_item = $this->strReplaceFirst($table_name, $new_table_name, $sql); + Db::execute($sql_item); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + } + } + + /** + * @param $sql_data + * @return array + */ + public function getSqlQuery($sql_data) + { + $sql_data = preg_replace("/TYPE=(InnoDB|MyISAM|MEMORY)( DEFAULT CHARSET=[^; ]+)?/", "ENGINE=\\1 DEFAULT CHARSET=utf8", $sql_data); + + $sql_data = str_replace("\r", "\n", $sql_data); + $sql_query = []; + $num = 0; + $sql_array = explode(";\n", trim($sql_data)); + unset($sql); + foreach ($sql_array as $sql) { + $sql_query[ $num ] = ''; + $sqls = explode("\n", trim($sql)); + $sqls = array_filter($sqls); + foreach ($sqls as $query) { + $str1 = substr($query, 0, 1); + if ($str1 != '#' && $str1 != '-') + $sql_query[ $num ] .= $query; + } + $num++; + } + return $sql_query; + } + + /** + * 代码切换 + * @param $search + * @param $replace + * @param $subject + * @return string + */ + public function strReplaceFirst($search, $replace, $subject) + { + return implode($replace, explode($search, $subject, 2)); + } + +} diff --git a/niucloud/core/util/Terminal.php b/niucloud/core/util/Terminal.php new file mode 100644 index 000000000..a91e67e20 --- /dev/null +++ b/niucloud/core/util/Terminal.php @@ -0,0 +1,66 @@ +error_path = runtime_path() . 'terminal'.DIRECTORY_SEPARATOR.date('Ym').DIRECTORY_SEPARATOR.date('d').'.log'; + } + public function execute(){ +// $command = []; + //可以使自定义指令也可以是 + $command = $this->command(); + + $cwd = $command['cwd'];//程序运行的目录 + +// proc_open($command['command'], $descriptorspec, $pipes, $cwd); + //通道消息不及时 + $descriptorspec = array( + 0 => array('pipe', 'r'), // 标准输入,子进程从此管道中读取数据 + 1 => array('pipe', 'w'), // 标准输出,子进程向此管道中写入数据 + 2 => array('file', $this->out_file, 'a') // 标准错误,写入到一个文件 + ); + //放在文件中是同步的,放在管道中可能是不及时的 +// $descriptorspec = [ +// 0 => ['pipe', 'r'], +// 1 => ['file', $this->out_file, 'w'], +// 2 => ['file', $this->out_file, 'w']]; +// $env_vars = array('some_option' => 'aeiou');//可以不启用其他的环境变量,使用和系统一致的环境变量 + $env_vars = null; + $process = proc_open($command['command'], $descriptorspec, $pipes, $cwd, $env_vars); + + if (is_resource($process)) { + // $pipes 现在看起来是这样的: + // 0 => 可以向子进程标准输入写入的句柄 + // 1 => 可以从子进程标准输出读取的句柄 + // 错误输出将被追加到文件 /tmp/error-output.txt + + fwrite($pipes[0], ''); + fclose($pipes[0]); + + echo stream_get_contents($pipes[1]); + fclose($pipes[1]); + + + // 切记:在调用 proc_close 之前关闭所有的管道以避免死锁。 + $return_value = proc_close($process); + + echo "command returned $return_value\n"; + } + } + + public function command($key){ + //通过健名获取详细的命令字典 + + return [ + 'command' => '', + 'cwd' => '', + ]; + } +} \ No newline at end of file diff --git a/niucloud/core/util/TokenAuth.php b/niucloud/core/util/TokenAuth.php new file mode 100644 index 000000000..43beebf48 --- /dev/null +++ b/niucloud/core/util/TokenAuth.php @@ -0,0 +1,107 @@ +request->host(); + $time = time(); + $params += [ + 'iss' => $host, + 'aud' => $host, + 'iat' => $time, + 'nbf' => $time, + 'exp' => $time + $expire_time, + ]; + + $params['jti'] = $id . "_" . $type; + $token = JWT::encode($params, Env::get('app.app_key', 'niushop456$%^')); + $cache_token = Cache::get("token_" . $params['jti']); + $cache_token_arr = $cache_token ?: []; +// if(!empty($cache_token)) +// { +// +// $cache_token_arr[] = $token; +// } + $cache_token_arr[] = $token; + Cache::tag("token")->set("token_" . $params['jti'], $cache_token_arr); + return compact('token', 'params'); + } + + /** + * 解析token + * @param string $token + * @param string $type + * @return array + */ + public static function parseToken(string $token, string $type): array + { + $payload = JWT::decode($token, Env::get('app.app_key', 'niushop456$%^'), ['HS256']); + if (!empty($payload)) { + $token_info = json_decode(json_encode($payload), true); + + if (explode("_", $token_info['jti'])[1] != $type) { + return []; + } + if (!empty($token_info) && !in_array($token, Cache::get('token_' . $token_info['jti'], []))) { + return []; + } + return $token_info; + } else { + return []; + } + } + + /** + * 清理token + * @param int $id + * @param string $type + */ + public static function clearToken(int $id, string $type, ?string $token = '') + { + if (!empty($token)) { + $token_cache = Cache::get("token_" . $id . "_" . $type, []); + //todo 也可以通过修改过期时间来实现 + if (!empty($token_cache)) { + if (($key = array_search($token, $token_cache)) !== false) { + array_splice($token_cache, $key, 1); + } + Cache::set("token_" . $id . "_" . $type, $token_cache); + } + } else { + Cache::set("token_" . $id . "_" . $type, []); + } + return success(); + } +}