Files
SignatureSystem/routes/db_api.py

438 lines
17 KiB
Python
Raw Normal View History

2026-07-20 13:16:17 +08:00
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