Files
SignatureSystem/routes/db_api.py
2026-07-20 13:16:17 +08:00

438 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/<user_id>', 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/<user_id>', 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/<user_id>', 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/<user_id>', 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/<user_id>', 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/<user_id>', 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/<user_id>', 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/<user_id>/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