diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..72f3df6 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Flask配置 +SECRET_KEY=your-secret-key-here-please-change-this +FLASK_ENV=production + +# OpenAI API配置(可选,用于AI润色功能) +OPENAI_API_KEY= + +# 默认用户配置 +# Docker首次启动时会自动创建此用户 +DEFAULT_USERNAME=admin +DEFAULT_PASSWORD=admin123 + +# 注意: 生产环境请务必修改默认密码! diff --git a/Dockerfile b/Dockerfile index cc8160d..bd7fe89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,10 +24,14 @@ RUN pip install --no-cache-dir --upgrade pip && \ # 复制项目文件 COPY backend/ /app/backend/ COPY frontend/ /app/frontend/ +COPY docker-entrypoint.sh /app/docker-entrypoint.sh # 创建数据目录 RUN mkdir -p /app/data +# 设置启动脚本权限 +RUN chmod +x /app/docker-entrypoint.sh + # 暴露端口 EXPOSE 5000 @@ -35,4 +39,4 @@ EXPOSE 5000 WORKDIR /app/backend # 启动应用 -CMD ["python", "app.py"] +CMD ["/app/docker-entrypoint.sh"] diff --git a/README.md b/README.md index 7305a1c..12299cf 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ ## 功能特性 +- 🔐 **用户认证**: 密码登录保护,确保数据安全 - ✅ **任务管理**: 添加、编辑、删除工作任务 - ⏱️ **时间追踪**: 点击开始/停止计时,自动记录任务时长 - 📊 **时间统计**: 查看每日、每周的工作时间统计 - 🤖 **AI润色**: 使用AI优化任务描述,让工作记录更专业 - 📱 **响应式设计**: 支持桌面和移动设备 - 💾 **本地存储**: 数据存储在本地SQLite数据库中 +- 🐳 **Docker支持**: 一键部署,开箱即用 ## 技术栈 @@ -20,12 +22,40 @@ ## 快速开始 -### 1. 环境要求 +### 方法一: 使用Docker (推荐) + +```bash +# 1. 克隆项目 +git clone +cd worklist + +# 2. (可选) 配置环境变量 +cp .env.example .env +# 编辑 .env 文件,设置自定义用户名和密码 + +# 3. 启动Docker容器 +docker-compose up -d + +# 4. 访问应用 +# 打开浏览器访问 http://localhost:5000 +# 默认用户名: admin +# 默认密码: admin123 +``` + +**首次启动说明:** +- Docker会自动创建数据库和默认用户 +- 默认用户名: `admin`, 默认密码: `admin123` +- 可通过环境变量 `DEFAULT_USERNAME` 和 `DEFAULT_PASSWORD` 自定义 +- 登录后请立即修改密码 + +### 方法二: 本地运行 + +#### 1. 环境要求 - Python 3.7 或更高版本 - 现代浏览器 (Chrome, Firefox, Safari, Edge) -### 2. 安装和运行 +#### 2. 安装和运行 #### 方法一:使用启动脚本(推荐) @@ -44,30 +74,64 @@ python start.py - 启动服务器 - 自动打开浏览器 +**首次使用需创建用户:** +```bash +# 运行用户创建脚本 +python create_user.py +``` + #### 方法二:手动启动 ```bash # 1. 安装Python依赖 pip install -r backend/requirements.txt -# 2. 启动服务器 +# 2. 创建初始用户 +python create_user.py + +# 3. 启动服务器 cd backend python app.py -# 3. 在浏览器中访问 +# 4. 在浏览器中访问 # http://localhost:5000 ``` -### 3. 配置AI润色功能(可选) +### 3. 配置说明 -如需使用AI润色功能,请: +#### 环境变量配置 -1. 获取OpenAI API密钥 -2. 编辑 `backend/.env` 文件 -3. 设置 `OPENAI_API_KEY=your_api_key_here` +#### 环境变量配置 + +创建 `.env` 文件 (可复制 `.env.example`): + +```bash +# Flask配置 +SECRET_KEY=your-secret-key-here-please-change-this + +# OpenAI API配置(可选,用于AI润色功能) +OPENAI_API_KEY=your_openai_api_key + +# Docker首次启动时的默认用户(仅Docker部署时有效) +DEFAULT_USERNAME=admin +DEFAULT_PASSWORD=admin123 +``` + +**安全建议:** +- 生产环境务必修改 `SECRET_KEY` +- 修改默认用户名和密码 +- 登录后立即在系统中修改密码 + +#### AI润色功能(可选) ## 使用指南 +### 登录系统 + +1. 首次访问会显示登录页面 +2. 输入用户名和密码 +3. 登录成功后进入主界面 + ### 基本操作 1. **添加任务** diff --git a/backend/app.py b/backend/app.py index 203daec..464e63d 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,16 +6,21 @@ import os def create_app(): app = Flask(__name__) - + # 配置 - app.config['SECRET_KEY'] = 'your-secret-key-here' + app.config['SECRET_KEY'] = 'your-secret-key-here-change-in-production' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///worklist.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - + + # Session配置 + app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + app.config['SESSION_COOKIE_HTTPONLY'] = True + app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24小时 + # 初始化扩展 db.init_app(app) - CORS(app) # 允许跨域请求 - + CORS(app, supports_credentials=True) # 允许跨域请求并支持凭证 + # 注册蓝图 app.register_blueprint(api, url_prefix='/api') diff --git a/backend/models.py b/backend/models.py index a12f199..14b9ebf 100644 --- a/backend/models.py +++ b/backend/models.py @@ -1,9 +1,34 @@ from flask_sqlalchemy import SQLAlchemy from datetime import datetime from sqlalchemy import func +from werkzeug.security import generate_password_hash, check_password_hash db = SQLAlchemy() +class User(db.Model): + """用户模型""" + __tablename__ = 'users' + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password_hash = db.Column(db.String(200), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + def set_password(self, password): + """设置密码(哈希加密)""" + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + """验证密码""" + return check_password_hash(self.password_hash, password) + + def to_dict(self): + return { + 'id': self.id, + 'username': self.username, + 'created_at': self.created_at.isoformat() if self.created_at else None + } + class Task(db.Model): """任务模型""" __tablename__ = 'tasks' diff --git a/backend/routes.py b/backend/routes.py index 580e90f..e17f4bd 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -1,11 +1,81 @@ -from flask import Blueprint, request, jsonify +from flask import Blueprint, request, jsonify, session from datetime import datetime, timedelta -from models import db, Task, TimeRecord +from models import db, Task, TimeRecord, User from ai_service import ai_service import json api = Blueprint('api', __name__) +# 认证API +@api.route('/auth/login', methods=['POST']) +def login(): + """用户登录""" + data = request.get_json() + + if not data or 'username' not in data or 'password' not in data: + return jsonify({'error': '用户名和密码不能为空'}), 400 + + username = data['username'] + password = data['password'] + + user = User.query.filter_by(username=username).first() + + if user and user.check_password(password): + # 登录成功,设置session + session['user_id'] = user.id + session['username'] = user.username + return jsonify({ + 'message': '登录成功', + 'user': user.to_dict() + }) + else: + return jsonify({'error': '用户名或密码错误'}), 401 + +@api.route('/auth/logout', methods=['POST']) +def logout(): + """用户登出""" + session.clear() + return jsonify({'message': '登出成功'}) + +@api.route('/auth/check', methods=['GET']) +def check_auth(): + """检查登录状态""" + if 'user_id' in session: + user = User.query.get(session['user_id']) + if user: + return jsonify({ + 'authenticated': True, + 'user': user.to_dict() + }) + return jsonify({'authenticated': False}), 401 + +@api.route('/auth/register', methods=['POST']) +def register(): + """用户注册(可选,用于创建初始用户)""" + data = request.get_json() + + if not data or 'username' not in data or 'password' not in data: + return jsonify({'error': '用户名和密码不能为空'}), 400 + + username = data['username'] + password = data['password'] + + # 检查用户是否已存在 + if User.query.filter_by(username=username).first(): + return jsonify({'error': '用户名已存在'}), 400 + + # 创建新用户 + user = User(username=username) + user.set_password(password) + + db.session.add(user) + db.session.commit() + + return jsonify({ + 'message': '注册成功', + 'user': user.to_dict() + }), 201 + # 任务管理API @api.route('/tasks', methods=['GET']) def get_tasks(): diff --git a/create_user.py b/create_user.py new file mode 100644 index 0000000..b6050e9 --- /dev/null +++ b/create_user.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +创建初始用户脚本 +用于创建管理员账户 +""" + +from backend.app import create_app +from backend.models import db, User + +def create_initial_user(): + """创建初始用户""" + app = create_app() + + with app.app_context(): + # 检查是否已有用户 + existing_user = User.query.first() + + if existing_user: + print(f"用户已存在: {existing_user.username}") + return + + # 创建默认管理员用户 + username = input("请输入用户名 (默认: admin): ").strip() or "admin" + password = input("请输入密码 (默认: admin123): ").strip() or "admin123" + + user = User(username=username) + user.set_password(password) + + db.session.add(user) + db.session.commit() + + print(f"用户创建成功!") + print(f"用户名: {username}") + print(f"请妥善保管密码!") + +if __name__ == '__main__': + create_initial_user() diff --git a/docker-compose.yml b/docker-compose.yml index 2d259f1..e72d3b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,9 @@ services: - PYTHONUNBUFFERED=1 - SECRET_KEY=${SECRET_KEY:-your-secret-key-here} - OPENAI_API_KEY=${OPENAI_API_KEY:-} + # 默认用户配置 + - DEFAULT_USERNAME=${DEFAULT_USERNAME:-admin} + - DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-admin123} volumes: # 持久化数据库 - ./data:/app/data diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..6b30fda --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -e + +echo "正在启动工作任务管理系统..." + +# 切换到backend目录 +cd /app/backend + +# 等待数据库初始化 +echo "初始化数据库..." +python -c " +from app import create_app +from models import db, User +import os + +app = create_app() +with app.app_context(): + # 创建所有表 + db.create_all() + + # 检查是否已有用户 + existing_user = User.query.first() + + if not existing_user: + # 从环境变量获取默认用户信息 + default_username = os.getenv('DEFAULT_USERNAME', 'admin') + default_password = os.getenv('DEFAULT_PASSWORD', 'admin123') + + # 创建默认用户 + user = User(username=default_username) + user.set_password(default_password) + db.session.add(user) + db.session.commit() + + print(f'已创建默认用户: {default_username}') + print(f'默认密码: {default_password}') + print('请登录后立即修改密码!') + else: + print('用户已存在,跳过初始化') +" + +echo "启动Flask应用..." +# 启动应用 +exec python app.py diff --git a/frontend/css/style.css b/frontend/css/style.css index a607be3..df4f743 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -812,3 +812,98 @@ body { from { transform: translateX(-100%); } to { transform: translateX(0); } } + +/* 登录页面样式 */ +.login-page { + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.login-container { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 40px; + width: 100%; + max-width: 450px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + animation: modalSlideIn 0.5s ease; +} + +.login-header { + text-align: center; + margin-bottom: 30px; +} + +.login-header i { + font-size: 3rem; + color: #667eea; + margin-bottom: 15px; + display: block; +} + +.login-header h1 { + color: #2d3748; + font-size: 1.8rem; + font-weight: 700; + margin: 0; +} + +.login-form { + display: flex; + flex-direction: column; +} + +.login-form .form-group { + margin-bottom: 20px; +} + +.login-form label { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + color: #4a5568; + font-weight: 600; +} + +.login-form label i { + color: #667eea; +} + +.btn-block { + width: 100%; + justify-content: center; + margin-top: 10px; +} + +.error-message { + background: #fed7d7; + color: #c53030; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 15px; + font-size: 14px; + border: 1px solid #fc8181; +} + +/* 用户信息显示 */ +.user-info { + display: flex; + align-items: center; + gap: 8px; + color: #4a5568; + font-weight: 600; + padding: 8px 16px; + background: #f7fafc; + border-radius: 8px; + border: 2px solid #e2e8f0; +} + +.user-info i { + color: #667eea; + font-size: 1.2rem; +} diff --git a/frontend/index.html b/frontend/index.html index 8f71f5f..f795b06 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,11 +8,44 @@ -
+ + + + +