Browse Source

得润宝

cqp 1 week ago
parent
commit
b2652597e0

+ 68 - 0
app/Http/Controllers/Api/DingTalkController.php

@@ -2,9 +2,11 @@
 
 namespace App\Http\Controllers\Api;
 
+use App\Service\DingTalkCrypto;
 use App\Service\DrbService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
 
 class DingTalkController extends BaseController
 {
@@ -56,4 +58,70 @@ class DingTalkController extends BaseController
             return $this->json_return(201,$data);
         }
     }
+
+    /**
+     * 钉钉事件回调
+     */
+    public function callback(Request $request)
+    {
+        $token  = config('dingtalk.token');    // 钉钉事件订阅 token
+        $aesKey = config('dingtalk.aes_key');  // 钉钉事件订阅 aes_key
+
+        $crypt = new DingTalkCrypto($token, $aesKey);
+
+        // 1. 获取钉钉传来的参数
+        $signature = $request->get('signature');
+        $timestamp = $request->get('timestamp');
+        $nonce     = $request->get('nonce');
+        $encrypt   = $request->input('encrypt');
+
+        // 2. 解密
+        $event = $crypt->decryptMsg($signature, $timestamp, $nonce, $encrypt);
+        if ($event === false) {
+            Log::error('钉钉解密失败', [
+                'signature' => $signature,
+                'timestamp' => $timestamp,
+                'nonce'     => $nonce,
+                'encrypt'   => $encrypt
+            ]);
+            return response()->json(['errcode' => 1, 'errmsg' => '解密失败']);
+        }
+
+        Log::info('钉钉解密后的事件数据', $event);
+
+        // 3. 验证 challenge
+        if (isset($event['EventType']) && $event['EventType'] === 'check_url') {
+            $encryptMsg = '';
+            $crypt->encryptMsg($event['Random'], $timestamp, $nonce, $encryptMsg);
+            return response()->json($encryptMsg);
+        }
+
+        // 4. 处理事件
+        if (isset($event['EventType'])) {
+            switch ($event['EventType']) {
+                case 'bpms_instance_change': // 审批实例变更
+                    $processInstanceId = $event['processInstanceId'] ?? null;
+                    $result            = $event['result'] ?? null; // agree / oppose / terminate
+                    Log::info("审批实例变更事件", compact('processInstanceId', 'result'));
+                    // TODO: 在这里执行你的业务逻辑,例如更新数据库状态
+                    break;
+
+                case 'bpms_task_change': // 审批任务变更
+                    $processInstanceId = $event['processInstanceId'] ?? null;
+                    $taskId            = $event['taskId'] ?? null;
+                    $result            = $event['result'] ?? null; // agree / oppose
+                    Log::info("审批任务变更事件", compact('processInstanceId', 'taskId', 'result'));
+                    // TODO: 在这里执行你的业务逻辑,例如记录审批人操作
+                    break;
+
+                default:
+                    Log::warning("未处理的事件类型", ['EventType' => $event['EventType']]);
+            }
+        }
+
+        // 5. 返回 success(加密)
+        $resEncrypt = $crypt->encryptMsg('success', $timestamp, $nonce);
+
+        return response()->json($resEncrypt);
+    }
 }

+ 13 - 0
app/Model/Record.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace App\Model;
+
+use Illuminate\Database\Eloquent\Model;
+
+class Record extends Model
+{
+    protected $table = "record"; //指定表
+    const CREATED_AT = 'crt_time';
+    const UPDATED_AT = 'upd_time';
+    protected $dateFormat = 'U';
+}

