import base64 import logging from flask import Blueprint, request, jsonify, Response import lib.db as db from lib.validators import validate_user_id, validate_user_name, validate_image_size from lib.config import get_limits_config from lib.image_processor import preview_remove_white, preview_binarize db_api_bp = Blueprint('db_api', __name__) logger = logging.getLogger(__name__) @db_api_bp.route('/api/db/list', methods=['GET']) def db_list(): page = request.args.get('page', 1, type=int) page_size = request.args.get('page_size', 20, type=int) keyword = request.args.get('keyword', '') try: result = db.query_all(page=page, page_size=page_size, keyword=keyword or None) return jsonify({'success': True, 'data': result}) except Exception as e: logger.error("查询列表失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/get/', methods=['GET']) def db_get(user_id): try: record = db.query_by_user_id(user_id) if not record: return jsonify({'success': False, 'message': f'未找到人员ID={user_id}的记录'}), 404 record['is_signature'] = bool(record['is_signature']) record.pop('sign_image', None) return jsonify({'success': True, 'data': record}) except Exception as e: logger.error("查询记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/image/', methods=['GET']) def db_get_image(user_id): try: record = db.query_by_user_id(user_id) if not record: return jsonify({'success': False, 'message': '未找到记录'}), 404 image_data = record['sign_image'] return Response(image_data, mimetype='image/png') except Exception as e: logger.error("获取图片失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/preview/', methods=['GET']) def db_preview_image(user_id): try: record = db.query_by_user_id(user_id) if not record: return jsonify({'success': False, 'message': '未找到记录'}), 404 image_data = record['sign_image'] is_sig = bool(record['is_signature']) if is_sig: processed = preview_binarize(image_data) else: processed = preview_remove_white(image_data) return Response(processed, mimetype='image/png') except Exception as e: logger.error("预览图片失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/add', methods=['POST']) def db_add(): try: content_type = request.content_type or '' if 'multipart/form-data' in content_type: user_id = request.form.get('user_id', '') user_name = request.form.get('user_name', '') is_signature = request.form.get('is_signature', '0') image_file = request.files.get('sign_image') valid, msg = validate_user_id(user_id) if not valid: return jsonify({'success': False, 'message': msg}), 400 valid, msg = validate_user_name(user_name) if not valid: return jsonify({'success': False, 'message': msg}), 400 if not image_file: return jsonify({'success': False, 'message': '缺少签章图片'}), 400 image_data = image_file.read() is_sig = 1 if is_signature in ('1', 'true', 'True') else 0 db.insert_record(user_id, user_name, image_data, is_sig) return jsonify({'success': True, 'message': '新增成功'}) json_data = request.get_json(silent=True) if not json_data: return jsonify({'success': False, 'message': '无效请求数据'}), 400 user_id = json_data.get('user_id', '') user_name = json_data.get('user_name', '') is_signature = json_data.get('is_signature', 0) image_b64 = json_data.get('sign_image_base64', '') valid, msg = validate_user_id(user_id) if not valid: return jsonify({'success': False, 'message': msg}), 400 valid, msg = validate_user_name(user_name) if not valid: return jsonify({'success': False, 'message': msg}), 400 if not image_b64: return jsonify({'success': False, 'message': '缺少签章图片'}), 400 image_data = base64.b64decode(image_b64) is_sig = 1 if is_signature in (1, '1', True, 'true') else 0 db.insert_record(user_id, user_name, image_data, is_sig) return jsonify({'success': True, 'message': '新增成功'}) except ValueError as e: return jsonify({'success': False, 'message': str(e)}), 400 except Exception as e: logger.error("新增记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/update/', methods=['PUT']) def db_update(user_id): try: content_type = request.content_type or '' user_name = None sign_image = None is_signature = None if 'multipart/form-data' in content_type: user_name = request.form.get('user_name') is_sig_str = request.form.get('is_signature') if is_sig_str is not None: is_signature = 1 if is_sig_str in ('1', 'true', 'True') else 0 image_file = request.files.get('sign_image') if image_file: sign_image = image_file.read() else: json_data = request.get_json(silent=True) if json_data: user_name = json_data.get('user_name') is_sig_val = json_data.get('is_signature') if is_sig_val is not None: is_signature = 1 if is_sig_val in (1, '1', True, 'true') else 0 image_b64 = json_data.get('sign_image_base64') if image_b64: sign_image = base64.b64decode(image_b64) db.update_record(user_id, user_name=user_name, sign_image=sign_image, is_signature=is_signature) return jsonify({'success': True, 'message': '更新成功'}) except Exception as e: logger.error("更新记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/delete/', methods=['DELETE']) def db_delete(user_id): try: db.delete_record(user_id) return jsonify({'success': True, 'message': '删除成功'}) except Exception as e: logger.error("删除记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/batch_import', methods=['POST']) def db_batch_import(): try: files = request.files.getlist('files') auto_id = request.form.get('auto_id', '0') == '1' id_prefix = request.form.get('id_prefix', 'USER') is_signature = request.form.get('is_signature', '0') is_sig = 1 if is_signature in ('1', 'true', 'True') else 0 results = [] success_count = 0 fail_count = 0 id_counter = 1 for f in files: filename = f.filename or '' name_part = filename.rsplit('.', 1)[0] if '.' in filename else filename if auto_id: user_id = f"{id_prefix}{id_counter:04d}" id_counter += 1 else: user_id = name_part user_name = name_part image_data = f.read() try: db.insert_record(user_id, user_name, image_data, is_sig) results.append({'filename': filename, 'user_id': user_id, 'status': 'success'}) success_count += 1 except ValueError as e: results.append({'filename': filename, 'user_id': user_id, 'status': 'fail', 'message': str(e)}) fail_count += 1 except Exception as e: results.append({'filename': filename, 'user_id': user_id, 'status': 'fail', 'message': str(e)}) fail_count += 1 return jsonify({ 'success': True, 'data': { 'total': len(files), 'success': success_count, 'fail': fail_count, 'results': results, } }) except Exception as e: logger.error("批量导入失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 # ============================================================================ # SPA 兼容端点:/api/db/records* (供 templates/index.html 调用) # 与上方原 /api/db/* 端点并存,不互相影响。 # ============================================================================ def _records_to_spa(record): """将内部记录转换为 SPA 期望的列表项格式。""" image_size = 0 if record.get('sign_image'): try: image_size = len(record['sign_image']) except Exception: image_size = 0 return { 'user_id': record.get('user_id'), 'user_name': record.get('user_name'), 'is_signature': bool(record.get('is_signature')), 'image_size': image_size, 'create_time': str(record.get('create_time') or ''), 'update_time': str(record.get('update_time') or ''), } @db_api_bp.route('/api/db/records', methods=['GET']) def db_records_list_spa(): try: keyword = request.args.get('q') or None page = request.args.get('page', 1, type=int) page_size = request.args.get('page_size', 200, type=int) result = db.query_all(page=page, page_size=page_size, keyword=keyword) records = [_records_to_spa(r) for r in result.get('data', [])] return jsonify({'success': True, 'records': records, 'total': result.get('total', len(records))}) except Exception as e: logger.error("查询记录列表失败: %s", str(e)) return jsonify({'success': False, 'message': str(e), 'records': []}), 500 @db_api_bp.route('/api/db/records', methods=['POST']) def db_records_add_spa(): payload = request.get_json(silent=True) or {} user_id = (payload.get('user_id') or '').strip() user_name = (payload.get('user_name') or '').strip() is_signature = payload.get('is_signature', False) image_b64 = payload.get('image_base64') or '' valid, msg = validate_user_id(user_id) if not valid: return jsonify({'success': False, 'message': msg}), 400 valid, msg = validate_user_name(user_name) if not valid: return jsonify({'success': False, 'message': msg}), 400 if not image_b64: return jsonify({'success': False, 'message': '缺少图片'}), 400 try: image_data = base64.b64decode(image_b64) except Exception as e: return jsonify({'success': False, 'message': f'图片 Base64 解码失败: {e}'}), 400 limits = get_limits_config() valid, msg = validate_image_size(image_data, limits['image_max_bytes']) if not valid: return jsonify({'success': False, 'message': msg}), 400 is_sig = 1 if is_signature else 0 try: db.insert_record(user_id, user_name, image_data, is_sig) except ValueError as e: return jsonify({'success': False, 'message': str(e)}), 400 except Exception as e: logger.error("新增记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 return jsonify({'success': True, 'record': {'user_id': user_id, 'user_name': user_name, 'is_signature': bool(is_sig)}}) @db_api_bp.route('/api/db/records/', methods=['PUT']) def db_records_update_spa(user_id): payload = request.get_json(silent=True) or {} user_name = payload.get('user_name') is_signature = payload.get('is_signature') image_b64 = payload.get('image_base64') sign_image = None if image_b64: try: sign_image = base64.b64decode(image_b64) except Exception as e: return jsonify({'success': False, 'message': f'图片 Base64 解码失败: {e}'}), 400 limits = get_limits_config() valid, msg = validate_image_size(sign_image, limits['image_max_bytes']) if not valid: return jsonify({'success': False, 'message': msg}), 400 if user_name is not None: valid, msg = validate_user_name(user_name) if not valid: return jsonify({'success': False, 'message': msg}), 400 is_sig_val = None if is_signature is not None: is_sig_val = 1 if is_signature else 0 try: db.update_record(user_id, user_name=user_name, sign_image=sign_image, is_signature=is_sig_val) except Exception as e: logger.error("更新记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 return jsonify({'success': True, 'record': {'user_id': user_id, 'user_name': user_name, 'is_signature': bool(is_sig_val) if is_sig_val is not None else None}}) @db_api_bp.route('/api/db/records/', methods=['DELETE']) def db_records_delete_spa(user_id): try: db.delete_record(user_id) return jsonify({'success': True}) except Exception as e: logger.error("删除记录失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500 @db_api_bp.route('/api/db/records/import', methods=['POST']) def db_records_import_spa(): """SPA 批量导入:JSON + Base64。 返回结构需让前端能识别失败条目(含 index)以便保留待二次处理。 """ payload = request.get_json(silent=True) or {} items = payload.get('items') or [] id_prefix = (payload.get('auto_id_prefix') or 'U').strip() or 'U' id_start = int(payload.get('auto_id_start') or 1) id_padding = max(1, min(int(payload.get('auto_id_padding') or 4), 20)) default_is_signature = bool(payload.get('default_is_signature', False)) default_is_sig = 1 if default_is_signature else 0 limits = get_limits_config() imported = 0 failed = 0 failures = [] for idx, item in enumerate(items): filename = item.get('image_filename') or f"{id_prefix}{idx + 1:0{id_padding}d}.png" name_part = filename.rsplit('.', 1)[0] if '.' in filename else filename user_name = item.get('user_name') or name_part user_id = f"{id_prefix}{(id_start + idx):0{id_padding}d}" is_signature = item.get('is_signature', default_is_signature) is_sig = 1 if is_signature else 0 image_b64 = item.get('image_base64') or '' if not image_b64: failed += 1 failures.append({'index': idx, 'code': 'MISSING_IMAGE', 'message': '缺少图片 Base64', 'filename': filename}) continue try: image_data = base64.b64decode(image_b64) except Exception as e: failed += 1 failures.append({'index': idx, 'code': 'INVALID_BASE64', 'message': f'Base64 解码失败: {e}', 'filename': filename}) continue valid, msg = validate_image_size(image_data, limits['image_max_bytes']) if not valid: failed += 1 failures.append({'index': idx, 'code': 'IMAGE_TOO_LARGE', 'message': msg, 'filename': filename}) continue try: db.insert_record(user_id, user_name, image_data, is_sig) imported += 1 except ValueError as e: failed += 1 failures.append({'index': idx, 'code': 'DUPLICATE', 'message': str(e), 'filename': filename}) except Exception as e: failed += 1 failures.append({'index': idx, 'code': 'ERROR', 'message': str(e), 'filename': filename}) return jsonify({ 'success': True, 'imported': imported, 'failed': failed, 'failures': failures, }) @db_api_bp.route('/api/db/records//image', methods=['GET']) def db_records_image_spa(user_id): try: record = db.query_by_user_id(user_id) if not record: return jsonify({'success': False, 'message': '未找到记录'}), 404 processed = request.args.get('processed') in ('1', 'true', 'True', 'yes') image_data = record['sign_image'] if processed: if bool(record.get('is_signature')): payload = preview_binarize(image_data) else: payload = preview_remove_white(image_data) else: payload = image_data return Response(payload, mimetype='image/png') except Exception as e: logger.error("获取图片失败: %s", str(e)) return jsonify({'success': False, 'message': str(e)}), 500