chore: 添加静态资源文件和版本配置文件
添加大量图片、字体、音频等静态资源文件,包括用户界面图片、模板文件和安装界面素材 添加系统版本配置文件(.version)和常量定义文件(.constant) 更新供应商依赖文件,包括Symfony VarDumper组件和相关许可证文件 添加验证码配置文件(captcha.php)和.gitignore规则
This commit is contained in:
293
crmeb/services/erp/AccessToken.php
Normal file
293
crmeb/services/erp/AccessToken.php
Normal file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace crmeb\services\erp;
|
||||
|
||||
use crmeb\services\HttpService;
|
||||
use crmeb\services\CacheService;
|
||||
use think\facade\Config;
|
||||
use think\facade\Log;
|
||||
use think\helper\Str;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* Class AccessTokenServeService
|
||||
* @package crmeb\services
|
||||
*/
|
||||
class AccessToken extends HttpService
|
||||
{
|
||||
/**
|
||||
* 配置
|
||||
* @var string
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $secret;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $apiUrl;
|
||||
|
||||
/**
|
||||
* 授权登录账号
|
||||
* @var string
|
||||
*/
|
||||
protected $authAccount;
|
||||
|
||||
/**
|
||||
* 授权登录密码
|
||||
* @var string
|
||||
*/
|
||||
protected $authPassword;
|
||||
|
||||
/**
|
||||
* @var CacheInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/**
|
||||
* 驱动类型
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* 配置文件名
|
||||
* @var string
|
||||
*/
|
||||
protected $configFile;
|
||||
|
||||
/**
|
||||
* 缓存token
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheTokenPrefix = "_crmeb_erp";
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheRefreshTokenPrefix = "_crmeb_erp_re";
|
||||
|
||||
/**
|
||||
* AccessTokenServeService constructor.
|
||||
* AccessToken constructor.
|
||||
* @param string $name
|
||||
* @param string $configFile
|
||||
* @param array $config
|
||||
* @param CacheInterface|null $cache
|
||||
*/
|
||||
public function __construct(string $name, string $configFile, array $config, ?CacheInterface $cache = null)
|
||||
{
|
||||
if (!$cache) {
|
||||
/** @var CacheService $cache */
|
||||
$cache = app()->make(CacheService::class);
|
||||
}
|
||||
$this->account = isset($config["app_key"]) ? $config["app_key"] : config($configFile . '.stores.' . $name . '.app_key', '');
|
||||
$this->secret = isset($config["secret"]) ? $config["secret"] : config($configFile . '.stores.' . $name . '.secret', '');
|
||||
$this->authAccount = $config['login_account'] ?? config($configFile . '.stores.' . $name . '.login_account', '');
|
||||
$this->authPassword = $config['login_password'] ?? config($configFile . '.stores.' . $name . '.login_password', '');
|
||||
$this->cache = $cache;
|
||||
$this->name = $name;
|
||||
$this->configFile = $configFile;
|
||||
$this->apiUrl = Config::get($configFile . '.stores.' . $name . '.url', '');
|
||||
$this->apiUrl = 'https://openapi.jushuitan.com';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return [
|
||||
'account' => $this->account,
|
||||
'secret' => $this->secret
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求链接
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public function getApiUrl(string $url = ''): string
|
||||
{
|
||||
return $url ? $this->apiUrl . $url : $this->apiUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取appKey
|
||||
* @return string
|
||||
*/
|
||||
public function getAccount(): string
|
||||
{
|
||||
return $this->account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getAuthAccount()
|
||||
{
|
||||
return $this->authAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getAuthPassword()
|
||||
{
|
||||
return $this->authPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取appSecret
|
||||
* @return string
|
||||
*/
|
||||
public function getSecret(): string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAccessToken(): ?string
|
||||
{
|
||||
if (isset($this->accessToken)) {
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getJushuitanAccessToken
|
||||
*/
|
||||
$action = 'get' . Str::studly($this->name) . 'AccessToken';
|
||||
if (method_exists($this, $action)) {
|
||||
return $this->{$action}();
|
||||
} else {
|
||||
throw new \RuntimeException(__CLASS__ . '->' . $action . '(),Method not worn in');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聚水潭token
|
||||
* @return mixed|null|string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getJushuitanAccessToken(): ?string
|
||||
{
|
||||
//读缓存
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . $this->cacheTokenPrefix);
|
||||
$this->accessToken = $this->cache->get($cacheKey);
|
||||
|
||||
//需要前端异步授权
|
||||
if (empty($this->accessToken)) {
|
||||
throw new \RuntimeException("请跳转授权", 610);
|
||||
}
|
||||
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置AccessToken缓存
|
||||
* @param string $accessToken
|
||||
* @param int $expiresIn
|
||||
* @return bool
|
||||
*/
|
||||
public function setAccessToken(string $accessToken, int $expiresIn): bool
|
||||
{
|
||||
if (empty($accessToken) || !is_numeric($expiresIn) || $expiresIn <= 0) {
|
||||
return false;
|
||||
}
|
||||
//写缓存
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . $this->cacheTokenPrefix);
|
||||
$this->cache->redisHandler()->tag('erp_shop')->set($cacheKey, $accessToken, $expiresIn);
|
||||
|
||||
$this->accessToken = $accessToken;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置AccessToken提前过期缓存
|
||||
* @param string $accessToken
|
||||
* @param int $expiresIn
|
||||
* @return bool
|
||||
*/
|
||||
public function setTokenExpire(string $accessToken, int $expiresIn): bool
|
||||
{
|
||||
if (empty($accessToken) || !is_numeric($expiresIn) || $expiresIn <= 0) {
|
||||
return false;
|
||||
}
|
||||
//写缓存
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . '_epr_expire');
|
||||
$this->cache->redisHandler()->tag('erp_shop')->set($cacheKey, $accessToken, $expiresIn);
|
||||
|
||||
$this->accessToken = $accessToken;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提前过期缓存
|
||||
* @return bool|mixed|null
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function getTokenExpire()
|
||||
{
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . '_epr_expire');
|
||||
return $this->cache->get($cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置refreshToken缓存
|
||||
* @param string refreshToken
|
||||
* @return bool
|
||||
*/
|
||||
public function setRefreshToken(string $refreshToken): bool
|
||||
{
|
||||
if (empty($refreshToken)) {
|
||||
return false;
|
||||
}
|
||||
//写缓存
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . $this->cacheRefreshTokenPrefix);
|
||||
$this->cache->redisHandler()->tag('erp_shop')->set($cacheKey, $refreshToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取refreshToken缓存
|
||||
* @param string refreshToken
|
||||
* @return string
|
||||
*/
|
||||
public function getRefreshToken(): string
|
||||
{
|
||||
//读缓存
|
||||
$cacheKey = md5($this->account . '_' . $this->secret . $this->cacheRefreshTokenPrefix);
|
||||
|
||||
return $this->cache->get($cacheKey);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
75
crmeb/services/erp/Erp.php
Normal file
75
crmeb/services/erp/Erp.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace crmeb\services\erp;
|
||||
|
||||
use crmeb\basic\BaseManager;
|
||||
use crmeb\services\erp\storage\Jushuitan;
|
||||
use think\Container;
|
||||
use think\facade\Config;
|
||||
|
||||
|
||||
/**
|
||||
* Class Erp
|
||||
* @package crmeb\services\erp
|
||||
* @mixin Jushuitan
|
||||
*/
|
||||
class Erp extends BaseManager
|
||||
{
|
||||
|
||||
/**
|
||||
* 空间名
|
||||
* @var string
|
||||
*/
|
||||
protected string $namespace = '\\crmeb\\services\\erp\\storage\\';
|
||||
|
||||
protected $type = [
|
||||
'nothing',
|
||||
'jushuitan',
|
||||
];
|
||||
|
||||
/**
|
||||
* 默认驱动
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getDefaultDriver()
|
||||
{
|
||||
$this->config = [
|
||||
'app_key' => sys_config('jst_appkey'),
|
||||
'secret' => sys_config('jst_appsecret'),
|
||||
'login_account' => sys_config('jst_login_account'),
|
||||
'login_password' => sys_config('jst_login_password'),
|
||||
];
|
||||
return $this->type[sys_config('erp_type')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类的实例
|
||||
* @param $class
|
||||
* @return mixed|void
|
||||
*/
|
||||
protected function invokeClass($class)
|
||||
{
|
||||
if (!class_exists($class)) {
|
||||
throw new \RuntimeException('class not exists: ' . $class);
|
||||
}
|
||||
$this->getConfigFile();
|
||||
|
||||
if (!$this->config) {
|
||||
$this->config = Config::get($this->configFile . '.stores.' . $this->name, []);
|
||||
}
|
||||
$handleAccessToken = new AccessToken($this->name, $this->configFile, $this->config);
|
||||
$handle = Container::getInstance()->invokeClass($class, [$this->name, $handleAccessToken, $this->configFile]);
|
||||
$this->config = [];
|
||||
|
||||
return $handle;
|
||||
}
|
||||
}
|
||||
460
crmeb/services/erp/storage/Jushuitan.php
Normal file
460
crmeb/services/erp/storage/Jushuitan.php
Normal file
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\erp\storage;
|
||||
|
||||
use crmeb\basic\BaseErp;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\exceptions\ErpException;
|
||||
use crmeb\services\erp\storage\jushuitan\Comment;
|
||||
use crmeb\services\erp\storage\jushuitan\Order;
|
||||
use crmeb\services\erp\storage\jushuitan\Product;
|
||||
use crmeb\services\erp\storage\jushuitan\Stock;
|
||||
use EasyWeChat\Kernel\Support\Str;
|
||||
use think\Collection;
|
||||
use think\facade\Cache;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* Class Jushuitan
|
||||
* @package crmeb\services\erp\storage
|
||||
*/
|
||||
class Jushuitan extends BaseErp
|
||||
{
|
||||
|
||||
//==================商家授权==================
|
||||
|
||||
/**
|
||||
* 获取授权参数
|
||||
* @param string $state 透传数据
|
||||
* @return array
|
||||
*/
|
||||
public function getAuthParams($state = ""): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
//开发者应用Key
|
||||
$params["app_key"] = $this->accessToken->getAccount();
|
||||
|
||||
//当前请求的时间戳【单位是秒】
|
||||
$params["timestamp"] = time();
|
||||
|
||||
//透传数据 非必填
|
||||
$params["state"] = $state;
|
||||
|
||||
//交互数据的编码【utf-8】目前只能传utf-8,不能不传!
|
||||
$params["charset"] = "utf-8";
|
||||
|
||||
//签名
|
||||
$params["sign"] = $this->sign($params);
|
||||
|
||||
//授权跳转地址
|
||||
$params["url"] = "https://openweb.jushuitan.com/auth";
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AccessToken 用于验证授权回调是否成功
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getAccessToken(): string
|
||||
{
|
||||
return $this->accessToken->getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置AccessToken
|
||||
* @param $at
|
||||
* @return string
|
||||
*/
|
||||
public function setAccessToken($at): string
|
||||
{
|
||||
return $this->accessToken->setAccessToken($at, 999999);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台授权回调
|
||||
* @return Response
|
||||
*/
|
||||
public function authCallback(): Response
|
||||
{
|
||||
$params = request()->get();
|
||||
//验证必要参数 返回失败
|
||||
if (!isset($params["app_key"]) || !isset($params["code"]) || !isset($params["sign"])) {
|
||||
return response(["code" => 504]);
|
||||
}
|
||||
$appKey = $params["app_key"];
|
||||
$code = $params["code"]; //授权码,有效期为15分钟
|
||||
$sign = $params["sign"];
|
||||
$state = isset($params["state"]) ? $params["state"] : ""; //透传数据
|
||||
|
||||
//appKey是否匹配 不匹配返回成功-抛弃消息
|
||||
if ($appKey !== $this->accessToken->getAccount()) {
|
||||
return response(["code" => 0, "msg" => "appKey不匹配"]);
|
||||
}
|
||||
|
||||
//sign验证失败 返回失败
|
||||
if ($sign !== $this->sign($params)) {
|
||||
return response(["code" => 505, "msg" => "签名错误"]);
|
||||
}
|
||||
|
||||
//code换access_token
|
||||
$request = $this->code2accessToken($code);
|
||||
|
||||
//缓存token
|
||||
$this->accessToken->setAccessToken($request["access_token"], $request["expires_in"]);
|
||||
//token提前过期时间
|
||||
$this->accessToken->setTokenExpire($request["access_token"], $request["expires_in"] - (48 * 60 * 60));
|
||||
//缓存刷新token
|
||||
$this->accessToken->setRefreshToken($request["refresh_token"]);
|
||||
|
||||
return response(["code" => 0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|mixed|null
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
*/
|
||||
public function getTokenExpire()
|
||||
{
|
||||
return $this->accessToken->getTokenExpire();
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权临时code换access_token
|
||||
* @param $code
|
||||
* @return array
|
||||
*/
|
||||
public function code2accessToken($code): array
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/openWeb/auth/accessToken");
|
||||
|
||||
//请求参数
|
||||
$params = [];
|
||||
|
||||
//开发者应用Key
|
||||
$params["app_key"] = $this->accessToken->getAccount();
|
||||
|
||||
//当前请求的时间戳【单位是秒】
|
||||
$params["timestamp"] = time();
|
||||
|
||||
//固定值:authorization_code
|
||||
$params["grant_type"] = "authorization_code";
|
||||
|
||||
//交互数据的编码【utf-8】目前只能传utf-8,不能不传!
|
||||
$params["charset"] = "utf-8";
|
||||
|
||||
//授权码
|
||||
$params["code"] = $code;
|
||||
|
||||
//签名
|
||||
$params["sign"] = $this->sign($params);
|
||||
|
||||
try {
|
||||
$request = $this->accessToken::postRequest($url, $params);
|
||||
} catch (\Exception $e) {
|
||||
throw new AdminException($e->getMessage());
|
||||
}
|
||||
//处理平台响应异常
|
||||
$this->checkRequestError($request);
|
||||
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $content
|
||||
* @return Collection
|
||||
*/
|
||||
protected function json($content)
|
||||
{
|
||||
if (false === $content) {
|
||||
return collect();
|
||||
}
|
||||
$data = json_decode($content, true);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new ErpException(sprintf('Failed to parse JSON: %s', json_last_error_msg()));
|
||||
}
|
||||
|
||||
return collect($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过接口方式授权登录聚水潭
|
||||
* @param string $account
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public function authLogin(string $account = null, string $password = null)
|
||||
{
|
||||
if (Cache::has('erp_login_count') && Cache::get('erp_login_count') > 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authParams = $this->getAuthParams();
|
||||
|
||||
$loginInfo = $this->accessToken->postRequest('https://api.jushuitan.com/erp/webapi/UserApi/WebLogin/Passport', json_encode([
|
||||
'ipAddress' => '',
|
||||
'uid' => '',
|
||||
'data' => [
|
||||
'account' => $account ?: $this->accessToken->getAuthAccount(),
|
||||
'j_d_3' => '',
|
||||
'password' => $password ?: $this->accessToken->getAuthPassword(),
|
||||
'v_d_144' => ''
|
||||
],
|
||||
]), ['Content-Type:application/json; charset=utf-8']);
|
||||
|
||||
$loginInfo = $this->json($loginInfo);
|
||||
|
||||
if ($loginInfo['code'] === null || $loginInfo['code'] !== 0) {
|
||||
|
||||
if ($loginInfo['code'] === 10001) {
|
||||
$erpLoginCount = 0;
|
||||
if (Cache::has('erp_login_count')) {
|
||||
$erpLoginCount = Cache::get('erp_login_count', 0);
|
||||
$erpLoginCount++;
|
||||
}
|
||||
Cache::set('erp_login_count', $erpLoginCount, 60);
|
||||
}
|
||||
throw new ErpException($loginInfo['msg'] ?? '登录失败');
|
||||
}
|
||||
|
||||
$cookie = $loginInfo['cookie'];
|
||||
$cookieData = [];
|
||||
foreach ($cookie as $k => $v) {
|
||||
$cookieData[] = $k . '=' . $v;
|
||||
}
|
||||
|
||||
$res = $this->accessToken->postRequest('https://api.jushuitan.com/openWeb/auth/oauthAction', json_encode([
|
||||
'uid' => '',
|
||||
'data' => [
|
||||
'app_key' => $authParams['app_key'],
|
||||
'charset' => $authParams['charset'],
|
||||
'sign' => $authParams['sign'],
|
||||
'state' => '',
|
||||
'timestamp' => $authParams['timestamp'],
|
||||
],
|
||||
]), [
|
||||
'Content-Type:application/json',
|
||||
'Cookie:' . implode(';', $cookieData),
|
||||
]);
|
||||
|
||||
$res = $this->json($res);
|
||||
|
||||
if (!$res) {
|
||||
throw new ErpException('请求失败');
|
||||
}
|
||||
if ($res['code'] != 0) {
|
||||
throw new ErpException($res['msg'] ?? '授权失败');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新access_token
|
||||
* @return bool
|
||||
*/
|
||||
public function refreshToken(): bool
|
||||
{
|
||||
$refreshToken = $this->accessToken->getRefreshToken();
|
||||
if (empty($refreshToken)) {
|
||||
throw new AdminException("请跳转授权手动授权", 610);
|
||||
}
|
||||
|
||||
$url = $this->accessToken->getApiUrl("/openWeb/auth/refreshToken");
|
||||
|
||||
//请求参数
|
||||
$params = [];
|
||||
|
||||
//开发者应用Key
|
||||
$params["app_key"] = $this->accessToken->getAccount();
|
||||
|
||||
//当前请求的时间戳【单位是秒】
|
||||
$params["timestamp"] = time();
|
||||
|
||||
//固定值:refresh_token
|
||||
$params["grant_type"] = "refresh_token";
|
||||
|
||||
//交互数据的编码【utf-8】目前只能传utf-8,不能不传!
|
||||
$params["charset"] = "utf-8";
|
||||
|
||||
//更新令牌
|
||||
$params["refresh_token"] = $refreshToken;
|
||||
|
||||
//固定值:all
|
||||
$params["scope"] = "all";
|
||||
|
||||
//签名
|
||||
$params["sign"] = $this->sign($params);
|
||||
|
||||
try {
|
||||
$request = $this->accessToken::postRequest($url, $params);
|
||||
} catch (\Exception $e) {
|
||||
throw new AdminException($e->getMessage());
|
||||
}
|
||||
|
||||
//处理平台响应异常
|
||||
$this->checkRequestError($request);
|
||||
|
||||
//缓存token
|
||||
$this->accessToken->setAccessToken($request["access_token"], $request["expires_in"]);
|
||||
//token提前过期时间
|
||||
$this->accessToken->setTokenExpire($request["access_token"], $request["expires_in"] - (48 * 60 * 60));
|
||||
//缓存刷新token
|
||||
$this->accessToken->setRefreshToken($request["refresh_token"]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//==================内部方法==================
|
||||
|
||||
/**
|
||||
* 发送post请求并处理异常
|
||||
* @param $url
|
||||
* @param $params
|
||||
* @return mixed
|
||||
*/
|
||||
public function postRequest($url, $params)
|
||||
{
|
||||
//请求平台接口
|
||||
$request = $this->accessToken->postRequest($url, $params);
|
||||
$this->checkRequestError($request);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测平台响应异常 并将响应转换为数组
|
||||
* @param $request
|
||||
*/
|
||||
protected function checkRequestError(&$request)
|
||||
{
|
||||
if ($request === false || empty($request)) {
|
||||
throw new AdminException('平台请求失败,请稍后重试');
|
||||
}
|
||||
$request = is_string($request) ? json_decode($request, true) : $request;
|
||||
if (empty($request) || !isset($request['code'])) {
|
||||
throw new AdminException('平台请求失败,请稍后重试!');
|
||||
}
|
||||
if (intval($request['code']) === 100) {
|
||||
throw new AdminException("请重新授权", 610);
|
||||
}
|
||||
if ($request['code'] != 0) {
|
||||
throw new AdminException(isset($request['msg']) ? '平台错误:' . $request['msg'] : '平台错误:发生异常,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼装请求参数
|
||||
* @param array $biz
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getParams(array $biz = []): array
|
||||
{
|
||||
//请求参数
|
||||
$params = [];
|
||||
|
||||
$accessToken = null;
|
||||
try {
|
||||
$accessToken = $this->accessToken->getAccessToken();
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
//刷新token
|
||||
if (!$accessToken) {
|
||||
$this->refreshToken();
|
||||
$accessToken = $this->accessToken->getAccessToken();
|
||||
}
|
||||
|
||||
if (!$accessToken) {
|
||||
throw new ErpException('缺少access_token,请手动登录聚水潭开放平台进行授权登录');
|
||||
}
|
||||
|
||||
//商户授权token值
|
||||
$params["access_token"] = $accessToken;
|
||||
|
||||
//开发者应用Key
|
||||
$params["app_key"] = $this->accessToken->getAccount();
|
||||
|
||||
//当前请求的时间戳【单位是秒】
|
||||
$params["timestamp"] = time();
|
||||
|
||||
//接口版本,当前版本为【2】,目前只能传2,不能不传!
|
||||
$params["version"] = "2";
|
||||
|
||||
//交互数据的编码【utf-8】目前只能传utf-8,不能不传!
|
||||
$params["charset"] = "utf-8";
|
||||
|
||||
//业务请求参数,格式为jsonString
|
||||
if (empty($biz)) {
|
||||
$biz = new \ArrayObject();
|
||||
}
|
||||
$params["biz"] = json_encode($biz, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
//签名
|
||||
$params["sign"] = $this->sign($params);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算签名
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
protected function sign(array $params): string
|
||||
{
|
||||
if (empty($params)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
//1.将请求参数中除 sign 外的多个键值对,根据键按照字典序排序
|
||||
ksort($params);
|
||||
|
||||
//按照 "key1value1key2value2..." 的格式拼成一个字符串。
|
||||
$str = "";
|
||||
foreach ($params as $k => $v) {
|
||||
if ($k == null || $k == "" || $k == "sign" || $v == "") {
|
||||
continue;
|
||||
}
|
||||
if (is_array($v) || is_object($v)) {
|
||||
$v = json_encode($v, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$str .= $k . $v;
|
||||
}
|
||||
|
||||
//2.将 app_secret 拼接在 1 中排序后的字符串前面得到待签名字符串
|
||||
$str = $this->accessToken->getSecret() . $str;
|
||||
|
||||
//3.使用 MD5 算法加密待加密字符串并转为小写
|
||||
return bin2hex(md5($str, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return Stock|Order|Product|Comment
|
||||
*/
|
||||
public function serviceDriver(string $type = '')
|
||||
{
|
||||
$namespace = '\\crmeb\\services\\erp\\storage\\jushuitan\\';
|
||||
$class = strpos($type, '\\') ? $type : $namespace . Str::studly($type);
|
||||
if (!class_exists($class)) {
|
||||
throw new \RuntimeException('class not exists: ' . $class);
|
||||
}
|
||||
|
||||
return \think\Container::getInstance()->invokeClass($class, [$this->accessToken, $this]);
|
||||
}
|
||||
}
|
||||
51
crmeb/services/erp/storage/jushuitan/Comment.php
Normal file
51
crmeb/services/erp/storage/jushuitan/Comment.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace crmeb\services\erp\storage\jushuitan;
|
||||
|
||||
|
||||
use crmeb\services\erp\AccessToken;
|
||||
use crmeb\services\erp\storage\Jushuitan;
|
||||
|
||||
/**
|
||||
* 公用接口
|
||||
* Class Comment
|
||||
* @package crmeb\services\erp\storage\jushuitan
|
||||
*/
|
||||
class Comment
|
||||
{
|
||||
/**
|
||||
* token句柄
|
||||
* @var AccessToken
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/*** @var Jushuitan */
|
||||
protected $jushuitan;
|
||||
|
||||
/**
|
||||
* Comment constructor.
|
||||
* @param AccessToken $accessToken
|
||||
* @param Jushuitan $jushuitan
|
||||
*/
|
||||
public function __construct(AccessToken $accessToken, Jushuitan $jushuitan)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
$this->jushuitan = $jushuitan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商铺列表
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getShopList(int $page = 1, int $limit = 10)
|
||||
{
|
||||
$api = $this->accessToken->getApiUrl('/open/shops/query');
|
||||
$biz['items'] = ['page_index' => $page, 'page_size' => $limit];
|
||||
$params = $this->jushuitan->getParams($biz);
|
||||
return $this->jushuitan->postRequest($api, $params);
|
||||
}
|
||||
}
|
||||
151
crmeb/services/erp/storage/jushuitan/Order.php
Normal file
151
crmeb/services/erp/storage/jushuitan/Order.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace crmeb\services\erp\storage\jushuitan;
|
||||
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\services\erp\AccessToken;
|
||||
use crmeb\services\erp\storage\Jushuitan;
|
||||
use Exception;
|
||||
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* token句柄
|
||||
* @var AccessToken
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/*** @var Jushuitan */
|
||||
protected $jushuitan;
|
||||
|
||||
/**
|
||||
* @param Jushuitan $jushuitan
|
||||
*/
|
||||
public function __construct(AccessToken $accessToken, Jushuitan $jushuitan)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
$this->jushuitan = $jushuitan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单上传(推荐)
|
||||
* @param array $data
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ordersUpload(array $data): array
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/jushuitan/orders/upload");
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->jushuitan->getParams($data);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->jushuitan->postRequest($url, $params);
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单查询
|
||||
* @param array $data
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ordersSingleQuery(array $data): array
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/orders/single/query");
|
||||
|
||||
//业务参数
|
||||
$biz = [];
|
||||
|
||||
if (isset($data["page_index"])) {
|
||||
//int 第几页,从第一页开始,默认1
|
||||
$biz["page_index"] = intval($data["page_index"]);
|
||||
}
|
||||
if (isset($data["page_size"])) {
|
||||
//int 每页多少条;默认30条,最大50条
|
||||
$biz["page_size"] = intval($data["page_size"]);
|
||||
}
|
||||
if (isset($data["modified_begin"])) {
|
||||
if (empty($data["modified_end"])) {
|
||||
throw new AdminException("起始和结束时间必须同时存在");
|
||||
}
|
||||
//string日志起始时间,起始时间和 结束时间必须同时存在,时间间隔不能超过七天
|
||||
$biz["modified_begin"] = strval($data["modified_begin"]);
|
||||
$biz["modified_end"] = strval($data["modified_end"]);
|
||||
}
|
||||
///list 线上单号号,最大限制20条
|
||||
if (isset($data["so_ids"])) {
|
||||
$biz["so_ids"] = $data["so_ids"];
|
||||
} elseif (empty($biz["modified_begin"]) || empty($biz["modified_end"])) {
|
||||
//线上单号与修改时间不能同时为空
|
||||
throw new AdminException("线上单号,与修改时间不能同时为空");
|
||||
}
|
||||
//店铺编号
|
||||
if (!empty($data["shop_id"])) {
|
||||
$biz["shop_id"] = $data["shop_id"];
|
||||
}
|
||||
//shop_id为0且is_offline_shop为true查询线下店铺单据 非必填 bool
|
||||
if (isset($data["is_offline_shop"]) && !is_null($data["is_offline_shop"])) {
|
||||
$biz["is_offline_shop"] = $data["is_offline_shop"];
|
||||
}
|
||||
|
||||
if (!empty($data["status"])) {
|
||||
$biz["status"] = $data["status"];
|
||||
}
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->jushuitan->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->jushuitan->postRequest($url, $params);
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单取消-按内部单号取消
|
||||
* @param array $data
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function orderByOIdCancel(array $data): bool
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/jushuitan/orderbyoid/cancel");
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->jushuitan->getParams($data);
|
||||
|
||||
//请求平台接口
|
||||
$this->jushuitan->postRequest($url, $params);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际收货上传
|
||||
* @param array $list
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function afterSaleUpload(array $list): array
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/aftersale/upload");
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->jushuitan->getParams($list);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->jushuitan->postRequest($url, $params);
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
}
|
||||
127
crmeb/services/erp/storage/jushuitan/Product.php
Normal file
127
crmeb/services/erp/storage/jushuitan/Product.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace crmeb\services\erp\storage\jushuitan;
|
||||
|
||||
use crmeb\services\erp\AccessToken;
|
||||
use crmeb\services\erp\storage\Jushuitan;
|
||||
|
||||
class Product extends Jushuitan
|
||||
{
|
||||
/**
|
||||
* token句柄
|
||||
* @var AccessToken
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/*** @var Jushuitan */
|
||||
protected $jushuitan;
|
||||
|
||||
/**
|
||||
* @param AccessToken $accessToken
|
||||
* @param Jushuitan $jushuitan
|
||||
*/
|
||||
public function __construct(AccessToken $accessToken, Jushuitan $jushuitan)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
$this->jushuitan = $jushuitan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传商品
|
||||
* @param $data
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateProduct($data)
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/jushuitan/itemsku/upload");
|
||||
|
||||
//业务参数
|
||||
$biz['items'] = $data;
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->postRequest($url, $params);
|
||||
|
||||
if ($request['code'] == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传店铺商品
|
||||
* @param $data
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateShopProduct($data)
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/jushuitan/skumap/upload");
|
||||
|
||||
//业务参数
|
||||
$biz['items'] = $data;
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->postRequest($url, $params);
|
||||
|
||||
if ($request['code'] == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 同步商品
|
||||
* @param $spuArr
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function syncProduct($spuArr)
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/mall/item/query");
|
||||
|
||||
//业务参数
|
||||
$biz['i_ids'] = $spuArr;
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->postRequest($url, $params);
|
||||
|
||||
//获取ERP商品信息
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
* @param string $codeStr
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function syncStock(string $codeStr)
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/inventory/query");
|
||||
|
||||
//业务参数
|
||||
$biz = [];
|
||||
|
||||
$biz["sku_ids"] = $codeStr;
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->postRequest($url, $params);
|
||||
return $request["data"];
|
||||
}
|
||||
}
|
||||
151
crmeb/services/erp/storage/jushuitan/Stock.php
Normal file
151
crmeb/services/erp/storage/jushuitan/Stock.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace crmeb\services\erp\storage\jushuitan;
|
||||
|
||||
use app\services\product\product\StoreProductServices;
|
||||
use app\services\product\sku\StoreProductAttrValueServices;
|
||||
use crmeb\exceptions\AdminException;
|
||||
use crmeb\services\erp\AccessToken;
|
||||
use crmeb\services\erp\storage\Jushuitan;
|
||||
|
||||
class Stock
|
||||
{
|
||||
/**
|
||||
* token句柄
|
||||
* @var AccessToken
|
||||
*/
|
||||
protected $accessToken;
|
||||
|
||||
/*** @var Jushuitan */
|
||||
protected $jushuitan;
|
||||
|
||||
/**
|
||||
* @param AccessToken $accessToken
|
||||
* @param Jushuitan $jushuitan
|
||||
*/
|
||||
public function __construct(AccessToken $accessToken, Jushuitan $jushuitan)
|
||||
{
|
||||
$this->accessToken = $accessToken;
|
||||
$this->jushuitan = $jushuitan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步商品库存
|
||||
* @param array $ids
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function syncStock(string $ids = '')
|
||||
{
|
||||
/** @var StoreProductServices $storeProductServices */
|
||||
$storeProductServices = app()->make(StoreProductServices::class);
|
||||
|
||||
/** @var StoreProductAttrValueServices $storeProductAttrValueServices */
|
||||
$storeProductAttrValueServices = app()->make(StoreProductAttrValueServices::class);
|
||||
|
||||
//查询ids下的所有规格对应的sku
|
||||
$ids = array_unique(array_map('intval', explode(',', $ids)));
|
||||
$list = $storeProductAttrValueServices->getSkuArray(['product_id' => $ids, 'type' => 0], 'code', 'id');
|
||||
|
||||
$values = array_filter(array_values($list));
|
||||
if (empty($values)) {
|
||||
throw new AdminException('没有符合同步库存的商品');
|
||||
}
|
||||
|
||||
$skuData = $skuMap = [];
|
||||
|
||||
$basic = 20; // 单次查询数量最多20
|
||||
$num = count($values);
|
||||
$rate = ceil($num / $basic);
|
||||
for ($i = 0; $i < $rate; $i++) {
|
||||
$code = array_slice($values, $i * $basic, $basic);
|
||||
$skuMap = $skuMap + $this->getSkuStockByCode($code);
|
||||
}
|
||||
|
||||
// 拼装规格数据
|
||||
if (!empty($skuMap)) {
|
||||
foreach ($skuMap as $key => $item) {
|
||||
if ($id = array_search($key, $list)) {
|
||||
$skuData[] = ['id' => $id, 'stock' => $item, 'sum_stock' => $item];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 同步库存 TODO:待添加至队列
|
||||
$storeProductServices->transaction(function () use ($ids, $skuData, $skuMap, $storeProductAttrValueServices, $storeProductServices) {
|
||||
// 同步规格库存
|
||||
$storeProductAttrValueServices->saveAll($skuData);
|
||||
// 同步商品库存
|
||||
$productData = $storeProductAttrValueServices->getProductStockByValues($ids);
|
||||
$storeProductServices->saveAll($productData);
|
||||
|
||||
//同步门店库存
|
||||
foreach ($skuMap as $item) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 库存查询
|
||||
* @param array $data
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function inventoryQuery(string $codeStr): array
|
||||
{
|
||||
$url = $this->accessToken->getApiUrl("/open/inventory/query");
|
||||
|
||||
//业务参数
|
||||
$biz = [];
|
||||
|
||||
$biz["sku_ids"] = $codeStr;
|
||||
|
||||
//拼装请求参数
|
||||
$params = $this->getParams($biz);
|
||||
|
||||
//请求平台接口
|
||||
$request = $this->postRequest($url, $params);
|
||||
return $request["data"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取erp库存
|
||||
* @param array $code
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getSkuStockByCode(array $code = []): array
|
||||
{
|
||||
$skuMap = [];
|
||||
$codeStr = implode(',', $code);
|
||||
$result = $this->inventoryQuery($codeStr);
|
||||
if (!empty($result['inventorys'])) {
|
||||
foreach ($result['inventorys'] as $inventory) {
|
||||
$skuMap[$inventory['sku_id']] = $inventory['qty'];
|
||||
}
|
||||
}
|
||||
return $skuMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return call_user_func_array([$this->jushuitan, $name], $arguments);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user