+ 132 - 0
app/Service/DingTalkCrypto.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace App\Service;
+
+class DingTalkCrypto
+{
+    protected $token;
+    protected $encodingAesKey;  // URL safe base64 字符串,长度 43
+    // protected $corpid; // 如果需要
+
+    public function __construct(string $token, string $encodingAesKey)
+    {
+        $this->token = $token;
+        $this->encodingAesKey = $encodingAesKey;
+    }
+
+    /**
+     * 验签 + 解密消息体
+     *
+     * @param string $signature 从请求 query 参数 signature
+     * @param string $timestamp 从请求参数 timestamp
+     * @param string $nonce 从请求参数 nonce
+     * @param string $encrypt 消息体中的 encrypt 字段
+     * @return array|false 解密后的数组,失败返回 false
+     */
+    public function decryptMsg(string $signature, string $timestamp, string $nonce, string $encrypt)
+    {
+        // 1. 验签
+        $tmpArr = [$this->token, $timestamp, $nonce, $encrypt];
+        sort($tmpArr, SORT_STRING);
+        $tmpStr = implode($tmpArr);
+        $hash = sha1($tmpStr);
+        if ($hash !== $signature) {
+            return false;
+        }
+
+        // 2. 解密
+        $aesKey = base64_decode($this->encodingAesKey . '=');  // 注意要加一个 '=' 补足 padding
+        $iv = substr($aesKey, 0, 16);
+
+        // 解密,使用 AES-256-CBC
+        $cipherText = base64_decode($encrypt);
+        $decrypted = openssl_decrypt($cipherText, 'AES-256-CBC', $aesKey, OPENSSL_ZERO_PADDING, $iv);
+        if ($decrypted === false) {
+            return false;
+        }
+
+        // 去掉 padding, PKCS#7
+        $decrypted = $this->pkcs7Unpad($decrypted);
+
+        // 解出: 16 位随机字符串 + 4 字节内容长度 network-order + 内容 + corpId(如果有的话)
+        // 跳过前 16 位
+        $content = substr($decrypted, 16);
+        // 读长度
+        $lenList = unpack("N", substr($content, 0, 4));
+        $xmlLen = $lenList[1];
+        $xmlContent = substr($content, 4, $xmlLen);
+        // 如果有 corpId 在 xmlContent 后还有
+        // $fromCorpId = substr($content, 4 + $xmlLen);
+
+        $json = $xmlContent;  // 钉钉回调 body 是 JSON 字符串
+        // 解析
+        $arr = json_decode($json, true);
+        return $arr;
+    }
+
+    /**
+     * 加密回复消息,比如返回 “success”
+     *
+     * @param string $replyMsg 明文消息,比如 "success"
+     * @param string $timestamp
+     * @param string $nonce
+     * @return array 包含加密后json结构
+     */
+    public function encryptMsg(string $replyMsg, string $timestamp, string $nonce)
+    {
+        // 1. 构造消息内容
+        $random16 = $this->getRandomStr(16);
+        $replyMsgXml = $replyMsg;  // 钉钉回调用 JSON 格式内容
+        $msgLen = strlen($replyMsgXml);
+        $msgLenBin = pack("N", $msgLen);
+        $corpId = '';  // 如果不需要 corpId,可以空
+
+        $plain = $random16 . $msgLenBin . $replyMsgXml . $corpId;
+
+        // 2. PKCS#7 padding
+        $blockSize = 32;
+        $pad = $blockSize - (strlen($plain) % $blockSize);
+        if ($pad == 0) {
+            $pad = $blockSize;
+        }
+        $plainPadded = $plain . str_repeat(chr($pad), $pad);
+
+        // 3. AES 加密
+        $aesKey = base64_decode($this->encodingAesKey . '=');
+        $iv = substr($aesKey, 0, 16);
+        $encrypted = openssl_encrypt($plainPadded, 'AES-256-CBC', $aesKey, OPENSSL_ZERO_PADDING, $iv);
+        $encryptBase64 = base64_encode($encrypted);
+
+        // 4. 签名
+        $signatureArr = [$this->token, $timestamp, $nonce, $encryptBase64];
+        sort($signatureArr, SORT_STRING);
+        $signature = sha1(implode($signatureArr));
+
+        // 5. 返回结构
+        return [
+            'msg_signature' => $signature,
+            'encrypt'       => $encryptBase64
+        ];
+    }
+
+    private function getRandomStr($length = 16)
+    {
+        $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        $str = '';
+        for ($i = 0; $i < $length; $i++) {
+            $str .= $chars[ord(bin2hex(random_bytes(1))) % strlen($chars)];
+        }
+        return $str;
+    }
+
+    private function pkcs7Unpad($text)
+    {
+        // 去掉结尾的 padding
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > 32) {
+            // padding 有问题
+            return false;
+        }
+        return substr($text, 0, -$pad);
+    }
+}

