feat: 增强IP地址记录功能以支持地理位置获取
- 在app.py中新增GeoIP2数据库支持,获取用户IP的地理位置信息 - 更新RawResponse模型,新增location、raw_response、sum_response和avg_response字段以存储更多用户响应数据 - 修改结果处理逻辑,保存用户的地理位置信息和响应数据 - 更新.gitignore以排除GeoLite2-City.mmdb文件
This commit is contained in:
parent
e08909eacd
commit
ee9e6a5cca
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
scales/
|
||||
psychoscales.db
|
||||
__pycache__/
|
||||
psychoscales.db
|
||||
GeoLite2-City.mmdb
|
52
app.py
52
app.py
@ -10,11 +10,34 @@ from datetime import datetime
|
||||
from xml.etree import ElementTree as ET
|
||||
from sqlalchemy.orm import Session
|
||||
from database import get_db, RawResponse
|
||||
import geoip2.database
|
||||
|
||||
app = FastAPI()
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
# Initialize GeoIP2 reader
|
||||
try:
|
||||
geoip_reader = geoip2.database.Reader('GeoLite2-City.mmdb')
|
||||
except FileNotFoundError:
|
||||
print("Warning: GeoLite2-City.mmdb not found. IP location lookup will be disabled.")
|
||||
geoip_reader = None
|
||||
|
||||
def get_location_from_ip(ip):
|
||||
if not geoip_reader:
|
||||
return None
|
||||
try:
|
||||
response = geoip_reader.city(ip)
|
||||
return {
|
||||
'country': response.country.name,
|
||||
'city': response.city.name,
|
||||
'latitude': response.location.latitude,
|
||||
'longitude': response.location.longitude
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error getting location for IP {ip}: {e}")
|
||||
return None
|
||||
|
||||
# 加载所有问卷数据
|
||||
def load_all_scales():
|
||||
scales = {}
|
||||
@ -83,19 +106,6 @@ async def result(request: Request, scale_id: str, db: Session = Depends(get_db))
|
||||
tags, scales = load_all_scales()
|
||||
scale = scales.get(scale_id)
|
||||
if scale:
|
||||
# Save response to database
|
||||
# Get real IP address considering proxy headers
|
||||
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or \
|
||||
request.headers.get("X-Real-IP", "") or \
|
||||
request.client.host
|
||||
db_response = RawResponse(
|
||||
scale_id=scale_id,
|
||||
user_agent=request.headers.get("user-agent", "Unknown"),
|
||||
ip_address=ip,
|
||||
response=dict(form_data)
|
||||
)
|
||||
db.add(db_response)
|
||||
db.commit()
|
||||
responses = {}
|
||||
average = {}
|
||||
options = {}
|
||||
@ -110,6 +120,22 @@ async def result(request: Request, scale_id: str, db: Session = Depends(get_db))
|
||||
else:
|
||||
responses[subscale] += int(form_data[str(qid)])
|
||||
average[subscale] = round(responses[subscale]/len(qids),2)
|
||||
# Save response to database
|
||||
ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or \
|
||||
request.headers.get("X-Real-IP", "") or \
|
||||
request.client.host # Get real IP address considering proxy headers
|
||||
location = get_location_from_ip(ip)# Get location information
|
||||
db_response = RawResponse(
|
||||
scale_id=scale_id,
|
||||
user_agent=request.headers.get("user-agent", "Unknown"),
|
||||
ip_address=ip,
|
||||
location=json.dumps(location) if location else None,
|
||||
raw_response=dict(form_data),
|
||||
sum_response=responses,
|
||||
avg_response=average
|
||||
)
|
||||
db.add(db_response)
|
||||
db.commit()
|
||||
return templates.TemplateResponse("result.html", {
|
||||
"request": request,
|
||||
"responses": responses,
|
||||
|
13
database.py
13
database.py
@ -2,24 +2,29 @@ from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime,
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker, relationship
|
||||
from datetime import datetime, UTC
|
||||
import json
|
||||
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./psychoscales.db"
|
||||
|
||||
engine = create_engine(
|
||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
||||
SQLALCHEMY_DATABASE_URL,
|
||||
connect_args={"check_same_thread": False},
|
||||
json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False)
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class RawResponse(Base):
|
||||
__tablename__ = "responses_raw"
|
||||
|
||||
__tablename__ = "responses"
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
scale_id = Column(String, index=True)
|
||||
user_agent = Column(String)
|
||||
ip_address = Column(String)
|
||||
response = Column(JSON)
|
||||
location = Column(String)
|
||||
raw_response = Column(JSON)
|
||||
sum_response = Column(JSON)
|
||||
avg_response = Column(JSON)
|
||||
created_at = Column(DateTime, default=datetime.now(UTC))
|
||||
|
||||
# Create tables
|
||||
|
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user