Zory 4 days ago
parent
commit
f1895ec026

+ 18 - 0
app/controller/admin/Config.php

@@ -3,6 +3,7 @@
 namespace app\controller\admin;
 
 use app\extra\basic\Base;
+use app\extra\tools\OssRegionExtend;
 use app\middleware\AuthMiddleware;
 use app\model\system\SystemConfig;
 use LinFly\Annotation\Route\Controller;
@@ -60,7 +61,24 @@ class Config extends Base
         } catch (\Throwable $exception){
             return error($exception->getMessage());
         }
+    }
 
+    /**
+     * 获取地区列表
+     * @return Response
+     */
+    #[Route(path: "regin",methods: "get")]
+    public function getConfigRegin(): Response
+    {
+        try {
+            return successTrans(100010,[
+                "oss"       => OssRegionExtend::OssRegion(),
+                "cos"       => OssRegionExtend::CosRegion(),
+                "qiniu"     => OssRegionExtend::QiniuRegion(),
+            ]);
+        } catch (\Exception $exception) {
+            return error($exception->getMessage());
+        }
     }
 
 }

+ 109 - 0
app/controller/api/Home.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace app\controller\api;
+
+use app\extra\basic\Base;
+use app\extra\tools\CodeExtend;
+use app\extra\weMini\Crypt;
+use app\middleware\WxMiddleware;
+use app\model\saas\SaasShop;
+use app\model\saas\SaasUser;
+use app\model\saas\SaasUserOpen;
+use LinFly\Annotation\Route\Controller;
+use LinFly\Annotation\Route\Route;
+use support\Request;
+use support\Response;
+use Webman\Annotation\Middleware;
+use Shopwwi\WebmanAuth\Facade\Auth as AuthMode;
+
+
+#[Controller(prefix: "/wx_api/default"),Middleware(WxMiddleware::class)]
+class Home extends Base
+{
+
+    /**
+     * 跳出授权
+     * @var array|string[]
+     */
+    protected array $noNeedLogin = ['loginHome'];
+
+    /**
+     * 首页信息
+     * @return Response
+     */
+    #[Route(path: "home",methods: "post")]
+    public function loginHome(Request $request)
+    {
+        try {
+            $param = $this->_valid([
+                "shop.require"  => "",
+                "print.require" => "",
+                "code.require"  => "",
+                "card.default"  => ""
+            ],$request->method());
+            if (!is_array($param)) return error($param);
+            $userinfo = (new Crypt([
+                "appid"     => sConf("wechat.mini_appid"),
+                "appsecret" => sConf("wechat.mini_secret")
+            ]))->session($param['code']);
+            if (!isset($userinfo['openid'])) return error("获取数据失败");
+            $map = ['is_deleted' => 0, 'openid' => $userinfo['openid']];
+            $user = (new SaasUserOpen)->where($map)->findOrEmpty();
+            if ($user->isEmpty()) {
+                $user->insertGetId(['openid' => $userinfo['openid'],"create_ip" => $request->getRealIp(),"nickname" => "微信用户","headimg" => "https://inmei-print.oss-cn-guangzhou.aliyuncs.com/logo.png"]);
+                $user = (new SaasUserOpen)->where($map)->findOrEmpty();
+            }
+            $userAuth = get_object_vars(AuthMode::guard("member")->login($user->toArray()));
+            $shopData = [];
+            $cardState = 0;
+            if (!empty($param['shop']))
+            {
+                $shop = (new SaasShop)->where("shop_id", $param['shop'])->field("shop_name,start_at,end_at,vip_end,shop_status,shop_notice,line_time,status,shop_address")->findOrEmpty();
+                if ($shop->isEmpty()) return error("店铺已被关闭");
+                $currentTime = time();
+                $vipEndTime = strtotime($shop['vip_end'] ?? '');
+                $lineEndTime = strtotime("+10020000 min", strtotime($shop['line_time'] ?? ''));
+                $today = date('Y-m-d');
+                $startTime = strtotime("{$today} {$shop['start_at']}");
+                $endTime = strtotime("{$today} {$shop['end_at']}");
+                if ($currentTime > $vipEndTime){
+                    $status = ['text' => '已过期', 'val' => 0];
+                } else if ($shop['status'] == 2) {
+                    $status = ['text' => '已冻结', 'val' => 0];
+                } else if ($currentTime > $lineEndTime) {
+                    $status = ['text' => '已离线', 'val' => 0];
+                } else if ($currentTime > $endTime || $currentTime < $startTime) // 过了营业时间
+                {
+                    $status = ['text' => '休息中', 'val' => 0];
+                } else {
+                    $status = ['text' => '营业中', 'val' => 1];
+                }
+                $shop['status_text'] = $status['text'];
+                $shop['status_val'] = $status['val'];
+                $shop['time'] = ($shop['start_at'] == '00:00' && $shop['end_at'] == '23:59')
+                    ? '24小时营业'
+                    : date('H:i', $startTime) . '-' . date('H:i', $endTime);
+                if ($param['card'] == 1) { // 领取会员卡
+                    $cardInfo = (new SaasUser)->where("shop_id",$param['shop'])->where("openid",$userinfo['openid'])->findOrEmpty();
+                    if ($cardInfo->isEmpty()) {
+                        $cardInfo->insertGetId([
+                            "shop_id"   => $param['shop'],
+                            "openid"    => $userinfo['openid'],
+                            "card_no"   => CodeExtend::uniqidDate(16,"1088")
+                        ]);
+                        $cardState = 1;
+                    }
+                }
+                $shopData = $shop->toArray();
+            }
+            $share = sConf("wechat.share");
+            return success("ok",["shop" => $shopData,'user' => ['nickname' => $user['nickname']??'','avatar' => $user['avatar']??''],"token" => $userAuth,"card" => $cardState,"share" => $share]);
+        }catch (\Throwable $e){
+            echo $e->getMessage()."\n";
+            echo $e->getFile()."\n";
+            echo $e->getLine()."\n";
+            return error($e->getMessage());
+        }
+    }
+
+}

