AliyunOcrClass.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. namespace bizGhs\lakala\classes;
  3. use AlibabaCloud\Client\AlibabaCloud;
  4. use AlibabaCloud\Client\Exception\ClientException;
  5. use AlibabaCloud\Client\Exception\ServerException;
  6. use common\components\util;
  7. use Yii;
  8. class AliyunOcrClass
  9. {
  10. public static function recognize($id, $mainId, $shopId, $imgType, $imageUrl)
  11. {
  12. $application = LakalaApplicationClass::getByCondition([
  13. 'id' => $id,
  14. 'mainId' => $mainId,
  15. 'shopId' => $shopId,
  16. ]);
  17. if (empty($application)) {
  18. util::fail('没有找到申请记录');
  19. }
  20. try {
  21. $response = self::request($imgType, self::formatImageUrl($imageUrl));
  22. } catch (ClientException $e) {
  23. Yii::warning('阿里云OCR客户端异常:' . $e->getErrorMessage(), __METHOD__);
  24. util::fail('OCR识别失败,请稍后重试');
  25. } catch (ServerException $e) {
  26. Yii::warning('阿里云OCR服务端异常:' . $e->getErrorMessage(), __METHOD__);
  27. util::fail('OCR识别失败,请稍后重试');
  28. } catch (\Exception $e) {
  29. Yii::warning('阿里云OCR异常:' . $e->getMessage(), __METHOD__);
  30. util::fail('OCR识别失败,请稍后重试');
  31. }
  32. return [
  33. 'status' => '00',
  34. 'result' => self::formatResult($imgType, $response),
  35. ];
  36. }
  37. private static function request($imgType, $imageUrl)
  38. {
  39. $accessKeyId = getenv('OCR_ACCESS_KEY_ID');
  40. $accessKeySecret = getenv('OCR_ACCESS_KEY_SECRET');
  41. AlibabaCloud::accessKeyClient(
  42. $accessKeyId,
  43. $accessKeySecret
  44. )->regionId('cn-shanghai')->asDefaultClient();
  45. $action = 'RecognizeCharacter';
  46. $query = [
  47. 'RegionId' => 'cn-shanghai',
  48. 'ImageURL' => $imageUrl,
  49. ];
  50. if ($imgType === 'ID_CARD_FRONT') {
  51. $action = 'RecognizeIdentityCard';
  52. $query['Side'] = 'face';
  53. } elseif ($imgType === 'ID_CARD_BEHIND') {
  54. $action = 'RecognizeIdentityCard';
  55. $query['Side'] = 'back';
  56. } elseif ($imgType === 'BANK_CARD') {
  57. $action = 'RecognizeBankCard';
  58. }
  59. return AlibabaCloud::rpc()
  60. ->product('ocr')
  61. ->version('2019-12-30')
  62. ->action($action)
  63. ->method('POST')
  64. ->options([
  65. 'query' => $query,
  66. ])
  67. ->request()
  68. ->toArray();
  69. }
  70. private static function formatImageUrl($imageUrl)
  71. {
  72. $imageUrl = trim((string)$imageUrl);
  73. $host = parse_url($imageUrl, PHP_URL_HOST);
  74. if ($host && strpos($host, '.aliyuncs.com') !== false) {
  75. return $imageUrl;
  76. }
  77. $objectPath = $imageUrl;
  78. if (strpos($imageUrl, 'http://') === 0 || strpos($imageUrl, 'https://') === 0) {
  79. $objectPath = parse_url($imageUrl, PHP_URL_PATH);
  80. }
  81. $objectPath = ltrim((string)$objectPath, '/');
  82. if ($objectPath === '') {
  83. util::fail('图片地址无效');
  84. }
  85. return sprintf(
  86. 'https://%s.%s/%s',
  87. Yii::$app->params['ossBucket'],
  88. Yii::$app->params['endpoint'],
  89. $objectPath
  90. );
  91. }
  92. private static function formatResult($imgType, array $response)
  93. {
  94. $data = self::decodeData($response);
  95. $lines = self::extractLines($data);
  96. $text = implode("\n", $lines);
  97. $result = [
  98. 'text' => $text,
  99. 'lines' => $lines,
  100. ];
  101. if ($imgType === 'ID_CARD_FRONT') {
  102. $result = array_merge($result, self::parseIdCardFront($text, $data));
  103. } elseif ($imgType === 'ID_CARD_BEHIND') {
  104. $result = array_merge($result, self::parseIdCardBehind($text, $data));
  105. } elseif ($imgType === 'BANK_CARD') {
  106. $result = array_merge($result, self::parseBankCard($text, $lines, $data));
  107. }
  108. return $result;
  109. }
  110. private static function decodeData(array $response)
  111. {
  112. $data = isset($response['Data']) ? $response['Data'] : $response;
  113. if (is_string($data)) {
  114. $decoded = json_decode($data, true);
  115. return is_array($decoded) ? $decoded : ['text' => $data];
  116. }
  117. return $data;
  118. }
  119. private static function extractLines($data)
  120. {
  121. $lines = [];
  122. self::collectLines($data, $lines);
  123. $lines = array_values(array_unique(array_filter(array_map('trim', $lines))));
  124. return $lines;
  125. }
  126. private static function collectLines($data, array &$lines)
  127. {
  128. if (is_string($data)) {
  129. $lines[] = $data;
  130. return;
  131. }
  132. if (!is_array($data)) {
  133. return;
  134. }
  135. foreach (['word', 'words', 'content', 'text', 'value', 'textContent'] as $key) {
  136. if (isset($data[$key]) && is_string($data[$key])) {
  137. $lines[] = $data[$key];
  138. }
  139. }
  140. foreach ($data as $value) {
  141. self::collectLines($value, $lines);
  142. }
  143. }
  144. private static function parseIdCardFront($text, array $data)
  145. {
  146. $result = [];
  147. $name = self::findValue($data, ['Name', 'name', '姓名']);
  148. if ($name !== '') {
  149. $result['name'] = $name;
  150. }
  151. $idNumber = self::findValue($data, ['IDNumber', 'IdNumber', 'idNumber', 'CardNumber', '公民身份号码', '身份证号']);
  152. if ($idNumber !== '') {
  153. $result['id_number'] = strtoupper($idNumber);
  154. }
  155. if (preg_match('/姓名\s*([\x{4e00}-\x{9fa5}·]{2,20})/u', $text, $match)) {
  156. $result['name'] = $result['name'] ?? $match[1];
  157. }
  158. if (preg_match('/\d{17}[\dXx]/', $text, $match)) {
  159. $result['id_number'] = $result['id_number'] ?? strtoupper($match[0]);
  160. }
  161. return $result;
  162. }
  163. private static function parseIdCardBehind($text, array $data)
  164. {
  165. $result = [];
  166. $startDate = self::findValue($data, ['StartDate', 'startDate', 'ValidStartDate', '有效期限起始日期']);
  167. $endDate = self::findValue($data, ['EndDate', 'endDate', 'ValidEndDate', '有效期限结束日期']);
  168. if ($startDate !== '' && $endDate !== '') {
  169. $result['validity'] = self::normalizeDate($startDate) . '-' . self::normalizeDate($endDate);
  170. }
  171. if (preg_match_all('/\d{4}[.年-]\d{1,2}[.月-]\d{1,2}日?/', $text, $matches) && count($matches[0]) >= 2) {
  172. $result['validity'] = $result['validity'] ?? (self::normalizeDate($matches[0][0]) . '-' . self::normalizeDate($matches[0][1]));
  173. } elseif (strpos($text, '长期') !== false && preg_match('/\d{4}[.年-]\d{1,2}[.月-]\d{1,2}日?/', $text, $match)) {
  174. $result['validity'] = $result['validity'] ?? (self::normalizeDate($match[0]) . '-9999-12-31');
  175. }
  176. return $result;
  177. }
  178. private static function parseBankCard($text, array $lines, array $data)
  179. {
  180. $result = [];
  181. $cardNumber = self::findValue($data, ['CardNumber', 'cardNumber', 'BankCardNumber', 'bankCardNumber', '银行卡号']);
  182. if ($cardNumber !== '') {
  183. $result['card_number'] = preg_replace('/\s+/', '', $cardNumber);
  184. $result['bank_card_number'] = $result['card_number'];
  185. }
  186. $bankName = self::findValue($data, ['BankName', 'bankName', '银行名称', '银行']);
  187. if ($bankName !== '') {
  188. $result['bank_name'] = $bankName;
  189. }
  190. $numberText = preg_replace('/\s+/', '', $text);
  191. if (preg_match('/\d{12,30}/', $numberText, $match)) {
  192. $result['card_number'] = $result['card_number'] ?? $match[0];
  193. $result['bank_card_number'] = $result['bank_card_number'] ?? $match[0];
  194. }
  195. foreach ($lines as $line) {
  196. if (strpos($line, '银行') !== false) {
  197. $result['bank_name'] = $result['bank_name'] ?? $line;
  198. break;
  199. }
  200. }
  201. return $result;
  202. }
  203. private static function normalizeDate($date)
  204. {
  205. if (preg_match('/(\d{4})[.年-](\d{1,2})[.月-](\d{1,2})日?/', $date, $match)) {
  206. return sprintf('%04d-%02d-%02d', $match[1], $match[2], $match[3]);
  207. }
  208. return $date;
  209. }
  210. private static function findValue(array $data, array $keys)
  211. {
  212. foreach ($data as $key => $value) {
  213. if (in_array($key, $keys, true) && (is_string($value) || is_numeric($value))) {
  214. return trim((string)$value);
  215. }
  216. if (is_array($value)) {
  217. $found = self::findValue($value, $keys);
  218. if ($found !== '') {
  219. return $found;
  220. }
  221. }
  222. }
  223. return '';
  224. }
  225. }