payment-voucher-recognition.service.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { Injectable, inject } from '@angular/core';
  2. import { HttpClient } from '@angular/common/http';
  3. import { Observable, of } from 'rxjs';
  4. import { catchError, map } from 'rxjs/operators';
  5. // 支付凭证识别结果接口
  6. export interface PaymentVoucherRecognitionResult {
  7. success: boolean;
  8. amount?: number;
  9. paymentDate?: Date;
  10. paymentMethod?: string;
  11. payerName?: string;
  12. payerAccount?: string;
  13. receiverName?: string;
  14. receiverAccount?: string;
  15. transactionNumber?: string;
  16. confidence: number;
  17. extractedText?: string;
  18. error?: string;
  19. }
  20. // 支付凭证文件信息接口
  21. export interface PaymentVoucherFile {
  22. id: string;
  23. fileName: string;
  24. fileType: string;
  25. fileSize: number;
  26. fileUrl: string;
  27. previewUrl?: string;
  28. uploadDate: Date;
  29. }
  30. @Injectable({
  31. providedIn: 'root'
  32. })
  33. export class PaymentVoucherRecognitionService {
  34. private http = inject(HttpClient);
  35. /**
  36. * 识别支付凭证图片或PDF文件
  37. * @param file 支付凭证文件
  38. * @returns 识别结果的可观察对象
  39. */
  40. recognizePaymentVoucher(file: File): Observable<PaymentVoucherRecognitionResult> {
  41. // 检查文件类型
  42. if (!this.isSupportedFileType(file.type)) {
  43. return of({
  44. success: false,
  45. confidence: 0,
  46. error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。'
  47. });
  48. }
  49. // 检查文件大小(限制为10MB)
  50. if (file.size > 10 * 1024 * 1024) {
  51. return of({
  52. success: false,
  53. confidence: 0,
  54. error: '文件大小不能超过10MB。'
  55. });
  56. }
  57. // 在实际应用中,这里会调用OCR API服务
  58. // 目前先模拟识别过程
  59. return this.simulateRecognition(file);
  60. }
  61. /**
  62. * 批量识别支付凭证
  63. * @param files 支付凭证文件数组
  64. * @returns 批量识别结果的可观察对象
  65. */
  66. async recognizePaymentVouchers(files: File[]): Promise<PaymentVoucherRecognitionResult[]> {
  67. const recognitionPromises = files.map(file =>
  68. this.recognizePaymentVoucher(file).toPromise()
  69. );
  70. const results = await Promise.all(recognitionPromises);
  71. return results.filter(result => result !== undefined) as PaymentVoucherRecognitionResult[];
  72. }
  73. /**
  74. * 检查是否支持的文件类型
  75. * @param fileType 文件MIME类型
  76. */
  77. private isSupportedFileType(fileType: string): boolean {
  78. const supportedTypes = [
  79. 'image/jpeg',
  80. 'image/png',
  81. 'image/gif',
  82. 'application/pdf'
  83. ];
  84. return supportedTypes.includes(fileType);
  85. }
  86. /**
  87. * 模拟支付凭证识别过程
  88. * @param file 支付凭证文件
  89. */
  90. private simulateRecognition(file: File): Observable<PaymentVoucherRecognitionResult> {
  91. return new Observable(observer => {
  92. // 模拟处理延迟
  93. setTimeout(() => {
  94. try {
  95. // 根据文件名和类型生成模拟识别结果
  96. const fileName = file.name.toLowerCase();
  97. // 模拟不同的识别场景
  98. if (fileName.includes('alipay') || fileName.includes('支付宝')) {
  99. observer.next(this.generateAlipayResult(file));
  100. } else if (fileName.includes('wechat') || fileName.includes('微信')) {
  101. observer.next(this.generateWechatResult(file));
  102. } else if (fileName.includes('bank') || fileName.includes('银行')) {
  103. observer.next(this.generateBankTransferResult(file));
  104. } else {
  105. observer.next(this.generateGenericResult(file));
  106. }
  107. observer.complete();
  108. } catch (error) {
  109. observer.next({
  110. success: false,
  111. confidence: 0,
  112. error: `识别过程中发生错误: ${error}`
  113. });
  114. observer.complete();
  115. }
  116. }, 2000); // 2秒延迟模拟处理时间
  117. });
  118. }
  119. /**
  120. * 生成支付宝支付凭证识别结果
  121. */
  122. private generateAlipayResult(file: File): PaymentVoucherRecognitionResult {
  123. const amount = Math.round(Math.random() * 10000 + 1000); // 1000-11000之间的随机金额
  124. const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度
  125. return {
  126. success: true,
  127. amount,
  128. paymentDate: new Date(),
  129. paymentMethod: '支付宝',
  130. payerName: '付款人***',
  131. payerAccount: 'alipay***@xxx.com',
  132. receiverName: '收款人***',
  133. receiverAccount: 'alipay***@xxx.com',
  134. transactionNumber: `ALP${Date.now().toString().slice(-8)}`,
  135. confidence,
  136. extractedText: `支付宝交易凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
  137. };
  138. }
  139. /**
  140. * 生成微信支付凭证识别结果
  141. */
  142. private generateWechatResult(file: File): PaymentVoucherRecognitionResult {
  143. const amount = Math.round(Math.random() * 5000 + 500); // 500-5500之间的随机金额
  144. const confidence = Math.random() * 0.3 + 0.7; // 70%-100%的置信度
  145. return {
  146. success: true,
  147. amount,
  148. paymentDate: new Date(),
  149. paymentMethod: '微信支付',
  150. payerName: '微信用户***',
  151. payerAccount: 'wxid_***',
  152. receiverName: '商户***',
  153. receiverAccount: 'mch_***',
  154. transactionNumber: `WX${Date.now().toString().slice(-8)}`,
  155. confidence,
  156. extractedText: `微信支付凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
  157. };
  158. }
  159. /**
  160. * 生成银行转账凭证识别结果
  161. */
  162. private generateBankTransferResult(file: File): PaymentVoucherRecognitionResult {
  163. const amount = Math.round(Math.random() * 20000 + 2000); // 2000-22000之间的随机金额
  164. const confidence = Math.random() * 0.2 + 0.8; // 80%-100%的置信度
  165. return {
  166. success: true,
  167. amount,
  168. paymentDate: new Date(),
  169. paymentMethod: '银行转账',
  170. payerName: '付款人***',
  171. payerAccount: '6222********1234',
  172. receiverName: '收款人***',
  173. receiverAccount: '6222********5678',
  174. transactionNumber: `BANK${Date.now().toString().slice(-8)}`,
  175. confidence,
  176. extractedText: `银行转账凭证 金额: ${amount}元 交易时间: ${new Date().toLocaleString()}`
  177. };
  178. }
  179. /**
  180. * 生成通用支付凭证识别结果
  181. */
  182. private generateGenericResult(file: File): PaymentVoucherRecognitionResult {
  183. const amount = Math.round(Math.random() * 15000 + 1000); // 1000-16000之间的随机金额
  184. const confidence = Math.random() * 0.4 + 0.6; // 60%-100%的置信度
  185. const methods = ['支付宝', '微信支付', '银行转账', '现金'];
  186. const paymentMethod = methods[Math.floor(Math.random() * methods.length)];
  187. return {
  188. success: true,
  189. amount,
  190. paymentDate: new Date(),
  191. paymentMethod,
  192. payerName: '付款人***',
  193. receiverName: '收款人***',
  194. confidence,
  195. extractedText: `支付凭证 金额: ${amount}元 支付方式: ${paymentMethod} 交易时间: ${new Date().toLocaleString()}`
  196. };
  197. }
  198. /**
  199. * 验证识别结果的可信度
  200. * @param result 识别结果
  201. * @param expectedAmount 预期金额(可选)
  202. */
  203. validateRecognitionResult(
  204. result: PaymentVoucherRecognitionResult,
  205. expectedAmount?: number
  206. ): { isValid: boolean; reasons: string[] } {
  207. const reasons: string[] = [];
  208. if (!result.success) {
  209. reasons.push('识别失败');
  210. return { isValid: false, reasons };
  211. }
  212. if (result.confidence < 0.7) {
  213. reasons.push(`置信度过低: ${(result.confidence * 100).toFixed(1)}%`);
  214. }
  215. if (!result.amount || result.amount <= 0) {
  216. reasons.push('金额识别无效');
  217. }
  218. if (expectedAmount && result.amount && Math.abs(result.amount - expectedAmount) > expectedAmount * 0.1) {
  219. reasons.push(`识别金额与预期金额差异过大`);
  220. }
  221. if (!result.paymentDate) {
  222. reasons.push('支付日期识别失败');
  223. }
  224. if (!result.paymentMethod) {
  225. reasons.push('支付方式识别失败');
  226. }
  227. return {
  228. isValid: reasons.length === 0,
  229. reasons
  230. };
  231. }
  232. /**
  233. * 格式化金额显示
  234. * @param amount 金额
  235. */
  236. formatAmount(amount: number): string {
  237. return new Intl.NumberFormat('zh-CN', {
  238. style: 'currency',
  239. currency: 'CNY'
  240. }).format(amount);
  241. }
  242. /**
  243. * 获取支持的文件类型描述
  244. */
  245. getSupportedFileTypesDescription(): string {
  246. return '支持的文件类型: JPG, PNG, GIF图片或PDF文件,最大10MB';
  247. }
  248. /**
  249. * 获取支持的文件类型列表
  250. */
  251. getSupportedFileTypes(): string[] {
  252. return [
  253. 'image/jpeg',
  254. 'image/png',
  255. 'image/gif',
  256. 'application/pdf'
  257. ];
  258. }
  259. /**
  260. * 验证文件
  261. * @param file 要验证的文件
  262. */
  263. validateFile(file: File): { isValid: boolean; error?: string } {
  264. if (!this.isSupportedFileType(file.type)) {
  265. return {
  266. isValid: false,
  267. error: '不支持的文件类型。请上传图片(JPG, PNG, GIF)或PDF文件。'
  268. };
  269. }
  270. if (file.size > 10 * 1024 * 1024) {
  271. return {
  272. isValid: false,
  273. error: '文件大小不能超过10MB。'
  274. };
  275. }
  276. return { isValid: true };
  277. }
  278. }