+ 27 - 0
app/controller/api/User.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\controller\api;
+
+use app\extra\basic\Base;
+use app\middleware\WxMiddleware;
+use LinFly\Annotation\Route\Controller;
+use LinFly\Annotation\Route\Route;
+use Webman\Annotation\Middleware;
+
+
+#[Controller(prefix: "/wx_api/user"),Middleware(WxMiddleware::class)]
+class User extends Base
+{
+
+
+    #[Route(path: "data",methods: "get")]
+    public function getUserData()
+    {
+        try {
+
+        } catch (\Throwable $th) {
+            return error($th->getMessage());
+        }
+    }
+
+}

+ 62 - 0
app/extra/tools/CodeExtend.php

@@ -4,6 +4,68 @@ namespace app\extra\tools;
 
 class CodeExtend
 {
+
+    /**
+     * 数组转XML内容
+     * @param array $data
+     * @return string
+     */
+    public static function arr2xml($data)
+    {
+        return "<xml>" . self::_arr2xml($data) . "</xml>";
+    }
+
+    /**
+     * 解析XML内容到数组
+     * @param string $xml
+     * @return array
+     */
+    public static function xml2arr($xml)
+    {
+        // 使用simplexml_load_string并禁用外部实体加载
+        $xmlObject = simplexml_load_string(
+            $xml,
+            'SimpleXMLElement',
+            LIBXML_NOCDATA | LIBXML_NOBLANKS | LIBXML_NOENT
+        );
+
+        if ($xmlObject === false) {
+            throw new \RuntimeException("Failed to parse XML");
+        }
+        return json_decode(json_encode($xmlObject), true);
+    }
+    /**
+     * XML内容生成
+     * @param array $data 数据
+     * @param string $content
+     * @return string
+     */
+    private static function _arr2xml($data, $content = '')
+    {
+        foreach ($data as $key => $val) {
+            is_numeric($key) && $key = 'item';
+            $content .= "<{$key}>";
+            if (is_array($val) || is_object($val)) {
+                $content .= self::_arr2xml($val);
+            } elseif (is_string($val)) {
+                $content .= '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $val) . ']]>';
+            } else {
+                $content .= $val;
+            }
+            $content .= "</{$key}>";
+        }
+        return $content;
+    }
+
+    public static function createNoncestr($length = 32, $str = "")
+    {
+        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
+        for ($i = 0; $i < $length; $i++) {
+            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
+        }
+        return $str;
+    }
+
     /**
      * 生成随机编码
      * @param integer $size 编码长度

+ 80 - 0
app/extra/tools/OssRegionExtend.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace app\extra\tools;
+
+class OssRegionExtend
+{
+    /**
+     * 获取存储区域
+     * @return array
+     */
+    public static function OssRegion(): array
+    {
+        return [
+            'oss-cn-hangzhou.aliyuncs.com'       => '华东 1(杭州)',
+            'oss-cn-shanghai.aliyuncs.com'       => '华东 2(上海)',
+            'oss-cn-nanjing.aliyuncs.com'        => '华东 5(南京本地地域)',
+            'oss-cn-fuzhou.aliyuncs.com'         => '华东 6(福州本地地域)',
+            'oss-cn-qingdao.aliyuncs.com'        => '华北 1(青岛)',
+            'oss-cn-beijing.aliyuncs.com'        => '华北 2(北京)',
+            'oss-cn-zhangjiakou.aliyuncs.com'    => '华北 3(张家口)',
+            'oss-cn-huhehaote.aliyuncs.com'      => '华北 5(呼和浩特)',
+            'oss-cn-wulanchabu.aliyuncs.com'     => '华北 6(乌兰察布)',
+            'oss-cn-shenzhen.aliyuncs.com'       => '华南 1(深圳)',
+            'oss-cn-heyuan.aliyuncs.com'         => '华南 2(河源)',
+            'oss-cn-guangzhou.aliyuncs.com'      => '华南 3(广州)',
+            'oss-cn-chengdu.aliyuncs.com'        => '西南 1(成都)',
+            'oss-cn-hongkong.aliyuncs.com'       => '中国(香港)',
+            'oss-us-west-1.aliyuncs.com'         => '美国(硅谷)',
+            'oss-us-east-1.aliyuncs.com'         => '美国(弗吉尼亚)',
+            'oss-ap-northeast-1.aliyuncs.com'    => '日本(东京)',
+            'oss-ap-northeast-2.aliyuncs.com'    => '韩国(首尔)',
+            'oss-ap-southeast-1.aliyuncs.com'    => '新加坡',
+            'oss-ap-southeast-2.aliyuncs.com'    => '澳大利亚(悉尼)',
+            'oss-ap-southeast-3.aliyuncs.com'    => '马来西亚(吉隆坡)',
+            'oss-ap-southeast-5.aliyuncs.com'    => '印度尼西亚(雅加达)',
+            'oss-ap-southeast-6.aliyuncs.com'    => '菲律宾(马尼拉)',
+            'oss-ap-southeast-7.aliyuncs.com'    => '泰国(曼谷)',
+            'oss-ap-south-1.aliyuncs.com'        => '印度(孟买)',
+            'oss-eu-central-1.aliyuncs.com'      => '德国(法兰克福)',
+            'oss-eu-west-1.aliyuncs.com'         => '英国(伦敦)',
+            'oss-me-east-1.aliyuncs.com'         => '阿联酋(迪拜)',
+            'oss-rg-china-mainland.aliyuncs.com' => '无地域属性(中国内地)'
+        ];
+    }
+
+    /**
+     * 获取存储区域
+     * @return array
+     */
+    public static function CosRegion(): array
+    {
+        return [
+            'ap-guangzhou'                => '华南地区(广州)',
+            'up-cn-east-2.qiniup.com'      => '华东-浙江2',
+            'up-z1.qiniup.com'             => '华北-河北',
+            'up-z2.qiniup.com'             => '华南-广东',
+            'up-na0.qiniup.com'            => '北美-洛杉矶',
+            'up-as0.qiniup.com'            => '亚太-新加坡',
+            'up-ap-northeast-1.qiniup.com' => '亚太-首尔',
+        ];
+    }
+
+    /**
+     * 获取存储区域
+     * @return array
+     */
+    public static function QiniuRegion(): array
+    {
+        return [
+            'up.qiniup.com'                => '华东-浙江',
+            'up-cn-east-2.qiniup.com'      => '华东-浙江2',
+            'up-z1.qiniup.com'             => '华北-河北',
+            'up-z2.qiniup.com'             => '华南-广东',
+            'up-na0.qiniup.com'            => '北美-洛杉矶',
+            'up-as0.qiniup.com'            => '亚太-新加坡',
+            'up-ap-northeast-1.qiniup.com' => '亚太-首尔',
+        ];
+    }
+
+}

