first commit
This commit is contained in:
438
routes/db_api.py
Normal file
438
routes/db_api.py
Normal file
@@ -0,0 +1,438 @@
|
||||
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
|
||||
Reference in New Issue
Block a user