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