+ 116 - 0
app/extra/weMini/BasicWeChat.php

@@ -0,0 +1,116 @@
+<?php
+
+namespace app\extra\weMini;
+
+use think\Exception;
+use WeChat\Contracts\DataArray;
+use WeChat\Contracts\Tools;
+use yzh52521\EasyHttp\Http;
+
+class BasicWeChat
+{
+
+    /**
+     * 当前微信配置
+     * @var DataArray
+     */
+    public $config;
+
+    /**
+     * 访问AccessToken
+     * @var string
+     */
+    public $access_token = '';
+
+    /**
+     * 当前请求方法参数
+     * @var array
+     */
+    protected $currentMethod = [];
+
+    /**
+     * 当前模式
+     * @var bool
+     */
+    protected $isTry = false;
+
+    /**
+     * 静态缓存
+     * @var static
+     */
+    protected static $cache;
+
+    /**
+     * 注册代替函数
+     * @var string
+     */
+    protected $GetAccessTokenCallback;
+
+    public function __construct(array $options)
+    {
+        if (empty($options['appid'])) {
+            throw new Exception("Missing Config -- [appid]");
+        }
+        if (empty($options['appsecret'])) {
+            throw new Exception("Missing Config -- [appsecret]");
+        }
+        if (isset($options['GetAccessTokenCallback']) && is_callable($options['GetAccessTokenCallback'])) {
+            $this->GetAccessTokenCallback = $options['GetAccessTokenCallback'];
+        }
+        $this->config = new DataArray($options);
+    }
+
+    public function getAccessToken()
+    {
+        if (!empty($this->access_token)) {
+            return $this->access_token;
+        }
+        $cache = $this->config->get('appid') . '_access_token';
+        $this->access_token = Tools::getCache($cache);
+        if (!empty($this->access_token)) {
+            return $this->access_token;
+        }
+        // 处理开放平台授权公众号获取AccessToken
+        if (!empty($this->GetAccessTokenCallback) && is_callable($this->GetAccessTokenCallback)) {
+            $this->access_token = call_user_func_array($this->GetAccessTokenCallback, [$this->config->get('appid'), $this]);
+            if (!empty($this->access_token)) {
+                Tools::setCache($cache, $this->access_token, 7000);
+            }
+            return $this->access_token;
+        }
+        list($appid, $secret) = [$this->config->get('appid'), $this->config->get('appsecret')];
+        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
+        $result = Http::get($url)->array();
+        if (!empty($result['access_token'])) {
+            Tools::setCache($cache, $result['access_token'], 7000);
+        }
+        return $this->access_token = $result['access_token'];
+    }
+
+    public function setAccessToken($accessToken)
+    {
+        if (!is_string($accessToken)) {
+            throw new InvalidArgumentException("Invalid AccessToken type, need string.");
+        }
+        $cache = $this->config->get('appid') . '_access_token';
+        Tools::setCache($cache, $this->access_token = $accessToken);
+    }
+
+    /**
+     * 清理删除 AccessToken
+     * @return bool
+     */
+    public function delAccessToken()
+    {
+        $this->access_token = '';
+        return Tools::delCache($this->config->get('appid') . '_access_token');
+    }
+
+    protected function registerApi(&$url, $method, $arguments = [])
+    {
+        $this->currentMethod = ['method' => $method, 'arguments' => $arguments];
+        if (empty($this->access_token)) $this->access_token = $this->getAccessToken();
+        return $url = str_replace('ACCESS_TOKEN', urlencode($this->access_token), $url);
+    }
+
+}

+ 198 - 0
app/extra/weMini/BasicWePay.php