+ 205 - 62
app/Service/DrbService.php

@@ -2,6 +2,7 @@
 
 namespace App\Service;
 
+use App\Model\Record;
 use Illuminate\Support\Facades\Redis;
 
 class DrbService extends Service
@@ -74,12 +75,7 @@ class DrbService extends Service
         return [true, $res];
     }
 
-    private function getManDetail($user){
-        // 1. 获取 access_token
-        [$success, $tokenData] = $this->getAccessToken();
-        if (! $success) return [false, $tokenData]; // tokenData 是错误信息
-        $accessToken = $tokenData['access_token'];
-
+    private function getManDetail($user, $accessToken){
         // 3. 根据 userid 获取详细用户信息(包括部门)
         $urlDetail = "https://oapi.dingtalk.com/topapi/v2/user/get?access_token={$accessToken}";
         $respDetail = $this->curlOpen1($urlDetail, [
@@ -101,25 +97,61 @@ class DrbService extends Service
     {
         if(empty($data['type'])) return [false, '单据类型不能为空'];
         $type = $data['type'];
-        $userId = $user['userId'];
-        [$success, $userDetail] = $this->getManDetail($user);
-        if(!$success) return [false, $userDetail];
+        if(empty($data['order_number'])) return [false,'订单号不能为空'];
+        [$success, $msg] = $this->checkCreateProcessInstance($data, $user);
+        if(! $success) return [false, $msg];
+
+        //获取模板id
+        $code = $this->getModelCode($type);
+        //获取模板数据
+        [$success, $formData] = $this->getFormData($data, $user);
+        if(! $success) return [false, $formData];
 
         // 1. 获取 access_token
         [$success, $tokenData] = $this->getAccessToken();
         if (!$success) return [false, $tokenData];
         $accessToken = $tokenData['access_token'];
 
-        //获取模板id
-        $code = $this->getModelCode($type);
-        //获取模板数据
-        $formData = $this->getFormData($data);
+        $userId = $user['userId'];
+        [$success, $userDetail] = $this->getManDetail($user, $accessToken);
+        if(!$success) return [false, $userDetail];
 
         //创建审批
         [$success, $msg] = $this->createFlow($accessToken, $code, $userId, $userDetail, $formData);
         if(! $success) return [false, $msg];
 
         //记录信息
+        $this->recordDatabase($data, $user, $msg);
+
+        return [true, ''];
+    }
+
+    private function recordDatabase($data, $user, $process_instance_id){
+        $type = $data['type'];
+
+        Record::insert([
+            'type' => $type,
+            'database' => $user['zt_database'],
+            'order_number'=> $data['order_number'],
+            'crt_time' => time(),
+            'process_instance_id' => $process_instance_id
+        ]);
+
+        return [true, ''];
+    }
+
+    private function checkCreateProcessInstance($data, $user){
+        list($status,$msg) = $this->limitingSendRequestBackgExpire($data['order_number'].$data['type'].$user['zt_database']);
+        if(! $status) return [false,$msg];
+
+        $type = $data['type'];
+
+        $bool = Record::where('del_time',0)
+            ->where('type', $type)
+            ->where('database', $user['zt_database'])
+            ->where('order_number',$data['order_number'])
+            ->exists();
+        if($bool) return [false, '单号' . $data['order_number'] . '已创建审批流'];
 
         return [true, ''];
     }
@@ -132,7 +164,7 @@ class DrbService extends Service
         $payload = [
             "process_code" => $code,            // 审批模板编码
             "originator_user_id" => $userId,    // 发起人 userId
-            "dept_id" => $userDetail['dept_id_list'],               // 发起人部门 ID
+            "dept_id" => $userDetail['dept_id_list'][0],               // 发起人部门 ID
             "form_component_values" => $formData, // 表单数据
         ];
 
@@ -173,78 +205,189 @@ class DrbService extends Service
         return $code;
     }
 
-    private function getFormData($data){
-        $model = [];
+    private function getFormData($data, $user){
+        //cs
+//        $formData = [ [ "name" => "订单日期", "value" => "2025-09-23" ], [ "name" => "订单编号", "value" => "PO20250923001" ], [ "name" => "业务类型", "value" => "标准采购" ], [ "name" => "供应商", "value" => "XX供应商有限公司" ], [ "name" => "制单人", "value" => "陈庆鹏" ], [ "name" => "表体", "value" => json_encode([ [ [ "name" => "存货名称", "value" => "打印机" ], [ "name" => "数量", "value" => "2" ], [ "name" => "主计量单位", "value" => "台" ], [ "name" => "原币价税合计", "value" => "3000" ] ], [ [ "name" => "存货名称", "value" => "显示器" ], [ "name" => "数量", "value" => "5" ], [ "name" => "主计量单位", "value" => "个" ], [ "name" => "原币价税合计", "value" => "5000" ] ] ], JSON_UNESCAPED_UNICODE) ] ];
+//        return [true, $formData];
+        //cs
+        $service = new U8ServerService($user);
+        $error = $service->getError();
+        if(! empty($error)) return [false, $error];
+        [$success, $order] = $service->getOrderDetails($data, $user);
+        if(! $success) return [false, $order];
 
+        $type = $data['type'];
+        if($type == 1){
+            // 采购单
+            $formData = $this->typeOne($order);
+        }elseif ($type == 2){
+            // 请购单
+            $formData = $this->typeTwo($order);
+        }else{
+            // 付款单
+            $formData = $this->typeThree($order);
+        }
+        if(empty($formData)) return [false, '审批参数不能为空'];
+
+        return [true, $formData];
+    }
+
+    private function typeOne($userOrder){
+        if (empty($userOrder)) return [];
         $formData = [
             [
                 "name"  => "订单日期",
-                "value" => "2025-09-23" // 日期型字段,字符串 yyyy-MM-dd
+                "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
             ],
             [
                 "name"  => "订单编号",
-                "value" => "PO20250923001"
+                "value" => $userOrder['order_number'] ?? ''
             ],
             [
                 "name"  => "业务类型",
-                "value" => "标准采购"
+                "value" => $userOrder['business_type'] ?? ''
             ],
             [
                 "name"  => "供应商",
-                "value" => "XX供应商有限公司"
+                "value" => $userOrder['supplier_title'] ?? ''
             ],
             [
                 "name"  => "制单人",
-                "value" => "陈庆鹏"
+                "value" => $userOrder['crt_name'] ?? ''
             ],
             [
-                "name"  => "表格",
-                "value" => json_encode([
-                    [
-                        [
-                            "name"  => "存货名称",
-                            "value" => "打印机"
-                        ],
-                        [
-                            "name"  => "数量",
-                            "value" => "2"
-                        ],
-                        [
-                            "name"  => "主计量单位",
-                            "value" => "台"
-                        ],
-                        [
-                            "name"  => "原币价税合计",
-                            "value" => "3000"
-                        ]
-                    ],
-                    [
-                        [
-                            "name"  => "存货名称",
-                            "value" => "显示器"
-                        ],
-                        [
-                            "name"  => "数量",
-                            "value" => "5"
-                        ],
-                        [
-                            "name"  => "主计量单位",
-                            "value" => "个"
-                        ],
-                        [
-                            "name"  => "原币价税合计",
-                            "value" => "5000"
-                        ]
-                    ]
-                ], JSON_UNESCAPED_UNICODE) // 表格必须是字符串化的 JSON 数组
+                "name"  => "表体", // 对应 TableField 的 label
+                "value" => json_encode(
+                    array_map(function($item){
+                        return [
+                            [
+                                "name"  => "存货名称",
+                                "value" => $item['product_title'] ?? ''
+                            ],
+                            [
+                                "name"  => "数量",
+                                "value" => $item['quantity'] ?? ''
+                            ],
+                            [
+                                "name"  => "主计量", // 修改这里,对应模板字段
+                                "value" => $item['unit_title'] ?? ''
+                            ],
+                            [
+                                "name"  => "原币价税合计",
+                                "value" => $item['amount'] ?? ''
+                            ]
+                        ];
+                    }, $userOrder['detail'] ?? []),
+                    JSON_UNESCAPED_UNICODE
+                )
             ]
         ];
 
-        return $model;
+        return $formData;
     }
 
-    private function recordDatabase($data, $user, $process_instance_id){
+    private function typeTwo($userOrder){
+        if (empty($userOrder)) return [];
+        $formData = [
+            [
+                "name"  => "单据号",
+                "value" => $userOrder['order_number'] ?? ''
+            ],
+            [
+                "name"  => "日期",
+                "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
+            ],
+            [
+                "name"  => "业务类型",
+                "value" => $userOrder['business_type'] ?? ''
+            ],
+            [
+                "name"  => "请购人",
+                "value" => $userOrder['purchase_name'] ?? ''
+            ],
+            [
+                "name"  => "制单人",
+                "value" => $userOrder['crt_name'] ?? ''
+            ],
+            [
+                "name"  => "表体", // 对应 TableField 的 label
+                "value" => json_encode(
+                    array_map(function($item){
+                        return [
+                            [
+                                "name"  => "存货名称",
+                                "value" => $item['product_title'] ?? ''
+                            ],
+                            [
+                                "name"  => "数量",
+                                "value" => $item['quantity'] ?? ''
+                            ],
+                            [
+                                "name"  => "主计量", // 修改这里,对应模板字段
+                                "value" => $item['unit_title'] ?? ''
+                            ],
+                            [
+                                "name"  => "要求到货日期",
+                                "value" => $item['need_arrived_date'] ?? ''
+                            ]
+                        ];
+                    }, $userOrder['detail'] ?? []),
+                    JSON_UNESCAPED_UNICODE
+                )
+            ]
+        ];
+
+        return $formData;
+    }
+
+    private function typeThree($userOrder){
+        if (empty($userOrder)) return [];
+        $formData = [
+            [
+                "name"  => "单据编号",
+                "value" => $userOrder['order_number'] ?? ''
+            ],
+            [
+                "name"  => "日期",
+                "value" => date('Y-m-d', strtotime($userOrder['order_date'] ?? ''))
+            ],
+            [
+                "name"  => "供应商",
+                "value" => $userOrder['supplier_title'] ?? ''
+            ],
+            [
+                "name"  => "制单人",
+                "value" => $userOrder['crt_name'] ?? ''
+            ],
+            [
+                "name"  => "表体", // 对应 TableField 的 label
+                "value" => json_encode(
+                    array_map(function($item){
+                        return [
+                            [
+                                "name"  => "来源",
+                                "value" => $item['source'] ?? ''
+                            ],
+                            [
+                                "name"  => "来源单据号",
+                                "value" => $item['source_order_number'] ?? ''
+                            ],
+                            [
+                                "name"  => "主计量", // 修改这里,对应模板字段
+                                "value" => $item['unit_title'] ?? ''
+                            ],
+                            [
+                                "name"  => "申请金额",
+                                "value" => $item['amount'] ?? ''
+                            ]
+                        ];
+                    }, $userOrder['detail'] ?? []),
+                    JSON_UNESCAPED_UNICODE
+                )
+            ]
+        ];
 
+        return $formData;
     }
 
     public function getTemplateFields($data)

+ 22 - 5
app/Service/U8ServerService.php

@@ -63,7 +63,8 @@ class U8ServerService extends Service
 
         $order = $this->databaseService->table('PU_AppVouch as a')
             ->leftJoin('Person as c', 'c.cPersonCode', 'a.cPersonCode')
-            ->where('a.cMaker',$user['username'])
+//            ->where('a.cMaker',$user['username'])
+//            ->where('a.iverifystateex',0)
             ->select('a.cMaker as crt_name', 'c.cPersonName as purchase_name','a.cBusType as business_type','a.cCode as order_number','a.dDate as order_date','a.ID as id')
             ->first();
         if(empty($order)) return [false, '采购请购单不存在'];
@@ -98,8 +99,8 @@ class U8ServerService extends Service
 
         $order = $this->databaseService->table('PO_Pomain as a')
             ->leftJoin('Vendor as c', 'c.cVenCode', 'a.cVenCode')
-            ->where('a.cMaker',$user['username'])
-            ->where('a.iverifystateex',0)
+//            ->where('a.cMaker',$user['username'])
+//            ->where('a.iverifystateex',0)
             ->select('a.cMaker as crt_name', 'a.cBusType as business_type','a.cPOID as order_number','a.dPODate as order_date','c.cVenName as supplier_title','a.POID as id')
             ->first();
         if(empty($order)) return [false, '采购单不存在'];
@@ -134,8 +135,8 @@ class U8ServerService extends Service
 
         $order = $this->databaseService->table('AP_ApplyPayVouch as a')
             ->leftJoin('Vendor as c', 'c.cVenCode', 'a.cDwCode')
-            ->where('a.cOperator',$user['username'])
-            ->whereNull('a.cCheckMan')
+//            ->where('a.cOperator',$user['username'])
+//            ->whereNull('a.cCheckMan')
             ->select('a.cOperator as crt_name', 'c.cVenName as supplier_title','a.cVouchID as order_number','a.dVouchDate as order_date','a.PID as id')
             ->first();
         if(empty($order)) return [false, '付款单不存在'];
@@ -149,4 +150,20 @@ class U8ServerService extends Service
 
         return [true, $order];
     }
+
+    public function getOrderDetails($data,$user){
+        $type = $data['type'];
+        if($type == 1){
+            // 采购单
+            [$success, $order] = $this->purchaseOrderDetail($data,$user);
+        }elseif ($type == 2){
+            // 请购单
+            [$success, $order] = $this->purchaseRequisitionDetail($data,$user);
+        }else{
+            // 付款单
+            [$success, $order] = $this->paymentOrderDetail($data,$user);
+        }
+
+        return [$success, $order];
+    }
 }

+ 3 - 1
config/dingtalk.php

@@ -5,6 +5,8 @@ return [
     'app_secret' => 'Ct2Z35_xbfu6nGuzMcx6PyAoMXFLdrWnJtxkeXHV--t1hhq287ja9CKrNoB6vvGQ',
     'corpid' => 'ding6cc81583b8b32dc724f2f5cc6abecb85',
     'SSOsecret' => '',
-    'apitoken' => '51826be785e23496a533605d9a66f839'
+    'apitoken' => '51826be785e23496a533605d9a66f839',
+    'aes_key' => 'qSQls2WxoqWgkzGDXiLcdcCLI2bMJGmC2Ux8MrGGTf9',
+    'token' => 'DFFi1H9j3Bz5RSqV6M8s9AUqjJp5HSEiMRD9UJKaXEYQgPVgR3',
 ];
 

+ 2 - 0
routes/api.php

@@ -28,6 +28,8 @@ Route::any('getAccessToken','Api\DingTalkController@getAccessToken');
 Route::any('getUserByCode','Api\DingTalkController@getUserByCode');
 Route::any('getTemplateFields','Api\DingTalkController@getTemplateFields');
 
+Route::any('dinCallback','Api\DingTalkController@dinCallback');
+
 Route::group(['middleware'=> ['checkLogin']],function ($route){
     //文件上传统一方法
     $route->any('uploadFile', 'Api\FileUploadController@uploadFile');