api_db.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import os
  2. import sqlite3
  3. from flask import Flask, jsonify, request, send_file, g
  4. from werkzeug.utils import secure_filename
  5. from flask_cors import CORS
  6. import pandas as pd
  7. from io import BytesIO
  8. import logging
  9. app = Flask(__name__)
  10. # 设置数据库文件和上传文件夹的路径
  11. DATABASE = 'SoilAcidification.db'
  12. UPLOAD_FOLDER = 'uploads'
  13. app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
  14. os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
  15. # 跨源资源共享(CORS)配置,允许跨域请求
  16. CORS(app)
  17. # 设置日志
  18. logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
  19. logging.basicConfig(level=logging.INFO)
  20. logger = logging.getLogger(__name__)
  21. # 创建一个数据库连接池
  22. def get_db():
  23. db = getattr(g, '_database', None)
  24. if db is None:
  25. db = g._database = sqlite3.connect(DATABASE)
  26. return db
  27. # 获取列名的函数
  28. def get_column_names(table_name):
  29. db = get_db()
  30. cur = db.cursor()
  31. cur.execute(f"PRAGMA table_info({table_name});")
  32. columns = [column[1] for column in cur.fetchall()]
  33. db.close()
  34. return columns
  35. @app.after_request
  36. def add_headers(response):
  37. response.headers['Content-Type'] = 'application/json; charset=utf-8'
  38. return response
  39. @app.route('/')
  40. def index():
  41. return 'Hello, World!'
  42. # 有这个函数来获取列名
  43. def get_column_names(table_name):
  44. """
  45. 根据表名获取对应的列名(字段)。
  46. """
  47. if table_name == 'current_reflux':
  48. # 返回列名映射
  49. return ['OM', 'CL', 'CEC', 'H_plus', 'HN', 'Al3_plus', 'Free_alumina', 'Free_iron_oxides', 'Delta_pH'], \
  50. ["有机质含量", "土壤粘粒重量", "阳离子交换量", "氢离子含量", "硝态氮含量", "铝离子含量", "游离氧化铝", "游离氧化铁", "酸碱差值"]
  51. elif table_name == 'current_reduce':
  52. # 返回列名映射
  53. return ['Q_over_b', 'pH', 'OM', 'CL', 'H', 'Al'], \
  54. ["比值", "酸碱度", "有机质含量", "氯离子含量", "氢离子含量", "铝离子含量"]
  55. else:
  56. return [], [] # 不支持的表名,返回空列表
  57. @app.route('/download_template', methods=['GET'])
  58. def download_template():
  59. """
  60. 根据表名生成模板文件(Excel 或 CSV),并返回下载。
  61. :return: 返回模板文件
  62. """
  63. table_name = request.args.get('table') # 从请求参数中获取表名
  64. if not table_name:
  65. return jsonify({'error': '表名参数缺失'}), 400
  66. # 获取表的列名
  67. try:
  68. column_names = get_column_names(table_name)
  69. except sqlite3.Error as e:
  70. return jsonify({'error': f'获取列名失败: {str(e)}'}), 400
  71. if not column_names:
  72. return jsonify({'error': f'未找到表 {table_name} 或列名为空'}), 400
  73. # 根据表名映射列名(可自定义标题)
  74. if table_name == 'current_reflux':
  75. column_titles = ["有机质含量", "土壤粘粒重量", "阳离子交换量", "氢离子含量", "硝态氮含量", "铝离子含量",
  76. "游离氧化铝", "游离氧化铁", "酸碱差值"]
  77. elif table_name == 'current_reduce':
  78. column_titles = ["比值", "酸碱度", "有机质含量", "氯离子含量", "氢离子含量1", "铝离子含量1"]
  79. else:
  80. return jsonify({'error': f'不支持的表名 {table_name}'}), 400
  81. # 使用 pandas 创建一个空的 DataFrame,列名为自定义的标题
  82. df = pd.DataFrame(columns=column_titles)
  83. # 根据需求选择模板格式:Excel 或 CSV
  84. file_format = request.args.get('format', 'excel').lower()
  85. if file_format == 'csv':
  86. # 将 DataFrame 写入内存中的 CSV 文件
  87. output = BytesIO()
  88. df.to_csv(output, index=False, encoding='utf-8')
  89. output.seek(0) # 返回文件开头
  90. return send_file(output, as_attachment=True, download_name=f'{table_name}_template.csv',
  91. mimetype='text/csv')
  92. else:
  93. # 默认生成 Excel 文件
  94. output = BytesIO()
  95. df.to_excel(output, index=False, engine='openpyxl')
  96. output.seek(0) # 返回文件开头
  97. return send_file(output, as_attachment=True, download_name=f'{table_name}_template.xlsx',
  98. mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
  99. # 模板下载接口
  100. @app.route('/import_data', methods=['POST'])
  101. def import_data():
  102. """
  103. 导入数据接口,根据表名将 Excel 或 CSV 数据导入到对应的数据库表中。
  104. """
  105. # 检查是否有文件
  106. if 'file' not in request.files:
  107. logging.error("请求中没有文件")
  108. return jsonify({'success': False, 'message': '文件缺失'}), 400
  109. file = request.files['file']
  110. table_name = request.form.get('table')
  111. # 检查表名参数
  112. if not table_name:
  113. logging.error("缺少表名参数")
  114. return jsonify({'success': False, 'message': '缺少表名参数'}), 400
  115. # 检查文件是否为空
  116. if file.filename == '':
  117. logging.error("文件为空")
  118. return jsonify({'success': False, 'message': '未选择文件'}), 400
  119. # 获取表的字段映射
  120. db_columns, display_titles = get_column_names(table_name)
  121. # 校验是否支持该表名
  122. if not db_columns:
  123. logging.error(f"不支持的表名: {table_name}")
  124. return jsonify({'success': False, 'message': f'不支持的表名: {table_name}'}), 400
  125. logging.info(f"表名 {table_name} 对应的字段: {db_columns}")
  126. # 中文列名到数据库列名的映射
  127. column_name_mapping = {
  128. "有机质含量": "OM",
  129. "土壤粘粒重量": "CL",
  130. "阳离子交换量": "CEC",
  131. "氢离子含量": "H_plus",
  132. "硝态氮含量": "HN",
  133. "铝离子含量": "Al3_plus",
  134. "游离氧化铝": "Free_alumina",
  135. "游离氧化铁": "Free_iron_oxides",
  136. "酸碱差值": "Delta_pH",
  137. "比值": "Q_over_b",
  138. "酸碱度": "pH",
  139. "氯离子含量": "CL",
  140. "氢离子含量1": "H",
  141. "铝离子含量1": "Al"
  142. }
  143. try:
  144. # 保存上传文件到临时目录
  145. temp_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
  146. logging.info(f"正在保存文件到临时路径: {temp_path}")
  147. file.save(temp_path)
  148. # 读取文件内容(支持 Excel 和 CSV 格式)
  149. if file.filename.endswith('.xlsx'):
  150. logging.info(f"读取 Excel 文件: {file.filename}")
  151. df = pd.read_excel(temp_path)
  152. elif file.filename.endswith('.csv'):
  153. logging.info(f"读取 CSV 文件: {file.filename}")
  154. df = pd.read_csv(temp_path)
  155. else:
  156. logging.error("仅支持 Excel 和 CSV 文件")
  157. return jsonify({'success': False, 'message': '仅支持 Excel 和 CSV 文件'}), 400
  158. logging.info(f"文件读取成功,列名: {df.columns.tolist()}")
  159. # 1. 移除“序号”列(如果存在)
  160. if '序号' in df.columns:
  161. df = df.drop(columns=['序号'])
  162. # 2. 将文件中的列名(中文)转换为数据库列名(英文)
  163. df.columns = [column_name_mapping.get(col, col) for col in df.columns]
  164. logging.info(f"转换后的列名: {df.columns.tolist()}")
  165. # 校验文件的列是否与数据库表字段匹配
  166. if not set(db_columns).issubset(set(df.columns)):
  167. logging.error(f"文件列名与数据库表不匹配. 文件列: {df.columns.tolist()}, 期望列: {db_columns}")
  168. return jsonify({'success': False, 'message': '文件列名与数据库表不匹配'}), 400
  169. logging.info("开始清洗数据:移除空行并按需要格式化")
  170. # 数据清洗:移除空行并按需要格式化
  171. df_cleaned = df[db_columns].dropna()
  172. logging.info(f"清洗后数据行数: {len(df_cleaned)}")
  173. # 插入数据到数据库
  174. conn = get_db()
  175. logging.info(f"开始将数据插入到表 {table_name} 中")
  176. # 使用事务确保操作的一致性
  177. with conn:
  178. df_cleaned.to_sql(table_name, conn, if_exists='append', index=False)
  179. logging.info(f"数据成功插入到表 {table_name} 中,插入行数: {len(df_cleaned)}")
  180. # 删除临时文件
  181. os.remove(temp_path)
  182. logging.info(f"临时文件 {temp_path} 删除成功")
  183. return jsonify({'success': True, 'message': '数据导入成功'}), 200
  184. except Exception as e:
  185. logging.error(f"导入失败: {e}", exc_info=True) # 捕获堆栈信息,方便调试
  186. return jsonify({'success': False, 'message': f'导入失败: {str(e)}'}), 500
  187. # 导出数据接口
  188. @app.route('/export_data', methods=['GET'])
  189. def export_data():
  190. """
  191. 根据表名从数据库导出数据,并返回 Excel 或 CSV 格式的文件
  192. :return: 返回文件
  193. """
  194. # 获取表名和文件格式
  195. table_name = request.args.get('table')
  196. if not table_name:
  197. return jsonify({'error': '缺少表名参数'}), 400
  198. file_format = request.args.get('format', 'excel').lower() # 默认生成 Excel 文件
  199. # 获取数据
  200. conn = get_db()
  201. query = f"SELECT * FROM {table_name};" # 查询表中的所有数据
  202. df = pd.read_sql(query, conn)
  203. conn.close()
  204. if file_format == 'csv':
  205. # 将数据保存为 CSV 格式
  206. output = BytesIO()
  207. df.to_csv(output, index=False)
  208. output.seek(0)
  209. return send_file(output, as_attachment=True, download_name=f'{table_name}_data.csv',
  210. mimetype='text/csv')
  211. else:
  212. # 默认生成 Excel 格式
  213. output = BytesIO()
  214. df.to_excel(output, index=False, engine='openpyxl')
  215. output.seek(0)
  216. return send_file(output, as_attachment=True, download_name=f'{table_name}_data.xlsx',
  217. mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
  218. # 添加记录
  219. @app.route('/add_item', methods=['POST'])
  220. def add_item():
  221. db = get_db()
  222. try:
  223. # 确保请求体是JSON格式
  224. data = request.get_json()
  225. if not data:
  226. raise ValueError("No JSON data provided")
  227. table_name = data.get('table')
  228. item_data = data.get('item')
  229. if not table_name or not item_data:
  230. return jsonify({'error': 'Missing table name or item data'}), 400
  231. cur = db.cursor()
  232. # 动态构建 SQL 语句
  233. columns = ', '.join(item_data.keys())
  234. placeholders = ', '.join(['?'] * len(item_data))
  235. sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
  236. cur.execute(sql, tuple(item_data.values()))
  237. db.commit()
  238. return jsonify({'success': True, 'message': 'Item added successfully'}), 201
  239. except ValueError as e:
  240. return jsonify({'error': str(e)}), 400
  241. except KeyError as e:
  242. return jsonify({'error': f'Missing data field: {e}'}), 400
  243. except sqlite3.IntegrityError as e:
  244. return jsonify({'error': 'Database integrity error', 'details': str(e)}), 409
  245. except sqlite3.Error as e:
  246. return jsonify({'error': 'Database error', 'details': str(e)}), 500
  247. finally:
  248. db.close()
  249. # 删除记录
  250. @app.route('/delete_item', methods=['POST'])
  251. def delete_item():
  252. data = request.get_json()
  253. table_name = data.get('table')
  254. condition = data.get('condition')
  255. # 日志:检查是否收到正确的参数
  256. logging.debug(f"Received data: table={table_name}, condition={condition}")
  257. if not table_name or not condition:
  258. logging.error("缺少表名或条件参数")
  259. return jsonify({"success": False, "message": "缺少表名或条件参数"}), 400
  260. try:
  261. key, value = condition.split('=')
  262. except ValueError:
  263. logging.error(f"条件格式错误,received condition: {condition}")
  264. return jsonify({"success": False, "message": "条件格式错误,应为 'key=value'"}), 400
  265. db = get_db()
  266. cur = db.cursor()
  267. # 日志:确认开始删除操作
  268. logging.debug(f"Attempting to delete from {table_name} where {key} = {value}")
  269. try:
  270. cur.execute(f"DELETE FROM {table_name} WHERE {key} = ?", (value,))
  271. db.commit()
  272. # 日志:删除成功
  273. logging.info(f"Record deleted from {table_name} where {key} = {value}")
  274. return jsonify({"success": True, "message": "记录删除成功"}), 200
  275. except sqlite3.Error as e:
  276. db.rollback()
  277. # 日志:捕获删除失败的错误
  278. logging.error(f"Failed to delete from {table_name} where {key} = {value}. Error: {e}")
  279. return jsonify({"success": False, "message": f"删除失败: {e}"}), 400
  280. # 更新记录
  281. @app.route('/update_item', methods=['PUT'])
  282. def update_record():
  283. data = request.get_json()
  284. if not data or 'table' not in data or 'item' not in data:
  285. return jsonify({"success": False, "message": "请求数据不完整"}), 400
  286. table_name = data['table']
  287. item = data['item']
  288. if not item or next(iter(item.keys())) is None:
  289. return jsonify({"success": False, "message": "记录数据为空"}), 400
  290. id_key = next(iter(item.keys()))
  291. record_id = item[id_key]
  292. updates = {key: value for key, value in item.items() if key != id_key}
  293. db = get_db()
  294. cur = db.cursor()
  295. try:
  296. record_id = int(record_id)
  297. except ValueError:
  298. return jsonify({"success": False, "message": "ID 必须是整数"}), 400
  299. parameters = list(updates.values()) + [record_id]
  300. set_clause = ','.join([f"{k} = ?" for k in updates.keys()])
  301. sql = f"UPDATE {table_name} SET {set_clause} WHERE {id_key} = ?"
  302. try:
  303. cur.execute(sql, parameters)
  304. db.commit()
  305. if cur.rowcount == 0:
  306. return jsonify({"success": False, "message": "未找到要更新的记录"}), 404
  307. return jsonify({"success": True, "message": "数据更新成功"}), 200
  308. except sqlite3.Error as e:
  309. db.rollback()
  310. return jsonify({"success": False, "message": f"更新失败: {e}"}), 400
  311. # 查询记录
  312. @app.route('/search/record', methods=['GET'])
  313. def sql_search():
  314. try:
  315. data = request.get_json()
  316. sql_table = data['table']
  317. record_id = data['id']
  318. db = get_db()
  319. cur = db.cursor()
  320. sql = f"SELECT * FROM {sql_table} WHERE id = ?"
  321. cur.execute(sql, (record_id,))
  322. rows = cur.fetchall()
  323. column_names = [desc[0] for desc in cur.description]
  324. if not rows:
  325. return jsonify({'error': '未查找到对应数据。'}), 400
  326. results = []
  327. for row in rows:
  328. result = {column_names[i]: row[i] for i in range(len(row))}
  329. results.append(result)
  330. cur.close()
  331. db.close()
  332. return jsonify(results), 200
  333. except sqlite3.Error as e:
  334. return jsonify({'error': str(e)}), 400
  335. except KeyError as e:
  336. return jsonify({'error': f'缺少必要的数据字段: {e}'}), 400
  337. # 提供表格数据
  338. @app.route('/tables', methods=['POST'])
  339. def get_table():
  340. data = request.get_json()
  341. table_name = data.get('table')
  342. if not table_name:
  343. return jsonify({'error': '需要表名'}), 400
  344. db = get_db()
  345. try:
  346. cur = db.cursor()
  347. cur.execute(f"SELECT * FROM {table_name}")
  348. rows = cur.fetchall()
  349. if not rows:
  350. return jsonify({'error': f'表 {table_name} 为空或不存在'}), 400
  351. headers = [description[0] for description in cur.description]
  352. return jsonify(rows=rows, headers=headers), 200
  353. except sqlite3.Error as e:
  354. return jsonify({'error': str(e)}), 400
  355. finally:
  356. db.close()
  357. if __name__ == '__main__':
  358. app.run(debug=True)