@@ -0,0 +1,198 @@
+<?php
+
+namespace app\extra\weMini;
+
+use GuzzleHttp\Client;
+use WeChat\Contracts\DataArray;
+use WeChat\Contracts\Tools;
+use WeChat\Exceptions\InvalidArgumentException;
+use WeChat\Exceptions\InvalidResponseException;
+use yzh52521\EasyHttp\Http;
+
+class BasicWePay
+{
+
+
+    /**
+     * 商户配置
+     * @var DataArray
+     */
+    protected $config;
+
+    /**
+     * 当前请求数据
+     * @var DataArray
+     */
+    protected $params;
+
+    /**
+     * 静态缓存
+     * @var static
+     */
+    protected static $cache;
+
+    /**
+     * WeChat constructor.
+     * @param array $options
+     */
+    public function __construct(array $options)
+    {
+        if (empty($options['appid'])) {
+            throw new InvalidArgumentException("Missing Config -- [appid]");
+        }
+        if (empty($options['mch_id'])) {
+            throw new InvalidArgumentException("Missing Config -- [mch_id]");
+        }
+        if (empty($options['mch_key'])) {
+            throw new InvalidArgumentException("Missing Config -- [mch_key]");
+        }
+        if (!empty($options['cache_path'])) {
+            Tools::$cache_path = $options['cache_path'];
+        }
+        $this->config = new DataArray($options);
+        // 商户基础参数
+        $this->params = new DataArray([
+            'appid'     => $this->config->get('appid'),
+            'mch_id'    => $this->config->get('mch_id'),
+            'nonce_str' => Tools::createNoncestr(),
+        ]);
+        // 商户参数支持
+        if ($this->config->get('sub_appid')) {
+            $this->params->set('sub_appid', $this->config->get('sub_appid'));
+        }
+        if ($this->config->get('sub_mch_id')) {
+            $this->params->set('sub_mch_id', $this->config->get('sub_mch_id'));
+        }
+    }
+
+    /**
+     * 静态创建对象
+     * @param array $config
+     * @return static
+     */
+    public static function instance(array $config)
+    {
+        $key = md5(get_called_class() . serialize($config));
+        print_r($key);
+        if (isset(self::$cache[$key])) return self::$cache[$key];
+        return self::$cache[$key] = new static($config);
+    }
+
+    /**
+     * 获取微信支付通知
+     * @param string|array $xml
+     * @return array
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     */
+    public function getNotify($xml = '')
+    {
+        $data = is_array($xml) ? $xml : Tools::xml2arr(empty($xml) ? Tools::getRawInput() : $xml);
+        if (isset($data['sign']) && $this->getPaySign($data) === $data['sign']) {
+            return $data;
+        }
+        throw new InvalidResponseException('Invalid Notify.', '0');
+    }
+
+    /**
+     * 获取微信支付通知回复内容
+     * @return string
+     */
+    public function getNotifySuccessReply()
+    {
+        return Tools::arr2xml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']);
+    }
+
+    /**
+     * 生成支付签名
+     * @param array $data 参与签名的数据
+     * @param string $signType 参与签名的类型
+     * @param string $buff 参与签名字符串前缀
+     * @return string
+     */
+    public function getPaySign(array $data, $signType = 'MD5', $buff = '')
+    {
+        ksort($data);
+        if (isset($data['sign'])) unset($data['sign']);
+        foreach ($data as $k => $v) {
+            if ('' === $v || null === $v) continue;
+            $buff .= "{$k}={$v}&";
+        }
+        $buff .= ("key=" . $this->config->get('mch_key'));
+        if (strtoupper($signType) === 'MD5') {
+            return strtoupper(md5($buff));
+        }
+        return strtoupper(hash_hmac('SHA256', $buff, $this->config->get('mch_key')));
+    }
+
+    /**
+     * 转换短链接
+     * @param string $longUrl 需要转换的URL,签名用原串,传输需URLencode
+     * @return array
+     */
+    public function shortUrl($longUrl)
+    {
+        $url = 'https://api.mch.weixin.qq.com/tools/shorturl';
+        return $this->callPostApi($url, ['long_url' => $longUrl]);
+    }
+
+    /**
+     * 数组直接转xml数据输出
+     * @param array $data
+     * @param bool $isReturn
+     * @return string|void
+     */
+    public function toXml(array $data, $isReturn = false)
+    {
+        $xml = Tools::arr2xml($data);
+        if ($isReturn) return $xml;
+        echo $xml;
+    }
+
+    /**
+     * 以 Post 请求接口
+     * @param string $url 请求
+     * @param array $data 接口参数
+     * @param bool $isCert 是否需要使用双向证书
+     * @param string $signType 数据签名类型 MD5|SHA256
+     * @param bool $needSignType 是否需要传签名类型参数
+     * @param bool $needNonceStr
+     * @return array
+     */
+    protected function callPostApi($url, array $data, $isCert = false, $signType = 'HMAC-SHA256', $needSignType = true, $needNonceStr = true)
+    {
+        $option = [];
+        if ($isCert) {
+            $option['ssl_p12'] = $this->config->get('ssl_p12');
+            $option['ssl_cer'] = $this->config->get('ssl_cer');
+            $option['ssl_key'] = $this->config->get('ssl_key');
+            if (is_string($option['ssl_p12']) && file_exists($option['ssl_p12'])) {
+                $content = file_get_contents($option['ssl_p12']);
+                if (openssl_pkcs12_read($content, $certs, $this->config->get('mch_id'))) {
+//                    $option['ssl_key'] = Tools::pushFile(md5($certs['pkey']) . '.pem', $certs['pkey']);
+//                    $option['ssl_cer'] = Tools::pushFile(md5($certs['cert']) . '.pem', $certs['cert']);
+                } else return ['code' => 0 ,"msg" => "P12 certificate does not match MCH_ID --- ssl_p12"];
+            }
+            if (empty($option['ssl_cer']) || !file_exists($option['ssl_cer'])) {
+                return ['code' => 0 ,"msg" => "Missing Config -- ssl_cer"];
+            }
+            if (empty($option['ssl_key']) || !file_exists($option['ssl_key'])) {
+                return ['code' => 0 ,"msg" => "Missing Config -- ssl_key"];
+            }
+        }
+        $params = $this->params->merge($data);
+        if (!$needNonceStr) unset($params['nonce_str']);
+        if ($needSignType) $params['sign_type'] = strtoupper($signType);
+        $params['sign'] = $this->getPaySign($params, $signType);
+        $client = new Client();
+        $resp = $client->post($url,[
+            "header"    => $client,
+            'body' => Tools::arr2xml($params)
+        ]);
+        $result = Tools::xml2arr($resp->getBody());
+        return $result;
+//        if ($result['return_code'] !== 'SUCCESS') {
+//            return $result;
+//        }
+    }
+
+}

