首次提交
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
data
|
||||
.env
|
||||
@@ -0,0 +1,22 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install production dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm install --production
|
||||
|
||||
# Copy server code
|
||||
COPY server.js ./
|
||||
|
||||
# Copy the pre-built frontend files
|
||||
COPY dist ./dist
|
||||
|
||||
# Create a data directory to store JSON files locally
|
||||
RUN mkdir -p data
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3001
|
||||
|
||||
# Start the server
|
||||
CMD ["npm", "start"]
|
||||
@@ -0,0 +1,43 @@
|
||||
# ClassHomeworkBoard - Docker 部署版本
|
||||
|
||||
这是一个专门为服务器部署设计的 Node.js 后端程序,它包含了 Express 服务器和构建好的前端静态文件。此版本去除了对 Windows 特有功能(如通过 `user32.dll` 抓取 PPT 和 WPS 窗口)的依赖,完美适配 Linux 和 Docker 环境。
|
||||
|
||||
## 包含内容
|
||||
|
||||
- `server.js`: 提供 `/api/local-file/*` 本地存储接口的 Express 后端。
|
||||
- `dist/`: 已经构建好的前端静态资源文件。
|
||||
- `Dockerfile`: 用于构建 Docker 镜像的配置文件。
|
||||
- `package.json`: 服务器的 npm 依赖配置文件。
|
||||
|
||||
## 如何使用 Docker 部署
|
||||
|
||||
1. **构建 Docker 镜像**
|
||||
在 `docker-server` 目录下运行以下命令:
|
||||
```bash
|
||||
docker build -t classhomeworkboard .
|
||||
```
|
||||
|
||||
2. **运行 Docker 容器**
|
||||
```bash
|
||||
docker run -d -p 3001:3001 -v chwb_data:/app/data --name chwb classhomeworkboard
|
||||
```
|
||||
> 这里的 `-v chwb_data:/app/data` 表示将容器内的 `/app/data`(存储 JSON 数据的文件夹)挂载到外部卷,防止数据随容器销毁而丢失。
|
||||
|
||||
3. **访问服务**
|
||||
打开浏览器,访问 `http://<服务器IP>:3001` 即可使用。
|
||||
|
||||
## 如果你需要更新前端代码
|
||||
|
||||
如果你在主项目中修改了 Vue 前端代码:
|
||||
1. 在项目根目录运行 `npm run build`(或 `pnpm run build`)重新打包前端。
|
||||
2. 将打包后生成的 `dist` 文件夹覆盖到本目录的 `dist` 文件夹。
|
||||
3. 重新执行 Docker 镜像的构建与部署步骤。
|
||||
|
||||
## 本地(不使用 Docker)直接运行
|
||||
|
||||
如果你想直接在服务器的 Node 环境中运行:
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
默认会在 3001 端口启动。
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "classhomeworkboard-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Dockerized server for ClassHomeworkBoard",
|
||||
"main": "server.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3001;
|
||||
|
||||
// Middlewares
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
|
||||
// Data Directory for JSON files
|
||||
const dataDir = path.resolve(__dirname, 'data');
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
// =======================
|
||||
// API Routes
|
||||
// =======================
|
||||
|
||||
// Mock PPT APIs (Windows GUI features are not supported on Linux/Docker)
|
||||
app.get('/api/ppt/check', (req, res) => {
|
||||
res.json({ success: true, hasPpt: false, ppts: [] });
|
||||
});
|
||||
|
||||
app.post('/api/ppt/control', (req, res) => {
|
||||
res.json({ success: false, error: 'Not supported on server environment' });
|
||||
});
|
||||
|
||||
// Local File Storage APIs
|
||||
app.post('/api/local-file/save', (req, res) => {
|
||||
try {
|
||||
const { key, data } = req.body;
|
||||
if (!key || !data) throw new Error('Missing key or data');
|
||||
|
||||
// Sanitize key
|
||||
const safeKey = String(key).replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const filePath = path.join(dataDir, `${safeKey}.json`);
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/local-file/load', (req, res) => {
|
||||
try {
|
||||
const key = req.query.key;
|
||||
if (!key) {
|
||||
return res.status(400).json({ success: false, error: 'Missing key' });
|
||||
}
|
||||
|
||||
const safeKey = String(key).replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
const filePath = path.join(dataDir, `${safeKey}.json`);
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
const data = fs.readFileSync(filePath, 'utf-8');
|
||||
res.json({ success: true, data: JSON.parse(data) });
|
||||
} else {
|
||||
res.status(404).json({ success: false, error: 'Not found' });
|
||||
}
|
||||
} catch (e) {
|
||||
res.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/local-file/keys', (req, res) => {
|
||||
try {
|
||||
const files = fs.readdirSync(dataDir);
|
||||
const keys = files
|
||||
.filter(f => f.endsWith('.json'))
|
||||
.map(f => f.replace('.json', ''));
|
||||
res.json({ success: true, keys });
|
||||
} catch (e) {
|
||||
res.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// =======================
|
||||
// Static Files & SPA
|
||||
// =======================
|
||||
const staticDir = path.resolve(__dirname, 'dist');
|
||||
|
||||
if (fs.existsSync(staticDir)) {
|
||||
// Serve static files
|
||||
app.use(express.static(staticDir, {
|
||||
setHeaders: (res, path, stat) => {
|
||||
// Prevent caching for sw.js and index.html
|
||||
if (path.endsWith('sw.js') || path.endsWith('index.html')) {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
} else {
|
||||
// Cache other static assets
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Catch-all route for SPA fallback
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(staticDir, 'index.html'));
|
||||
});
|
||||
} else {
|
||||
// Fallback if dist doesn't exist yet
|
||||
app.get('*', (req, res) => {
|
||||
res.send('<h3>Frontend not found!</h3><p>Please build the Vue frontend (<code>npm run build</code>) and copy the <code>dist</code> folder into the <code>docker-server</code> directory before running.</p>');
|
||||
});
|
||||
}
|
||||
|
||||
// Start Server
|
||||
app.listen(port, () => {
|
||||
console.log(`ClassHomeworkBoard Server running on port ${port}`);
|
||||
console.log(`Data directory: ${dataDir}`);
|
||||
console.log(`Static files directory: ${staticDir}`);
|
||||
});
|
||||
Reference in New Issue
Block a user