feat: implement custom node.js sync server and secure extension client with login/register UI
This commit is contained in:
17
server/Dockerfile
Normal file
17
server/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM node:18-slim
|
||||
|
||||
# 安装基本运行环境(Debian slim 版通常有预编译的 sqlite3 二进制包,速度极快)
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
# 强制安装不从源码编译的 sqlite3 以节省时间
|
||||
RUN npm install express cors sqlite3 --build-from-source=false
|
||||
|
||||
# 复制服务端源码
|
||||
COPY server.js .
|
||||
|
||||
# 暴露 3000 端口
|
||||
EXPOSE 3000
|
||||
|
||||
# 启动服务
|
||||
CMD ["node", "server.js"]
|
||||
12
server/docker-compose.yml
Normal file
12
server/docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
quickpurge-sync:
|
||||
build: .
|
||||
container_name: quickpurge-sync
|
||||
restart: always
|
||||
ports:
|
||||
- "3001:3001"
|
||||
volumes:
|
||||
# 将数据目录整体挂载,防止被 Docker 误识别为文件夹
|
||||
- ./data:/app/data
|
||||
14
server/package.json
Normal file
14
server/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "quickpurge-sync",
|
||||
"version": "1.0.0",
|
||||
"description": "QuickPurge Sync Server",
|
||||
"main": "server.js",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
}
|
||||
}
|
||||
129
server/server.js
Normal file
129
server/server.js
Normal file
@@ -0,0 +1,129 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const INVITE_CODE = 'Moka'; // 注册需校验此邀请码
|
||||
|
||||
// 启用中间件
|
||||
app.use(cors()); // 允许 Chrome 扩展跨域请求
|
||||
app.use(express.json()); // 解析 application/json
|
||||
|
||||
// 初始化 SQLite 数据库 (建议使用绝对路径并指向挂载的目录)
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, 'data', 'data.db');
|
||||
const dbDir = path.dirname(dbPath);
|
||||
|
||||
// 确保目录存在
|
||||
const fs = require('fs');
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('Could not connect to database', err);
|
||||
} else {
|
||||
console.log('Connected to sqlite3 database at:', dbPath);
|
||||
// 初始化用户表
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
rules TEXT DEFAULT '[]',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
// === 接口 1: 注册 ===
|
||||
app.post('/api/register', (req, res) => {
|
||||
const { username, password, inviteCode } = req.body;
|
||||
|
||||
if (inviteCode !== INVITE_CODE) {
|
||||
return res.json({ code: 1, msg: '注册码错误' });
|
||||
}
|
||||
if (!username || !password) {
|
||||
return res.json({ code: 1, msg: '用户名和密码不能为空' });
|
||||
}
|
||||
|
||||
// 写入数据库 (直接使用 db.run 更简洁)
|
||||
db.run('INSERT INTO users (username, password) VALUES (?, ?)', [username, password], function (err) {
|
||||
if (err) {
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
return res.json({ code: 1, msg: '用户名太火爆,已被注册' });
|
||||
}
|
||||
console.error('Register error:', err);
|
||||
return res.json({ code: 1, msg: '服务器内部错误' });
|
||||
}
|
||||
res.json({ code: 0, msg: '注册成功' });
|
||||
});
|
||||
});
|
||||
|
||||
// === 接口 2: 登录 ===
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.json({ code: 1, msg: '请输入用户名和密码' });
|
||||
}
|
||||
|
||||
db.get('SELECT * FROM users WHERE username = ? AND password = ?', [username, password], (err, row) => {
|
||||
if (err) {
|
||||
console.error('Login error:', err);
|
||||
return res.json({ code: 1, msg: '服务器内部错误' });
|
||||
}
|
||||
if (!row) {
|
||||
return res.json({ code: 1, msg: '账号或密码错误' });
|
||||
}
|
||||
|
||||
let parsedRules = [];
|
||||
try {
|
||||
parsedRules = row.rules ? JSON.parse(row.rules) : [];
|
||||
} catch (e) {
|
||||
console.error('Failed to parse rules JSON', e);
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 0,
|
||||
msg: '登录成功',
|
||||
data: { rules: parsedRules }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// === 接口 3: 同步配置数据 (保存更新) ===
|
||||
app.post('/api/sync', (req, res) => {
|
||||
const { username, rules } = req.body;
|
||||
|
||||
if (!username || !Array.isArray(rules)) {
|
||||
return res.json({ code: 1, msg: '参数不合法' });
|
||||
}
|
||||
|
||||
const rulesJsonStr = JSON.stringify(rules);
|
||||
|
||||
// 覆盖更新该用户的 rules 列
|
||||
// (简化起见,此处信任前端传来的 username。真实生产在这一步应当检验 token/session,不过做个人小工具够用即可防君子)
|
||||
db.run('UPDATE users SET rules = ? WHERE username = ?', [rulesJsonStr, username], function (err) {
|
||||
if (err) {
|
||||
console.error('Sync error:', err);
|
||||
return res.json({ code: 1, msg: '同步失败:服务器内部错误' });
|
||||
}
|
||||
if (this.changes === 0) {
|
||||
return res.json({ code: 1, msg: '同步失败:未找到该用户' });
|
||||
}
|
||||
res.json({ code: 0, msg: '同步成功' });
|
||||
});
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`Server is running on http://0.0.0.0:${PORT}`);
|
||||
console.log(`API endpoints available:`);
|
||||
console.log(`- POST /api/register`);
|
||||
console.log(`- POST /api/login`);
|
||||
console.log(`- POST /api/sync`);
|
||||
});
|
||||
Reference in New Issue
Block a user