+ 69 - 0
app/extra/weMini/Crypt.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace app\extra\weMini;
+
+use app\extra\weMini\crypt\wxBizDataCrypt;
+use WeChat\Exceptions\InvalidDecryptException;
+use WeChat\Exceptions\InvalidResponseException;
+use yzh52521\EasyHttp\Http;
+
+class Crypt extends BasicWeChat
+{
+    /**
+     * 数据签名校验
+     * @param string $iv
+     * @param string $sessionKey
+     * @param string $encryptedData
+     * @return bool|array
+     */
+    public function decode($iv, $sessionKey, $encryptedData)
+    {
+        $pc = new WXBizDataCrypt($this->config->get('appid'), $sessionKey);
+        $errCode = $pc->decryptData($encryptedData, $iv, $data);
+        if ($errCode == 0) {
+            return json_decode($data, true);
+        }
+        return false;
+    }
+
+
+    /**
+     * 登录凭证校验
+     * @param string $code 登录时获取的 code
+     * @return array
+     * @throws \WeChat\Exceptions\LocalCacheException
+     */
+    public function session($code)
+    {
+        $appid = $this->config->get('appid');
+        $secret = $this->config->get('appsecret');
+        $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code";
+        return Http::get($url)->array();
+    }
+
+    /**
+     * 换取用户信息
+     * @param string $code 用户登录凭证(有效期五分钟)
+     * @param string $iv 加密算法的初始向量
+     * @param string $encryptedData 加密数据( encryptedData )
+     * @return array
+     * @throws \WeChat\Exceptions\InvalidDecryptException
+     * @throws \WeChat\Exceptions\InvalidResponseException
+     * @throws \WeChat\Exceptions\LocalCacheException
+     */
+    public function userInfo($code, $iv, $encryptedData)
+    {
+        $result = $this->session($code);
+        if (empty($result['session_key'])) {
+            throw new InvalidResponseException('Code 换取 SessionKey 失败', 403);
+        }
+        $userinfo = $this->decode($iv, $result['session_key'], $encryptedData);
+        if (empty($userinfo)) {
+            throw new InvalidDecryptException('用户信息解析失败', 403);
+        }
+        return array_merge($result, $userinfo);
+    }
+
+
+
+}

+ 144 - 0
app/extra/weMini/Link.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace app\extra\weMini;
+
+use yzh52521\EasyHttp\Http;
+
+class Link extends BasicWeChat
+{
+
+
+
+    public function createQrcodeWx(string $path = "",string $query = "", string $filePath = "")
+    {
+        $url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token={$this->getAccessToken()}";
+        $data = [
+            "path"  => $path."?".$query,
+            'width' => 430
+        ];
+
+        // 初始化cURL
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_HEADER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+
+        // 执行请求
+        $response = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+
+        // 检查请求是否成功
+        if ($httpCode != 200) {
+            return false;
+        }
+
+        // 检查返回的是否是JSON(错误情况)
+        $decode = json_decode($response, true);
+        if ($decode && isset($decode['errcode'])) {
+            error_log("微信接口返回错误: " . $decode['errmsg']);
+            return false;
+        }
+
+        // 保存为JPG文件
+        $filePath = public_path().$filePath;
+        $result = file_put_contents($filePath, $response);
+
+        if ($result === false) {
+            error_log("文件保存失败: " . $filePath);
+            return false;
+        }
+        return $filePath;
+//        echo $url;
+//        print_r($param);
+//        return Http::asJson()->post($url,$param);
+    }
+
+    public function createQrcode(string $path = "",string $query = "", string $filePath = "")
+    {
+        $url = "https://api.weixin.qq.com/wxa/getwxacode?access_token={$this->getAccessToken()}";
+        $data = [
+            "path"  => $path."?".$query,
+            'width' => 430
+        ];
+
+        // 初始化cURL
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_HEADER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+
+        // 执行请求
+        $response = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+
+        // 检查请求是否成功
+        if ($httpCode != 200) {
+            return false;
+        }
+
+        // 检查返回的是否是JSON(错误情况)
+        $decode = json_decode($response, true);
+        if ($decode && isset($decode['errcode'])) {
+            error_log("微信接口返回错误: " . $decode['errmsg']);
+            return false;
+        }
+
+        // 保存为JPG文件
+        $filePath = public_path().$filePath;
+        $result = file_put_contents($filePath, $response);
+
+        if ($result === false) {
+            error_log("文件保存失败: " . $filePath);
+            return false;
+        }
+        return $filePath;
+//        echo $url;
+//        print_r($param);
+//        return Http::asJson()->post($url,$param);
+    }
+
+
+    public function createLink(string $path = "",string $query = "",string $version = "release")
+    {
+        $url = "https://api.weixin.qq.com/wxa/generate_urllink?access_token={$this->getAccessToken()}";
+        $param = [
+            "path"          => $path,
+            "query"         => $query,
+            "env_version"   => $version
+        ];
+        return Http::asJson()->post($url,$param)->array();
+    }
+
+
+    public function createLinkJump(string $path = "",string $query = "",string $version = "release")
+    {
+        $url = "https://api.weixin.qq.com/wxa/generatescheme?access_token={$this->getAccessToken()}";
+        $param = [
+            "jump_wxa"  => [
+                "path"          => $path,
+                "query"         => $query,
+                "env_version"   => $version
+            ],
+        ];
+        return Http::asJson()->post($url,$param)->array();
+    }
+
+
+    public function createShortLink(string $path = "",string $query = "",string $version = "release")
+    {
+        $url = "https://api.weixin.qq.com/wxa/genwxashortlink?access_token={$this->getAccessToken()}";
+        $param = [
+            "page_url"   => $path."?".$query,
+        ];
+        return Http::asJson()->post($url,$param)->array();
+    }
+
+}

+ 143 - 0
app/extra/weMini/Order.php

