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