Initial commit: 日记系统

完整的日记记录系统
- Django 后端 (diary app)
- 前端页面
- 部署脚本 (本地 + 云端)
- Nginx 配置
- 数据迁移和同步工具
This commit is contained in:
maoshen
2026-04-14 10:07:27 +00:00
commit b273789ae8
31 changed files with 1457 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
__pycache__/
*.pyc
*.pyo
*.db
*.sqlite3
static/
media/
.env
*.log
.DS_Store
*.swp
*.swo
*~

107
DEPLOYMENT_REPORT.md Normal file
View 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
README.md Executable file
View 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
add_first_experience.py Normal file
View 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()}")

View 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
backend/diary/__init__.py Executable file
View File

@@ -0,0 +1 @@
# Diary app

View 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'],
},
),
]

View 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'],
},
),
]

View File

66
backend/diary/models.py Executable file
View 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
backend/diary/serializers.py Executable file
View 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
backend/diary/urls.py Executable file
View 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
backend/diary/views.py Executable file
View 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)

View File

@@ -0,0 +1 @@
# Diary System Project

103
backend/diary_system/settings.py Executable file
View 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
backend/diary_system/urls.py Executable file
View 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
backend/diary_system/wsgi.py Executable file
View 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
backend/manage.py Executable file
View 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
backend/requirements.txt Executable file
View File

@@ -0,0 +1,4 @@
Django>=4.2
djangorestframework>=3.14
django-cors-headers>=4.0
psycopg2-binary>=2.9

8
collect_static.py Executable file
View 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
create_first_entry.py Normal file
View 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
deploy.sh Executable file
View 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
deploy_cloud.sh Normal file
View 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
deploy_to_cloud.sh Normal file
View 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
frontend/index.html Executable file
View 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
makemigrations.py Normal file
View 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
migrate.py Normal file
View 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
nginx.conf Executable file
View 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
nginx_cloud.conf Normal file
View 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
start.sh Executable file
View 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
sync_diary.py Normal file
View 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)}")