@@ -0,0 +1,143 @@
+<?php
+
+namespace app\extra\weMini;
+
+use WeChat\Contracts\Tools;
+
+class Order extends BasicWePay
+{
+
+
+    /**
+     * 统一下单
+     * @param array $options
+     * @return array
+     */
+    public function create(array $options)
+    {
+        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
+        return $this->callPostApi($url, $options, false, 'MD5');
+    }
+
+    /**
+     * 刷卡支付
+     * @param array $options
+     * @return array
+     */
+    public function micropay(array $options)
+    {
+        $url = 'https://api.mch.weixin.qq.com/pay/micropay';
+        return $this->callPostApi($url, $options, false, 'MD5');
+    }
+
+    /**
+     * 查询订单
+     * @param array $options
+     * @return array
+     */
+    public function query(array $options)
+    {
+        $url = 'https://api.mch.weixin.qq.com/pay/orderquery';
+        return $this->callPostApi($url, $options);
+    }
+
+    /**
+     * 关闭订单
+     * @param string $outTradeNo 商户订单号
+     * @return array
+     */
+    public function close($outTradeNo)
+    {
+        $url = 'https://api.mch.weixin.qq.com/pay/closeorder';
+        return $this->callPostApi($url, ['out_trade_no' => $outTradeNo]);
+    }
+
+    /**
+     * 创建JsApi及H5支付参数
+     * @param string $prepayId 统一下单预支付码
+     * @return array
+     */
+    public function jsapiParams($prepayId)
+    {
+        $option = [];
+        $option["appId"] = $this->config->get('appid');
+        $option["timeStamp"] = (string)time();
+        $option["nonceStr"] = Tools::createNoncestr();
+        $option["package"] = "prepay_id={$prepayId}";
+        $option["signType"] = "MD5";
+        $option["paySign"] = $this->getPaySign($option, 'MD5');
+        $option['timestamp'] = $option['timeStamp'];
+        return $option;
+    }
+
+    /**
+     * 获取支付规则二维码
+     * @param string $productId 商户定义的商品id或者订单号
+     * @return string
+     */
+    public function qrcParams($productId)
+    {
+        $data = [
+            'appid'      => $this->config->get('appid'),
+            'mch_id'     => $this->config->get('mch_id'),
+            'time_stamp' => (string)time(),
+            'nonce_str'  => Tools::createNoncestr(),
+            'product_id' => (string)$productId,
+        ];
+        $data['sign'] = $this->getPaySign($data, 'MD5');
+        return "weixin://wxpay/bizpayurl?" . http_build_query($data);
+    }
+
+    /**
+     * 获取微信App支付秘需参数
+     * @param string $prepayId 统一下单预支付码
+     * @return array
+     */
+    public function appParams($prepayId)
+    {
+        $data = [
+            'appid'     => $this->config->get('appid'),
+            'partnerid' => $this->config->get('mch_id'),
+            'prepayid'  => (string)$prepayId,
+            'package'   => 'Sign=WXPay',
+            'timestamp' => (string)time(),
+            'noncestr'  => Tools::createNoncestr(),
+        ];
+        $data['sign'] = $this->getPaySign($data, 'MD5');
+        return $data;
+    }
+
+    /**
+     * 刷卡支付 撤销订单
+     * @param array $options
+     * @return array
+     */
+    public function reverse(array $options)
+    {
+        $url = 'https://api.mch.weixin.qq.com/secapi/pay/reverse';
+        return $this->callPostApi($url, $options, true);
+    }
+
+    /**
+     * 刷卡支付 授权码查询openid
+     * @param string $authCode 扫码支付授权码,设备读取用户微信中的条码或者二维码信息
+     * @return array
+     */
+    public function queryAuthCode($authCode)
+    {
+        $url = 'https://api.mch.weixin.qq.com/tools/authcodetoopenid';
+        return $this->callPostApi($url, ['auth_code' => $authCode], false, 'MD5', false);
+    }
+
+    /**
+     * 刷卡支付 交易保障
+     * @param array $options
+     * @return array
+     */
+    public function report(array $options)
+    {
+        $url = 'https://api.mch.weixin.qq.com/payitil/report';
+        return $this->callPostApi($url, $options);
+    }
+
+}

+ 188 - 0
app/extra/weMini/Pay.php

@@ -0,0 +1,188 @@
+<?php
+
+namespace app\extra\weMini;
+
+
+use WePay\Bill;
+use WePay\Refund;
+use WePay\Transfers;
+use WePay\TransfersBank;
+
+class Pay extends BasicWePay
+{
+
+
+    /**
+     * 统一下单
+     * @param array $options
+     * @return array
+     */
+    public function createOrder(array $options)
+    {
+        $resp = (new Order($this->config->get()))->create($options);
+        print_r($resp);
+        return $resp;
+    }
+
+    /**
+     * 刷卡支付
+     * @param array $options
+     * @return array
+     */
+    public function createMicropay($options)
+    {
+        return Order::instance($this->config->get())->micropay($options);
+    }
+
+    /**
+     * 创建JsApi及H5支付参数
+     * @param string $prepay_id 统一下单预支付码
+     * @return array
+     */
+    public function createParamsForJsApi($prepay_id)
+    {
+        return Order::instance($this->config->get())->jsapiParams($prepay_id);
+    }
+
+    /**
+     * 获取APP支付参数
+     * @param string $prepay_id 统一下单预支付码
+     * @return array
+     */
+    public function createParamsForApp($prepay_id)
+    {
+        return Order::instance($this->config->get())->appParams($prepay_id);
+    }
+
+    /**
+     * 获取支付规则二维码
+     * @param string $product_id 商户定义的商品id 或者订单号
+     * @return string
+     */
+    public function createParamsForRuleQrc($product_id)
+    {
+        return Order::instance($this->config->get())->qrcParams($product_id);
+    }
+
+    /**
+     * 查询订单
+     * @param array $options
+     * @return array
+     */
+    public function queryOrder(array $options)
+    {
+        return Order::instance($this->config->get())->query($options);
+    }
+
+    /**
+     * 关闭订单
+     * @param string $out_trade_no 商户订单号
+     * @return array
+     */
+    public function closeOrder($out_trade_no)
+    {
+        return Order::instance($this->config->get())->close($out_trade_no);
+    }
+
+    /**
+     * 申请退款
+     * @param array $options
+     * @return array
+     */
+    public function createRefund(array $options)
+    {
+        return Refund::instance($this->config->get())->create($options);
+    }
+
+    /**
+     * 查询退款
+     * @param array $options
+     * @return array
+     */
+    public function queryRefund(array $options)
+    {
+        return Refund::instance($this->config->get())->query($options);
+    }
+
+    /**
+     * 交易保障
+     * @param array $options
+     * @return array
+     */
+    public function report(array $options)
+    {
+        return Order::instance($this->config->get())->report($options);
+    }
+
+    /**
+     * 授权码查询openid
+     * @param string $authCode 扫码支付授权码,设备读取用户微信中的条码或者二维码信息
+     * @return array
+     */
+    public function queryAuthCode($authCode)
+    {
+        return Order::instance($this->config->get())->queryAuthCode($authCode);
+    }
+
+    /**
+     * 下载对账单
+     * @param array $options 静音参数
+     * @param null|string $outType 输出类型
+     * @return bool|string
+     */
+    public function billDownload(array $options, $outType = null)
+    {
+        return Bill::instance($this->config->get())->download($options, $outType);
+    }
+
+    /**
+     * 拉取订单评价数据
+     * @param array $options
+     * @return array
+     */
+    public function billCommtent(array $options)
+    {
+        return Bill::instance($this->config->get())->comment($options);
+    }
+
+    /**
+     * 企业付款到零钱
+     * @param array $options
+     * @return array
+     */
+    public function createTransfers(array $options)
+    {
+        return Transfers::instance($this->config->get())->create($options);
+    }
+
+    /**
+     * 查询企业付款到零钱
+     * @param string $partner_trade_no 商户调用企业付款API时使用的商户订单号
+     * @return array
+     */
+    public function queryTransfers($partner_trade_no)
+    {
+        return Transfers::instance($this->config->get())->query($partner_trade_no);
+    }
+
+    /**
+     * 企业付款到银行卡
+     * @param array $options
+     * @return array
+     */
+    public function createTransfersBank(array $options)
+    {
+        return TransfersBank::instance($this->config->get())->create($options);
+    }
+
+    /**
+     * 商户企业付款到银行卡操作进行结果查询
+     * @param string $partner_trade_no 商户订单号,需保持唯一
+     * @return array
+     */
+    public function queryTransFresBank($partner_trade_no)
+    {
+        return TransfersBank::instance($this->config->get())->query($partner_trade_no);
+    }
+
+}

