bank.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import re
  2. from time import time
  3. from utils.util import *
  4. from json import load, dump, loads
  5. from requests import request as http_req
  6. from utils.conf import MAX_FILE_SIZE
  7. from flask import Blueprint, views, render_template, request
  8. from utils.logger import Logger
  9. bank = Blueprint("bank", __name__, url_prefix="/bank")
  10. _InfoFile = "utils/bank-info.json"
  11. __fp = open(_InfoFile, "r", encoding="utf-8")
  12. _InfoForShort = load(__fp)
  13. __fp.close()
  14. __InfoForBin = {__v["bin"]: __v for __v in _InfoForShort.values() if __v["bin"]}
  15. __NumPtn = re.compile(r"(?P<number>\d{15,19})")
  16. _UnresolvedInfo = []
  17. __UnresolvedIncludes = []
  18. _ErrorInfo = []
  19. __ErrorIncludes = []
  20. _FailIcon = "A-Fail"
  21. def __get_bank_and_icon(num: "str") -> "tuple[str, str]":
  22. _bin = num[:6]
  23. if _bin in __InfoForBin.keys():
  24. return __InfoForBin[_bin]["name"], __InfoForBin[_bin]["icon"]
  25. if num in __UnresolvedIncludes or num in __ErrorIncludes:
  26. return "", _FailIcon
  27. url = "https://ccdcapi.alipay.com/validateAndCacheCardInfo.json" \
  28. f"?_input_charset=utf-8&cardBinCheck=true&cardNo={num}"
  29. resp, bank_type = http_req("GET", url), ""
  30. if resp.status_code == 200:
  31. data = resp.json()
  32. if data["stat"] == "ok":
  33. bank_type = data["bank"]
  34. if bank_type:
  35. if bank_type in _InfoForShort.keys():
  36. _InfoForShort[bank_type]["bin"] = _bin
  37. __InfoForBin[_bin] = _InfoForShort[bank_type]
  38. return __InfoForBin[_bin]["name"], __InfoForBin[_bin]["icon"]
  39. if num in __UnresolvedIncludes or bank_type in __UnresolvedIncludes:
  40. return "", _FailIcon
  41. __UnresolvedIncludes.append(num)
  42. __UnresolvedIncludes.append(bank_type)
  43. _UnresolvedInfo.append({"number": num, "short": bank_type, "name": "", "icon": ""})
  44. return "", _FailIcon
  45. __ErrorIncludes.append(num)
  46. _ErrorInfo.append([num, current_time("%Y-%m-%d %H:%M:%S")])
  47. return "", _FailIcon
  48. def _get_bank_info(full_str: "str") -> "tuple[bool, dict]":
  49. res = {"number": "", "bank": "", "icon": ""}
  50. if search := re.search(__NumPtn, full_str):
  51. res["number"] = search.group("number")
  52. res["bank"], res["icon"] = __get_bank_and_icon(res["number"])
  53. if res["bank"]:
  54. return True, res
  55. return False, res
  56. class BankView(views.MethodView):
  57. @staticmethod
  58. def get():
  59. return render_template("bank/index.html")
  60. @staticmethod
  61. def post():
  62. start = time()
  63. pic = request.files.get("picture")
  64. if pic is None:
  65. return Response("empty body")
  66. ext = get_ext_name(pic.filename)
  67. if not is_image_ext(ext):
  68. return Response("文件类型错误")
  69. content = pic.read()
  70. if len(content) > MAX_FILE_SIZE:
  71. return Response("文件过大,请重新选择")
  72. cropped = preprocess_img(read_img(content)) # 预处理,对深色背景的效果很好
  73. images = rot_img_2(cropped)
  74. recognizes = Engine.rec_multi(images)
  75. info, sta = {}, False
  76. for i, ocr_res in enumerate(recognizes):
  77. rec_str = "".join([it[0] for it in ocr_res])
  78. sta, info = _get_bank_info(rec_str)
  79. if sta:
  80. info["duration"] = time() - start
  81. raw_path = f"static/images/{current_time()}_{rand_str()}.{ext}"
  82. save_img(raw_path, images[i])
  83. return Response(data=info)
  84. else:
  85. Logger.error(rec_str)
  86. info["duration"] = time() - start
  87. return Response("识别失败,请重新选择", info)
  88. class BankHtmlView(views.MethodView):
  89. @staticmethod
  90. def post():
  91. start = time()
  92. pic = request.files.get("picture")
  93. if pic is None:
  94. return Response("empty body")
  95. ext = get_ext_name(pic.filename)
  96. if not is_image_ext(ext):
  97. return Response("文件类型错误")
  98. content = pic.read()
  99. if len(content) > MAX_FILE_SIZE:
  100. return Response("文件过大,请重新选择")
  101. cropped = preprocess_img(read_img(content))
  102. images = rot_img_2(cropped)
  103. recognizes = Engine.rec_multi(images)
  104. info, err_rec, sta, idx = {}, [], False, 0
  105. msg = "识别失败,请重新选择"
  106. for i, ocr_res in enumerate(recognizes):
  107. rec_str = "".join([it[0] for it in ocr_res])
  108. sta, info = _get_bank_info(rec_str)
  109. if sta:
  110. idx, msg = i, "识别成功"
  111. break
  112. else:
  113. Logger.error(rec_str)
  114. err_rec.append(rec_str)
  115. file_path = f"static/images/{current_time()}_{rand_str()}.{ext}"
  116. save_img(file_path, images[idx])
  117. info["SUCCESS"] = str(sta).upper()
  118. info["MESSAGE"] = msg
  119. if not sta:
  120. info["icon"] = _FailIcon
  121. if len(err_rec):
  122. info["MESSAGE"] += "<br>识别结果:<br>" + "<br>".join(err_rec)
  123. info["DURATION"] = time() - start # noqa
  124. return render_template("bank/result.html", raw=file_path, data=info)
  125. class BankManageView(views.MethodView):
  126. @staticmethod
  127. def get():
  128. info = request.args.get("info")
  129. if info is None:
  130. return render_template("bank/manage.html")
  131. return Response(data={"full": _InfoForShort, "unresolved": _UnresolvedInfo, "error": _ErrorInfo})
  132. @staticmethod
  133. def post():
  134. which = request.form.get("which")
  135. opt = request.form.get("opt")
  136. if not which or not opt:
  137. return Response("key param lost")
  138. which, opt = which.lower(), opt.lower()
  139. if which == "full":
  140. if opt == "update":
  141. try:
  142. data = request.form.get("data")
  143. global _InfoForShort
  144. _InfoForShort = loads(data)
  145. return Response()
  146. except Exception: # noqa
  147. return Response("JSON格式错误")
  148. elif opt == "persist":
  149. with open(_InfoFile, "w", encoding="utf-8") as fp:
  150. dump(_InfoForShort, fp, ensure_ascii=False, indent=2) # noqa
  151. fp.close()
  152. return Response()
  153. return Response(f"unrecognized opt: <{opt}>")
  154. if which == "unresolved":
  155. if opt == "update":
  156. ... # TODO
  157. return Response(f"unrecognized opt: <{opt}>")
  158. if which == "error":
  159. if opt == "clear":
  160. _ErrorInfo.clear()
  161. return Response()
  162. return Response(f"unrecognized opt: <{opt}>")
  163. if which == "icon":
  164. if opt == "upload":
  165. name = request.form.get("name")
  166. icon = request.files.get("icon")
  167. if name is None:
  168. return Response("key param lost")
  169. if icon is None:
  170. return Response("empty body")
  171. ext = get_ext_name(icon.filename)
  172. if ext != "png":
  173. return Response("icon仅支持png格式")
  174. content = icon.read()
  175. if len(content) > MAX_FILE_SIZE:
  176. return Response("文件过大,请重新选择")
  177. try:
  178. save_img(f"static/bank/{name}.png", content)
  179. return Response()
  180. except Exception: # noqa
  181. return Response("文件名称不合法")
  182. return Response(f"unrecognized which: <{which}>")
  183. bank.add_url_rule("/", view_func=BankView.as_view("bank"))
  184. bank.add_url_rule("/html/", view_func=BankHtmlView.as_view("bank-html"))
  185. bank.add_url_rule("/manage/", view_func=BankManageView.as_view("bank-manage"))