diff --git a/.gitignore b/.gitignore
index a2ac47b..1ca160d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
/runtime/
+.env
+.idea
+.Ds_Store
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/crm_php.iml b/.idea/crm_php.iml
deleted file mode 100644
index 2ae7265..0000000
--- a/.idea/crm_php.iml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 8e8947d..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
deleted file mode 100644
index a9eba08..0000000
--- a/.idea/php.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/application/common/wework/api/Api.php b/application/common/wework/api/Api.php
new file mode 100644
index 0000000..8b8406f
--- /dev/null
+++ b/application/common/wework/api/Api.php
@@ -0,0 +1,66 @@
+corpId = $corpId;
+ $this->corpSecret = $corpSecret;
+ }
+
+ /**
+ * 获取客户详情
+ * @param $externalUserId
+ * @return mixed
+ */
+ function contactInfo($externalUserId) {
+ return $this->get(self::$contactInfo, [
+ 'access_token' => $this->getAccessToken(),
+ 'external_userid' => $externalUserId
+ ]);
+ }
+
+ /**
+ * 获取accessToken
+ * @return false|mixed
+ */
+ protected function getAccessToken() {
+ if ($this->accessToken == '') {
+ $this->accessToken = Cache::get('accessToken:' . $this->corpId);
+ if ($this->accessToken == '') {
+ $result = $this->get(self::$getAccessToken, [
+ 'corpid' => $this->corpId,
+ 'corpsecret' => $this->corpSecret
+ ]);
+ if (isset($result['access_token']) && $result['access_token']) {
+ Cache::set('accessToken:' . $this->corpId, $result['access_token'],$result['expires_in']-60);
+ $this->accessToken = $result['access_token'];
+ }
+ }
+ }
+ return $this->accessToken;
+ }
+
+
+
+ protected function get($action, $data) {
+ return json_decode(Curl::get(self::$baseUrl . $action,null,$data), true);
+ }
+}
\ No newline at end of file
diff --git a/application/common/wework/api/Curl.php b/application/common/wework/api/Curl.php
new file mode 100644
index 0000000..06dac1c
--- /dev/null
+++ b/application/common/wework/api/Curl.php
@@ -0,0 +1,110 @@
+ $val) {
+ $sets[] = $key . '=' . urlencode($val);
+ }
+ $data = implode('&', $sets);
+ }
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ }
+
+ $method = strtolower($method);
+ if ('post' == $method) {
+ curl_setopt($ch, CURLOPT_POST, true);
+ } elseif ('put' == $method) {
+ //curl_setopt($ch,CURLOPT_PUT,true);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
+ }
+// curl_setopt($ch, CURLOPT_PROXY, "socks5://127.0.0.1:18080");
+
+ //获取头部信息
+ if ($isHeader) {
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ }
+
+ $output = curl_exec($ch);
+ if ($output === false) {
+ var_dump(curl_error($ch));
+ }
+ curl_close($ch);
+ return $output;
+ }
+}
\ No newline at end of file
diff --git a/application/common/wework/callback/ErrorCode.php b/application/common/wework/callback/ErrorCode.php
new file mode 100644
index 0000000..b4bb964
--- /dev/null
+++ b/application/common/wework/callback/ErrorCode.php
@@ -0,0 +1,35 @@
+
+ *
-40001: 签名验证错误
+ * -40002: xml解析失败
+ * -40003: sha加密生成签名失败
+ * -40004: encodingAesKey 非法
+ * -40005: corpid 校验错误
+ * -40006: aes 加密失败
+ * -40007: aes 解密失败
+ * -40008: 解密后得到的buffer非法
+ * -40009: base64加密失败
+ * -40010: base64解密失败
+ * -40011: 生成xml失败
+ *
+ */
+class ErrorCode
+{
+ public static $OK = 0;
+ public static $ValidateSignatureError = -40001;
+ public static $ParseXmlError = -40002;
+ public static $ComputeSignatureError = -40003;
+ public static $IllegalAesKey = -40004;
+ public static $ValidateCorpidError = -40005;
+ public static $EncryptAESError = -40006;
+ public static $DecryptAESError = -40007;
+ public static $IllegalBuffer = -40008;
+ public static $EncodeBase64Error = -40009;
+ public static $DecodeBase64Error = -40010;
+ public static $GenReturnXmlError = -40011;
+}
+
+?>
\ No newline at end of file
diff --git a/application/common/wework/callback/PKCS7Encoder.php b/application/common/wework/callback/PKCS7Encoder.php
new file mode 100644
index 0000000..7919f30
--- /dev/null
+++ b/application/common/wework/callback/PKCS7Encoder.php
@@ -0,0 +1,51 @@
+ PKCS7Encoder::$block_size) {
+ $pad = 0;
+ }
+ return substr($text, 0, (strlen($text) - $pad));
+ }
+
+}
diff --git a/application/common/wework/callback/Prpcrypt.php b/application/common/wework/callback/Prpcrypt.php
new file mode 100644
index 0000000..74fc70f
--- /dev/null
+++ b/application/common/wework/callback/Prpcrypt.php
@@ -0,0 +1,110 @@
+key = base64_decode($k . '=');
+ $this->iv = substr($this->key, 0, 16);
+
+ }
+
+ /**
+ * 加密
+ *
+ * @param $text
+ * @param $receiveId
+ * @return array
+ */
+ public function encrypt($text, $receiveId)
+ {
+ try {
+ //拼接
+ $text = $this->getRandomStr() . pack('N', strlen($text)) . $text . $receiveId;
+ //添加PKCS#7填充
+ $pkc_encoder = new PKCS7Encoder;
+ $text = $pkc_encoder->encode($text);
+ //加密
+ if (function_exists('openssl_encrypt')) {
+ $encrypted = openssl_encrypt($text, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
+ } else {
+ $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->key, base64_decode($text), MCRYPT_MODE_CBC, $this->iv);
+ }
+ return array(ErrorCode::$OK, $encrypted);
+ } catch (Exception $e) {
+ print $e;
+ return array(MyErrorCode::$EncryptAESError, null);
+ }
+ }
+
+ /**
+ * 解密
+ *
+ * @param $encrypted
+ * @param $receiveId
+ * @return array
+ */
+ public function decrypt($encrypted, $receiveId)
+ {
+ try {
+ //解密
+ if (function_exists('openssl_decrypt')) {
+ $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
+ } else {
+ $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->key, base64_decode($encrypted), MCRYPT_MODE_CBC, $this->iv);
+ }
+ } catch (Exception $e) {
+ return array(ErrorCode::$DecryptAESError, null);
+ }
+ try {
+ //删除PKCS#7填充
+ $pkc_encoder = new PKCS7Encoder;
+ $result = $pkc_encoder->decode($decrypted);
+ if (strlen($result) < 16) {
+ return array();
+ }
+ //拆分
+ $content = substr($result, 16, strlen($result));
+ $len_list = unpack('N', substr($content, 0, 4));
+ $xml_len = $len_list[1];
+ $xml_content = substr($content, 4, $xml_len);
+ $from_receiveId = substr($content, $xml_len + 4);
+ } catch (Exception $e) {
+ print $e;
+ return array(ErrorCode::$IllegalBuffer, null);
+ }
+ if ($from_receiveId != $receiveId) {
+ return array(ErrorCode::$ValidateCorpidError, null);
+ }
+ return array(0, $xml_content);
+ }
+
+ /**
+ * 生成随机字符串
+ *
+ * @return string
+ */
+ private function getRandomStr()
+ {
+ $str = '';
+ $str_pol = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyl';
+ $max = strlen($str_pol) - 1;
+ for ($i = 0; $i < 16; $i++) {
+ $str .= $str_pol[mt_rand(0, $max)];
+ }
+ return $str;
+ }
+}
diff --git a/application/common/wework/callback/SHA1.php b/application/common/wework/callback/SHA1.php
new file mode 100644
index 0000000..00fd1ad
--- /dev/null
+++ b/application/common/wework/callback/SHA1.php
@@ -0,0 +1,34 @@
+m_sToken = $token;
+ $this->m_sEncodingAesKey = $encodingAesKey;
+ $this->m_sReceiveId = $receiveId;
+ }
+
+ /*
+ *验证URL
+ *@param sMsgSignature: 签名串,对应URL参数的msg_signature
+ *@param sTimeStamp: 时间戳,对应URL参数的timestamp
+ *@param sNonce: 随机串,对应URL参数的nonce
+ *@param sEchoStr: 随机串,对应URL参数的echostr
+ *@param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
+ *@return:成功0,失败返回对应的错误码
+ */
+ public function VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, &$sReplyEchoStr)
+ {
+ if (strlen($this->m_sEncodingAesKey) != 43) {
+ return ErrorCode::$IllegalAesKey;
+ }
+
+ $pc = new Prpcrypt($this->m_sEncodingAesKey);
+ //verify msg_signature
+ $sha1 = new SHA1();
+ $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $sEchoStr);
+ $ret = $array[0];
+
+ if ($ret != 0) {
+ return $ret;
+ }
+
+ $signature = $array[1];
+ if ($signature != $sMsgSignature) {
+ return ErrorCode::$ValidateSignatureError;
+ }
+
+ $result = $pc->decrypt($sEchoStr, $this->m_sReceiveId);
+ if ($result[0] != 0) {
+ return $result[0];
+ }
+ $sReplyEchoStr = $result[1];
+
+ return ErrorCode::$OK;
+ }
+ /**
+ * 将公众平台回复用户的消息加密打包.
+ *
+ * - 对要发送的消息进行AES-CBC加密
+ * - 生成安全签名
+ * - 将消息密文和安全签名打包成xml格式
+ *
+ *
+ * @param $replyMsg string 公众平台待回复用户的消息,xml格式的字符串
+ * @param $timeStamp string 时间戳,可以自己生成,也可以用URL参数的timestamp
+ * @param $nonce string 随机串,可以自己生成,也可以用URL参数的nonce
+ * @param &$encryptMsg string 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
+ * 当return返回0时有效
+ *
+ * @return int 成功0,失败返回对应的错误码
+ */
+ public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, &$sEncryptMsg)
+ {
+ $pc = new Prpcrypt($this->m_sEncodingAesKey);
+
+ //加密
+ $array = $pc->encrypt($sReplyMsg, $this->m_sReceiveId);
+ $ret = $array[0];
+ if ($ret != 0) {
+ return $ret;
+ }
+
+ if ($sTimeStamp == null) {
+ $sTimeStamp = time();
+ }
+ $encrypt = $array[1];
+
+ //生成安全签名
+ $sha1 = new SHA1;
+ $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
+ $ret = $array[0];
+ if ($ret != 0) {
+ return $ret;
+ }
+ $signature = $array[1];
+
+ //生成发送的xml
+ $xmlparse = new XMLParse;
+ $sEncryptMsg = $xmlparse->generate($encrypt, $signature, $sTimeStamp, $sNonce);
+ return ErrorCode::$OK;
+ }
+
+
+ /**
+ * 检验消息的真实性,并且获取解密后的明文.
+ *
+ * - 利用收到的密文生成安全签名,进行签名验证
+ * - 若验证通过,则提取xml中的加密消息
+ * - 对消息进行解密
+ *
+ *
+ * @param $msgSignature string 签名串,对应URL参数的msg_signature
+ * @param $timestamp string 时间戳 对应URL参数的timestamp
+ * @param $nonce string 随机串,对应URL参数的nonce
+ * @param $postData string 密文,对应POST请求的数据
+ * @param &$msg string 解密后的原文,当return返回0时有效
+ *
+ * @return int 成功0,失败返回对应的错误码
+ */
+ public function DecryptMsg($sMsgSignature, $sTimeStamp = null, $sNonce, $sPostData, &$sMsg)
+ {
+ if (strlen($this->m_sEncodingAesKey) != 43) {
+ return ErrorCode::$IllegalAesKey;
+ }
+
+ $pc = new Prpcrypt($this->m_sEncodingAesKey);
+
+ //提取密文
+ $xmlparse = new XMLParse;
+ $array = $xmlparse->extract($sPostData);
+ $ret = $array[0];
+
+ if ($ret != 0) {
+ return $ret;
+ }
+
+ if ($sTimeStamp == null) {
+ $sTimeStamp = time();
+ }
+
+ $encrypt = $array[1];
+
+ //验证安全签名
+ $sha1 = new SHA1;
+ $array = $sha1->getSHA1($this->m_sToken, $sTimeStamp, $sNonce, $encrypt);
+ $ret = $array[0];
+
+ if ($ret != 0) {
+ return $ret;
+ }
+
+ $signature = $array[1];
+ if ($signature != $sMsgSignature) {
+ return ErrorCode::$ValidateSignatureError;
+ }
+
+ $result = $pc->decrypt($encrypt, $this->m_sReceiveId);
+ if ($result[0] != 0) {
+ return $result[0];
+ }
+ $sMsg = $result[1];
+
+ return ErrorCode::$OK;
+ }
+
+}
+
diff --git a/application/common/wework/callback/XMLParse.php b/application/common/wework/callback/XMLParse.php
new file mode 100644
index 0000000..d6f7fc3
--- /dev/null
+++ b/application/common/wework/callback/XMLParse.php
@@ -0,0 +1,61 @@
+loadXML($xmltext);
+ $array_e = $xml->getElementsByTagName('Encrypt');
+ $encrypt = $array_e->item(0)->nodeValue;
+ return array(0, $encrypt);
+ } catch (Exception $e) {
+ print $e . "\n";
+ return array(ErrorCode::$ParseXmlError, null);
+ }
+ }
+
+ /**
+ * 生成xml消息
+ * @param string $encrypt 加密后的消息密文
+ * @param string $signature 安全签名
+ * @param string $timestamp 时间戳
+ * @param string $nonce 随机字符串
+ */
+ public function generate($encrypt, $signature, $timestamp, $nonce)
+ {
+ $format = "
+
+
+%s
+
+";
+ return sprintf($format, $encrypt, $signature, $timestamp, $nonce);
+ }
+
+}
+
+//
+// Test
+/*
+$sPostData = "";
+$xmlparse = new XMLParse;
+$array = $xmlparse->extract($sPostData);
+var_dump($array);
+*/
+
+
+?>
diff --git a/application/crm/controller/Callback.php b/application/crm/controller/Callback.php
new file mode 100644
index 0000000..4538223
--- /dev/null
+++ b/application/crm/controller/Callback.php
@@ -0,0 +1,86 @@
+isPost()) {
+ $sReqMsgSig = Request::instance()->get('msg_signature');
+ $sReqTimeStamp = Request::instance()->get('timestamp');
+ $sReqNonce = Request::instance()->get('nonce');
+
+ $sReqData =Request::instance()->getContent();
+ $sMsg = ""; // 解析之后的明文
+
+ $errCode = $wxcpt->DecryptMsg($sReqMsgSig, $sReqTimeStamp, $sReqNonce, $sReqData, $sMsg);
+ if ($errCode == 0) {
+ // 解密成功,sMsg即为xml格式的明文
+ $simpleXMLElement = simplexml_load_string($sMsg,'SimpleXMLElement', LIBXML_NOCDATA);
+ switch ($simpleXMLElement->Event->__toString()) {
+ case 'change_external_contact':
+ $api = new Api(config('wework.corpId'), config('wework.corpSecret'));
+ $contactInfo = $api->contactInfo('wm9nLQEAAA6lshIXRN5xdd1iZjqevSyA');
+ foreach ($contactInfo['follow_user'] as $contactUserInfo) {
+ if ($contactUserInfo['userid'] == $simpleXMLElement->UserID->__toString()) {
+ $customerInfo = model('Customer')->where('name', $contactUserInfo['remark_corp_name'])->find();
+ if ($customerInfo) {
+ $contactsInfo = model('Contacts')->where([
+ 'name' => $contactUserInfo['remark'],
+ 'customer_id' => $customerInfo['customer_id']
+ ])->find();
+ if (!$contactsInfo) {
+ $param = [
+ 'business_id' => null,
+ 'create_user_id' => 1,
+ 'owner_user_id' => 1,
+ 'customer_id' => $customerInfo['customer_id'],
+ 'name' => $contactUserInfo['remark'],
+ 'mobile' => $contactUserInfo['remark_mobiles'][0],
+ ];
+ if (model('Contacts')->createData($param)) {
+ Log::record('联系人添加成功');
+ } else {
+ Log::record('联系人添加失败');
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ } else {
+ print("ERR: " . $errCode . "\n\n");
+ }
+ } else {
+ $sVerifyMsgSig = Request::instance()->get('msg_signature');
+ $sVerifyTimeStamp = Request::instance()->get('timestamp');
+ $sVerifyNonce = Request::instance()->get('nonce');
+ $sVerifyEchoStr = Request::instance()->get('echostr');
+ // 需要返回的明文
+ $sEchoStr = "";
+
+ $errCode = $wxcpt->VerifyURL($sVerifyMsgSig, $sVerifyTimeStamp, $sVerifyNonce, $sVerifyEchoStr, $sEchoStr);
+ if ($errCode == 0) {
+ echo ($sEchoStr);
+ // 验证URL成功,将sEchoStr返回
+ // HttpUtils.SetResponce($sEchoStr);
+ } else {
+ print("ERR: " . $errCode . "\n\n");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/application/crm/model/Contacts.php b/application/crm/model/Contacts.php
index 0f0e9b5..c90b3ff 100644
--- a/application/crm/model/Contacts.php
+++ b/application/crm/model/Contacts.php
@@ -313,13 +313,13 @@ class Contacts extends Common
$businessId = $param['business_id'];
unset($param['business_id']);
$fieldModel = new \app\admin\model\Field();
-
+
// 数据验证
- $validateResult = $this->fieldDataValidate($param, $this->name, $param['create_user_id']);
- if (!empty($validateResult)) {
- $this->error = $validateResult;
- return false;
- }
+// $validateResult = $this->fieldDataValidate($param, $this->name, $param['create_user_id']);
+// if (!empty($validateResult)) {
+// $this->error = $validateResult;
+// return false;
+// }
# 处理客户首要联系人
$primaryStatus = Db::name('crm_contacts')->where('customer_id', $param['customer_id'])->value('contacts_id');
diff --git a/config/config.php b/config/config.php
index 86051c5..a53cef8 100644
--- a/config/config.php
+++ b/config/config.php
@@ -4,6 +4,8 @@
// +----------------------------------------------------------------------
// | Author:
// +----------------------------------------------------------------------
+use think\Env;
+
error_reporting(E_ERROR | E_WARNING | E_PARSE);
return [
@@ -183,7 +185,7 @@ return [
// 驱动方式
'type' => 'redis',
// 连接地址
- 'host' => '127.0.0.1',
+ 'host' => Env::get('cache_host','127.0.0.1'),
// 端口
'port' => 6379,
// 密码
@@ -271,5 +273,12 @@ return [
// 商机状态组列表缓存时间(秒)
'business_status_cache_time' => 1800,
+ 'wework' => [
+ 'corpId' => Env::get('wework_corpId',''),
+ 'corpSecret' => Env::get('wework_corpSecret',''),
+ 'token' => Env::get('wework_token',''),
+ 'encodingAesKey' => Env::get('wework_encodingAesKey',''),
+ ],
+
'public_key' => 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkqKFcAQtIp4rlkB5LOMnViyVY/hhA6x0R9ftwtEXsAFu4hBZrm9txdEvxSrDCUsx3Zwv/gdimeOzTtfSKffdoE/DwllNP9Zu6nsr2kGRgPrRwjtlO+j2FOM0b9UY1SQ/bWE+a9oQL2jL9xMSbtX1xG/+HcMo1bT+pa6FNQzs3egmvMt75/jaxINPSraj4kgNFawSBk7qDBEqDYiQwtPTuaNW1YZIs++/gZHsCRgGs/JrAbxNpl7+v/+Z503I3I2rs/8eUM5d16NXR8M7vtobUDCTIiQOgRahO8WMadgFlwavyVCYhy/TBXyj5RUfWaS26LrEN3vkj4TjoJu5m9LQ5QIDAQAB',
];
diff --git a/config/database.php b/config/database.php
index 8c67704..ac67535 100644
--- a/config/database.php
+++ b/config/database.php
@@ -3,13 +3,13 @@ return [
// 数据库类型
'type' => 'mysql',
// 服务器地址
- 'hostname' => '127.0.0.1',
+ 'hostname' => 'mysql',
// 数据库名
- 'database' => 'test_com',
+ 'database' => 'wkcrm',
// 用户名
- 'username' => 'test_com',
+ 'username' => 'wkcrm',
// 密码
- 'password' => 'YNKdaMS2XBHKcAh7',
+ 'password' => 'lpC049ZvZdmNIzDvbY0rXKgl',
// 端口
'hostport' => '3306',
// 连接dsn
diff --git a/config/route_crm.php b/config/route_crm.php
index 4a4da78..973af3a 100644
--- a/config/route_crm.php
+++ b/config/route_crm.php
@@ -522,6 +522,10 @@ return [
'crm/setting/appMenuConfig' => ['crm/setting/appMenuConfig', ['method' => 'POST']],
//办公数量
'crm/setting/oaNumber' => ['crm/setting/oaNumber', ['method' => 'POST']],
+
+ // 企业微信回调
+ 'crm/callback/index' => ['crm/callback/index', ['method' => 'POST|GET']],
+
// MISS路由
'__miss__' => 'admin/base/miss',
];