+ 88 - 0
app/extra/weMini/PayExtra.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace app\extra\weMini;
+
+use app\extra\tools\CodeExtend;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use WeChat\Contracts\DataArray;
+use WeChat\Contracts\Tools;
+use WeChat\Exceptions\InvalidArgumentException;
+
+class PayExtra
+{
+
+
+    /**
+     * 商户配置
+     */
+    protected $config = [];
+
+
+    public function setConfig(array $options)
+    {
+        $options['nonce_str'] = CodeExtend::createNoncestr();
+        $this->config = $options;
+        return $this;
+    }
+
+    public function createOrder(array $options): array
+    {
+        $options = array_merge($this->config,$options);
+        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
+        $options['sign_type'] = "MD5";
+        $options['sign'] = $this->getPaySign($options);
+        $client = new Client();
+        try {
+            $resp = $client->post($url, [
+                "header" => $client,
+                'body' => CodeExtend::arr2xml($options)
+            ]);
+        } catch (GuzzleException $e) {
+            return [];
+        }
+        return CodeExtend::xml2arr($resp->getBody());
+    }
+
+    /**
+     * 创建JsApi及H5支付参数
+     * @param string $prepayId 统一下单预支付码
+     * @return array
+     */
+    public function createParamsForJsApi($prepayId)
+    {
+        $option = [];
+        $option["appId"] = $this->config['appid'];
+        $option["timeStamp"] = (string)time();
+        $option["nonceStr"] = CodeExtend::createNoncestr();
+        $option["package"] = "prepay_id={$prepayId}";
+        $option["signType"] = "MD5";
+        $option["paySign"] = $this->getPaySign($option, 'MD5');
+        $option['timestamp'] = $option['timeStamp'];
+        return $option;
+    }
+
+    /**
+     * 生成支付签名
+     * @param array $data 参与签名的数据
+     * @param string $signType 参与签名的类型
+     * @param string $buff 参与签名字符串前缀
+     * @return string
+     */
+    public function getPaySign(array $data, $signType = 'MD5', $buff = '')
+    {
+        ksort($data);
+        if (isset($data['sign'])) unset($data['sign']);
+        foreach ($data as $k => $v) {
+            if ('' === $v || null === $v) continue;
+            $buff .= "{$k}={$v}&";
+        }
+        $buff .= ("key=" . $this->config['mch_key']);
+        if (strtoupper($signType) === 'MD5') {
+            return strtoupper(md5($buff));
+        }
+        return strtoupper(hash_hmac('SHA256', $buff, $this->config['mch_key']));
+    }
+
+
+}

+ 24 - 0
app/extra/weMini/Subscribe.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace app\extra\weMini;
+
+use yzh52521\EasyHttp\Http;
+
+class Subscribe extends BasicWeChat
+{
+
+    public function sendMsg(string $id = "",string $openid = "",array $data = [])
+    {
+        $url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={$this->getAccessToken()}";
+        $param = [
+            "template_id"   => $id,
+            "touser"        => $openid,
+            "data"          => $data,
+            "page"          => "pages/order/order",
+            "miniprogram_state" => "formal",
+            "lang"          => "zh_CN"
+        ];
+        return Http::asJson()->post($url,$param)->array();
+    }
+
+}

+ 21 - 0
app/extra/weMini/crypt/errorCode.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace app\extra\weMini\crypt;
+/**
+ * error code 说明.
+ * <ul>
+ *    <li>-41001: encodingAesKey 非法</li>
+ *    <li>-41003: aes 解密失败</li>
+ *    <li>-41004: 解密后得到的buffer非法</li>
+ *    <li>-41005: base64加密失败</li>
+ *    <li>-41016: base64解密失败</li>
+ * </ul>
+ */
+class errorCode
+{
+    public static $OK = 0;
+    public static $IllegalAesKey = -41001;
+    public static $IllegalIv = -41002;
+    public static $IllegalBuffer = -41003;
+    public static $DecodeBase64Error = -41004;
+}

