fix: 增加登陆权

alphaFuthures
Lxy 1 week ago
parent 17b8b990ac
commit e1bc7c8ca3

@ -15,3 +15,8 @@ build/
docker-compose.yml
Dockerfile
.dockerignore
android_app/
data/
logs/
*.log
.env.local

@ -0,0 +1,236 @@
# 期货智析平台 - Docker 部署指南
## 📋 部署前准备
### 1. 环境要求
- Docker Desktop for Windows已安装并运行
- Windows 10/11 专业版或家庭版WSL2后端
- 至少 2GB 可用内存
- 至少 5GB 磁盘空间
### 2. 检查Docker状态
```powershell
docker --version
docker-compose --version
```
## 🚀 快速部署
### 方法一:使用部署脚本(推荐)
1. 双击运行 `deploy.bat`
2. 等待自动完成构建和部署
3. 访问 http://localhost:9600
### 方法二:手动部署
```powershell
# 进入项目目录
cd e:\docker_workspace\cobot2.0_WorkHorse\app\working\workspaces\default\share_data\project\market_data_colector_platform\buffer_platform
# 停止旧容器
docker-compose down
# 构建镜像
docker-compose build --no-cache
# 启动服务
docker-compose up -d
```
## 🌐 访问地址
部署成功后,可访问以下地址:
| 服务 | 地址 |
|------|------|
| 主页 | http://localhost:9600 |
| 品种分析 | http://localhost:9600/futures-analysis |
| AI配置 | http://localhost:9600/ai-config |
| API文档 | http://localhost:9600/docs |
| 健康检查 | http://localhost:9600/api/v1/health |
## 🛠️ 日常管理
### 使用管理脚本
双击运行 `manage.bat`,可选择:
1. 启动服务
2. 停止服务
3. 重启服务
4. 查看日志
5. 查看状态
6. 进入容器
7. 清理资源
### 常用Docker命令
```powershell
# 查看容器状态
docker-compose ps
# 查看实时日志
docker-compose logs -f
# 查看最近100行日志
docker-compose logs --tail=100
# 重启服务
docker-compose restart
# 停止服务
docker-compose stop
# 启动服务
docker-compose start
# 停止并删除容器(保留数据)
docker-compose down
# 完全清理(包括数据卷)
docker-compose down -v
# 进入容器bash
docker exec -it futures-buffer-platform /bin/bash
# 查看容器资源使用
docker stats futures-buffer-platform
```
## 📁 目录结构
```
buffer_platform/
├── data/ # SQLite数据库自动创建
├── logs/ # 日志文件(自动创建)
├── app/
│ └── static/ # 前端静态文件
├── docker-compose.yml # Docker编排配置
├── Dockerfile # Docker镜像构建文件
├── deploy.bat # 一键部署脚本
└── manage.bat # 管理脚本
```
## 🔧 配置说明
### 环境变量
`docker-compose.yml` 中可配置:
| 变量 | 说明 | 默认值 |
|------|------|--------|
| BUFFER_DB_PATH | 数据库路径 | /app/data/buffer.db |
| BUFFER_HOST | 服务监听地址 | 0.0.0.0 |
| BUFFER_PORT | 服务端口 | 8600 |
| CACHE_TTL | 缓存过期时间(秒) | 300 |
| BUFFER_LOG_LEVEL | 日志级别 | INFO |
| MAX_WORKERS | 并发采集数 | 2 |
| TZ | 时区 | Asia/Shanghai |
### 端口映射
- 宿主机端口9600
- 容器端口8600
如需修改端口,编辑 `docker-compose.yml` 中的 `ports` 配置:
```yaml
ports:
- "你想要的端口:8600"
```
## 🔍 故障排查
### 1. 容器无法启动
```powershell
# 查看详细日志
docker-compose logs
# 检查端口占用
netstat -ano | findstr "9600"
```
### 2. 数据库问题
```powershell
# 进入容器检查数据库
docker exec -it futures-buffer-platform ls -la /app/data
```
### 3. 重新构建镜像
```powershell
# 清理旧镜像
docker-compose down
docker system prune -a
# 重新构建
docker-compose build --no-cache
docker-compose up -d
```
### 4. 查看健康状态
```powershell
# 查看容器健康检查状态
docker inspect --format='{{.State.Health.Status}}' futures-buffer-platform
```
## 🔄 更新部署
当代码有更新时:
```powershell
# 方法一:使用部署脚本
deploy.bat
# 方法二:手动更新
docker-compose down
docker-compose build --no-cache
docker-compose up -d
```
## 📊 监控和日志
### 实时日志
```powershell
docker-compose logs -f buffer-platform
```
### 容器资源监控
```powershell
docker stats futures-buffer-platform
```
### 日志位置
- 容器内:/app/logs/
- 宿主机:./logs/
## 🗄️ 数据备份
### 备份数据库
```powershell
docker cp futures-buffer-platform:/app/data/buffer.db ./backup_buffer.db
```
### 恢复数据库
```powershell
docker cp ./backup_buffer.db futures-buffer-platform:/app/data/buffer.db
docker-compose restart
```
## 🎯 生产环境建议
1. **修改默认端口**:避免端口冲突
2. **配置日志轮转**在docker-compose.yml中添加
```yaml
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
3. **数据卷备份**定期备份data目录
4. **监控资源**使用docker stats监控资源使用
5. **设置重启策略**:已配置为 `unless-stopped`
## 📞 技术支持
如遇问题,请检查:
1. Docker Desktop是否正常运行
2. 端口9600是否被占用
3. 查看容器日志:`docker-compose logs`
4. 检查健康状态:`docker-compose ps`

@ -0,0 +1,149 @@
"""
用户权限系统 - API路由
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
from app.database import get_db
from app.user_models import User
from app import auth_service
router = APIRouter(prefix="/auth", tags=["用户认证"])
security = HTTPBearer()
# 请求模型
class LoginRequest(BaseModel):
username: str
password: str
class LoginResponse(BaseModel):
success: bool
token: Optional[str] = None
user: Optional[dict] = None
message: Optional[str] = None
class UserInfo(BaseModel):
id: int
username: str
role: str
email: Optional[str]
# 登录接口
@router.post("/login", response_model=LoginResponse)
def login(request: LoginRequest, db: Session = Depends(get_db)):
"""用户登录"""
user = auth_service.authenticate_user(db, request.username, request.password)
if not user:
return LoginResponse(
success=False,
message="用户名或密码错误"
)
# 创建会话
token = auth_service.create_session(db, user.id)
auth_service.update_last_login(db, user)
return LoginResponse(
success=True,
token=token,
user={
"id": user.id,
"username": user.username,
"role": user.role,
"email": user.email
},
message="登录成功"
)
# 登出接口
@router.post("/logout")
def logout(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
"""用户登出"""
auth_service.invalidate_session(db, credentials.credentials)
return {"success": True, "message": "已登出"}
# 验证令牌接口
@router.get("/verify")
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
"""验证用户令牌"""
user = auth_service.validate_session(db, credentials.credentials)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的会话令牌"
)
return {
"success": True,
"user": {
"id": user.id,
"username": user.username,
"role": user.role,
"email": user.email,
"last_login": user.last_login.isoformat() if user.last_login else None
}
}
# 获取当前用户信息
@router.get("/me")
def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
"""获取当前用户信息"""
user = auth_service.validate_session(db, credentials.credentials)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的会话令牌"
)
return {
"id": user.id,
"username": user.username,
"role": user.role,
"email": user.email,
"is_active": user.is_active,
"created_at": user.created_at.isoformat(),
"last_login": user.last_login.isoformat() if user.last_login else None
}
# 依赖注入:验证用户已登录
def get_current_user_dependency(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
"""依赖注入:获取当前用户"""
user = auth_service.validate_session(db, credentials.credentials)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="请先登录"
)
return user
# 依赖注入:仅管理员可访问
def require_admin(credentials: HTTPAuthorizationCredentials = Depends(security), db: Session = Depends(get_db)):
"""依赖注入:验证管理员权限"""
user = auth_service.validate_session(db, credentials.credentials)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="请先登录"
)
if user.role != 'admin':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="需要管理员权限"
)
return user

@ -0,0 +1,129 @@
"""
用户权限系统 - 认证服务
"""
import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional
from sqlalchemy.orm import Session
from app.user_models import User, Session as UserSession
# 密码加密工具
def hash_password(password: str) -> str:
"""对密码进行哈希加密"""
salt = secrets.token_hex(16)
pwd_hash = hashlib.sha256((salt + password).encode()).hexdigest()
return f"{salt}${pwd_hash}"
def verify_password(password: str, hashed: str) -> bool:
"""验证密码"""
try:
salt, pwd_hash = hashed.split('$')
return hashlib.sha256((salt + password).encode()).hexdigest() == pwd_hash
except:
return False
def generate_token() -> str:
"""生成会话令牌"""
return secrets.token_urlsafe(32)
# 用户操作
def create_user(db: Session, username: str, password: str, email: str = None, role: str = 'user') -> User:
"""创建新用户"""
user = User(
username=username,
password_hash=hash_password(password),
email=email,
role=role,
is_active=True
)
db.add(user)
db.commit()
db.refresh(user)
return user
def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
"""验证用户登录"""
user = db.query(User).filter(User.username == username).first()
if not user:
return None
if not user.is_active:
return None
if not verify_password(password, user.password_hash):
return None
return user
def update_last_login(db: Session, user: User):
"""更新最后登录时间"""
user.last_login = datetime.utcnow()
db.commit()
# 会话操作
def create_session(db: Session, user_id: int, expires_hours: int = 24) -> str:
"""创建用户会话"""
token = generate_token()
session = UserSession(
user_id=user_id,
token=token,
expires_at=datetime.utcnow() + timedelta(hours=expires_hours),
is_valid=True
)
db.add(session)
db.commit()
return token
def validate_session(db: Session, token: str) -> Optional[User]:
"""验证会话令牌"""
session = db.query(UserSession).filter(
UserSession.token == token,
UserSession.is_valid == True,
UserSession.expires_at > datetime.utcnow()
).first()
if not session:
return None
user = db.query(User).filter(User.id == session.user_id).first()
if not user or not user.is_active:
return None
return user
def invalidate_session(db: Session, token: str):
"""使会话失效(登出)"""
session = db.query(UserSession).filter(UserSession.token == token).first()
if session:
session.is_valid = False
db.commit()
def cleanup_expired_sessions(db: Session):
"""清理过期会话"""
db.query(UserSession).filter(
UserSession.expires_at < datetime.utcnow()
).update({"is_valid": False})
db.commit()
# 默认用户创建
def create_default_admin(db: Session):
"""创建默认管理员账户"""
admin = db.query(User).filter(User.username == 'lxy_root').first()
if not admin:
create_user(
db=db,
username='lxy_root',
password='admin123',
email='admin@system.local',
role='admin'
)
print("✓ 默认管理员账户已创建: lxy_root / admin123")

@ -5,14 +5,15 @@ import logging
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.responses import FileResponse, RedirectResponse
from app.database import engine, Base
from app.user_models import Base as UserBase
from app.config import HOST, PORT, LOG_LEVEL
from app.api import data, tasks, config, futures_analysis, ai_config
from app.api import data, tasks, config, futures_analysis, ai_config, auth
from app.services.scheduler import start_scheduler, stop_scheduler
# 配置日志
@ -29,15 +30,25 @@ async def lifespan(app: FastAPI):
# 启动时:建表 + 启动调度器
logger.info("创建数据库表...")
Base.metadata.create_all(bind=engine)
UserBase.metadata.create_all(bind=engine)
from app.analysis_db import init_analysis_db
init_analysis_db()
logger.info("期货智析数据库初始化完成")
# 创建默认管理员账户
from app.database import SessionLocal
from app import auth_service
db = SessionLocal()
try:
auth_service.create_default_admin(db)
finally:
db.close()
logger.info("启动定时调度器...")
start_scheduler()
# 恢复已启用的任务
from app.database import SessionLocal
from app.services.cache import list_tasks
from app.services.scheduler import add_job
@ -81,12 +92,29 @@ STATIC_DIR.mkdir(parents=True, exist_ok=True)
app.mount("/static", StaticFiles(directory=str(STATIC_DIR), html=True), name="static")
# 登录页面(无需认证)
@app.get("/login")
def login_page():
"""登录页面"""
return FileResponse(str(STATIC_DIR / "login.html"))
# 角色选择页面(需要管理员权限)
@app.get("/role-select")
def role_select_page():
"""角色选择页面"""
return FileResponse(str(STATIC_DIR / "role_select.html"))
# 品种配置管理页面(需要管理员权限)
@app.get("/ui")
def ui_page():
"""品种配置管理页面"""
return FileResponse(str(STATIC_DIR / "index.html"))
# 注册路由
app.include_router(auth.router, prefix="/api/v1")
app.include_router(data.router, prefix="/api/v1")
app.include_router(tasks.router, prefix="/api/v1")
app.include_router(config.router, prefix="/api/v1")
@ -111,13 +139,11 @@ def health():
return {"status": "ok", "service": "market-data-buffer"}
# 根路径重定向到登录页
@app.get("/")
def root():
return {
"message": "数据缓冲平台 API",
"docs": "/docs",
"health": "/api/v1/health",
}
"""根路径重定向到登录页"""
return RedirectResponse(url="/login")
if __name__ == "__main__":

@ -0,0 +1,284 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 期货智析系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 420px;
padding: 40px;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.login-header {
text-align: center;
margin-bottom: 40px;
}
.login-logo {
font-size: 48px;
margin-bottom: 10px;
}
.login-title {
font-size: 28px;
font-weight: 700;
color: #1D1D1F;
margin-bottom: 8px;
}
.login-subtitle {
font-size: 14px;
color: #86868B;
}
.form-group {
margin-bottom: 24px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 600;
color: #1D1D1F;
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #E5E5E7;
border-radius: 12px;
font-size: 15px;
transition: all 0.3s;
outline: none;
}
.form-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.login-btn {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
margin-top: 10px;
}
.login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.login-btn:active {
transform: translateY(0);
}
.login-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.error-message {
background: #FFF3F3;
color: #DC2626;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
margin-bottom: 20px;
display: none;
border-left: 4px solid #DC2626;
}
.error-message.show {
display: block;
animation: shake 0.5s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: #86868B;
}
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
margin-right: 8px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<div class="login-logo"></div>
<h1 class="login-title">期货智析</h1>
<p class="login-subtitle">智能期货期权分析系统</p>
</div>
<div class="error-message" id="error-message"></div>
<form id="login-form">
<div class="form-group">
<label class="form-label" for="username">用户名</label>
<input type="text" id="username" class="form-input" placeholder="请输入用户名" required autocomplete="username">
</div>
<div class="form-group">
<label class="form-label" for="password">密码</label>
<input type="password" id="password" class="form-input" placeholder="请输入密码" required autocomplete="current-password">
</div>
<button type="submit" class="login-btn" id="login-btn">
登录
</button>
</form>
<div class="footer">
<p>© 2026 期货智析系统 · 安全登录</p>
</div>
</div>
<script>
const loginForm = document.getElementById('login-form');
const loginBtn = document.getElementById('login-btn');
const errorMessage = document.getElementById('error-message');
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
loginBtn.disabled = true;
loginBtn.innerHTML = '<span class="loading-spinner"></span>登录中...';
errorMessage.classList.remove('show');
try {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
// 保存令牌
localStorage.setItem('auth_token', data.token);
localStorage.setItem('user_info', JSON.stringify(data.user));
// 根据角色跳转
if (data.user.role === 'admin') {
window.location.href = '/role-select';
} else {
window.location.href = '/futures-analysis';
}
} else {
showError(data.message || '登录失败,请重试');
}
} catch (error) {
showError('网络错误,请检查连接后重试');
} finally {
loginBtn.disabled = false;
loginBtn.innerHTML = '登录';
}
});
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.add('show');
setTimeout(() => {
errorMessage.classList.remove('show');
}, 5000);
}
// 检查是否已登录
window.addEventListener('DOMContentLoaded', async () => {
const token = localStorage.getItem('auth_token');
if (token) {
try {
const response = await fetch('/api/v1/auth/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const data = await response.json();
if (data.user.role === 'admin') {
window.location.href = '/role-select';
} else {
window.location.href = '/futures-analysis';
}
}
} catch (error) {
// 令牌无效,继续显示登录页
localStorage.removeItem('auth_token');
localStorage.removeItem('user_info');
}
}
});
</script>
</body>
</html>

@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>选择入口 - 期货智析系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
text-align: center;
max-width: 800px;
width: 100%;
}
.header {
color: white;
margin-bottom: 50px;
}
.header h1 {
font-size: 36px;
font-weight: 700;
margin-bottom: 10px;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.user-info {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 12px 24px;
display: inline-block;
margin-top: 15px;
color: white;
font-size: 14px;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-bottom: 40px;
}
.card {
background: white;
border-radius: 20px;
padding: 40px 30px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
text-decoration: none;
color: inherit;
display: block;
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3);
}
.card-icon {
font-size: 64px;
margin-bottom: 20px;
}
.card-title {
font-size: 24px;
font-weight: 700;
color: #1D1D1F;
margin-bottom: 12px;
}
.card-description {
font-size: 14px;
color: #86868B;
line-height: 1.6;
}
.card-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 6px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-top: 15px;
}
.logout-btn {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 12px 32px;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.logout-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>◈ 欢迎使用期货智析系统</h1>
<p>请选择您要进入的模块</p>
<div class="user-info">
👤 <span id="username">管理员</span> · 管理员权限
</div>
</div>
<div class="cards">
<a href="/futures-analysis" class="card">
<div class="card-icon">📊</div>
<div class="card-title">品种分析</div>
<div class="card-description">
智能期货品种分析工具提供K线图表、技术指标、AI智能分析等功能帮助您做出更明智的交易决策。
</div>
<div class="card-badge">进入分析 →</div>
</a>
<a href="/ui" class="card">
<div class="card-icon">⚙️</div>
<div class="card-title">系统管理</div>
<div class="card-description">
管理系统配置、品种设置、定时任务、数据源配置等后台管理功能。
</div>
<div class="card-badge">进入管理 →</div>
</a>
</div>
<button class="logout-btn" onclick="logout()">退出登录</button>
</div>
<script>
// 验证登录状态
window.addEventListener('DOMContentLoaded', async () => {
const token = localStorage.getItem('auth_token');
if (!token) {
window.location.href = '/login';
return;
}
try {
const response = await fetch('/api/v1/auth/verify', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
window.location.href = '/login';
return;
}
const data = await response.json();
document.getElementById('username').textContent = data.user.username;
if (data.user.role !== 'admin') {
window.location.href = '/futures-analysis';
}
} catch (error) {
window.location.href = '/login';
}
});
async function logout() {
const token = localStorage.getItem('auth_token');
if (token) {
try {
await fetch('/api/v1/auth/logout', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
}
});
} catch (error) {
console.error('登出失败:', error);
}
}
localStorage.removeItem('auth_token');
localStorage.removeItem('user_info');
window.location.href = '/login';
}
</script>
</body>
</html>

@ -0,0 +1,38 @@
"""
用户权限系统 - 数据库模型
"""
from datetime import datetime
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from app.database import Base
class User(Base):
"""用户表"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String(50), unique=True, nullable=False, index=True)
password_hash = Column(String(255), nullable=False)
email = Column(String(100), unique=True, nullable=True)
role = Column(String(20), nullable=False, default='user') # 'admin' 或 'user'
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
last_login = Column(DateTime, nullable=True)
def __repr__(self):
return f"<User(id={self.id}, username='{self.username}', role='{self.role}')>"
class Session(Base):
"""用户会话表"""
__tablename__ = "sessions"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, nullable=False, index=True)
token = Column(String(255), unique=True, nullable=False, index=True)
created_at = Column(DateTime, default=datetime.utcnow)
expires_at = Column(DateTime, nullable=False)
is_valid = Column(Boolean, default=True)
def __repr__(self):
return f"<Session(id={self.id}, user_id={self.user_id}, token='{self.token}')>"

@ -0,0 +1,41 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ========================================
echo 数据库备份工具
echo ========================================
echo.
REM 生成备份文件名
set BACKUP_DATE=%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%
set BACKUP_DATE=%BACKUP_DATE: =0%
set BACKUP_FILE=buffer_backup_%BACKUP_DATE%.db
echo 备份文件: %BACKUP_FILE%
echo.
REM 停止服务
echo [1/3] 停止服务...
docker-compose stop
echo ✓ 服务已停止
REM 备份数据库
echo [2/3] 备份数据库...
if exist "E:\docker_workspace\futures_datas\buffer.db" (
copy "E:\docker_workspace\futures_datas\buffer.db" "E:\docker_workspace\futures_datas\%BACKUP_FILE%"
echo ✓ 数据库已备份到: E:\docker_workspace\futures_datas\%BACKUP_FILE%
) else (
echo ✗ 数据库文件不存在
)
REM 启动服务
echo [3/3] 启动服务...
docker-compose start
echo ✓ 服务已启动
echo.
echo ========================================
echo 备份完成!
echo ========================================
pause

Binary file not shown.

@ -0,0 +1,55 @@
@echo off
chcp 65001 >nul
echo ====================================
echo 期货智析平台 - Docker 部署脚本
echo ====================================
echo.
echo [1/4] 检查Docker环境...
docker --version >nul 2>&1
if errorlevel 1 (
echo [错误] 未检测到Docker请先安装Docker Desktop
pause
exit /b 1
)
echo [√] Docker已安装
echo.
echo [2/4] 停止并清理旧容器...
docker-compose down
echo [√] 清理完成
echo.
echo [3/4] 构建Docker镜像...
docker-compose build --no-cache
if errorlevel 1 (
echo [错误] 镜像构建失败
pause
exit /b 1
)
echo [√] 镜像构建完成
echo.
echo [4/4] 启动Docker容器...
docker-compose up -d
if errorlevel 1 (
echo [错误] 容器启动失败
pause
exit /b 1
)
echo.
echo ====================================
echo 部署完成!
echo ====================================
echo.
echo 访问地址: http://localhost:9600
echo 品种分析: http://localhost:9600/futures-analysis
echo API文档: http://localhost:9600/docs
echo 健康检查: http://localhost:9600/api/v1/health
echo.
echo 查看日志: docker-compose logs -f
echo 停止服务: docker-compose down
echo 重启服务: docker-compose restart
echo.
pause

@ -16,11 +16,14 @@ services:
- CACHE_TTL=300
- BUFFER_LOG_LEVEL=INFO
- MAX_WORKERS=2
- TZ=Asia/Shanghai
volumes:
# 数据持久化 - SQLite数据库
- E:\docker_workspace\futures_datas:/app/data
- ./data:/app/data
# 静态文件(开发模式,生产环境应该打包到镜像中)
- ./app/static:/app/app/static
# 日志持久化(可选)
- E:\docker_workspace\futures_datas\logs:/app/logs
- ./logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8600/api/v1/health"]
interval: 30s

@ -0,0 +1,98 @@
@echo off
chcp 65001 >nul
echo ====================================
echo 期货智析平台 - Docker 管理脚本
echo ====================================
echo.
echo 请选择操作:
echo 1. 启动服务
echo 2. 停止服务
echo 3. 重启服务
echo 4. 查看日志
echo 5. 查看状态
echo 6. 进入容器
echo 7. 清理资源
echo 0. 退出
echo.
set /p choice=请输入选项 (0-7):
if "%choice%"=="1" goto start
if "%choice%"=="2" goto stop
if "%choice%"=="3" goto restart
if "%choice%"=="4" goto logs
if "%choice%"=="5" goto status
if "%choice%"=="6" goto shell
if "%choice%"=="7" goto clean
if "%choice%"=="0" goto end
goto menu
:start
echo.
echo [启动服务...]
docker-compose up -d
echo.
echo 服务已启动!访问 http://localhost:9600
pause
goto menu
:stop
echo.
echo [停止服务...]
docker-compose stop
echo.
echo 服务已停止!
pause
goto menu
:restart
echo.
echo [重启服务...]
docker-compose restart
echo.
echo 服务已重启!
pause
goto menu
:logs
echo.
echo [查看日志 - 按Ctrl+C退出]
docker-compose logs -f --tail=100
goto menu
:status
echo.
echo [服务状态]
docker-compose ps
echo.
echo [容器资源使用]
docker stats --no-stream futures-buffer-platform
pause
goto menu
:shell
echo.
echo [进入容器...]
docker exec -it futures-buffer-platform /bin/bash
goto menu
:clean
echo.
echo [警告] 这将停止并删除容器,但保留数据卷
set /p confirm=确认继续? (y/n):
if /i not "%confirm%"=="y" goto menu
echo.
echo [清理资源...]
docker-compose down
echo.
echo 资源已清理!
pause
goto menu
:menu
cls
goto start
:end
echo.
echo 再见!
exit

@ -0,0 +1,25 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 启动期货智析缓冲平台
echo ========================================
echo.
docker-compose start
echo.
echo 等待服务启动...
timeout /t 3 /nobreak >nul
echo.
echo ========================================
echo 访问地址:
echo 品种分析页面: http://localhost:9600/futures-analysis
echo 配置管理页面: http://localhost:9600/ui
echo AI配置页面: http://localhost:9600/ai-config
echo API文档: http://localhost:9600/docs
echo ========================================
echo.
docker-compose ps
pause

@ -0,0 +1,13 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 停止期货智析缓冲平台
echo ========================================
echo.
docker-compose stop
echo.
echo ✓ 服务已停止
echo.
echo 使用 start.bat 重新启动服务
pause
Loading…
Cancel
Save