routes.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. """
  2. 数据服务 API 路由
  3. 提供数据产品列表、数据预览、Excel下载等接口
  4. 提供数据订单创建、分析、审批等接口
  5. """
  6. import json
  7. import logging
  8. from flask import request, send_file
  9. from app.api.data_service import bp
  10. from app.core.data_service.data_product_service import (
  11. DataOrderService,
  12. DataProductService,
  13. )
  14. from app.core.graph.graph_operations import MyEncoder
  15. from app.models.result import failed, success
  16. logger = logging.getLogger(__name__)
  17. # ==================== 数据产品列表接口 ====================
  18. @bp.route("/products", methods=["GET"])
  19. def get_products():
  20. """
  21. 获取数据产品列表
  22. Query Parameters:
  23. page: 页码,默认 1
  24. page_size: 每页数量,默认 20
  25. search: 搜索关键词
  26. status: 状态过滤 (active/inactive/error)
  27. """
  28. try:
  29. page = request.args.get("page", 1, type=int)
  30. page_size = request.args.get("page_size", 20, type=int)
  31. search = request.args.get("search", "")
  32. status = request.args.get("status")
  33. result = DataProductService.get_data_products(
  34. page=page,
  35. page_size=page_size,
  36. search=search,
  37. status=status,
  38. )
  39. res = success(result, "获取数据产品列表成功")
  40. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  41. except Exception as e:
  42. logger.error(f"获取数据产品列表失败: {str(e)}")
  43. res = failed(f"获取数据产品列表失败: {str(e)}")
  44. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  45. @bp.route("/products/<int:product_id>", methods=["GET"])
  46. def get_product(product_id: int):
  47. """
  48. 获取数据产品详情
  49. Path Parameters:
  50. product_id: 数据产品ID
  51. """
  52. try:
  53. product = DataProductService.get_product_by_id(product_id)
  54. if not product:
  55. res = failed("数据产品不存在", code=404)
  56. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  57. res = success(product.to_dict(), "获取数据产品详情成功")
  58. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  59. except Exception as e:
  60. logger.error(f"获取数据产品详情失败: {str(e)}")
  61. res = failed(f"获取数据产品详情失败: {str(e)}")
  62. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  63. # ==================== 数据预览接口 ====================
  64. @bp.route("/products/<int:product_id>/preview", methods=["GET"])
  65. def get_product_preview(product_id: int):
  66. """
  67. 获取数据产品的数据预览(默认200条)
  68. Path Parameters:
  69. product_id: 数据产品ID
  70. Query Parameters:
  71. limit: 预览数据条数,默认200,最大1000
  72. """
  73. try:
  74. limit = request.args.get("limit", 200, type=int)
  75. # 限制最大预览条数
  76. limit = min(limit, 1000)
  77. result = DataProductService.get_product_preview(
  78. product_id=product_id,
  79. limit=limit,
  80. )
  81. # 自动标记为已查看
  82. DataProductService.mark_as_viewed(product_id)
  83. res = success(result, "获取数据预览成功")
  84. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  85. except ValueError as ve:
  86. logger.warning(f"获取数据预览参数错误: {str(ve)}")
  87. res = failed(str(ve), code=404)
  88. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  89. except Exception as e:
  90. logger.error(f"获取数据预览失败: {str(e)}")
  91. res = failed(f"获取数据预览失败: {str(e)}")
  92. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  93. # ==================== 数据加工可视化接口 ====================
  94. @bp.route("/products/<int:product_id>/lineage-visualization", methods=["POST"])
  95. def get_lineage_visualization(product_id: int):
  96. """
  97. 获取数据产品的血缘可视化数据
  98. 通过数据产品关联的 BusinessDomain 节点,追溯其 INPUT/OUTPUT 血缘关系,
  99. 直到到达具有 DataResource 标签的源节点。同时将样例数据的键值映射到各节点字段。
  100. Path Parameters:
  101. product_id: 数据产品ID
  102. Request Body:
  103. sample_data: 单条样例数据(JSON对象,key为中文字段名)
  104. Returns:
  105. nodes: 节点列表,包含 BusinessDomain 和 DataFlow 节点
  106. lines: 关系列表,包含 INPUT 和 OUTPUT 关系
  107. lineage_depth: 血缘追溯深度
  108. """
  109. try:
  110. data = request.get_json()
  111. if not data:
  112. res = failed("请求数据不能为空", code=400)
  113. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  114. sample_data = data.get("sample_data")
  115. if not sample_data or not isinstance(sample_data, dict):
  116. res = failed("sample_data 必须是非空的 JSON 对象", code=400)
  117. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  118. result = DataProductService.get_data_lineage_visualization(
  119. product_id=product_id,
  120. sample_data=sample_data,
  121. )
  122. res = success(result, "获取血缘可视化数据成功")
  123. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  124. except ValueError as ve:
  125. logger.warning(f"获取血缘可视化参数错误: {str(ve)}")
  126. res = failed(str(ve), code=404)
  127. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  128. except Exception as e:
  129. logger.error(f"获取血缘可视化数据失败: {str(e)}")
  130. res = failed(f"获取血缘可视化数据失败: {str(e)}")
  131. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  132. # ==================== Excel下载接口 ====================
  133. @bp.route("/products/<int:product_id>/download", methods=["GET"])
  134. def download_product_excel(product_id: int):
  135. """
  136. 下载数据产品数据为Excel文件
  137. Path Parameters:
  138. product_id: 数据产品ID
  139. Query Parameters:
  140. limit: 导出数据条数,默认200,最大10000
  141. """
  142. try:
  143. limit = request.args.get("limit", 200, type=int)
  144. # 限制最大导出条数
  145. limit = min(limit, 10000)
  146. excel_file, filename = DataProductService.export_to_excel(
  147. product_id=product_id,
  148. limit=limit,
  149. )
  150. # 标记为已查看
  151. DataProductService.mark_as_viewed(product_id)
  152. return send_file(
  153. excel_file,
  154. mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  155. as_attachment=True,
  156. download_name=filename,
  157. )
  158. except ValueError as ve:
  159. logger.warning(f"下载Excel参数错误: {str(ve)}")
  160. res = failed(str(ve), code=404)
  161. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  162. except Exception as e:
  163. logger.error(f"下载Excel失败: {str(e)}")
  164. res = failed(f"下载Excel失败: {str(e)}")
  165. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  166. # ==================== 标记已查看接口 ====================
  167. @bp.route("/products/<int:product_id>/viewed", methods=["POST"])
  168. def mark_product_viewed(product_id: int):
  169. """
  170. 标记数据产品为已查看(消除更新提示)
  171. Path Parameters:
  172. product_id: 数据产品ID
  173. """
  174. try:
  175. product = DataProductService.mark_as_viewed(product_id)
  176. if not product:
  177. res = failed("数据产品不存在", code=404)
  178. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  179. res = success(product.to_dict(), "标记已查看成功")
  180. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  181. except Exception as e:
  182. logger.error(f"标记已查看失败: {str(e)}")
  183. res = failed(f"标记已查看失败: {str(e)}")
  184. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  185. # ==================== 刷新统计信息接口 ====================
  186. @bp.route("/products/<int:product_id>/refresh", methods=["POST"])
  187. def refresh_product_stats(product_id: int):
  188. """
  189. 刷新数据产品的统计信息
  190. Path Parameters:
  191. product_id: 数据产品ID
  192. """
  193. try:
  194. product = DataProductService.refresh_product_stats(product_id)
  195. if not product:
  196. res = failed("数据产品不存在", code=404)
  197. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  198. res = success(product.to_dict(), "刷新统计信息成功")
  199. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  200. except Exception as e:
  201. logger.error(f"刷新统计信息失败: {str(e)}")
  202. res = failed(f"刷新统计信息失败: {str(e)}")
  203. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  204. # ==================== 删除数据产品接口 ====================
  205. @bp.route("/products/<int:product_id>", methods=["DELETE"])
  206. def delete_product(product_id: int):
  207. """
  208. 删除数据产品
  209. Path Parameters:
  210. product_id: 数据产品ID
  211. """
  212. try:
  213. result = DataProductService.delete_product(product_id)
  214. if not result:
  215. res = failed("数据产品不存在", code=404)
  216. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  217. res = success({}, "删除数据产品成功")
  218. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  219. except Exception as e:
  220. logger.error(f"删除数据产品失败: {str(e)}")
  221. res = failed(f"删除数据产品失败: {str(e)}")
  222. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  223. # ==================== 手动注册数据产品接口 ====================
  224. @bp.route("/products", methods=["POST"])
  225. def register_product():
  226. """
  227. 手动注册数据产品
  228. Request Body:
  229. product_name: 数据产品名称(必填)
  230. product_name_en: 数据产品英文名(必填)
  231. target_table: 目标表名(必填)
  232. target_schema: 目标schema(可选,默认public)
  233. description: 描述(可选)
  234. """
  235. try:
  236. data = request.get_json()
  237. if not data:
  238. res = failed("请求数据不能为空", code=400)
  239. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  240. # 验证必填字段
  241. required_fields = ["product_name", "product_name_en", "target_table"]
  242. for field in required_fields:
  243. if not data.get(field):
  244. res = failed(f"缺少必填字段: {field}", code=400)
  245. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  246. product = DataProductService.register_data_product(
  247. product_name=data["product_name"],
  248. product_name_en=data["product_name_en"],
  249. target_table=data["target_table"],
  250. target_schema=data.get("target_schema", "public"),
  251. description=data.get("description"),
  252. source_dataflow_id=data.get("source_dataflow_id"),
  253. source_dataflow_name=data.get("source_dataflow_name"),
  254. created_by=data.get("created_by", "manual"),
  255. )
  256. res = success(product.to_dict(), "注册数据产品成功")
  257. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  258. except Exception as e:
  259. logger.error(f"注册数据产品失败: {str(e)}")
  260. res = failed(f"注册数据产品失败: {str(e)}")
  261. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  262. # ==================== 数据订单接口 ====================
  263. @bp.route("/orderlist", methods=["GET"])
  264. def get_orders():
  265. """
  266. 获取数据订单列表
  267. Query Parameters:
  268. page: 页码,默认 1
  269. page_size: 每页数量,默认 20
  270. search: 搜索关键词
  271. status: 状态过滤 (pending/analyzing/processing/completed/rejected等)
  272. """
  273. try:
  274. page = request.args.get("page", 1, type=int)
  275. page_size = request.args.get("page_size", 20, type=int)
  276. search = request.args.get("search", "")
  277. status = request.args.get("status")
  278. result = DataOrderService.get_orders(
  279. page=page,
  280. page_size=page_size,
  281. search=search,
  282. status=status,
  283. )
  284. res = success(result, "获取数据订单列表成功")
  285. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  286. except Exception as e:
  287. logger.error(f"获取数据订单列表失败: {str(e)}")
  288. res = failed(f"获取数据订单列表失败: {str(e)}")
  289. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  290. @bp.route("/orders/<int:order_id>/detail", methods=["GET"])
  291. def get_order(order_id: int):
  292. """
  293. 获取数据订单详情
  294. Path Parameters:
  295. order_id: 数据订单ID
  296. """
  297. try:
  298. order = DataOrderService.get_order_by_id(order_id)
  299. if not order:
  300. res = failed("数据订单不存在", code=404)
  301. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  302. res = success(order.to_dict(), "获取数据订单详情成功")
  303. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  304. except Exception as e:
  305. logger.error(f"获取数据订单详情失败: {str(e)}")
  306. res = failed(f"获取数据订单详情失败: {str(e)}")
  307. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  308. @bp.route("/neworder", methods=["POST"])
  309. def create_order():
  310. """
  311. 创建数据订单
  312. Request Body:
  313. title: 订单标题(必填)
  314. description: 需求描述(必填)
  315. created_by: 创建人(可选,默认user)
  316. """
  317. try:
  318. data = request.get_json()
  319. if not data:
  320. res = failed("请求数据不能为空", code=400)
  321. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  322. # 验证必填字段
  323. required_fields = ["title", "description"]
  324. for field in required_fields:
  325. if not data.get(field):
  326. res = failed(f"缺少必填字段: {field}", code=400)
  327. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  328. order = DataOrderService.create_order(
  329. title=data["title"],
  330. description=data["description"],
  331. created_by=data.get("created_by", "user"),
  332. )
  333. res = success(order.to_dict(), "创建数据订单成功")
  334. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  335. except Exception as e:
  336. logger.error(f"创建数据订单失败: {str(e)}")
  337. res = failed(f"创建数据订单失败: {str(e)}")
  338. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  339. @bp.route("/orders/<int:order_id>/update", methods=["PUT"])
  340. def update_order(order_id: int):
  341. """
  342. 更新数据订单(支持修改描述和提取结果)
  343. 只允许在 pending、manual_review、need_supplement 状态下修改
  344. Path Parameters:
  345. order_id: 数据订单ID
  346. Request Body:
  347. title: 订单标题(可选)
  348. description: 需求描述(可选)
  349. extracted_domains: 提取的业务领域列表(可选)
  350. extracted_fields: 提取的数据字段列表(可选)
  351. extraction_purpose: 数据用途(可选)
  352. """
  353. try:
  354. data = request.get_json()
  355. if not data:
  356. res = failed("请求数据不能为空", code=400)
  357. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  358. order = DataOrderService.update_order(
  359. order_id=order_id,
  360. title=data.get("title"),
  361. description=data.get("description"),
  362. extracted_domains=data.get("extracted_domains"),
  363. extracted_fields=data.get("extracted_fields"),
  364. extraction_purpose=data.get("extraction_purpose"),
  365. )
  366. if not order:
  367. res = failed("数据订单不存在", code=404)
  368. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  369. res = success(order.to_dict(), "更新数据订单成功")
  370. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  371. except ValueError as ve:
  372. logger.warning(f"更新数据订单参数错误: {str(ve)}")
  373. res = failed(str(ve), code=400)
  374. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  375. except Exception as e:
  376. logger.error(f"更新数据订单失败: {str(e)}")
  377. res = failed(f"更新数据订单失败: {str(e)}")
  378. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  379. @bp.route("/orders/<int:order_id>/analyze", methods=["POST"])
  380. def analyze_order(order_id: int):
  381. """
  382. 分析数据订单(提取实体并检测图谱连通性)
  383. Path Parameters:
  384. order_id: 数据订单ID
  385. """
  386. try:
  387. order = DataOrderService.analyze_order(order_id)
  388. if not order:
  389. res = failed("数据订单不存在", code=404)
  390. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  391. res = success(order.to_dict(), "数据订单分析完成")
  392. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  393. except Exception as e:
  394. logger.error(f"分析数据订单失败: {str(e)}")
  395. res = failed(f"分析数据订单失败: {str(e)}")
  396. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  397. @bp.route("/orders/<int:order_id>/approve", methods=["POST"])
  398. def approve_order(order_id: int):
  399. """
  400. 审批通过数据订单,并自动生成 BusinessDomain 和 DataFlow 资源
  401. 只允许从 pending_approval 或 manual_review 状态审批
  402. Path Parameters:
  403. order_id: 数据订单ID
  404. Request Body:
  405. processed_by: 处理人(可选,默认admin)
  406. Returns:
  407. order: 更新后的订单信息
  408. generated_resources: 生成的资源信息(包含 dataflow_id、target_business_domain_id 等)
  409. """
  410. try:
  411. data = request.get_json() or {}
  412. processed_by = data.get("processed_by", "admin")
  413. result = DataOrderService.approve_order(order_id, processed_by)
  414. res = success(result, "数据订单审批通过,资源已生成")
  415. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  416. except ValueError as ve:
  417. logger.warning(f"审批数据订单参数错误: {str(ve)}")
  418. res = failed(str(ve), code=400)
  419. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  420. except Exception as e:
  421. logger.error(f"审批数据订单失败: {str(e)}")
  422. res = failed(f"审批数据订单失败: {str(e)}")
  423. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  424. @bp.route("/orders/<int:order_id>/reject", methods=["POST"])
  425. def reject_order(order_id: int):
  426. """
  427. 驳回数据订单
  428. Path Parameters:
  429. order_id: 数据订单ID
  430. Request Body:
  431. reason: 驳回原因(必填)
  432. processed_by: 处理人(可选,默认admin)
  433. """
  434. try:
  435. data = request.get_json()
  436. if not data:
  437. res = failed("请求数据不能为空", code=400)
  438. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  439. reason = data.get("reason")
  440. if not reason:
  441. res = failed("驳回原因不能为空", code=400)
  442. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  443. processed_by = data.get("processed_by", "admin")
  444. order = DataOrderService.reject_order(order_id, reason, processed_by)
  445. if not order:
  446. res = failed("数据订单不存在", code=404)
  447. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  448. res = success(order.to_dict(), "数据订单已驳回")
  449. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  450. except Exception as e:
  451. logger.error(f"驳回数据订单失败: {str(e)}")
  452. res = failed(f"驳回数据订单失败: {str(e)}")
  453. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  454. @bp.route("/orders/<int:order_id>/onboard", methods=["POST"])
  455. def onboard_order(order_id: int):
  456. """
  457. 数据工厂回调:设置订单为数据产品就绪状态
  458. 只允许从 processing 状态转换为 onboard 状态
  459. Path Parameters:
  460. order_id: 数据订单ID
  461. Request Body:
  462. product_id: 生成的数据产品ID(可选)
  463. dataflow_id: 数据流ID(可选)
  464. processed_by: 处理人(可选,默认n8n-workflow)
  465. """
  466. try:
  467. data = request.get_json() or {}
  468. order = DataOrderService.set_order_onboard(
  469. order_id=order_id,
  470. product_id=data.get("product_id"),
  471. dataflow_id=data.get("dataflow_id"),
  472. processed_by=data.get("processed_by", "n8n-workflow"),
  473. )
  474. if not order:
  475. res = failed("数据订单不存在", code=404)
  476. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  477. res = success(order.to_dict(), "数据订单已设置为数据产品就绪状态")
  478. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  479. except ValueError as ve:
  480. logger.warning(f"设置订单onboard状态参数错误: {str(ve)}")
  481. res = failed(str(ve), code=400)
  482. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  483. except Exception as e:
  484. logger.error(f"设置订单onboard状态失败: {str(e)}")
  485. res = failed(f"设置订单onboard状态失败: {str(e)}")
  486. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  487. @bp.route("/orders/<int:order_id>/complete", methods=["POST"])
  488. def complete_order(order_id: int):
  489. """
  490. 标记数据订单为最终完成状态
  491. 只允许从 onboard(数据产品就绪)状态标记完成
  492. Path Parameters:
  493. order_id: 数据订单ID
  494. Request Body:
  495. processed_by: 处理人(可选,默认user)
  496. """
  497. try:
  498. data = request.get_json() or {}
  499. order = DataOrderService.complete_order(
  500. order_id=order_id,
  501. processed_by=data.get("processed_by", "user"),
  502. )
  503. if not order:
  504. res = failed("数据订单不存在", code=404)
  505. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  506. res = success(order.to_dict(), "数据订单已完成")
  507. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  508. except ValueError as ve:
  509. logger.warning(f"完成数据订单参数错误: {str(ve)}")
  510. res = failed(str(ve), code=400)
  511. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  512. except Exception as e:
  513. logger.error(f"完成数据订单失败: {str(e)}")
  514. res = failed(f"完成数据订单失败: {str(e)}")
  515. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  516. @bp.route("/orders/<int:order_id>/delete", methods=["PUT"])
  517. def delete_order(order_id: int):
  518. """
  519. 删除数据订单(软删除)
  520. Path Parameters:
  521. order_id: 数据订单ID
  522. """
  523. try:
  524. result = DataOrderService.delete_order(order_id)
  525. if not result:
  526. res = failed("数据订单不存在", code=404)
  527. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  528. res = success({}, "删除数据订单成功")
  529. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  530. except Exception as e:
  531. logger.error(f"删除数据订单失败: {str(e)}")
  532. res = failed(f"删除数据订单失败: {str(e)}")
  533. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)