Commit 23e3ae0b authored by qionghong liu's avatar qionghong liu
Browse files

initial commit

parent 3bf09937
# .dockerignore for xlab_chatroom project
# 这个文件告诉Docker在构建镜像时忽略哪些文件和目录
# Docker相关文件
Dockerfile
.dockerignore
docker-compose.yml
nginx.conf
# Node.js相关
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn
.pnpm-debug.log*
# Next.js相关
.next/
out/
build/
dist/
# 环境变量文件 (敏感信息不应该打包进镜像)
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Git相关
.git
.gitignore
.gitattributes
# IDE和编辑器文件
.vscode/
.idea/
*.swp
*.swo
*~
# 操作系统文件
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# 日志文件
logs
*.log
# 测试相关
coverage/
.nyc_output/
.coverage/
junit.xml
# 临时文件
*.tmp
*.temp
.cache/
# 文档和说明文件 (可选,如果不需要在容器中可以忽略)
README.md
CHANGELOG.md
LICENSE
# 开发相关文件
.eslintrc*
.prettierrc*
.editorconfig
tsconfig.tsbuildinfo
# 数据目录 (如果有的话)
data/
# 备份文件
*.bak
*.backup
# Dockerfile for xlab_chatroom Next.js App
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
# Build application
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
[
{
"userId": 1,
"username": "111",
"email": "gracegrace100118@qq.com",
"passwordHash": "$2b$12$OpXpzChgWCaMzAS0IJIjme7ByXzOHyNMVs.wZu55h2d.gEEFjHgMm",
"createdAt": 1756289824103
}
]
\ No newline at end of file
services:
xlab-chatroom:
build: .
container_name: xlab_chatroom_app
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- JWT_SECRET=your_super_secret_jwt_key_here_change_in_production
volumes:
- ./data:/app/data
\ No newline at end of file
/** @type {import('next').NextConfig} */
const nextConfig = {
// Enable standalone output for Docker deployment
output: "standalone",
// Disable telemetry
experimental: {
// This is needed for standalone output
outputFileTracingRoot: undefined,
},
// Configure images for Docker environment
images: {
unoptimized: true,
},
// Environment variables
env: {
JWT_SECRET: process.env.JWT_SECRET,
},
};
module.exports = nextConfig;
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
events {
worker_connections 1024;
}
http {
# Upstream for xlab_chatroom app
upstream xlab-chatroom {
server xlab-chatroom:3000;
}
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
server {
listen 80;
server_name localhost;
# Security headers
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Main application routes
location / {
proxy_pass http://xlab-chatroom;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (for future SSE or WebSocket features)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# API routes optimization
location /api/ {
proxy_pass http://xlab-chatroom;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# API-specific settings
proxy_buffering off;
proxy_request_buffering off;
}
# Static files caching
location /_next/static/ {
proxy_pass http://xlab-chatroom;
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Cache-Status "STATIC";
}
# Images and other static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://xlab-chatroom;
expires 7d;
add_header Cache-Control "public";
add_header X-Cache-Status "ASSET";
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
......@@ -8,11 +8,16 @@
"name": "xlab_chatroom",
"version": "0.1.0",
"dependencies": {
"bcryptjs": "^3.0.2",
"jsonwebtoken": "^9.0.2",
"next": "15.4.6",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"swr": "^2.3.6"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
......@@ -590,6 +595,31 @@
"tslib": "^2.8.0"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.10",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/ms": "*",
"@types/node": "*"
}
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.10.tgz",
......@@ -620,6 +650,21 @@
"@types/react": "^19.0.0"
}
},
"node_modules/bcryptjs": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
"integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==",
"license": "BSD-3-Clause",
"bin": {
"bcrypt": "bin/bcrypt"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/caniuse-lite": {
"version": "1.0.30001733",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001733.tgz",
......@@ -698,6 +743,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
......@@ -708,6 +762,15 @@
"node": ">=8"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
......@@ -715,6 +778,97 @@
"license": "MIT",
"optional": true
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
......@@ -840,6 +994,26 @@
"react": "^19.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
......@@ -851,7 +1025,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"optional": true,
"bin": {
"semver": "bin/semver.js"
},
......@@ -944,6 +1117,19 @@
}
}
},
"node_modules/swr": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz",
"integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
......@@ -970,6 +1156,15 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/use-sync-external-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
}
}
}
......@@ -9,14 +9,20 @@
"lint": "next lint"
},
"dependencies": {
"bcryptjs": "^3.0.2",
"jsonwebtoken": "^9.0.2",
"next": "15.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"next": "15.4.6"
"swr": "^2.3.6"
},
"devDependencies": {
"typescript": "^5",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19"
"@types/react-dom": "^19",
"typescript": "^5"
}
}
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
\ No newline at end of file
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\ No newline at end of file
import { NextRequest, NextResponse } from 'next/server';
import { userStore } from '@/lib/dataStore';
import { verifyPassword, generateToken } from '@/lib/auth';
import { LoginArgs, AuthResponse } from '@/types/auth';
import { ApiResponse } from '@/types/api';
export async function POST(request: NextRequest) {
try {
const body: LoginArgs = await request.json();
const { username, password } = body;
// 验证输入
if (!username || !password) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名和密码不能为空',
});
}
// 查找用户
const user = userStore.findUserByUsername(username);
if (!user) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名或密码错误',
});
}
// 验证密码
const isPasswordValid = await verifyPassword(password, user.passwordHash);
if (!isPasswordValid) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名或密码错误',
});
}
// 生成 token
const token = generateToken({
userId: user.userId,
username: user.username,
});
const response: AuthResponse = {
user: {
userId: user.userId,
username: user.username,
email: user.email,
},
token,
};
return NextResponse.json<ApiResponse<AuthResponse>>({
code: 0,
data: response,
message: '登录成功',
});
} catch (error) {
console.error('登录失败:', error);
return NextResponse.json<ApiResponse>({
code: 1,
message: '服务器错误',
});
}
}
import { NextRequest, NextResponse } from 'next/server';
import { extractUserFromRequest } from '@/lib/auth';
import { userStore } from '@/lib/dataStore';
import { ApiResponse } from '@/types/api';
export async function GET(request: NextRequest) {
try {
const user = extractUserFromRequest(request);
if (!user) {
return NextResponse.json<ApiResponse>({
code: 401,
message: '未授权访问',
}, { status: 401 });
}
// 获取完整用户信息
const fullUser = userStore.findUserById(user.userId);
if (!fullUser) {
return NextResponse.json<ApiResponse>({
code: 404,
message: '用户不存在',
}, { status: 404 });
}
return NextResponse.json<ApiResponse>({
code: 200,
data: {
userId: fullUser.userId,
username: fullUser.username,
email: fullUser.email,
},
message: '获取用户信息成功',
});
} catch (error) {
console.error('获取用户信息失败:', error);
return NextResponse.json<ApiResponse>({
code: 500,
message: '服务器错误',
}, { status: 500 });
}
}
import { NextRequest, NextResponse } from 'next/server';
import { userStore } from '@/lib/dataStore';
import { hashPassword, generateToken } from '@/lib/auth';
import { RegisterArgs, AuthResponse } from '@/types/auth';
import { ApiResponse } from '@/types/api';
export async function POST(request: NextRequest) {
try {
const body: RegisterArgs = await request.json();
const { username, password, email } = body;
// 验证输入
if (!username || !password) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名和密码不能为空',
});
}
if (username.length < 3 || password.length < 6) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名至少3位,密码至少6位',
});
}
// 检查用户名是否已存在
if (userStore.isUsernameExists(username)) {
return NextResponse.json<ApiResponse>({
code: 1,
message: '用户名已存在',
});
}
// 创建用户
const passwordHash = await hashPassword(password);
const user = await userStore.createUser(username, email, passwordHash);
// 生成 token
const token = generateToken({
userId: user.userId,
username: user.username,
});
const response: AuthResponse = {
user: {
userId: user.userId,
username: user.username,
email: user.email,
},
token,
};
return NextResponse.json<ApiResponse<AuthResponse>>({
code: 0,
data: response,
message: '注册成功',
});
} catch (error) {
console.error('注册失败:', error);
return NextResponse.json<ApiResponse>({
code: 1,
message: '服务器错误',
});
}
}
import { NextResponse } from "next/server";
import { extractUserFromRequest } from "@/lib/auth";
import { MessageAddArgs } from "@/types/api";
import { Message } from "@/types";
import { getNextMessageId, addMessage } from "@/lib/dataStore";
export async function POST(request: Request) {
try {
// 验证用户身份
const user = extractUserFromRequest(request);
if (!user) {
return NextResponse.json({
code: 1,
message: "请先登录",
});
}
const body = await request.json();
const { roomId, content } = body;
console.log("收到发送消息请求:", { roomId, content, user: user.username });
if (!roomId || !content) {
return NextResponse.json({
code: 1,
message: "房间ID和消息内容不能为空",
});
}
// 创建新消息 - 使用认证用户的用户名
const newMessage: Message = {
messageId: getNextMessageId(),
roomId: roomId,
sender: user.username, // 使用认证用户的用户名
content: content.trim(),
time: Date.now(),
};
console.log("创建新消息:", newMessage);
// 添加消息到共享存储
addMessage(newMessage);
return NextResponse.json({
code: 0,
message: "消息发送成功",
});
} catch (error) {
console.error("发送消息失败:", error);
return NextResponse.json({
code: 1,
message: "发送消息失败",
});
}
}
import { NextResponse } from "next/server";
import { extractUserFromRequest } from "@/lib/auth";
import { RoomAddArgs, RoomAddRes } from "@/types/api";
import { Message } from "@/types";
import {
getNextRoomId,
getNextMessageId,
addRoom,
addMessage,
rooms,
} from "@/lib/dataStore";
export async function POST(request: Request) {
try {
// 验证用户身份
const user = extractUserFromRequest(request);
if (!user) {
return NextResponse.json({
code: 1,
message: "请先登录",
});
}
const body = await request.json();
const { roomName } = body;
console.log("创建房间请求:", {
user: user.username,
roomName,
currentRoomsCount: rooms.length,
});
if (!roomName) {
console.log("创建房间参数验证失败: roomName 为空");
return NextResponse.json({
code: 1,
message: "房间名称不能为空",
});
}
const newRoomId = getNextRoomId();
console.log("生成新房间ID:", newRoomId);
// 创建欢迎消息
const welcomeMessage: Message = {
messageId: getNextMessageId(),
roomId: newRoomId,
sender: "系统",
content: `欢迎来到${roomName}房间!`,
time: Date.now(),
};
console.log("创建欢迎消息:", welcomeMessage);
// 创建新房间 - 使用你原有的接口格式
const newRoom = {
roomId: newRoomId,
roomName: roomName.trim(),
lastMessage: welcomeMessage,
};
console.log("准备添加房间:", newRoom);
// 添加房间和欢迎消息
addRoom(newRoom);
addMessage(welcomeMessage);
console.log("房间和消息添加完成");
const response: RoomAddRes = { roomId: newRoomId };
console.log("创建房间成功:", response);
return NextResponse.json({
code: 0,
data: response,
});
} catch (error) {
console.error("创建房间API异常:", error);
return NextResponse.json({
code: 1,
message:
"创建房间失败: " +
(error instanceof Error ? error.message : String(error)),
});
}
}
import { NextResponse } from "next/server";
import { extractUserFromRequest } from "@/lib/auth";
import { RoomDeleteArgs } from "@/types/api";
import { rooms, removeRoom } from "@/lib/dataStore";
export async function POST(request: Request) {
try {
// 验证用户身份
const user = extractUserFromRequest(request);
if (!user) {
return NextResponse.json({
code: 1,
message: "请先登录",
});
}
const body = await request.json();
const { roomId } = body;
console.log("删除房间请求:", {
user: user.username,
roomId,
currentRoomsCount: rooms.length,
});
if (roomId === undefined || roomId === null) {
console.log("参数验证失败: roomId 为空");
return NextResponse.json({
code: 1,
message: "房间ID不能为空",
});
}
// 检查是否至少保留一个房间(提前检查)
if (rooms.length <= 1) {
console.log("房间数量不足,无法删除");
return NextResponse.json({
code: 1,
message: "至少需要保留一个房间",
});
}
// 确保 roomId 是数字类型
const roomIdToDelete = Number(roomId);
if (isNaN(roomIdToDelete)) {
console.log("房间ID格式错误:", roomId);
return NextResponse.json({
code: 1,
message: "房间ID格式错误",
});
}
// 检查房间是否存在
const roomExists = rooms.some((room) => room.roomId === roomIdToDelete);
console.log("房间存在性检查:", {
roomIdToDelete,
exists: roomExists,
});
if (!roomExists) {
console.log(`房间 ${roomIdToDelete} 不存在,视为已删除`);
return NextResponse.json({
code: 0,
message: "房间已删除或不存在",
});
}
console.log(`开始删除房间 ${roomIdToDelete}`);
const success = removeRoom(roomIdToDelete);
console.log("删除结果:", {
success,
remainingRoomsCount: rooms.length,
});
if (!success) {
console.log("删除房间操作失败");
return NextResponse.json({
code: 1,
message: "删除房间失败",
});
}
console.log(`房间 ${roomIdToDelete} 删除成功`);
return NextResponse.json({
code: 0,
message: "删除成功",
});
} catch (error) {
console.error("删除房间API异常:", error);
return NextResponse.json({
code: 1,
message:
"删除房间失败: " +
(error instanceof Error ? error.message : String(error)),
});
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment