compliance/mock_dms_server.py
2025-07-11 17:56:56 +08:00

193 lines
7.8 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 json
import logging
from flask import Flask, jsonify, request
from pathlib import Path
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
# --- 模拟数据和内存数据库 ---
DMS_ASSETS_DIR = Path(__file__).parent / 'assets' / 'doc' / 'dms'
API_LIST_DATA = {}
MODEL_DATA = {}
MOCK_SCHEMAS_CACHE = {}
IN_MEMORY_DB = {} # 新增: 作为内存数据库
try:
with open(DMS_ASSETS_DIR / '列表.json', 'r', encoding='utf-8') as f:
API_LIST_DATA = json.load(f)
except FileNotFoundError as e:
logging.error(f"错误: 模拟数据文件 '列表.json' 未找到。详情: {e}")
API_LIST_DATA = {"code": 500, "message": "模拟数据 '列表.json' 未找到."}
def generate_fake_schema(record: dict) -> dict:
"""根据列表中的单条记录生成一个假的、但结构合理的schema"""
name = record.get('name', 'unknown_name')
title = record.get('title', 'Unknown Title')
version = record.get('version', '0.0.0')
# 假设第一个属性是主键
pk_name = f"{name}_id"
schema_properties = {
pk_name: {"type": "string", "title": "主键", "description": f"主键 for {name}"},
"description": {"type": "string", "title": "描述", "description": "一个简单的描述字段"},
"update_date": {"type": "string", "format": "date-time", "title": "更新日期"},
"status": {
"type": "string", "title": "状态", "enum": ["active", "inactive", "archived"]
},
"record_count": {
"type": "integer", "title": "记录数", "minimum": 0, "maximum": 10000
}
}
model = {
"type": "object",
"title": title,
"properties": schema_properties,
"required": [pk_name, "status"]
}
# 按照parser.py中的逻辑构建删除请求的schema
delete_pk_schema = {
"type": "array",
"items": {
"type": "object",
"properties": { pk_name: schema_properties[pk_name] },
"required": [pk_name]
}
}
return {
"code": 0, "message": "操作处理成功",
"data": {
"version": version,
"model": model,
"_delete_schema_for_mock": delete_pk_schema # 内部使用,方便查找主键
}
}
def preload_schemas():
"""在服务器启动时预先生成并缓存所有schema"""
if API_LIST_DATA.get("code") == 0:
records = API_LIST_DATA.get("data", {}).get("records", [])
logging.info(f"预加载 {len(records)} 个API的模拟schema...")
for record in records:
record_id = record.get("id")
if record_id:
MOCK_SCHEMAS_CACHE[record_id] = generate_fake_schema(record)
logging.info("Schema预加载完成。")
# --- 元数据模拟端点 ---
@app.route('/api/schema/manage/schema', methods=['GET'])
def get_api_list():
"""模拟获取DMS中所有API列表的接口。"""
logging.info("Mock服务器: 收到API列表请求。")
return jsonify(API_LIST_DATA)
@app.route('/api/schema/manage/schema/<string:model_id>', methods=['GET'])
def get_schema_details(model_id):
"""模拟根据ID获取单个API模型(schema)的接口。"""
logging.info(f"Mock服务器: 收到ID为 '{model_id}' 的schema详情请求。")
schema_data = MOCK_SCHEMAS_CACHE.get(model_id)
if schema_data:
return jsonify(schema_data)
else:
return jsonify({"code": 404, "message": f"未找到ID为 '{model_id}' 的schema。"}), 404
# --- CRUD 模拟端点 ---
def get_pk_name_from_model(name: str) -> str:
"""辅助函数:根据资源名找到其主键字段名"""
# 这是一个简化逻辑。真实场景可能更复杂。
# 我们根据generate_fake_schema的逻辑假设主键是 '资源名_id'
return f"{name}_id"
@app.route('/api/dms/<string:dms_instance_code>/v1/<string:name>', methods=['POST'])
def create_resource(dms_instance_code, name):
"""Create (POST): 创建资源"""
logging.info(f"Mock服务器: 收到对 '{name}' 的CREATE请求")
request_data = request.get_json(silent=True)
if not request_data or 'data' not in request_data or not isinstance(request_data['data'], list):
return jsonify({"code": 400, "message": "请求体格式错误,应为 {'data': [...]}"}), 400
if name not in IN_MEMORY_DB:
IN_MEMORY_DB[name] = {}
pk_name = get_pk_name_from_model(name)
for item in request_data['data']:
if pk_name not in item:
return jsonify({"code": 400, "message": f"创建失败,数据项缺少主键 '{pk_name}'"}), 400
pk_value = item[pk_name]
IN_MEMORY_DB[name][pk_value] = item
logging.info(f" > 在 '{name}' 中创建/更新了资源 ID: {pk_value}")
return jsonify({"code": 0, "message": "创建成功", "data": True})
@app.route('/api/dms/<string:dms_instance_code>/v1/<string:name>', methods=['PUT'])
def update_resource(dms_instance_code, name):
"""Update (PUT): 更新资源"""
logging.info(f"Mock服务器: 收到对 '{name}' 的UPDATE请求")
# 实现逻辑与Create完全相同
return create_resource(dms_instance_code, name)
@app.route('/api/dms/<string:dms_instance_code>/v1/<string:name>/<string:version>/<string:id>', methods=['GET'])
def read_resource(dms_instance_code, name, version, id):
"""Read (GET by ID): 读取单个资源"""
logging.info(f"Mock服务器: 收到对 '{name}' 的READ请求, ID: {id}")
resource = IN_MEMORY_DB.get(name, {}).get(id)
if resource:
return jsonify({"code": 0, "message": "读取成功", "data": resource})
else:
return jsonify({"code": 404, "message": f"资源 '{name}' with ID '{id}' 未找到."}), 404
@app.route('/api/dms/<string:dms_instance_code>/v1/<string:name>', methods=['DELETE'])
def delete_resource(dms_instance_code, name):
"""Delete (DELETE): 删除资源"""
logging.info(f"Mock服务器: 收到对 '{name}' 的DELETE请求")
request_data = request.get_json(silent=True)
if not request_data or 'data' not in request_data or not isinstance(request_data['data'], list):
return jsonify({"code": 400, "message": "请求体格式错误,应为 {'data': [{pk: value}, ...]}"}), 400
pk_name = get_pk_name_from_model(name)
deleted_count = 0
for item_to_delete in request_data['data']:
if pk_name in item_to_delete:
pk_value = item_to_delete[pk_name]
if IN_MEMORY_DB.get(name, {}).pop(pk_value, None):
logging.info(f" > 从 '{name}' 删除了资源 ID: {pk_value}")
deleted_count += 1
if deleted_count > 0:
return jsonify({"code": 0, "message": "删除成功", "data": True})
else:
return jsonify({"code": 404, "message": "未找到要删除的资源", "data": False})
@app.route('/api/dms/<string:dms_instance_code>/v1/<string:name>/<string:version>', methods=['POST'])
def list_resources(dms_instance_code, name, version):
"""List (POST): 获取资源列表"""
logging.info(f"Mock服务器: 收到对 '{name}' 的LIST请求")
all_resources = list(IN_MEMORY_DB.get(name, {}).values())
return jsonify({
"code": 0,
"message": f"获取 '{name}' 列表成功",
"data": all_resources
})
def print_routes(app):
"""打印应用中所有已注册的路由。"""
logging.info("\n--- 已注册的API路由 ---")
for rule in sorted(app.url_map.iter_rules(), key=lambda r: str(r)):
methods = ','.join(sorted(list(rule.methods)))
logging.info(f"URL: {rule!s:55s} 方法: {methods:25s} 端点: {rule.endpoint}")
logging.info("-------------------------\n")
if __name__ == '__main__':
preload_schemas()
print_routes(app)
port = 5001
logging.info(f"正在启动用于DMS API的Flask模拟服务器地址为 http://127.0.0.1:{port}")
app.run(host='0.0.0.0', port=port, debug=False)