!7 merge qing-dev-sqlite3 into dev
sqlite3 micro_servers Created-by: qingss0 Commit-by: qingss0 Merged-by: qingss0 Description: !5 merge dev into main !6 merge dev into main init sqlite3 micro_servers update: 更新文件 LICENSE new: 新建文件 LICENSE ... See merge request: qingss0/micro_servers!7
This commit is contained in:
7 files changed
+343
-1
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) [year] [fullname]
|
Copyright (c) 2025 卿松
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 卿松
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
|
||||||
|
# Flask SQLite 执行服务
|
||||||
|
|
||||||
|
一个轻量级的 Flask HTTP 服务,用于安全地执行带参数的 SQLite SQL 语句,支持 `?` 和命名参数占位符,杜绝 SQL 注入。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- 通过 JSON 接口发送 SQL 和参数
|
||||||
|
- 强制使用参数绑定(`execute(sql, params)`),无 SQL 注入风险
|
||||||
|
- 统一返回格式,所有语句执行结果均包含 `data` 数组
|
||||||
|
- 自动事务管理,异常时回滚
|
||||||
|
- 单线程单进程模式,绑定 127.0.0.1,仅本地访问
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
- Python 3.7+
|
||||||
|
- pip
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install flask waitress
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
服务运行在 `http://127.0.0.1:5000`,仅允许本地访问。数据库文件 `database.db` 会在首次连接时自动创建(可提前建表或通过接口执行 `CREATE TABLE`)。
|
||||||
|
|
||||||
|
## API 文档
|
||||||
|
|
||||||
|
### 执行 SQL 语句
|
||||||
|
|
||||||
|
**端点**:`POST /execute`
|
||||||
|
|
||||||
|
**请求格式**:`Content-Type: application/json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sql": "SQL 语句,使用 ? 或 :key 作为占位符",
|
||||||
|
"params": [] 或 {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `sql`:待执行的 SQL 语句(仅允许单条语句)
|
||||||
|
- `params`:参数列表 `[值1, 值2, ...]` 或参数字典 `{"key": 值}`
|
||||||
|
|
||||||
|
**成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": [] // 查询结果数组,每条记录为字典;非查询语句返回空数组
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "错误描述"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
以下示例均通过 `curl` 和 Python `requests` 库演示。
|
||||||
|
|
||||||
|
### 1. 创建数据表
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER)"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
url = "http://127.0.0.1:5000/execute"
|
||||||
|
payload = {
|
||||||
|
"sql": "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER)"
|
||||||
|
}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
print(resp.json()) # {"success": true, "data": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 插入数据(使用 `?` 占位符)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "INSERT INTO users (name, age) VALUES (?, ?)", "params": ["Alice", 30]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
payload = {
|
||||||
|
"sql": "INSERT INTO users (name, age) VALUES (?, ?)",
|
||||||
|
"params": ["Bob", 25]
|
||||||
|
}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
print(resp.json()) # {"success": true, "data": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 插入数据(使用命名参数 `:key`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "INSERT INTO users (name, age) VALUES (:name, :age)", "params": {"name": "Charlie", "age": 28}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 查询全部数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "SELECT * FROM users"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{"id": 1, "name": "Alice", "age": 30},
|
||||||
|
{"id": 2, "name": "Bob", "age": 25},
|
||||||
|
{"id": 3, "name": "Charlie", "age": 28}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
payload = {"sql": "SELECT * FROM users"}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
users = resp.json()["data"]
|
||||||
|
for user in users:
|
||||||
|
print(user["name"], user["age"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 按条件查询
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "SELECT * FROM users WHERE age > ?", "params": [26]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
payload = {
|
||||||
|
"sql": "SELECT * FROM users WHERE age > :min_age",
|
||||||
|
"params": {"min_age": 26}
|
||||||
|
}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
print(resp.json()["data"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 更新数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "UPDATE users SET age = ? WHERE name = ?", "params": [31, "Alice"]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
payload = {
|
||||||
|
"sql": "UPDATE users SET age = :new_age WHERE id = :user_id",
|
||||||
|
"params": {"new_age": 26, "user_id": 2}
|
||||||
|
}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
print(resp.json()) # {"success": true, "data": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 删除数据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "DELETE FROM users WHERE id = ?", "params": [3]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Python 版本:
|
||||||
|
|
||||||
|
```python
|
||||||
|
payload = {
|
||||||
|
"sql": "DELETE FROM users WHERE name = :name",
|
||||||
|
"params": {"name": "Charlie"}
|
||||||
|
}
|
||||||
|
resp = requests.post(url, json=payload)
|
||||||
|
print(resp.json())
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. 错误处理示例
|
||||||
|
|
||||||
|
当 SQL 或参数错误时,返回明确的错误信息:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://127.0.0.1:5000/execute \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"sql": "SELECT * FROM nonexistent_table"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "no such table: nonexistent_table"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全机制
|
||||||
|
|
||||||
|
1. **参数绑定**:使用 `sqlite3` 官方推荐的 `execute(sql, params)` 方式传参,参数值与 SQL 结构完全分离。
|
||||||
|
2. **单语句执行**:`sqlite3.execute()` 仅执行第一条语句,无法通过分号注入多条命令。
|
||||||
|
3. **错误回滚**:若执行过程中出现异常,已修改的数据会回滚,确保数据一致性。
|
||||||
|
4. **本地绑定**:服务监听 `127.0.0.1`,仅本机可访问,避免网络暴露风险。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app.py # 主程序(使用 waitress 单线程模式)
|
||||||
|
└── README.md # 本文档
|
||||||
|
```
|
||||||
|
|
||||||
|
数据库文件 `database.db` 会在运行时自动生成。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- 服务采用 waitress 单线程模式(`threads=1`),确保请求顺序执行,适合低并发场景。
|
||||||
|
- 如需处理更高并发,可适当增加 `threads` 参数(例如 `threads=4`),但仍需注意 SQLite 的并发限制。
|
||||||
|
- 该服务无认证机制,推荐仅在本机或内网可信环境使用。如需对外服务,请添加鉴权并考虑使用 nginx 等反向代理。
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd "$(dirname "$0")" || exit
|
||||||
|
source ./venv/bin/activate
|
||||||
|
#exec ./main.bin
|
||||||
|
exec python3 main.py
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd "$(dirname "$0")" || exit
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
|
nuitka --onefile --jobs=1 main.py
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import sqlite3
|
||||||
|
from flask import Flask, request, jsonify, g
|
||||||
|
from waitress import serve
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['DATABASE'] = 'database.db'
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
if 'db' not in g:
|
||||||
|
conn = sqlite3.connect(app.config['DATABASE'])
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
g.db = conn
|
||||||
|
return g.db
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_db(exception):
|
||||||
|
db = g.pop('db', None)
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
@app.route('/execute', methods=['POST'])
|
||||||
|
def execute_sql():
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'sql' not in data:
|
||||||
|
return jsonify({'success': False, 'error': '缺少 sql 字段'}), 400
|
||||||
|
|
||||||
|
sql = data['sql'].strip()
|
||||||
|
params = data.get('params', [])
|
||||||
|
|
||||||
|
# 参数必须是列表、元组或字典
|
||||||
|
if not isinstance(params, (list, tuple, dict)):
|
||||||
|
params = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
cur = db.execute(sql, params)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
rows = cur.fetchall()
|
||||||
|
result = [dict(row) for row in rows]
|
||||||
|
except Exception:
|
||||||
|
result = []
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'data': result
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
db = g.get('db')
|
||||||
|
if db:
|
||||||
|
db.rollback()
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 400
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 单线程单进程,绑定 127.0.0.1:8005
|
||||||
|
serve(app, host='127.0.0.1', port=8005, threads=1)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
flask
|
||||||
|
waitress
|
||||||
Reference in new issue
Block a user