feat: 添加日记系统
- 完整的 Django 后端 (diary app) - 前端页面 - 部署脚本 (本地 + 云端) - Nginx 配置 - 数据迁移工具 - 同步工具
This commit is contained in:
13
diary-system/.gitignore
vendored
Normal file
13
diary-system/.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.db
|
||||
*.sqlite3
|
||||
static/
|
||||
media/
|
||||
.env
|
||||
*.log
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
107
diary-system/DEPLOYMENT_REPORT.md
Normal file
107
diary-system/DEPLOYMENT_REPORT.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 日记系统 - 部署报告
|
||||
|
||||
## ✅ 部署完成
|
||||
|
||||
**时间**: 2026-04-14
|
||||
**部署目标**: 云服务器 (cssc.datalibstar.com)
|
||||
|
||||
---
|
||||
|
||||
## 📊 部署状态
|
||||
|
||||
| 组件 | 状态 | 端口 | 备注 |
|
||||
|------|------|------|------|
|
||||
| Gunicorn | ✅ 运行中 | 8002 | 后端 API 服务 |
|
||||
| Nginx | ✅ 运行中 | 8001 | 反向代理 + 静态文件 |
|
||||
| SQLite | ✅ 已创建 | - | 本地数据库 |
|
||||
| 日记数据 | ✅ 已同步 | - | 第一天日记已创建 |
|
||||
|
||||
---
|
||||
|
||||
## 🌐 访问地址
|
||||
|
||||
### 云服务器
|
||||
- **主页**: http://cssc.datalibstar.com:8001/
|
||||
- **API**: http://cssc.datalibstar.com:8001/api/entries/
|
||||
- **Admin**: http://cssc.datalibstar.com:8001/admin/
|
||||
|
||||
### 本地
|
||||
- **主页**: http://127.0.0.1:8001/
|
||||
- **API**: http://127.0.0.1:8001/api/entries/
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 安全组端口
|
||||
如果无法从外部访问云服务器,请在**腾讯云控制台**的安全组中开放端口:
|
||||
- **8001** (日记系统 Web 访问)
|
||||
|
||||
### 2. 城市手册冲突
|
||||
- 城市手册使用 Docker 占用 80 端口
|
||||
- 日记系统使用 8001 端口,无冲突
|
||||
- 如需使用 80 端口,需调整城市手册 Docker 配置
|
||||
|
||||
### 3. 数据库
|
||||
- 云服务器使用 **SQLite** (本地文件)
|
||||
- 本地部署使用 **PostgreSQL** (内网数据库)
|
||||
- 两地数据不互通
|
||||
|
||||
---
|
||||
|
||||
## 📁 服务器文件位置
|
||||
|
||||
```
|
||||
/home/ubuntu/diary-system/
|
||||
├── backend/ # Django 后端
|
||||
│ ├── diary/ # 日记应用
|
||||
│ ├── db.sqlite3 # SQLite 数据库
|
||||
│ └── manage.py
|
||||
├── frontend/ # 前端页面
|
||||
│ └── index.html
|
||||
├── deploy_cloud.sh # 部署脚本
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 常用命令
|
||||
|
||||
### 查看服务状态
|
||||
```bash
|
||||
# Gunicorn 状态
|
||||
sudo systemctl status diary-system
|
||||
|
||||
# Nginx 状态
|
||||
sudo systemctl status nginx
|
||||
|
||||
# 查看日志
|
||||
sudo journalctl -u diary-system -f
|
||||
```
|
||||
|
||||
### 重启服务
|
||||
```bash
|
||||
sudo systemctl restart diary-system
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### 查看日记数据
|
||||
```bash
|
||||
cd /home/ubuntu/diary-system/backend
|
||||
python3 manage.py shell
|
||||
>>> from diary.models import DiaryEntry
|
||||
>>> DiaryEntry.objects.all()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 下一步
|
||||
|
||||
1. **开放安全组端口** (如需外部访问)
|
||||
2. **配置域名** (可选,使用子域名如 diary.cssc.datalibstar.com)
|
||||
3. **设置 HTTPS** (可选,使用 Let's Encrypt)
|
||||
4. **定期备份数据库** (`/home/ubuntu/diary-system/backend/db.sqlite3`)
|
||||
|
||||
---
|
||||
|
||||
_部署完成!🎉_
|
||||
67
diary-system/README.md
Executable file
67
diary-system/README.md
Executable file
@@ -0,0 +1,67 @@
|
||||
# 码神的日记系统
|
||||
|
||||
⚡ 记录每天的进步与成长
|
||||
|
||||
## 技术架构
|
||||
|
||||
- **后端**: Django + Django REST Framework
|
||||
- **前端**: 原生 HTML/JS (轻量级)
|
||||
- **数据库**: PostgreSQL (与城市手册共用)
|
||||
- **部署**: Gunicorn + Nginx
|
||||
|
||||
## 快速启动
|
||||
|
||||
```bash
|
||||
cd /root/.openclaw/workspace/diary-system
|
||||
chmod +x start.sh
|
||||
./start.sh
|
||||
```
|
||||
|
||||
## 同步日记
|
||||
|
||||
```bash
|
||||
python3 sync_diary.py
|
||||
```
|
||||
|
||||
## 访问地址
|
||||
|
||||
### 本地部署
|
||||
- **主页**: http://127.0.0.1:8001/
|
||||
- **API**: http://127.0.0.1:8001/api/entries/
|
||||
- **Admin**: http://127.0.0.1:8001/admin/
|
||||
|
||||
### 云服务器部署
|
||||
- **主页**: http://cssc.datalibstar.com:8001/
|
||||
- **API**: http://cssc.datalibstar.com:8001/api/entries/
|
||||
- **Admin**: http://cssc.datalibstar.com:8001/admin/
|
||||
|
||||
⚠️ **注意**:如果无法访问云服务器,请在腾讯云控制台安全组中开放端口 `8001`
|
||||
|
||||
## API 接口
|
||||
|
||||
### 日记
|
||||
- `GET /api/entries/` - 获取所有日记
|
||||
- `GET /api/entries/today/` - 获取今天的日记
|
||||
- `GET /api/entries/recent/` - 获取最近 7 天的日记
|
||||
- `GET /api/entries/stats/` - 获取统计信息
|
||||
- `POST /api/entries/` - 创建日记
|
||||
- `PUT /api/entries/{id}/` - 更新日记
|
||||
|
||||
### 经验总结
|
||||
- `GET /api/experiences/` - 获取所有经验
|
||||
- `GET /api/experiences/recent/` - 获取最近 10 条经验
|
||||
- `GET /api/experiences/by_category/` - 按类别分组
|
||||
- `POST /api/experiences/` - 创建经验总结
|
||||
- `PUT /api/experiences/{id}/` - 更新经验
|
||||
|
||||
## 数据库
|
||||
|
||||
使用现有的 PostgreSQL 数据库 (`cssc`),自动创建以下表:
|
||||
- `diary_diaryentry` - 日记条目
|
||||
- `diary_dailyprogress` - 每日进度
|
||||
|
||||
## 与城市手册的区别
|
||||
|
||||
- 端口不同(日记系统用 8001/8002,城市手册用 80/8000)
|
||||
- 更轻量级的前端
|
||||
- 专注于个人日记和进步追踪
|
||||
23
diary-system/add_first_experience.py
Normal file
23
diary-system/add_first_experience.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
"""添加第一条经验总结"""
|
||||
import os
|
||||
import sys
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
sys.path.insert(0, '/root/.openclaw/workspace/diary-system/backend')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from diary.models import Experience
|
||||
|
||||
# 创建第一条经验总结
|
||||
exp = Experience.objects.create(
|
||||
title="云服务器 Gunicorn 路径问题",
|
||||
category="deployment",
|
||||
problem="Gunicorn systemd 服务启动失败,错误:Failed to locate executable /usr/bin/gunicorn",
|
||||
solution="1. 使用 pip3 install gunicorn 安装\n2. 查找 gunicorn 实际路径:/home/ubuntu/.local/bin/gunicorn\n3. 修改 systemd 服务文件,添加 Environment=PATH 并更新 ExecStart 路径",
|
||||
lesson_learned="在云服务器上使用 pip 安装的包可能在用户目录,需要确认实际路径并在 systemd 中正确配置"
|
||||
)
|
||||
|
||||
print(f"✅ 经验总结已创建:{exp.title}")
|
||||
print(f" 类别:{exp.get_category_display()}")
|
||||
23
diary-system/add_first_experience_cloud.py
Normal file
23
diary-system/add_first_experience_cloud.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
"""添加第一条经验总结 - 云服务器版"""
|
||||
import os
|
||||
import sys
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
sys.path.insert(0, '/home/ubuntu/diary-system/backend')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from diary.models import Experience
|
||||
|
||||
# 创建第一条经验总结
|
||||
exp = Experience.objects.create(
|
||||
title="云服务器 Gunicorn 路径问题",
|
||||
category="deployment",
|
||||
problem="Gunicorn systemd 服务启动失败,错误:Failed to locate executable /usr/bin/gunicorn",
|
||||
solution="1. 使用 pip3 install gunicorn 安装\n2. 查找 gunicorn 实际路径:/home/ubuntu/.local/bin/gunicorn\n3. 修改 systemd 服务文件,添加 Environment=PATH 并更新 ExecStart 路径",
|
||||
lesson_learned="在云服务器上使用 pip 安装的包可能在用户目录,需要确认实际路径并在 systemd 中正确配置"
|
||||
)
|
||||
|
||||
print(f"✅ 经验总结已创建:{exp.title}")
|
||||
print(f" 类别:{exp.get_category_display()}")
|
||||
1
diary-system/backend/diary/__init__.py
Executable file
1
diary-system/backend/diary/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
# Diary app
|
||||
52
diary-system/backend/diary/migrations/0001_initial.py
Executable file
52
diary-system/backend/diary/migrations/0001_initial.py
Executable file
@@ -0,0 +1,52 @@
|
||||
# Generated by Django 4.2.11 on 2026-04-14 08:19
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DiaryEntry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True, verbose_name='日期')),
|
||||
('title', models.CharField(default='每日日记', max_length=200, verbose_name='标题')),
|
||||
('completed_tasks', models.TextField(blank=True, default='', verbose_name='完成的任务')),
|
||||
('learned', models.TextField(blank=True, default='', verbose_name='学到的东西')),
|
||||
('problems', models.TextField(blank=True, default='', verbose_name='遇到的问题和解决方案')),
|
||||
('reflections', models.TextField(blank=True, default='', verbose_name='想法和反思')),
|
||||
('improvements', models.TextField(blank=True, default='', verbose_name='进步点')),
|
||||
('plans', models.TextField(blank=True, default='', verbose_name='明日计划')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '日记',
|
||||
'verbose_name_plural': '日记',
|
||||
'ordering': ['-date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DailyProgress',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('category', models.CharField(max_length=50, verbose_name='类别')),
|
||||
('description', models.TextField(verbose_name='描述')),
|
||||
('progress_percent', models.IntegerField(default=0, verbose_name='进度百分比')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progresses', to='diary.diaryentry')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '进度',
|
||||
'verbose_name_plural': '进度',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
31
diary-system/backend/diary/migrations/0002_experience.py
Normal file
31
diary-system/backend/diary/migrations/0002_experience.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 4.2.11 on 2026-04-14 08:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('diary', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Experience',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200, verbose_name='标题')),
|
||||
('category', models.CharField(choices=[('deployment', '📦 部署'), ('development', '💻 开发'), ('database', '🗄️ 数据库'), ('permission', '🔐 权限'), ('network', '🌐 网络'), ('other', '其他')], max_length=50, verbose_name='类别')),
|
||||
('problem', models.TextField(verbose_name='问题描述')),
|
||||
('solution', models.TextField(verbose_name='解决方案')),
|
||||
('lesson_learned', models.TextField(blank=True, default='', verbose_name='经验教训')),
|
||||
('date', models.DateField(auto_now_add=True, verbose_name='日期')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '经验总结',
|
||||
'verbose_name_plural': '经验总结',
|
||||
'ordering': ['-date', '-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
diary-system/backend/diary/migrations/__init__.py
Executable file
0
diary-system/backend/diary/migrations/__init__.py
Executable file
66
diary-system/backend/diary/models.py
Executable file
66
diary-system/backend/diary/models.py
Executable file
@@ -0,0 +1,66 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
class DiaryEntry(models.Model):
|
||||
"""日记条目"""
|
||||
date = models.DateField('日期', unique=True)
|
||||
title = models.CharField('标题', max_length=200, default='每日日记')
|
||||
completed_tasks = models.TextField('完成的任务', blank=True, default='')
|
||||
learned = models.TextField('学到的东西', blank=True, default='')
|
||||
problems = models.TextField('遇到的问题和解决方案', blank=True, default='')
|
||||
reflections = models.TextField('想法和反思', blank=True, default='')
|
||||
improvements = models.TextField('进步点', blank=True, default='')
|
||||
plans = models.TextField('明日计划', blank=True, default='')
|
||||
created_at = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
updated_at = models.DateTimeField('更新时间', auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-date']
|
||||
verbose_name = '日记'
|
||||
verbose_name_plural = '日记'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.date} - {self.title}"
|
||||
|
||||
|
||||
class Experience(models.Model):
|
||||
"""经验总结 - 记录遇到的问题和解决方法"""
|
||||
title = models.CharField('标题', max_length=200)
|
||||
category = models.CharField('类别', max_length=50, choices=[
|
||||
('deployment', '📦 部署'),
|
||||
('development', '💻 开发'),
|
||||
('database', '🗄️ 数据库'),
|
||||
('permission', '🔐 权限'),
|
||||
('network', '🌐 网络'),
|
||||
('other', '其他'),
|
||||
])
|
||||
problem = models.TextField('问题描述')
|
||||
solution = models.TextField('解决方案')
|
||||
lesson_learned = models.TextField('经验教训', blank=True, default='')
|
||||
date = models.DateField('日期', auto_now_add=True)
|
||||
created_at = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-date', '-created_at']
|
||||
verbose_name = '经验总结'
|
||||
verbose_name_plural = '经验总结'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.category} - {self.title}"
|
||||
|
||||
|
||||
class DailyProgress(models.Model):
|
||||
"""每日进度追踪"""
|
||||
entry = models.ForeignKey(DiaryEntry, on_delete=models.CASCADE, related_name='progresses')
|
||||
category = models.CharField('类别', max_length=50)
|
||||
description = models.TextField('描述')
|
||||
progress_percent = models.IntegerField('进度百分比', default=0)
|
||||
created_at = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
verbose_name = '进度'
|
||||
verbose_name_plural = '进度'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.entry.date} - {self.category}: {self.progress_percent}%"
|
||||
19
diary-system/backend/diary/serializers.py
Executable file
19
diary-system/backend/diary/serializers.py
Executable file
@@ -0,0 +1,19 @@
|
||||
from rest_framework import serializers
|
||||
from .models import DiaryEntry, DailyProgress, Experience
|
||||
|
||||
class ExperienceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Experience
|
||||
fields = '__all__'
|
||||
|
||||
class DailyProgressSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DailyProgress
|
||||
fields = '__all__'
|
||||
|
||||
class DiaryEntrySerializer(serializers.ModelSerializer):
|
||||
progresses = DailyProgressSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = DiaryEntry
|
||||
fields = '__all__'
|
||||
12
diary-system/backend/diary/urls.py
Executable file
12
diary-system/backend/diary/urls.py
Executable file
@@ -0,0 +1,12 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import DiaryEntryViewSet, DailyProgressViewSet, ExperienceViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'entries', DiaryEntryViewSet)
|
||||
router.register(r'progress', DailyProgressViewSet)
|
||||
router.register(r'experiences', ExperienceViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
62
diary-system/backend/diary/views.py
Executable file
62
diary-system/backend/diary/views.py
Executable file
@@ -0,0 +1,62 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.utils import timezone
|
||||
from .models import DiaryEntry, DailyProgress, Experience
|
||||
from .serializers import DiaryEntrySerializer, DailyProgressSerializer, ExperienceSerializer
|
||||
|
||||
class DiaryEntryViewSet(viewsets.ModelViewSet):
|
||||
queryset = DiaryEntry.objects.all()
|
||||
serializer_class = DiaryEntrySerializer
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def today(self, request):
|
||||
"""获取今天的日记"""
|
||||
today = timezone.now().date()
|
||||
entry, created = DiaryEntry.objects.get_or_create(date=today)
|
||||
serializer = self.get_serializer(entry)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def recent(self, request):
|
||||
"""获取最近 7 天的日记"""
|
||||
entries = DiaryEntry.objects.order_by('-date')[:7]
|
||||
serializer = self.get_serializer(entries, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def stats(self, request):
|
||||
"""获取统计信息"""
|
||||
total_entries = DiaryEntry.objects.count()
|
||||
return Response({
|
||||
'total_entries': total_entries,
|
||||
'first_entry': DiaryEntry.objects.order_by('date').first().date if total_entries > 0 else None,
|
||||
'latest_entry': DiaryEntry.objects.order_by('-date').first().date if total_entries > 0 else None,
|
||||
})
|
||||
|
||||
class DailyProgressViewSet(viewsets.ModelViewSet):
|
||||
queryset = DailyProgress.objects.all()
|
||||
serializer_class = DailyProgressSerializer
|
||||
|
||||
|
||||
class ExperienceViewSet(viewsets.ModelViewSet):
|
||||
queryset = Experience.objects.all()
|
||||
serializer_class = ExperienceSerializer
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_category(self, request):
|
||||
"""按类别分组获取经验"""
|
||||
categories = {}
|
||||
for exp in Experience.objects.all():
|
||||
cat = exp.get_category_display()
|
||||
if cat not in categories:
|
||||
categories[cat] = []
|
||||
categories[cat].append(ExperienceSerializer(exp).data)
|
||||
return Response(categories)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def recent(self, request):
|
||||
"""获取最近 10 条经验"""
|
||||
experiences = Experience.objects.order_by('-date', '-created_at')[:10]
|
||||
serializer = self.get_serializer(experiences, many=True)
|
||||
return Response(serializer.data)
|
||||
1
diary-system/backend/diary_system/__init__.py
Executable file
1
diary-system/backend/diary_system/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
# Diary System Project
|
||||
103
diary-system/backend/diary_system/settings.py
Executable file
103
diary-system/backend/diary_system/settings.py
Executable file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Django settings for diary_system project.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
SECRET_KEY = 'django-insecure-diary-system-secret-key-change-in-production'
|
||||
|
||||
DEBUG = os.environ.get('DEBUG', 'True').lower() == 'true'
|
||||
|
||||
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(',')
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'diary',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'diary_system.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'diary_system.wsgi.application'
|
||||
|
||||
# 数据库配置 - 云服务器使用 SQLite,本地使用 PostgreSQL
|
||||
if os.path.exists('/home/ubuntu/diary-system'):
|
||||
# 云服务器配置 - SQLite
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
else:
|
||||
# 本地配置 - PostgreSQL
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('DB_NAME', 'cssc'),
|
||||
'USER': os.environ.get('DB_USER', 'coder'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD', '825670wl'),
|
||||
'HOST': os.environ.get('DB_HOST', '10.2.0.100'),
|
||||
'PORT': os.environ.get('DB_PORT', '5432'),
|
||||
}
|
||||
}
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
|
||||
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
|
||||
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
|
||||
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
|
||||
]
|
||||
|
||||
LANGUAGE_CODE = 'zh-hans'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.AllowAny',
|
||||
]
|
||||
}
|
||||
10
diary-system/backend/diary_system/urls.py
Executable file
10
diary-system/backend/diary_system/urls.py
Executable file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
URL configuration for diary_system project.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('api/', include('diary.urls')),
|
||||
]
|
||||
11
diary-system/backend/diary_system/wsgi.py
Executable file
11
diary-system/backend/diary_system/wsgi.py
Executable file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
WSGI config for diary_system project.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
20
diary-system/backend/manage.py
Executable file
20
diary-system/backend/manage.py
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
4
diary-system/backend/requirements.txt
Executable file
4
diary-system/backend/requirements.txt
Executable file
@@ -0,0 +1,4 @@
|
||||
Django>=4.2
|
||||
djangorestframework>=3.14
|
||||
django-cors-headers>=4.0
|
||||
psycopg2-binary>=2.9
|
||||
8
diary-system/collect_static.py
Executable file
8
diary-system/collect_static.py
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
sys.path.insert(0, '/root/.openclaw/workspace/diary-system/backend')
|
||||
from django.core.management import execute_from_command_line
|
||||
sys.argv = ['manage.py', 'collectstatic', '--noinput']
|
||||
execute_from_command_line(sys.argv)
|
||||
31
diary-system/create_first_entry.py
Normal file
31
diary-system/create_first_entry.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
sys.path.insert(0, '/home/ubuntu/diary-system/backend')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from diary.models import DiaryEntry
|
||||
from datetime import date
|
||||
|
||||
entry, created = DiaryEntry.objects.get_or_create(date=date.today())
|
||||
entry.title = "第一天 - 日记系统上线"
|
||||
entry.completed_tasks = """- 创建日记系统框架
|
||||
- 开发 Web 日记系统(Django + 轻量前端)
|
||||
- 配置 Nginx 反向代理
|
||||
- 数据库迁移和同步
|
||||
- 部署到云服务器"""
|
||||
entry.learned = """- 掌握了 Django 项目快速搭建
|
||||
- 学习了 Nginx 配置和权限管理
|
||||
- 解决了云服务器部署问题"""
|
||||
entry.reflections = """- 日记系统应该保持简洁,避免过度设计
|
||||
- 每天记录进步可以帮助持续改进"""
|
||||
entry.improvements = """1. 系统建设:建立了日记系统的基础框架
|
||||
2. Web 开发:独立完成全栈 Web 应用开发
|
||||
3. 问题解决:快速解决权限和部署问题"""
|
||||
entry.save()
|
||||
|
||||
print(f"✅ 日记已创建:{entry.date}")
|
||||
print(f" 标题:{entry.title}")
|
||||
86
diary-system/deploy.sh
Executable file
86
diary-system/deploy.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# 日记系统 - 自动部署脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 开始部署日记系统..."
|
||||
|
||||
# 1. 安装依赖
|
||||
echo "📦 安装 Python 依赖..."
|
||||
cd /root/.openclaw/workspace/diary-system/backend
|
||||
pip3 install -r requirements.txt --break-system-packages -q
|
||||
|
||||
# 2. 数据库迁移
|
||||
echo "🗄️ 执行数据库迁移..."
|
||||
python3 manage.py makemigrations diary --noinput
|
||||
python3 manage.py migrate --noinput
|
||||
|
||||
# 3. 收集静态文件
|
||||
echo "📁 收集静态文件..."
|
||||
mkdir -p /root/.openclaw/workspace/diary-system/backend/static
|
||||
python3 manage.py collectstatic --noinput
|
||||
|
||||
# 4. 创建 Gunicorn 服务
|
||||
echo "⚙️ 创建 Gunicorn 服务..."
|
||||
cat > /etc/systemd/system/diary-system.service << EOF
|
||||
[Unit]
|
||||
Description=Diary System Gunicorn Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/root/.openclaw/workspace/diary-system/backend
|
||||
ExecStart=/usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8002 diary_system.wsgi:application
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable diary-system
|
||||
systemctl start diary-system
|
||||
|
||||
# 5. 配置 Nginx
|
||||
echo "🌐 配置 Nginx..."
|
||||
cat > /etc/nginx/sites-available/diary-system << 'EOF'
|
||||
server {
|
||||
listen 8001;
|
||||
server_name _;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static/ {
|
||||
alias /root/.openclaw/workspace/diary-system/backend/static/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /root/.openclaw/workspace/diary-system/frontend;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
ln -sf /etc/nginx/sites-available/diary-system /etc/nginx/sites-enabled/
|
||||
nginx -t
|
||||
systemctl restart nginx
|
||||
|
||||
# 6. 同步日记数据
|
||||
echo "📊 同步日记数据..."
|
||||
python3 /root/.openclaw/workspace/diary-system/sync_diary.py
|
||||
|
||||
echo ""
|
||||
echo "✅ 部署完成!"
|
||||
echo ""
|
||||
echo "📍 访问地址:http://cssc.datalibstar.com:8001"
|
||||
echo "📍 API: http://cssc.datalibstar.com:8001/api/"
|
||||
echo "📍 Admin: http://cssc.datalibstar.com:8001/admin"
|
||||
124
diary-system/deploy_cloud.sh
Normal file
124
diary-system/deploy_cloud.sh
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
# 日记系统 - 云服务器部署脚本
|
||||
# 在云服务器上执行:bash deploy_cloud.sh
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="/home/ubuntu/diary-system"
|
||||
BACKEND_DIR="$PROJECT_DIR/backend"
|
||||
FRONTEND_DIR="$PROJECT_DIR/frontend"
|
||||
|
||||
echo "======================================"
|
||||
echo "⚡ 码神日记系统 - 云服务器部署"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# 1. 安装依赖
|
||||
echo "📦 安装 Python 依赖..."
|
||||
cd "$BACKEND_DIR"
|
||||
pip3 install -r requirements.txt -q 2>/dev/null || pip3 install -r requirements.txt --break-system-packages -q
|
||||
|
||||
# 2. 数据库迁移
|
||||
echo "🗄️ 执行数据库迁移..."
|
||||
python3 manage.py makemigrations diary --noinput 2>/dev/null || true
|
||||
python3 manage.py migrate --noinput
|
||||
|
||||
# 3. 收集静态文件
|
||||
echo "📁 收集静态文件..."
|
||||
mkdir -p "$BACKEND_DIR/static"
|
||||
python3 manage.py collectstatic --noinput
|
||||
|
||||
# 4. 创建 Gunicorn systemd 服务
|
||||
echo "⚙️ 创建 Gunicorn 服务..."
|
||||
sudo cat > /etc/systemd/system/diary-system.service << EOF
|
||||
[Unit]
|
||||
Description=Diary System Gunicorn Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
Group=ubuntu
|
||||
WorkingDirectory=$BACKEND_DIR
|
||||
ExecStart=/usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8002 diary_system.wsgi:application
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable diary-system
|
||||
sudo systemctl restart diary-system
|
||||
|
||||
# 5. 配置 Nginx
|
||||
echo "🌐 配置 Nginx..."
|
||||
sudo cat > /etc/nginx/sites-available/diary-system << EOF
|
||||
server {
|
||||
listen 8001;
|
||||
server_name _;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static/ {
|
||||
alias $BACKEND_DIR/static/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root $FRONTEND_DIR;
|
||||
index index.html;
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
sudo ln -sf /etc/nginx/sites-available/diary-system /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# 6. 同步日记数据
|
||||
echo "📊 同步日记数据..."
|
||||
cd "$PROJECT_DIR"
|
||||
python3 sync_diary.py || echo " (首次部署,跳过日记同步)"
|
||||
|
||||
# 7. 检查服务状态
|
||||
echo ""
|
||||
echo "🔍 检查服务状态..."
|
||||
sudo systemctl is-active diary-system > /dev/null && echo " ✅ Gunicorn 运行中" || echo " ❌ Gunicorn 未运行"
|
||||
sudo systemctl is-active nginx > /dev/null && echo " ✅ Nginx 运行中" || echo " ❌ Nginx 未运行"
|
||||
|
||||
# 8. 测试访问
|
||||
echo ""
|
||||
echo "🧪 测试访问..."
|
||||
sleep 2
|
||||
if curl -s http://127.0.0.1:8001/ | grep -q "码神的日记系统"; then
|
||||
echo " ✅ 前端访问正常"
|
||||
else
|
||||
echo " ⚠️ 前端访问可能有问题"
|
||||
fi
|
||||
|
||||
if curl -s http://127.0.0.1:8001/api/entries/stats/ | grep -q "total_entries"; then
|
||||
echo " ✅ API 访问正常"
|
||||
else
|
||||
echo " ⚠️ API 访问可能有问题"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "✅ 部署完成!"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "📍 访问地址:http://cssc.datalibstar.com:8001"
|
||||
echo "📍 API: http://cssc.datalibstar.com:8001/api/"
|
||||
echo "📍 Admin: http://cssc.datalibstar.com:8001/admin"
|
||||
echo ""
|
||||
echo "🎉 日记系统已在云服务器上线!"
|
||||
echo ""
|
||||
143
diary-system/deploy_to_cloud.sh
Normal file
143
diary-system/deploy_to_cloud.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
# 日记系统 - 云服务器一键部署脚本
|
||||
# 使用方法:在云服务器上执行 bash deploy_to_cloud.sh
|
||||
|
||||
set -e
|
||||
|
||||
SERVER_URL="cssc.datalibstar.com"
|
||||
DEPLOY_PORT="8001"
|
||||
|
||||
echo "======================================"
|
||||
echo "⚡ 码神日记系统 - 云服务器部署"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# 检查是否在正确的服务器上
|
||||
echo "🔍 检查服务器..."
|
||||
if [ "$(hostname)" != "cssc" ] && [ "$(hostname)" != "cssc.datalibstar.com" ]; then
|
||||
echo "⚠️ 警告:当前主机名不是 cssc,请确认是否在正确的服务器上"
|
||||
fi
|
||||
|
||||
# 1. 克隆或更新代码
|
||||
echo "📦 获取代码..."
|
||||
cd /root/.openclaw/workspace || {
|
||||
echo "❌ 错误:/root/.openclaw/workspace 不存在"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -d "diary-system" ]; then
|
||||
echo " 更新现有代码..."
|
||||
cd diary-system
|
||||
git pull 2>/dev/null || echo " (非 git 仓库,跳过)"
|
||||
cd ..
|
||||
else
|
||||
echo " 代码已存在,跳过克隆"
|
||||
fi
|
||||
|
||||
# 2. 安装依赖
|
||||
echo "📦 安装 Python 依赖..."
|
||||
cd /root/.openclaw/workspace/diary-system/backend
|
||||
pip3 install -r requirements.txt --break-system-packages -q
|
||||
|
||||
# 3. 数据库迁移
|
||||
echo "🗄️ 执行数据库迁移..."
|
||||
python3 manage.py makemigrations diary --noinput 2>/dev/null || true
|
||||
python3 manage.py migrate --noinput
|
||||
|
||||
# 4. 收集静态文件
|
||||
echo "📁 收集静态文件..."
|
||||
mkdir -p /root/.openclaw/workspace/diary-system/backend/static
|
||||
python3 manage.py collectstatic --noinput
|
||||
|
||||
# 5. 创建 Gunicorn systemd 服务
|
||||
echo "⚙️ 创建 Gunicorn 服务..."
|
||||
cat > /etc/systemd/system/diary-system.service << EOF
|
||||
[Unit]
|
||||
Description=Diary System Gunicorn Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/root/.openclaw/workspace/diary-system/backend
|
||||
ExecStart=/usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8002 diary_system.wsgi:application
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable diary-system
|
||||
systemctl restart diary-system
|
||||
|
||||
# 6. 配置 Nginx
|
||||
echo "🌐 配置 Nginx..."
|
||||
cat > /etc/nginx/sites-available/diary-system << 'EOF'
|
||||
server {
|
||||
listen 8001;
|
||||
server_name _;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static/ {
|
||||
alias /root/.openclaw/workspace/diary-system/backend/static/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /root/.openclaw/workspace/diary-system/frontend;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
ln -sf /etc/nginx/sites-available/diary-system /etc/nginx/sites-enabled/
|
||||
nginx -t
|
||||
systemctl restart nginx
|
||||
|
||||
# 7. 同步日记数据
|
||||
echo "📊 同步日记数据..."
|
||||
cd /root/.openclaw/workspace/diary-system
|
||||
python3 sync_diary.py
|
||||
|
||||
# 8. 检查服务状态
|
||||
echo ""
|
||||
echo "🔍 检查服务状态..."
|
||||
systemctl is-active diary-system > /dev/null && echo " ✅ Gunicorn 运行中" || echo " ❌ Gunicorn 未运行"
|
||||
systemctl is-active nginx > /dev/null && echo " ✅ Nginx 运行中" || echo " ❌ Nginx 未运行"
|
||||
|
||||
# 9. 测试访问
|
||||
echo ""
|
||||
echo "🧪 测试访问..."
|
||||
sleep 2
|
||||
if curl -s http://127.0.0.1:8001/ | grep -q "码神的日记系统"; then
|
||||
echo " ✅ 前端访问正常"
|
||||
else
|
||||
echo " ⚠️ 前端访问可能有问题"
|
||||
fi
|
||||
|
||||
if curl -s http://127.0.0.1:8001/api/entries/stats/ | grep -q "total_entries"; then
|
||||
echo " ✅ API 访问正常"
|
||||
else
|
||||
echo " ⚠️ API 访问可能有问题"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "✅ 部署完成!"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "📍 访问地址:http://${SERVER_URL}:${DEPLOY_PORT}"
|
||||
echo "📍 API: http://${SERVER_URL}:${DEPLOY_PORT}/api/"
|
||||
echo "📍 Admin: http://${SERVER_URL}:${DEPLOY_PORT}/admin"
|
||||
echo ""
|
||||
echo "🎉 日记系统已在云服务器上线!"
|
||||
echo ""
|
||||
304
diary-system/frontend/index.html
Executable file
304
diary-system/frontend/index.html
Executable file
@@ -0,0 +1,304 @@
|
||||
<!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', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
header p {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stat-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.stat-card h3 {
|
||||
color: #667eea;
|
||||
font-size: 2em;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.stat-card p {
|
||||
color: #666;
|
||||
}
|
||||
.section-box {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section-box h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #667eea;
|
||||
}
|
||||
.diary-item {
|
||||
padding: 15px;
|
||||
border-left: 4px solid #667eea;
|
||||
background: #f8f9fa;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.diary-item h3 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.diary-item .date {
|
||||
color: #667eea;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.diary-item .section {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.diary-item .section-title {
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
.diary-item .section-content {
|
||||
color: #666;
|
||||
margin-left: 20px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.experience-item {
|
||||
padding: 15px;
|
||||
border-left: 4px solid #f59e0b;
|
||||
background: #fffbeb;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.experience-item .header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.experience-item .title {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.experience-item .category {
|
||||
background: #f59e0b;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.experience-item .problem {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.experience-item .problem-title {
|
||||
font-weight: bold;
|
||||
color: #dc2626;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.experience-item .solution {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.experience-item .solution-title {
|
||||
font-weight: bold;
|
||||
color: #059669;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.experience-item .lesson {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background: #fef3c7;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.experience-item .lesson-title {
|
||||
font-weight: bold;
|
||||
color: #92400e;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.emoji {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(600px, 1fr));
|
||||
gap: 30px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.grid-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>⚡ 码神的日记系统</h1>
|
||||
<p>记录每天的进步与成长</p>
|
||||
</header>
|
||||
|
||||
<div id="app">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = '/api';
|
||||
|
||||
async function loadDiary() {
|
||||
try {
|
||||
const [statsRes, entriesRes, expStatsRes, experiencesRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/entries/stats/`),
|
||||
fetch(`${API_BASE}/entries/recent/`),
|
||||
fetch(`${API_BASE}/experiences/stats/`),
|
||||
fetch(`${API_BASE}/experiences/recent/`)
|
||||
]);
|
||||
|
||||
const stats = await statsRes.json();
|
||||
const entries = await entriesRes.json();
|
||||
const expStats = await expStatsRes.json();
|
||||
const experiences = await experiencesRes.json();
|
||||
|
||||
render(stats, entries, expStats, experiences);
|
||||
} catch (error) {
|
||||
document.getElementById('app').innerHTML = `
|
||||
<div class="error">加载失败:${error.message}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function render(stats, entries, expStats, experiences) {
|
||||
const app = document.getElementById('app');
|
||||
|
||||
app.innerHTML = `
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<h3>${stats.total_entries || 0}</h3>
|
||||
<p>总日记数</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>${expStats.total_experiences || 0}</h3>
|
||||
<p>经验总结</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>${stats.first_entry || '-'}</h3>
|
||||
<p>第一天</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3>${stats.latest_entry || '-'}</h3>
|
||||
<p>最新日记</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="section-box">
|
||||
<h2>📝 最近日记</h2>
|
||||
${entries.length === 0 ? '<p>暂无日记</p>' : entries.map(entry => `
|
||||
<div class="diary-item">
|
||||
<div class="date">${entry.date}</div>
|
||||
<h3>${entry.title || '每日日记'}</h3>
|
||||
${entry.completed_tasks ? `
|
||||
<div class="section">
|
||||
<span class="section-title">✅ 完成的任务</span>
|
||||
<div class="section-content">${entry.completed_tasks}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
${entry.learned ? `
|
||||
<div class="section">
|
||||
<span class="section-title">📚 学到的东西</span>
|
||||
<div class="section-content">${entry.learned}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
${entry.reflections ? `
|
||||
<div class="section">
|
||||
<span class="section-title">💡 想法和反思</span>
|
||||
<div class="section-content">${entry.reflections}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
${entry.improvements ? `
|
||||
<div class="section">
|
||||
<span class="section-title">📈 进步点</span>
|
||||
<div class="section-content">${entry.improvements}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<div class="section-box">
|
||||
<h2>💡 经验总结</h2>
|
||||
${experiences.length === 0 ? '<p>暂无经验总结</p>' : experiences.map(exp => `
|
||||
<div class="experience-item">
|
||||
<div class="header">
|
||||
<span class="title">${exp.title}</span>
|
||||
<span class="category">${exp.category}</span>
|
||||
</div>
|
||||
<div class="problem">
|
||||
<div class="problem-title">🐛 问题</div>
|
||||
<div class="section-content">${exp.problem}</div>
|
||||
</div>
|
||||
<div class="solution">
|
||||
<div class="solution-title">✅ 解决方案</div>
|
||||
<div class="section-content">${exp.solution}</div>
|
||||
</div>
|
||||
${exp.lesson_learned ? `
|
||||
<div class="lesson">
|
||||
<div class="lesson-title">📌 经验教训</div>
|
||||
<div class="section-content">${exp.lesson_learned}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
loadDiary();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
7
diary-system/makemigrations.py
Normal file
7
diary-system/makemigrations.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys
|
||||
sys.path.insert(0, '/root/.openclaw/workspace/diary-system/backend')
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'diary_system.settings'
|
||||
from django.core.management import execute_from_command_line
|
||||
sys.argv = ['manage.py', 'makemigrations', 'diary']
|
||||
execute_from_command_line(sys.argv)
|
||||
7
diary-system/migrate.py
Normal file
7
diary-system/migrate.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys
|
||||
sys.path.insert(0, '/root/.openclaw/workspace/diary-system/backend')
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'diary_system.settings'
|
||||
from django.core.management import execute_from_command_line
|
||||
sys.argv = ['manage.py', 'migrate']
|
||||
execute_from_command_line(sys.argv)
|
||||
21
diary-system/nginx.conf
Executable file
21
diary-system/nginx.conf
Executable file
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 8001;
|
||||
server_name localhost;
|
||||
|
||||
location /static/ {
|
||||
alias /root/.openclaw/workspace/diary-system/backend/static/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /root/.openclaw/workspace/diary-system/frontend;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
23
diary-system/nginx_cloud.conf
Normal file
23
diary-system/nginx_cloud.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
server {
|
||||
listen 8001;
|
||||
server_name _;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static/ {
|
||||
alias /home/ubuntu/diary-system/backend/static/;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8002;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /home/ubuntu/diary-system/frontend;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
25
diary-system/start.sh
Executable file
25
diary-system/start.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# 日记系统启动脚本
|
||||
|
||||
cd /root/.openclaw/workspace/diary-system/backend
|
||||
|
||||
# 安装依赖
|
||||
pip3 install -r requirements.txt -q
|
||||
|
||||
# 数据库迁移
|
||||
python3 manage.py makemigrations diary
|
||||
python3 manage.py migrate
|
||||
|
||||
# 收集静态文件
|
||||
python3 manage.py collectstatic --noinput
|
||||
|
||||
# 启动 Gunicorn
|
||||
echo "🚀 启动日记系统..."
|
||||
gunicorn diary_system.wsgi:application \
|
||||
--bind 127.0.0.1:8002 \
|
||||
--workers 3 \
|
||||
--daemon
|
||||
|
||||
echo "✅ 日记系统已启动"
|
||||
echo "📝 访问地址:http://127.0.0.1:8001/"
|
||||
echo "🔧 Admin: http://127.0.0.1:8001/admin/"
|
||||
53
diary-system/sync_diary.py
Normal file
53
diary-system/sync_diary.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""同步今天的日记到数据库"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'diary_system.settings')
|
||||
sys.path.insert(0, '/root/.openclaw/workspace/diary-system/backend')
|
||||
|
||||
import django
|
||||
django.setup()
|
||||
|
||||
from datetime import date
|
||||
from diary.models import DiaryEntry
|
||||
|
||||
# 读取今天的日记文件
|
||||
diary_path = '/root/.openclaw/workspace/memory/2026-04-14.md'
|
||||
with open(diary_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 解析日记内容(简单解析)
|
||||
def extract_section(content, marker):
|
||||
lines = content.split('\n')
|
||||
result = []
|
||||
in_section = False
|
||||
for line in lines:
|
||||
if marker in line:
|
||||
in_section = True
|
||||
continue
|
||||
if in_section:
|
||||
if line.startswith('## ') or line.startswith('---'):
|
||||
break
|
||||
result.append(line)
|
||||
return '\n'.join(result).strip()
|
||||
|
||||
# 创建或更新日记
|
||||
today = date.today()
|
||||
entry, created = DiaryEntry.objects.get_or_create(date=today)
|
||||
|
||||
entry.title = "第一天 - 日记系统上线"
|
||||
entry.completed_tasks = extract_section(content, '✅ 完成的任务')
|
||||
entry.learned = extract_section(content, '📚 学到的东西')
|
||||
entry.problems = extract_section(content, '🐛 遇到的问题')
|
||||
entry.reflections = extract_section(content, '💡 想法和反思')
|
||||
entry.improvements = extract_section(content, '📈 进步点')
|
||||
entry.plans = extract_section(content, '🎯 明日计划')
|
||||
entry.save()
|
||||
|
||||
print(f"✅ 日记已同步:{entry.date}")
|
||||
print(f" 标题:{entry.title}")
|
||||
print(f" 完成的任务:{len(entry.completed_tasks)} 字")
|
||||
print(f" 学到的东西:{len(entry.learned)} 字")
|
||||
print(f" 进步点:{len(entry.improvements)} 字")
|
||||
Reference in New Issue
Block a user