fix:bug
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
PORT=3000
|
||||||
|
WECHAT_UPSTREAM_BASE_URL=http://your-wechat-server-host:port
|
||||||
|
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY tsconfig.json ./tsconfig.json
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS runtime
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
COPY --from=build /app/node_modules ./node_modules
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
COPY .env.example ./ ./
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [\"node\", \"dist/server.js\"]
|
||||||
|
|
||||||
25
package.json
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "wechat-admin-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "WeChat backend admin service based on 8069 swagger with login support",
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"start": "node dist/server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"morgan": "^1.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/morgan": "^1.9.9",
|
||||||
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"typescript": "^5.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
run-docker.sh
Executable file
31
run-docker.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE_NAME="wechat-admin-backend"
|
||||||
|
CONTAINER_NAME="wechat-admin-backend"
|
||||||
|
PORT="${PORT:-3000}"
|
||||||
|
|
||||||
|
echo "Building Docker image: ${IMAGE_NAME}..."
|
||||||
|
docker build -t "${IMAGE_NAME}" .
|
||||||
|
|
||||||
|
echo "Stopping and removing existing container (if any)..."
|
||||||
|
if [ "$(docker ps -aq -f name=${CONTAINER_NAME})" ]; then
|
||||||
|
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENV_FILE=".env"
|
||||||
|
if [ ! -f "${ENV_FILE}" ]; then
|
||||||
|
echo "Env file ${ENV_FILE} not found, copying from .env.example ..."
|
||||||
|
cp .env.example "${ENV_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Running container ${CONTAINER_NAME} on port ${PORT}..."
|
||||||
|
docker run -d \
|
||||||
|
--name "${CONTAINER_NAME}" \
|
||||||
|
--env-file "${ENV_FILE}" \
|
||||||
|
-p "${PORT}:3000" \
|
||||||
|
"${IMAGE_NAME}"
|
||||||
|
|
||||||
|
echo "Container started. Health check: curl http://localhost:${PORT}/health"
|
||||||
|
|
||||||
28
src/server.ts
Normal file
28
src/server.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
import { authRouter } from './wechatAuth';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(morgan('dev'));
|
||||||
|
|
||||||
|
app.get('/health', (_req, res) => {
|
||||||
|
res.json({ status: 'ok' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use('/api/auth', authRouter);
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`WeChat admin backend listening on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
100
src/wechatAuth.ts
Normal file
100
src/wechatAuth.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const BASE_URL = process.env.WECHAT_UPSTREAM_BASE_URL || 'http://localhost:8080';
|
||||||
|
|
||||||
|
router.post('/qrcode', async (req, res) => {
|
||||||
|
const { key, proxy, ipadOrMac, check } = req.body as {
|
||||||
|
key: string;
|
||||||
|
proxy?: string;
|
||||||
|
ipadOrMac?: string;
|
||||||
|
check?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return res.status(400).json({ error: 'key is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${BASE_URL}/login/GetLoginQrCodeNewDirect`,
|
||||||
|
{
|
||||||
|
Proxy: proxy ?? '',
|
||||||
|
IpadOrmac: ipadOrMac ?? '',
|
||||||
|
Check: check ?? false,
|
||||||
|
},
|
||||||
|
{ params: { key } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error fetching login qrcode', err);
|
||||||
|
return res.status(502).json({ error: 'failed_to_get_qrcode' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/status', async (req, res) => {
|
||||||
|
const { key } = req.query as { key?: string };
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return res.status(400).json({ error: 'key is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${BASE_URL}/login/GetLoginStatus`, {
|
||||||
|
params: { key },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error getting login status', err);
|
||||||
|
return res.status(502).json({ error: 'failed_to_get_status' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/scan-status', async (req, res) => {
|
||||||
|
const { key } = req.query as { key?: string };
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return res.status(400).json({ error: 'key is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${BASE_URL}/login/CheckLoginStatus`, {
|
||||||
|
params: { key },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error checking scan status', err);
|
||||||
|
return res.status(502).json({ error: 'failed_to_check_scan_status' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/logout', async (req, res) => {
|
||||||
|
const { key } = req.body as { key?: string };
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return res.status(400).json({ error: 'key is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${BASE_URL}/login/LogOut`, {
|
||||||
|
params: { key },
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error logging out', err);
|
||||||
|
return res.status(502).json({ error: 'failed_to_logout' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const authRouter = router;
|
||||||
|
|
||||||
1
swagger.json
Normal file
1
swagger.json
Normal file
File diff suppressed because one or more lines are too long
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user