新建小程序后端,新建数据库
This commit is contained in:
11
mini-program/.env.example
Normal file
11
mini-program/.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
APP_NAME=mini-program-api
|
||||
APP_VERSION=0.1.0
|
||||
APP_DESCRIPTION=Backend service for mini program
|
||||
HOST=0.0.0.0
|
||||
PORT=8001
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USER=root
|
||||
DB_PASSWORD=change_me
|
||||
DB_NAME=mini_program
|
||||
1
mini-program/app/__init__.py
Normal file
1
mini-program/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
35
mini-program/app/main.py
Normal file
35
mini-program/app/main.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
try:
|
||||
# For module mode: `uvicorn app.main:app`
|
||||
from app.routers.health import router as health_router
|
||||
from app.settings import settings
|
||||
except ModuleNotFoundError:
|
||||
# For script mode: `python app/main.py` or VS Code "Run Python File"
|
||||
from routers.health import router as health_router
|
||||
from settings import settings
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version=settings.app_version,
|
||||
description=settings.app_description,
|
||||
)
|
||||
app.include_router(health_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=os.getenv("HOST", settings.host),
|
||||
port=int(os.getenv("PORT", str(settings.port))),
|
||||
)
|
||||
1
mini-program/app/routers/__init__.py
Normal file
1
mini-program/app/routers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
8
mini-program/app/routers/health.py
Normal file
8
mini-program/app/routers/health.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
46
mini-program/app/settings.py
Normal file
46
mini-program/app/settings.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from functools import lru_cache
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
app_name: str = Field(default="mini-program-api", validation_alias="APP_NAME")
|
||||
app_version: str = Field(default="0.1.0", validation_alias="APP_VERSION")
|
||||
app_description: str = Field(
|
||||
default="Backend service for mini program",
|
||||
validation_alias="APP_DESCRIPTION",
|
||||
)
|
||||
host: str = Field(default="0.0.0.0", validation_alias="HOST")
|
||||
port: int = Field(default=8001, validation_alias="PORT")
|
||||
|
||||
db_host: str = Field(default="127.0.0.1", validation_alias="DB_HOST")
|
||||
db_port: int = Field(default=3306, validation_alias="DB_PORT")
|
||||
db_user: str = Field(default="root", validation_alias="DB_USER")
|
||||
db_password: str = Field(default="", validation_alias="DB_PASSWORD")
|
||||
db_name: str = Field(default="mini_program", validation_alias="DB_NAME")
|
||||
|
||||
@property
|
||||
def mysql_dsn(self) -> str:
|
||||
user = quote_plus(self.db_user)
|
||||
password = quote_plus(self.db_password)
|
||||
auth = f"{user}:{password}" if password else user
|
||||
return (
|
||||
f"mysql+pymysql://{auth}@{self.db_host}:{self.db_port}/"
|
||||
f"{self.db_name}?charset=utf8mb4"
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
295
mini-program/database/sql/001_init_chat.sql
Normal file
295
mini-program/database/sql/001_init_chat.sql
Normal file
@@ -0,0 +1,295 @@
|
||||
-- Chat schema and seed data for mini-program
|
||||
-- Target: MySQL 8+
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS mini_program
|
||||
DEFAULT CHARACTER SET utf8mb4
|
||||
COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE mini_program;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
DROP TABLE IF EXISTS chat_message;
|
||||
DROP TABLE IF EXISTS chat_conversation_member;
|
||||
DROP TABLE IF EXISTS chat_conversation;
|
||||
DROP TABLE IF EXISTS chat_user;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_user (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
nickname VARCHAR(64) NULL,
|
||||
avatar_file_key VARCHAR(255) NULL COMMENT 'Stored file key, not URL',
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1-active 2-disabled 3-deleted',
|
||||
last_login_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_status (status),
|
||||
KEY idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_conversation (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(128) NULL,
|
||||
conversation_type TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1-direct 2-group',
|
||||
member_signature CHAR(64) NOT NULL COMMENT 'sha256 of sorted member user_ids, e.g. sha256("1,2")',
|
||||
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '1-active 2-archived',
|
||||
last_seq BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
message_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
last_message_preview VARCHAR(255) NULL,
|
||||
last_message_at DATETIME(3) NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_type_member_signature (conversation_type, member_signature),
|
||||
KEY idx_last_message_at (last_message_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_conversation_member (
|
||||
conversation_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
member_role TINYINT UNSIGNED NOT NULL DEFAULT 2 COMMENT '1-owner 2-member',
|
||||
joined_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
left_at DATETIME(3) NULL,
|
||||
PRIMARY KEY (conversation_id, user_id),
|
||||
KEY idx_user_conversation (user_id, conversation_id),
|
||||
CONSTRAINT fk_member_conv
|
||||
FOREIGN KEY (conversation_id) REFERENCES chat_conversation(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_member_user
|
||||
FOREIGN KEY (user_id) REFERENCES chat_user(id)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_message (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
conversation_id BIGINT UNSIGNED NOT NULL,
|
||||
seq BIGINT UNSIGNED NOT NULL COMMENT 'Conversation-local incremental sequence',
|
||||
sender_user_id BIGINT UNSIGNED NULL COMMENT 'Null means assistant/system sender',
|
||||
role TINYINT UNSIGNED NOT NULL COMMENT '1-user 2-assistant 3-system 4-tool',
|
||||
content_type TINYINT UNSIGNED NOT NULL COMMENT '1-text 2-audio 3-image 4-json',
|
||||
content_text MEDIUMTEXT NULL,
|
||||
content_json JSON NULL,
|
||||
media_file_key VARCHAR(255) NULL COMMENT 'Stored file key, not URL',
|
||||
media_duration_ms INT UNSIGNED NULL,
|
||||
client_msg_id VARCHAR(64) NULL COMMENT 'Idempotency key from client',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
deleted_at DATETIME(3) NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_conv_seq (conversation_id, seq),
|
||||
UNIQUE KEY uk_conv_client_msg (conversation_id, client_msg_id),
|
||||
KEY idx_conv_created (conversation_id, created_at),
|
||||
KEY idx_sender_created (sender_user_id, created_at),
|
||||
CONSTRAINT fk_msg_conv
|
||||
FOREIGN KEY (conversation_id) REFERENCES chat_conversation(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT fk_msg_sender_user
|
||||
FOREIGN KEY (sender_user_id) REFERENCES chat_user(id)
|
||||
ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT INTO chat_user (
|
||||
id,
|
||||
nickname,
|
||||
avatar_file_key,
|
||||
status,
|
||||
last_login_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1,
|
||||
'Parent A',
|
||||
'avatars/parent-a.png',
|
||||
1,
|
||||
'2026-03-25 09:58:00.000',
|
||||
'2026-03-25 09:00:00.000',
|
||||
'2026-03-25 09:58:00.000'
|
||||
),
|
||||
(
|
||||
2,
|
||||
'Parent B',
|
||||
'avatars/parent-b.png',
|
||||
1,
|
||||
'2026-03-25 10:01:00.000',
|
||||
'2026-03-25 09:20:00.000',
|
||||
'2026-03-25 10:01:00.000'
|
||||
),
|
||||
(
|
||||
3,
|
||||
'Parent C',
|
||||
'avatars/parent-c.png',
|
||||
1,
|
||||
'2026-03-25 21:11:00.000',
|
||||
'2026-03-25 20:30:00.000',
|
||||
'2026-03-25 21:11:00.000'
|
||||
);
|
||||
|
||||
INSERT INTO chat_conversation (
|
||||
id,
|
||||
title,
|
||||
conversation_type,
|
||||
member_signature,
|
||||
status,
|
||||
last_seq,
|
||||
message_count,
|
||||
last_message_preview,
|
||||
last_message_at,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1,
|
||||
'Direct Chat',
|
||||
1,
|
||||
'17f8af97ad4a7f7639a4c9171d5185cbafb85462877a4746c21bdb0a4f940ca0',
|
||||
1,
|
||||
4,
|
||||
4,
|
||||
'user: Great, explain how to solve it.',
|
||||
'2026-03-25 10:00:30.000',
|
||||
'2026-03-25 10:00:00.000',
|
||||
'2026-03-25 10:00:30.000'
|
||||
),
|
||||
(
|
||||
2,
|
||||
'Family Group',
|
||||
2,
|
||||
'8a6ae15122001229edb8866f56e342af12ae8187203c3e3b33931743e7c0c48d',
|
||||
1,
|
||||
3,
|
||||
3,
|
||||
'user: Okay, we will wait in the living room.',
|
||||
'2026-03-25 21:16:10.000',
|
||||
'2026-03-25 21:15:00.000',
|
||||
'2026-03-25 21:16:10.000'
|
||||
);
|
||||
|
||||
INSERT INTO chat_conversation_member (
|
||||
conversation_id,
|
||||
user_id,
|
||||
member_role,
|
||||
joined_at,
|
||||
left_at
|
||||
)
|
||||
VALUES
|
||||
(1, 1, 1, '2026-03-25 10:00:00.000', NULL),
|
||||
(1, 2, 2, '2026-03-25 10:00:00.000', NULL),
|
||||
(2, 1, 1, '2026-03-25 21:15:00.000', NULL),
|
||||
(2, 2, 2, '2026-03-25 21:15:00.000', NULL),
|
||||
(2, 3, 2, '2026-03-25 21:15:00.000', NULL);
|
||||
|
||||
INSERT INTO chat_message (
|
||||
id,
|
||||
conversation_id,
|
||||
seq,
|
||||
sender_user_id,
|
||||
role,
|
||||
content_type,
|
||||
content_text,
|
||||
content_json,
|
||||
media_file_key,
|
||||
media_duration_ms,
|
||||
client_msg_id,
|
||||
created_at
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1001,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
'Hi, did you finish homework?',
|
||||
JSON_OBJECT('lang', 'en'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c1-m1',
|
||||
'2026-03-25 10:00:05.000'
|
||||
),
|
||||
(
|
||||
1002,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
'Almost done, one question left.',
|
||||
JSON_OBJECT('lang', 'en'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c1-m2',
|
||||
'2026-03-25 10:00:12.000'
|
||||
),
|
||||
(
|
||||
1003,
|
||||
1,
|
||||
3,
|
||||
NULL,
|
||||
2,
|
||||
1,
|
||||
'I can help explain the last question.',
|
||||
JSON_OBJECT('tone', 'assistant'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c1-m3',
|
||||
'2026-03-25 10:00:20.000'
|
||||
),
|
||||
(
|
||||
1004,
|
||||
1,
|
||||
4,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
'Great, explain how to solve it.',
|
||||
JSON_OBJECT('lang', 'en'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c1-m4',
|
||||
'2026-03-25 10:00:30.000'
|
||||
),
|
||||
(
|
||||
2001,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
'Dinner at 7?',
|
||||
JSON_OBJECT('lang', 'en'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c2-m1',
|
||||
'2026-03-25 21:15:08.000'
|
||||
),
|
||||
(
|
||||
2002,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
1,
|
||||
2,
|
||||
NULL,
|
||||
JSON_OBJECT('note', 'voice message'),
|
||||
'audios/family-group-001.mp3',
|
||||
95000,
|
||||
'c2-m2',
|
||||
'2026-03-25 21:15:30.000'
|
||||
),
|
||||
(
|
||||
2003,
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
'Okay, we will wait in the living room.',
|
||||
JSON_OBJECT('lang', 'en'),
|
||||
NULL,
|
||||
NULL,
|
||||
'c2-m3',
|
||||
'2026-03-25 21:16:10.000'
|
||||
);
|
||||
4
mini-program/requirements.txt
Normal file
4
mini-program/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fastapi==0.115.11
|
||||
uvicorn[standard]==0.34.0
|
||||
pydantic-settings==2.8.1
|
||||
python-dotenv==1.0.1
|
||||
Reference in New Issue
Block a user