+ 59 - 0
app/extra/weMini/crypt/wxBizDataCrypt.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace app\extra\weMini\crypt;
+/**
+ * 对微信小程序用户加密数据的解密示例代码
+ * Class WXBizDataCrypt
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+class wxBizDataCrypt
+{
+    private $appid;
+    private $sessionKey;
+
+    /**
+     * 构造函数
+     * @param $sessionKey string 用户在小程序登录后获取的会话密钥
+     * @param $appid string 小程序的appid
+     */
+    public function __construct($appid, $sessionKey)
+    {
+        $this->appid = $appid;
+        $this->sessionKey = $sessionKey;
+//        include_once __DIR__ . DIRECTORY_SEPARATOR . "errorCode.php";
+    }
+
+    /**
+     * 检验数据的真实性,并且获取解密后的明文.
+     * @param $encryptedData string 加密的用户数据
+     * @param $iv string 与用户数据一同返回的初始向量
+     * @param $data string 解密后的原文
+     *
+     * @return int 成功0,失败返回对应的错误码
+     */
+    public function decryptData($encryptedData, $iv, &$data)
+    {
+        if (strlen($this->sessionKey) != 24) {
+            return errorCode::$IllegalAesKey;
+        }
+        $aesKey = base64_decode($this->sessionKey);
+        if (strlen($iv) != 24) {
+            return errorCode::$IllegalIv;
+        }
+        $aesIV = base64_decode($iv);
+        $aesCipher = base64_decode($encryptedData);
+        $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
+        $dataObj = json_decode($result);
+        if ($dataObj == null) {
+            return errorCode::$IllegalBuffer;
+        }
+        // 兼容新版本无 watermark 的情况
+        if (isset($dataObj->watermark) && $dataObj->watermark->appid != $this->appid) {
+            return errorCode::$IllegalBuffer;
+        }
+        $data = $result;
+        return errorCode::$OK;
+    }
+
+}
+

+ 12 - 1
app/model/saas/SaasPrintClient.php

@@ -6,7 +6,18 @@ use app\extra\basic\Model;
 
 
 /**
- * @property integer $id (主键)
+ * @property integer $id (主键)
+ * @property integer $agent_id 
+ * @property integer $shop_id 
+ * @property mixed $code 
+ * @property string $name 
+ * @property string $ipaddress 
+ * @property mixed $rule 打印机配置
+ * @property integer $status 1正常2禁用
+ * @property string $print_status 打印机状态
+ * @property integer $is_price 1不启用2启用
+ * @property mixed $price 额外收费规则
+ * @property mixed $create_at
  */
 class SaasPrintClient extends Model
 {

+ 2 - 2
app/model/saas/SaasAgent.php → app/model/saas/SaasShop.php

@@ -31,7 +31,7 @@ use app\extra\basic\Model;
  * @property mixed $line_time 最后在线时间
  * @property mixed $create_at
  */
-class SaasAgent extends Model
+class SaasShop extends Model
 {
     /**
      * The connection name for the model.
@@ -45,7 +45,7 @@ class SaasAgent extends Model
      *
      * @var string
      */
-    protected string $table = "saas_agent";
+    protected string $table = "saas_shop";
     
     /**
      * The primary key associated with the table.

+ 49 - 0
app/model/saas/SaasUser.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace app\model\saas;
+
+use app\extra\basic\Model;
+
+
+/**
+ * @property integer $id (主键)
+ * @property mixed $openid 
+ * @property integer $agent_id 
+ * @property integer $shop_id 
+ * @property integer $balance 余额
+ * @property integer $total_balance 累计充值
+ * @property integer $total_consume 累计消费
+ * @property mixed $create_at
+ */
+class SaasUser extends Model
+{
+    /**
+     * The connection name for the model.
+     *
+     * @var string|null
+     */
+    protected $connection = 'mysql';
+    
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected string $table = "saas_user";
+    
+    /**
+     * The primary key associated with the table.
+     *
+     * @var string
+     */
+    protected string $primaryKey = "id";
+    
+    /**
+     * Indicates if the model should be timestamped.
+     *
+     * @var bool
+     */
+    public bool $timestamps = false;
+
+
+}

+ 46 - 0
app/model/saas/SaasUserOpen.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace app\model\saas;
+
+use app\extra\basic\Model;
+
+
+/**
+ * @property integer $id (主键)
+ * @property string $nickname 
+ * @property mixed $openid 
+ * @property string $headimg 
+ * @property mixed $create_at
+ */
+class SaasUserOpen extends Model
+{
+    /**
+     * The connection name for the model.
+     *
+     * @var string|null
+     */
+    protected $connection = 'mysql';
+    
+    /**
+     * The table associated with the model.
+     *
+     * @var string
+     */
+    protected string $table = "saas_user_open";
+    
+    /**
+     * The primary key associated with the table.
+     *
+     * @var string
+     */
+    protected string $primaryKey = "id";
+    
+    /**
+     * Indicates if the model should be timestamped.
+     *
+     * @var bool
+     */
+    public bool $timestamps = false;
+
+
+}

+ 2 - 1
composer.json

@@ -53,7 +53,8 @@
     "qcloud/cos-sdk-v5": "^2.6",
     "topthink/think-validate": "^3.0",
     "hzdad/codecheck": "^1.0",
-    "php-di/php-di": "7.0"
+    "php-di/php-di": "7.0",
+    "zoujingli/wechat-developer": "^1.2"
   },
   "suggest": {
     "ext-event": "For better performance. "

+ 2 - 2
config/plugin/shopwwi/auth/app.php

@@ -12,9 +12,9 @@
          ],
          'member' => [
              'key' => 'id',
-             'field' => ['id','open_id'], //设置允许写入扩展中的字段
+             'field' => ['id','openid'], //设置允许写入扩展中的字段
              'num' => 0, //-1为不限制终端数量 0为只支持一个终端在线 大于0为同一账号同终端支持数量 建议设置为1 则同一账号同终端在线1个
-             'model'=> [\app\model\saas\SaasMember::class,'thinkphp'] // 当为数组时 [app\model\Test::class,'thinkphp'] 来说明模型归属
+             'model'=> [\app\model\saas\SaasUserOpen::class,'thinkphp'] // 当为数组时 [app\model\Test::class,'thinkphp'] 来说明模型归属
          ]
      ],
      'jwt' => [