|
|
|
|
@ -5,62 +5,135 @@ Flask 服务器 - 智途投送电脑端
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import sqlite3
|
|
|
|
|
import secrets
|
|
|
|
|
from functools import wraps
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from flask import Flask, send_from_directory, request, jsonify
|
|
|
|
|
from flask_cors import CORS
|
|
|
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 项目根目录(server 的上级目录)
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
# 数据文件路径
|
|
|
|
|
DATA_FILE = os.path.join(os.path.dirname(__file__), 'accounts.json')
|
|
|
|
|
|
|
|
|
|
# 加载账号数据
|
|
|
|
|
def load_accounts():
|
|
|
|
|
if os.path.exists(DATA_FILE):
|
|
|
|
|
try:
|
|
|
|
|
with open(DATA_FILE, 'r', encoding='utf-8') as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
# 默认账号
|
|
|
|
|
return {
|
|
|
|
|
"soldier_001": { "password": "123456", "name": "张三", "unit": "第3步兵师/1连", "role": "狙击手" },
|
|
|
|
|
"soldier_002": { "password": "123456", "name": "李四", "unit": "第3步兵师/2连", "role": "机枪手" },
|
|
|
|
|
"soldier_003": { "password": "123456", "name": "王五", "unit": "第3步兵师/3连", "role": "通讯员" }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 保存账号数据
|
|
|
|
|
def save_accounts():
|
|
|
|
|
try:
|
|
|
|
|
with open(DATA_FILE, 'w', encoding='utf-8') as f:
|
|
|
|
|
json.dump(_accounts, f, ensure_ascii=False, indent=2)
|
|
|
|
|
print("✅ 账号数据已保存")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"❌ 保存账号数据失败: {e}")
|
|
|
|
|
|
|
|
|
|
# 初始化账号数据
|
|
|
|
|
_accounts = load_accounts()
|
|
|
|
|
# 项目根目录(app.py 所在目录,部署时前端静态文件应放在同级目录)
|
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
DB_PATH = os.path.join(os.path.dirname(__file__), 'zhitu.db')
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__, static_folder=BASE_DIR)
|
|
|
|
|
CORS(app) # 允许跨域请求,支持单兵APP访问
|
|
|
|
|
|
|
|
|
|
# ---- 内存数据存储 ----
|
|
|
|
|
soldiers = {} # { soldier_id: { id, name, lat, lng, updated_at } }
|
|
|
|
|
danger_zones = [] # [ { id, lat, lng, radius, description, created_at } ]
|
|
|
|
|
_demands = [] # [ { id, soldier_id, soldier_name, type, quantity, unit, urgency, status, created_at } ]
|
|
|
|
|
_demand_id_counter = 0
|
|
|
|
|
_drop_points = [ # 推荐投放点(演示数据)
|
|
|
|
|
# ---- SQLite 数据库 ----
|
|
|
|
|
def get_db():
|
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
|
|
|
conn.row_factory = sqlite3.Row
|
|
|
|
|
return conn
|
|
|
|
|
|
|
|
|
|
def init_db():
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.executescript('''
|
|
|
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
|
|
|
soldier_id TEXT PRIMARY KEY,
|
|
|
|
|
password_hash TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
unit TEXT,
|
|
|
|
|
role TEXT,
|
|
|
|
|
created_at TEXT
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS demands (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
soldier_id TEXT,
|
|
|
|
|
soldier_name TEXT,
|
|
|
|
|
type TEXT,
|
|
|
|
|
items TEXT,
|
|
|
|
|
quantity INTEGER,
|
|
|
|
|
unit TEXT,
|
|
|
|
|
urgency TEXT,
|
|
|
|
|
status TEXT DEFAULT '待处理',
|
|
|
|
|
lat REAL,
|
|
|
|
|
lng REAL,
|
|
|
|
|
created_at TEXT
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS soldiers (
|
|
|
|
|
soldier_id TEXT PRIMARY KEY,
|
|
|
|
|
name TEXT,
|
|
|
|
|
lat REAL,
|
|
|
|
|
lng REAL,
|
|
|
|
|
updated_at TEXT
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS sos_alerts (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
soldier_id TEXT,
|
|
|
|
|
soldier_name TEXT,
|
|
|
|
|
lat REAL,
|
|
|
|
|
lng REAL,
|
|
|
|
|
alert_time TEXT,
|
|
|
|
|
handled INTEGER DEFAULT 0
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS danger_zones (
|
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
lat REAL,
|
|
|
|
|
lng REAL,
|
|
|
|
|
radius REAL,
|
|
|
|
|
description TEXT,
|
|
|
|
|
created_at TEXT
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
|
|
|
id TEXT PRIMARY KEY,
|
|
|
|
|
soldier_id TEXT,
|
|
|
|
|
soldier_name TEXT,
|
|
|
|
|
type TEXT,
|
|
|
|
|
status TEXT,
|
|
|
|
|
progress INTEGER DEFAULT 0,
|
|
|
|
|
eta TEXT,
|
|
|
|
|
remain_time TEXT,
|
|
|
|
|
start_name TEXT,
|
|
|
|
|
target_name TEXT,
|
|
|
|
|
start_lat REAL,
|
|
|
|
|
start_lng REAL,
|
|
|
|
|
end_lat REAL,
|
|
|
|
|
end_lng REAL,
|
|
|
|
|
safety_score INTEGER,
|
|
|
|
|
created_at TEXT
|
|
|
|
|
);
|
|
|
|
|
CREATE TABLE IF NOT EXISTS tokens (
|
|
|
|
|
token TEXT PRIMARY KEY,
|
|
|
|
|
soldier_id TEXT,
|
|
|
|
|
name TEXT,
|
|
|
|
|
unit TEXT,
|
|
|
|
|
role TEXT,
|
|
|
|
|
created_at TEXT
|
|
|
|
|
);
|
|
|
|
|
''')
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
def init_demo_accounts():
|
|
|
|
|
"""初始化演示账号到数据库,确保前后端账号统一"""
|
|
|
|
|
conn = get_db()
|
|
|
|
|
demo_accounts = [
|
|
|
|
|
("soldier_001", "123456", "张三", "第3步兵师/1连", "狙击手"),
|
|
|
|
|
("soldier_002", "123456", "李四", "第3步兵师/2连", "机枪手"),
|
|
|
|
|
("soldier_003", "123456", "王五", "第3步兵师/3连", "通讯员"),
|
|
|
|
|
]
|
|
|
|
|
for sid, pwd, name, unit, role in demo_accounts:
|
|
|
|
|
existing = conn.execute('SELECT 1 FROM accounts WHERE soldier_id=?', (sid,)).fetchone()
|
|
|
|
|
if not existing:
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO accounts (soldier_id, password_hash, name, unit, role, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (sid, generate_password_hash(pwd), name, unit, role,
|
|
|
|
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
init_db()
|
|
|
|
|
init_demo_accounts()
|
|
|
|
|
|
|
|
|
|
# ---- 内存数据(演示数据:投放点、无人机状态重启后恢复默认值)----
|
|
|
|
|
_drop_points = [
|
|
|
|
|
{"id": 1, "name": "安全点 #1", "safety_score": 92, "distance": 5, "reason": "深掩体保护,视角盲区", "lat": 30.0050, "lng": 120.0030},
|
|
|
|
|
{"id": 2, "name": "安全点 #2", "safety_score": 85, "distance": 12, "reason": "钢筋混凝土建筑内部", "lat": 30.0055, "lng": 120.0035},
|
|
|
|
|
{"id": 3, "name": "陷阱点 #3", "safety_score": 35, "distance": 20, "reason": "孤立断墙,成瞄准点", "lat": 30.0060, "lng": 120.0040}
|
|
|
|
|
]
|
|
|
|
|
_tasks = {} # { soldier_id: task }
|
|
|
|
|
_drone_status = { # 无人机实时状态(演示数据)
|
|
|
|
|
_drone_status = {
|
|
|
|
|
"drone_id": "无人机-01",
|
|
|
|
|
"task_id": "#001",
|
|
|
|
|
"status": "飞行中",
|
|
|
|
|
@ -74,20 +147,26 @@ _drone_status = { # 无人机实时状态(演示数据)
|
|
|
|
|
"lat": 30.0040,
|
|
|
|
|
"lng": 120.0025
|
|
|
|
|
}
|
|
|
|
|
_drone_logs = [ # 无人机动态日志
|
|
|
|
|
_drone_logs = [
|
|
|
|
|
{"time": "12:25:30", "message": "到达投放点"},
|
|
|
|
|
{"time": "12:20:45", "message": "接收任务指令"},
|
|
|
|
|
{"time": "12:10:00", "message": "任务分配"}
|
|
|
|
|
]
|
|
|
|
|
_sos_alerts = [] # 求救记录
|
|
|
|
|
# _accounts = { # 士兵账号 { soldier_id: { password, name, unit, role } }
|
|
|
|
|
# "soldier_001": { "password": "123456", "name": "张三", "unit": "第3步兵师/1连", "role": "狙击手" },
|
|
|
|
|
# "soldier_002": { "password": "123456", "name": "李四", "unit": "第3步兵师/2连", "role": "机枪手" },
|
|
|
|
|
# "soldier_003": { "password": "123456", "name": "王五", "unit": "第3步兵师/3连", "role": "通讯员" }
|
|
|
|
|
# }
|
|
|
|
|
_tasks = {} # { soldier_id: { id, status, progress, eta, ... } }
|
|
|
|
|
_task_id_counter = 0
|
|
|
|
|
_danger_id_counter = 0
|
|
|
|
|
# 计数器已从内存变量改为数据库查询,避免服务重启后主键冲突
|
|
|
|
|
|
|
|
|
|
def require_auth(f):
|
|
|
|
|
"""接口认证装饰器:验证 X-Auth-Token(从数据库查询,支持多 worker 共享)"""
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def decorated(*args, **kwargs):
|
|
|
|
|
token = request.headers.get('X-Auth-Token') or request.args.get('token')
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT soldier_id, name, unit, role FROM tokens WHERE token=?', (token,)).fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if not row:
|
|
|
|
|
return jsonify({"ok": False, "error": "未登录或token无效"}), 401
|
|
|
|
|
request.current_user = dict(row)
|
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
|
return decorated
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== 静态文件路由 =====
|
|
|
|
|
@ -110,78 +189,108 @@ def js_files(filename):
|
|
|
|
|
# ===== REST API: 士兵位置 =====
|
|
|
|
|
|
|
|
|
|
@app.route("/api/soldier/location", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def update_soldier_location():
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
sid = data.get("id")
|
|
|
|
|
if not sid:
|
|
|
|
|
return jsonify({"ok": False, "error": "missing id"}), 400
|
|
|
|
|
|
|
|
|
|
soldiers[sid] = {
|
|
|
|
|
"id": sid,
|
|
|
|
|
"name": data.get("name", sid),
|
|
|
|
|
"lat": float(data.get("lat", 0)),
|
|
|
|
|
"lng": float(data.get("lng", 0)),
|
|
|
|
|
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
}
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO soldiers (soldier_id, name, lat, lng, updated_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?)
|
|
|
|
|
ON CONFLICT(soldier_id) DO UPDATE SET
|
|
|
|
|
name=excluded.name, lat=excluded.lat, lng=excluded.lng, updated_at=excluded.updated_at
|
|
|
|
|
''', (sid, data.get("name", sid), float(data.get("lat", 0)), float(data.get("lng", 0)),
|
|
|
|
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/soldiers", methods=["GET"])
|
|
|
|
|
def get_soldiers():
|
|
|
|
|
return jsonify({"soldiers": list(soldiers.values())})
|
|
|
|
|
conn = get_db()
|
|
|
|
|
rows = conn.execute('SELECT * FROM soldiers').fetchall()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"soldiers": [dict(r) for r in rows]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== REST API: 危险区域 =====
|
|
|
|
|
|
|
|
|
|
@app.route("/api/danger-zones", methods=["GET"])
|
|
|
|
|
def get_danger_zones():
|
|
|
|
|
return jsonify({"danger_zones": danger_zones})
|
|
|
|
|
conn = get_db()
|
|
|
|
|
rows = conn.execute('SELECT * FROM danger_zones').fetchall()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"danger_zones": [dict(r) for r in rows]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/danger-zones", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def add_danger_zone():
|
|
|
|
|
global _danger_id_counter
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
_danger_id_counter += 1
|
|
|
|
|
zone = {
|
|
|
|
|
"id": _danger_id_counter,
|
|
|
|
|
"lat": float(data.get("lat", 0)),
|
|
|
|
|
"lng": float(data.get("lng", 0)),
|
|
|
|
|
"radius": float(data.get("radius", 50)),
|
|
|
|
|
"description": data.get("description", "危险区域"),
|
|
|
|
|
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
}
|
|
|
|
|
danger_zones.append(zone)
|
|
|
|
|
return jsonify({"ok": True, "id": zone["id"]})
|
|
|
|
|
conn = get_db()
|
|
|
|
|
cur = conn.execute('''
|
|
|
|
|
INSERT INTO danger_zones (lat, lng, radius, description, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?)
|
|
|
|
|
''', (zone["lat"], zone["lng"], zone["radius"], zone["description"], zone["created_at"]))
|
|
|
|
|
zone_id = cur.lastrowid
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True, "id": zone_id})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== REST API: 物资需求(单兵APP) =====
|
|
|
|
|
|
|
|
|
|
def _next_demand_id():
|
|
|
|
|
"""从数据库查询当前最大需求ID,生成下一个(避免内存计数器重启归零导致主键冲突)"""
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute("SELECT id FROM demands WHERE id LIKE 'REQ-%' ORDER BY id DESC LIMIT 1").fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if row:
|
|
|
|
|
try:
|
|
|
|
|
num = int(row["id"].replace("REQ-", ""))
|
|
|
|
|
except ValueError:
|
|
|
|
|
num = 0
|
|
|
|
|
else:
|
|
|
|
|
num = 0
|
|
|
|
|
return "REQ-" + str(num + 1).zfill(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/demand", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def post_demand():
|
|
|
|
|
global _demand_id_counter
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
_demand_id_counter += 1
|
|
|
|
|
|
|
|
|
|
# 获取投放点坐标(如果有)
|
|
|
|
|
drop_point = data.get("drop_point") or {}
|
|
|
|
|
lat = float(drop_point.get("lat", 0)) if isinstance(drop_point, dict) else 0
|
|
|
|
|
lng = float(drop_point.get("lng", 0)) if isinstance(drop_point, dict) else 0
|
|
|
|
|
|
|
|
|
|
# 如果APP没传坐标,尝试从士兵位置获取
|
|
|
|
|
soldier_id = data.get("soldier_id", "unknown")
|
|
|
|
|
if lat == 0 and lng == 0 and soldier_id in soldiers:
|
|
|
|
|
lat = soldiers[soldier_id].get("lat", 0)
|
|
|
|
|
lng = soldiers[soldier_id].get("lng", 0)
|
|
|
|
|
if lat == 0 and lng == 0:
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT lat, lng FROM soldiers WHERE soldier_id=?', (soldier_id,)).fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if row:
|
|
|
|
|
lat, lng = row["lat"], row["lng"]
|
|
|
|
|
|
|
|
|
|
# 构建物资清单描述
|
|
|
|
|
qty = int(data.get("quantity", 0))
|
|
|
|
|
unit = data.get("unit", "个")
|
|
|
|
|
item_type = data.get("type", "未知")
|
|
|
|
|
items = f"{item_type} × {qty}{unit}"
|
|
|
|
|
demand_id = _next_demand_id()
|
|
|
|
|
|
|
|
|
|
demand = {
|
|
|
|
|
"id": "REQ-" + str(_demand_id_counter).zfill(3),
|
|
|
|
|
"id": demand_id,
|
|
|
|
|
"soldier_id": soldier_id,
|
|
|
|
|
"soldier_name": data.get("soldier_name", "未知"),
|
|
|
|
|
"type": item_type + "补给",
|
|
|
|
|
@ -194,25 +303,38 @@ def post_demand():
|
|
|
|
|
"lng": lng,
|
|
|
|
|
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
}
|
|
|
|
|
_demands.append(demand)
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO demands (id, soldier_id, soldier_name, type, items, quantity, unit, urgency, status, lat, lng, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (demand["id"], demand["soldier_id"], demand["soldier_name"], demand["type"],
|
|
|
|
|
demand["items"], demand["quantity"], demand["unit"], demand["urgency"],
|
|
|
|
|
demand["status"], demand["lat"], demand["lng"], demand["created_at"]))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True, "id": demand["id"]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/demands", methods=["GET"])
|
|
|
|
|
def get_demands():
|
|
|
|
|
soldier_id = request.args.get("soldier_id")
|
|
|
|
|
conn = get_db()
|
|
|
|
|
if soldier_id:
|
|
|
|
|
return jsonify({"demands": [d for d in _demands if d["soldier_id"] == soldier_id]})
|
|
|
|
|
# 返回所有待处理需求(供电脑端使用)
|
|
|
|
|
pending = [d for d in _demands if d["status"] == "待处理"]
|
|
|
|
|
return jsonify({"demands": pending})
|
|
|
|
|
rows = conn.execute('SELECT * FROM demands WHERE soldier_id=?', (soldier_id,)).fetchall()
|
|
|
|
|
else:
|
|
|
|
|
rows = conn.execute("SELECT * FROM demands WHERE status='待处理'").fetchall()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"demands": [dict(r) for r in rows]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/demands/<int:demand_id>", methods=["GET"])
|
|
|
|
|
@app.route("/api/demands/<demand_id>", methods=["GET"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def get_demand(demand_id):
|
|
|
|
|
for d in _demands:
|
|
|
|
|
if d["id"] == demand_id:
|
|
|
|
|
return jsonify({"demand": d})
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT * FROM demands WHERE id=?', (demand_id,)).fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if row:
|
|
|
|
|
return jsonify({"demand": dict(row)})
|
|
|
|
|
return jsonify({"ok": False, "error": "not found"}), 404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -224,24 +346,37 @@ def get_drop_points():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/drop-point", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def post_drop_point():
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
# 记录士兵选择的投放点
|
|
|
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== REST API: 任务状态 =====
|
|
|
|
|
|
|
|
|
|
def _next_task_id():
|
|
|
|
|
"""从数据库查询当前最大任务ID,生成下一个"""
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute("SELECT id FROM tasks WHERE id LIKE '#%' ORDER BY id DESC LIMIT 1").fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if row:
|
|
|
|
|
try:
|
|
|
|
|
num = int(row["id"].replace("#", ""))
|
|
|
|
|
except ValueError:
|
|
|
|
|
num = 0
|
|
|
|
|
else:
|
|
|
|
|
num = 0
|
|
|
|
|
return "#" + str(num + 1).zfill(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/task/dispatch", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def dispatch_task():
|
|
|
|
|
"""电脑端调度任务时调用"""
|
|
|
|
|
global _task_id_counter
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
soldier_id = data.get("soldier_id", "unknown")
|
|
|
|
|
_task_id_counter += 1
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
"id": "#" + str(_task_id_counter).zfill(3),
|
|
|
|
|
"id": _next_task_id(),
|
|
|
|
|
"soldier_id": soldier_id,
|
|
|
|
|
"soldier_name": data.get("soldier_name", "未知"),
|
|
|
|
|
"type": data.get("type", "未知"),
|
|
|
|
|
@ -258,57 +393,76 @@ def dispatch_task():
|
|
|
|
|
"safety_score": data.get("safety_score", 90),
|
|
|
|
|
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
}
|
|
|
|
|
_tasks[soldier_id] = task
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO tasks (id, soldier_id, soldier_name, type, status, progress, eta, remain_time,
|
|
|
|
|
start_name, target_name, start_lat, start_lng, end_lat, end_lng, safety_score, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (task["id"], task["soldier_id"], task["soldier_name"], task["type"], task["status"],
|
|
|
|
|
task["progress"], task["eta"], task["remain_time"], task["start_name"], task["target_name"],
|
|
|
|
|
task["start_lat"], task["start_lng"], task["end_lat"], task["end_lng"],
|
|
|
|
|
task["safety_score"], task["created_at"]))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
# 更新需求状态为"已调度"
|
|
|
|
|
demand_id = data.get("demand_id")
|
|
|
|
|
if demand_id:
|
|
|
|
|
for d in _demands:
|
|
|
|
|
if d["id"] == demand_id:
|
|
|
|
|
d["status"] = "已调度"
|
|
|
|
|
break
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.execute("UPDATE demands SET status='已调度' WHERE id=?", (demand_id,))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
return jsonify({"ok": True, "task": task})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/task/current", methods=["GET"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def get_current_task():
|
|
|
|
|
soldier_id = request.args.get("soldier_id", "soldier_001")
|
|
|
|
|
task = _tasks.get(soldier_id)
|
|
|
|
|
if not task:
|
|
|
|
|
# 没有任务时返回模拟数据
|
|
|
|
|
task = {
|
|
|
|
|
"id": "#--",
|
|
|
|
|
"status": "无任务",
|
|
|
|
|
"progress": 0,
|
|
|
|
|
"eta": "--",
|
|
|
|
|
"remain_time": "--",
|
|
|
|
|
"start_name": "--",
|
|
|
|
|
"target_name": "--",
|
|
|
|
|
"safety_score": 0
|
|
|
|
|
}
|
|
|
|
|
return jsonify({"task": task})
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT * FROM tasks WHERE soldier_id=?', (soldier_id,)).fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if row:
|
|
|
|
|
return jsonify({"task": dict(row)})
|
|
|
|
|
return jsonify({"task": {
|
|
|
|
|
"id": "#--", "status": "无任务", "progress": 0, "eta": "--",
|
|
|
|
|
"remain_time": "--", "start_name": "--", "target_name": "--", "safety_score": 0
|
|
|
|
|
}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/task/update", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def update_task():
|
|
|
|
|
"""更新任务状态(进度、ETA等)"""
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
soldier_id = data.get("soldier_id", "unknown")
|
|
|
|
|
task = _tasks.get(soldier_id)
|
|
|
|
|
if not task:
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT * FROM tasks WHERE soldier_id=?', (soldier_id,)).fetchone()
|
|
|
|
|
if not row:
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": False, "error": "无进行中的任务"}), 404
|
|
|
|
|
|
|
|
|
|
updates = []
|
|
|
|
|
params = []
|
|
|
|
|
if "progress" in data:
|
|
|
|
|
task["progress"] = int(data["progress"])
|
|
|
|
|
updates.append("progress=?")
|
|
|
|
|
params.append(int(data["progress"]))
|
|
|
|
|
if "status" in data:
|
|
|
|
|
task["status"] = data["status"]
|
|
|
|
|
updates.append("status=?")
|
|
|
|
|
params.append(data["status"])
|
|
|
|
|
if "eta" in data:
|
|
|
|
|
task["eta"] = data["eta"]
|
|
|
|
|
updates.append("eta=?")
|
|
|
|
|
params.append(data["eta"])
|
|
|
|
|
if "remain_time" in data:
|
|
|
|
|
task["remain_time"] = data["remain_time"]
|
|
|
|
|
|
|
|
|
|
return jsonify({"ok": True, "task": task})
|
|
|
|
|
updates.append("remain_time=?")
|
|
|
|
|
params.append(data["remain_time"])
|
|
|
|
|
|
|
|
|
|
if updates:
|
|
|
|
|
params.append(soldier_id)
|
|
|
|
|
conn.execute(f"UPDATE tasks SET {','.join(updates)} WHERE soldier_id=?", params)
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== REST API: 无人机状态 =====
|
|
|
|
|
@ -326,32 +480,28 @@ def get_drone_logs():
|
|
|
|
|
# ===== REST API: SOS求救 =====
|
|
|
|
|
|
|
|
|
|
@app.route("/api/sos", methods=["POST"])
|
|
|
|
|
@require_auth
|
|
|
|
|
def post_sos():
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
alert = {
|
|
|
|
|
"soldier_id": data.get("soldier_id", "unknown"),
|
|
|
|
|
"soldier_name": data.get("soldier_name", "未知"),
|
|
|
|
|
"lat": float(data.get("lat", 0)),
|
|
|
|
|
"lng": float(data.get("lng", 0)),
|
|
|
|
|
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
|
|
|
"handled": False
|
|
|
|
|
}
|
|
|
|
|
_sos_alerts.append(alert)
|
|
|
|
|
alert_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
conn = get_db()
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO sos_alerts (soldier_id, soldier_name, lat, lng, alert_time, handled)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (data.get("soldier_id", "unknown"), data.get("soldier_name", "未知"),
|
|
|
|
|
float(data.get("lat", 0)), float(data.get("lng", 0)), alert_time, 0))
|
|
|
|
|
# 同时标记为危险区域
|
|
|
|
|
global _danger_id_counter
|
|
|
|
|
_danger_id_counter += 1
|
|
|
|
|
danger_zones.append({
|
|
|
|
|
"id": _danger_id_counter,
|
|
|
|
|
"lat": alert["lat"],
|
|
|
|
|
"lng": alert["lng"],
|
|
|
|
|
"radius": 100,
|
|
|
|
|
"description": f"士兵求救: {alert['soldier_name']}",
|
|
|
|
|
"created_at": alert["time"]
|
|
|
|
|
})
|
|
|
|
|
return jsonify({"ok": True, "alert_id": len(_sos_alerts)})
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO danger_zones (lat, lng, radius, description, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?)
|
|
|
|
|
''', (float(data.get("lat", 0)), float(data.get("lng", 0)), 100,
|
|
|
|
|
f"士兵求救: {data.get('soldier_name', '未知')}", alert_time))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ===== REST API: 账号系统 =====
|
|
|
|
|
# ===== REST API: 账号系统(核心改造:持久化 + Token + 密码加密) =====
|
|
|
|
|
|
|
|
|
|
@app.route("/api/auth/register", methods=["POST"])
|
|
|
|
|
def auth_register():
|
|
|
|
|
@ -364,16 +514,20 @@ def auth_register():
|
|
|
|
|
|
|
|
|
|
if not sid or not pwd or not name:
|
|
|
|
|
return jsonify({"ok": False, "error": "士兵编号、密码、姓名不能为空"}), 400
|
|
|
|
|
if sid in _accounts:
|
|
|
|
|
|
|
|
|
|
conn = get_db()
|
|
|
|
|
existing = conn.execute('SELECT 1 FROM accounts WHERE soldier_id=?', (sid,)).fetchone()
|
|
|
|
|
if existing:
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": False, "error": "该士兵编号已注册"}), 409
|
|
|
|
|
|
|
|
|
|
_accounts[sid] = {
|
|
|
|
|
"password": pwd,
|
|
|
|
|
"name": name,
|
|
|
|
|
"unit": unit,
|
|
|
|
|
"role": role
|
|
|
|
|
}
|
|
|
|
|
save_accounts() # 添加这一行
|
|
|
|
|
password_hash = generate_password_hash(pwd)
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO accounts (soldier_id, password_hash, name, unit, role, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (sid, password_hash, name, unit, role, datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": True, "message": "注册成功"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -383,51 +537,60 @@ def auth_login():
|
|
|
|
|
sid = data.get("soldier_id", "").strip()
|
|
|
|
|
pwd = data.get("password", "").strip()
|
|
|
|
|
|
|
|
|
|
account = _accounts.get(sid)
|
|
|
|
|
if not account:
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT * FROM accounts WHERE soldier_id=?', (sid,)).fetchone()
|
|
|
|
|
|
|
|
|
|
if not row:
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": False, "error": "士兵编号不存在"}), 404
|
|
|
|
|
if account["password"] != pwd:
|
|
|
|
|
|
|
|
|
|
if not check_password_hash(row["password_hash"], pwd):
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({"ok": False, "error": "密码错误"}), 401
|
|
|
|
|
|
|
|
|
|
token = secrets.token_hex(16)
|
|
|
|
|
conn.execute('''
|
|
|
|
|
INSERT INTO tokens (token, soldier_id, name, unit, role, created_at)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
|
|
|
''', (token, sid, row["name"], row["unit"], row["role"], datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
conn.commit()
|
|
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
"ok": True,
|
|
|
|
|
"token": token,
|
|
|
|
|
"soldier_id": sid,
|
|
|
|
|
"name": account["name"],
|
|
|
|
|
"unit": account["unit"],
|
|
|
|
|
"role": account["role"]
|
|
|
|
|
"name": row["name"],
|
|
|
|
|
"unit": row["unit"],
|
|
|
|
|
"role": row["role"]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/auth/accounts", methods=["GET"])
|
|
|
|
|
def get_accounts():
|
|
|
|
|
conn = get_db()
|
|
|
|
|
rows = conn.execute('SELECT soldier_id, name, unit, role FROM accounts').fetchall()
|
|
|
|
|
conn.close()
|
|
|
|
|
return jsonify({
|
|
|
|
|
"accounts": [
|
|
|
|
|
{"soldier_id": k, "name": v["name"], "unit": v["unit"], "role": v["role"]}
|
|
|
|
|
for k, v in _accounts.items()
|
|
|
|
|
]
|
|
|
|
|
"accounts": [dict(r) for r in rows]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@app.route("/api/auth/change-password", methods=["POST"])
|
|
|
|
|
def change_password():
|
|
|
|
|
data = request.get_json(force=True)
|
|
|
|
|
sid = data.get("soldier_id", "").strip()
|
|
|
|
|
old_pwd = data.get("old_password", "").strip()
|
|
|
|
|
new_pwd = data.get("new_password", "").strip()
|
|
|
|
|
|
|
|
|
|
if not sid or not old_pwd or not new_pwd:
|
|
|
|
|
return jsonify({"ok": False, "error": "所有字段都不能为空"}), 400
|
|
|
|
|
|
|
|
|
|
account = _accounts.get(sid)
|
|
|
|
|
if not account:
|
|
|
|
|
return jsonify({"ok": False, "error": "用户不存在"}), 404
|
|
|
|
|
|
|
|
|
|
if account["password"] != old_pwd:
|
|
|
|
|
return jsonify({"ok": False, "error": "原密码错误"}), 401
|
|
|
|
|
|
|
|
|
|
# 更新密码
|
|
|
|
|
account["password"] = new_pwd
|
|
|
|
|
save_accounts() # 添加这一行
|
|
|
|
|
return jsonify({"ok": True, "message": "密码修改成功"})
|
|
|
|
|
|
|
|
|
|
@app.route("/api/auth/me", methods=["GET"])
|
|
|
|
|
def auth_me():
|
|
|
|
|
token = request.headers.get('X-Auth-Token') or request.args.get('token')
|
|
|
|
|
conn = get_db()
|
|
|
|
|
row = conn.execute('SELECT soldier_id, name, unit, role FROM tokens WHERE token=?', (token,)).fetchone()
|
|
|
|
|
conn.close()
|
|
|
|
|
if not row:
|
|
|
|
|
return jsonify({"ok": False, "error": "未登录"}), 401
|
|
|
|
|
return jsonify({"ok": True, "user": dict(row)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/ping", methods=["GET"])
|
|
|
|
|
def ping():
|
|
|
|
|
return jsonify({"ok": True, "message": "智途投送后端运行正常"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import sys
|
|
|
|
|
|