diff --git a/.gitea/workflows/backend-deploy.yml b/.gitea/workflows/backend-deploy.yml index 6941895..fb5585b 100644 --- a/.gitea/workflows/backend-deploy.yml +++ b/.gitea/workflows/backend-deploy.yml @@ -33,7 +33,6 @@ jobs: cp server/Dockerfile deploy/ cp server/docker-compose.yml deploy/ cp server/.env.example deploy/ - cp -r server/migrations deploy/ - name: 部署文件到服务器 uses: up9cloud/action-rsync@master diff --git a/README.md b/README.md index 4c60697..2275409 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ InsightReply 采用现代化解耦的三端架构: ## 🛠️ 本地开发指南 (Local Development) ### 1. 启动 PostgreSQL 数据库 +> **注意**:本项目已禁用 Go 服务自动迁移。请使用本项目支持的 MCP 服务 (`InsightReply_PostgreSQL`) 或手动执行 `docs/schema.sql` 来同步数据库结构。 ### 2. 配置与启动后端 (Go Server) ```bash @@ -82,9 +83,10 @@ VITE_API_BASE_URL=https://insight.buildapp.eu.org/api/v1 ```bash cd /var/admin/InsightReply/server -docker-compose up -d --build +# 生产环境使用 docker compose (V2) +docker compose up -d --build --remove-orphans ``` -> 此时应用的全局运行日志将自动映射并写入由于宿主机的 `/root/logs/InsightReply.log` 内以供探查。 +> 此时应用将以 `network_mode: host` 模式运行,全局运行日志将自动映射并写入宿主机的 `/app/logs/InsightReply.log` 内以供探查。 ### 3. Caddyfile 反向代理与 SSL 自动签发 在宿主机中编辑 `/etc/caddy/Caddyfile`,配置以下动静分离策略: diff --git a/docs/schema.sql b/docs/schema.sql index 8400ce2..1e9de64 100644 --- a/docs/schema.sql +++ b/docs/schema.sql @@ -55,9 +55,9 @@ CREATE TABLE IF NOT EXISTS tweets ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_tweets_x_tweet_id ON tweets(x_tweet_id); -CREATE INDEX idx_tweets_heat_score ON tweets(heat_score DESC); -CREATE INDEX idx_tweets_crawl_queue ON tweets(crawl_queue, last_crawled_at); +CREATE INDEX IF NOT EXISTS idx_tweets_x_tweet_id ON tweets(x_tweet_id); +CREATE INDEX IF NOT EXISTS idx_tweets_heat_score ON tweets(heat_score DESC); +CREATE INDEX IF NOT EXISTS idx_tweets_crawl_queue ON tweets(crawl_queue, last_crawled_at); -- generated_replies 表:生成的 AI 评论记录 CREATE TABLE IF NOT EXISTS generated_replies ( @@ -71,8 +71,8 @@ CREATE TABLE IF NOT EXISTS generated_replies ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_generated_replies_user_id ON generated_replies(user_id); -CREATE INDEX idx_generated_replies_tweet_id ON generated_replies(tweet_id); +CREATE INDEX IF NOT EXISTS idx_generated_replies_user_id ON generated_replies(user_id); +CREATE INDEX IF NOT EXISTS idx_generated_replies_tweet_id ON generated_replies(tweet_id); -- reply_performance 表:针对已发布评论的效果数据回拨 CREATE TABLE IF NOT EXISTS reply_performance ( @@ -85,8 +85,8 @@ CREATE TABLE IF NOT EXISTS reply_performance ( check_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_reply_performance_reply_id ON reply_performance(reply_id); -CREATE INDEX idx_reply_performance_user_id ON reply_performance(user_id); +CREATE INDEX IF NOT EXISTS idx_reply_performance_reply_id ON reply_performance(reply_id); +CREATE INDEX IF NOT EXISTS idx_reply_performance_user_id ON reply_performance(user_id); -- ==================================================== -- 新增表 (v1.1) @@ -106,8 +106,8 @@ CREATE TABLE IF NOT EXISTS api_usage_logs ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_api_usage_logs_user_id ON api_usage_logs(user_id); -CREATE INDEX idx_api_usage_logs_created_at ON api_usage_logs(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_api_usage_logs_user_id ON api_usage_logs(user_id); +CREATE INDEX IF NOT EXISTS idx_api_usage_logs_created_at ON api_usage_logs(created_at DESC); -- subscriptions 表:用户订阅记录(支付历史) CREATE TABLE IF NOT EXISTS subscriptions ( @@ -122,8 +122,8 @@ CREATE TABLE IF NOT EXISTS subscriptions ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id); -CREATE INDEX idx_subscriptions_status ON subscriptions(status); +CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON subscriptions(user_id); +CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status); -- user_style_profiles 表:用户风格画像(用于个性化 Prompt) CREATE TABLE IF NOT EXISTS user_style_profiles ( @@ -147,7 +147,7 @@ CREATE TABLE IF NOT EXISTS crawl_snapshots ( created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_crawl_snapshots_created_at ON crawl_snapshots(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_crawl_snapshots_created_at ON crawl_snapshots(created_at DESC); -- ==================================================== -- 新增表 (v1.2) — 用户可配置系统 @@ -191,7 +191,7 @@ CREATE TABLE IF NOT EXISTS user_custom_strategies ( UNIQUE (user_id, strategy_key) ); -CREATE INDEX idx_user_custom_strategies_user_id ON user_custom_strategies(user_id); +CREATE INDEX IF NOT EXISTS idx_user_custom_strategies_user_id ON user_custom_strategies(user_id); -- competitor_monitors 表:竞品品牌监控(复用后端雷达,按品牌词自动抓取) CREATE TABLE IF NOT EXISTS competitor_monitors ( @@ -204,7 +204,7 @@ CREATE TABLE IF NOT EXISTS competitor_monitors ( UNIQUE (user_id, brand_name) ); -CREATE INDEX idx_competitor_monitors_user_id ON competitor_monitors(user_id); +CREATE INDEX IF NOT EXISTS idx_competitor_monitors_user_id ON competitor_monitors(user_id); -- ==================================================== -- 触发器:自动更新 updated_at @@ -219,12 +219,20 @@ END; $$ language 'plpgsql'; -- 为所有需要追踪更新时间的表添加触发器 -CREATE TRIGGER update_users_modtime - BEFORE UPDATE ON users - FOR EACH ROW - EXECUTE FUNCTION update_modified_column(); +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_users_modtime') THEN + CREATE TRIGGER update_users_modtime + BEFORE UPDATE ON users + FOR EACH ROW + EXECUTE FUNCTION update_modified_column(); + END IF; +END $$; -CREATE TRIGGER update_user_style_profiles_modtime - BEFORE UPDATE ON user_style_profiles - FOR EACH ROW - EXECUTE FUNCTION update_modified_column(); +DO $$ BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'update_user_style_profiles_modtime') THEN + CREATE TRIGGER update_user_style_profiles_modtime + BEFORE UPDATE ON user_style_profiles + FOR EACH ROW + EXECUTE FUNCTION update_modified_column(); + END IF; +END $$; diff --git a/rules/backend-guidelines.md b/rules/backend-guidelines.md index 13e5253..3d0b648 100644 --- a/rules/backend-guidelines.md +++ b/rules/backend-guidelines.md @@ -57,3 +57,7 @@ globs: *.go, *.sql * 对于本项目的初步开发,推荐使用如 **`gorm`** 或 **`sqlx`** 进行快速的数据交互操作。 * 所有表名、字段名在 Go 结构体 (`struct`) 的 tag 中必须显式定义为下划线 (snake_case)。 * UUID 作为主键,禁止前端或外部服务自行生成传入,一律由 PostgreSQL `gen_random_uuid()` 或者服务端生成。 +* **数据库迁移 (Migration)**: + * **禁用自动迁移**: 后端程序不再自动执行 `Up()` 迁移,所有变更需手动通过 MCP 或 DBA 工具执行。 + * **幂等性**: 所有 SQL 脚本(如 `CREATE INDEX`, `CREATE TABLE`)必须包含 `IF NOT EXISTS` 保护。 + * **触发器**: 创建触发器时必须先检查是否存在,避免重复定义导致部署中断。 diff --git a/server/Dockerfile b/server/Dockerfile index e3688e0..108d3b1 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -10,8 +10,7 @@ WORKDIR /app COPY server_bin . RUN chmod +x server_bin -# 拷贝数据库迁移文件 (服务启动时自动执行) -COPY migrations ./migrations +# 数据库迁移现已通过 MCP 手动管理,不再打包进镜像 EXPOSE 8009 diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go index 661c065..37c681a 100644 --- a/server/cmd/server/main.go +++ b/server/cmd/server/main.go @@ -21,9 +21,6 @@ import ( "github.com/zs/InsightReply/internal/service" "github.com/zs/InsightReply/internal/worker" - "github.com/golang-migrate/migrate/v4" - _ "github.com/golang-migrate/migrate/v4/database/postgres" - _ "github.com/golang-migrate/migrate/v4/source/file" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -58,19 +55,6 @@ func main() { } fmt.Println("Database connection established") - // 2.1 Run Database Migrations - log.Println("Running database migrations...") - m, err := migrate.New("file://migrations", dsn) - if err != nil { - log.Printf("Failed to initialize migrate, skipping: %v", err) - } else { - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - log.Printf("Failed to run migrate (maybe tables already exist), continuing: %v", err) - } else { - log.Println("Database migrations applied successfully") - } - } - // 3. Initialize Layers userRepo := repository.NewUserRepository(db) userSvc := service.NewUserService(userRepo)