添加后台代理代码
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
module.exports = {
|
||||
// 服务端口
|
||||
port: 3000,
|
||||
|
||||
// 是否开启日志输出
|
||||
enableLog: false,
|
||||
|
||||
// 是否开启头像上传
|
||||
enableAvatarUpload: false,
|
||||
|
||||
// 小程序配置
|
||||
miniProgram: {
|
||||
appId: 'wx51ab9a04fac56760',
|
||||
appSecret: 'd326aaf93eb4d106e35592667ef022f5'
|
||||
},
|
||||
|
||||
// 公众号配置 (用于获取永久头像)
|
||||
officialAccount: {
|
||||
appId: 'wx7a1c6f324182bc83',
|
||||
appSecret: 'a90ba94e3a2dca8d09656dcc364e1df0', // 请在此处填入您的公众号 Secret
|
||||
|
||||
// 远程配置 Key:用于动态获取网页授权域名
|
||||
// 在远程配置 json 中配置此 key 对应的值为您的测试/正式域名
|
||||
redirectDomainKey: 'minipro_api_url'
|
||||
},
|
||||
|
||||
// 远程配置
|
||||
remoteConfig: {
|
||||
// 方式一:直接使用 Gitee/GitHub 的 Raw 文件地址 (推荐,最简单稳定)
|
||||
// 示例:https://gitee.com/用户名/仓库名/raw/分支名/config.json
|
||||
configUrl: 'https://gitee.com/daoqijuyou/config/raw/master/update_jsonv2.txt',
|
||||
|
||||
interval: 30 * 1000, // 更新间隔,单位毫秒
|
||||
agentid: "veRa0qrBf0df2K1G4de2tgfmVxB2jxpv",
|
||||
gameid: "G2hw0ubng0zcoI0r4mx3H2yr4GejidwO",
|
||||
channelid: "FtJf073aa0d6rI1xD8J1Y42fINTm0ziK",
|
||||
marketid: 3
|
||||
}
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
http://8.139.255.236:56073?data=%7B%22app%22%3A%22youle%22%2C%22route%22%3A%22agent%22%2C%22rpc%22%3A%22bind_player_wechat%22%2C%22data%22%3A%7B%22agentid%22%3A%22veRa0qrBf0df2K1G4de2tgfmVxB2jxpv%22%2C%22channelid%22%3A%22FtJf073aa0d6rI1xD8J1Y42fINTm0ziK%22%2C%22marketid%22%3A3%2C%22gameid%22%3A%22G2hw0ubng0zcoI0r4mx3H2yr4GejidwO%22%2C%22openid%22%3A%22onJdG10JeHtS0Dbz8FtdVv7aeVBY%22%2C%22unionid%22%3A%22oLVKis6bj3_l8qspMybG60KV2GN5%22%2C%22nickname%22%3A%22%E5%85%AB%E4%B9%9D444%22%2C%22avatar%22%3A%22http%3A%2F%2F6bae8d8b.r29.cpolar.top%2Fuploads%2F1766995380449_yl4r842it.jpg%22%2C%22sex%22%3A0%2C%22province%22%3A%22%22%2C%22city%22%3A%22%22%2C%22telphone%22%3A%2215797777777%22%7D%7D
|
||||
@@ -1,510 +0,0 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const axios = require('axios');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
// const { S3Client } = require('@aws-sdk/client-s3'); // Removed S3
|
||||
// const { Upload } = require('@aws-sdk/lib-storage'); // Removed S3
|
||||
const config = require('./config/index');
|
||||
const remoteConfig = require('./services/remoteConfig');
|
||||
const logger = require('./utils/logger');
|
||||
|
||||
const app = express();
|
||||
const port = config.port;
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// 请求日志中间件
|
||||
app.use((req, res, next) => {
|
||||
// 忽略静态资源请求日志,避免刷屏
|
||||
if (!req.url.startsWith('/uploads') && !req.url.startsWith('/public')) {
|
||||
logger.info('HTTP', `${req.method} ${req.url}`);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// 配置静态文件服务,用于微信域名校验文件
|
||||
// 请将下载的 MP_verify_xxx.txt 文件放入 server/public 目录
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
// 配置上传文件的静态服务
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
|
||||
// ==================================================================
|
||||
// 公众号配置区域 (用于获取永久头像)
|
||||
// ==================================================================
|
||||
// 已移至 config.js
|
||||
|
||||
// Step 1: 小程序跳转到这里,服务器重定向到微信授权页
|
||||
app.get('/auth/oa/login', (req, res) => {
|
||||
// 1. 尝试从远程配置获取动态域名
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
const key = config.officialAccount.redirectDomainKey || 'auth_redirect_domain';
|
||||
|
||||
let currentDomain = remoteConfig.getParaValue(key, agentid, gameid, channelid, marketid);
|
||||
|
||||
if (!currentDomain) {
|
||||
return res.status(500).send('Configuration Error: No redirect domain available in remote config.');
|
||||
}
|
||||
|
||||
// 3. 格式化域名 (确保有 https且无末尾斜杠)
|
||||
currentDomain = currentDomain.trim().replace(/\/$/, '');
|
||||
if (!currentDomain.startsWith('http')) {
|
||||
currentDomain = `https://${currentDomain}`;
|
||||
}
|
||||
|
||||
// 这里的 redirect_uri 必须是 encodeURIComponent 后的完整 URL
|
||||
const redirectUri = encodeURIComponent(`${currentDomain}/auth/oa/callback`);
|
||||
const scope = 'snsapi_userinfo'; // 获取头像必须用 userinfo 作用域
|
||||
const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${config.officialAccount.appId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`;
|
||||
|
||||
// console.log('Redirecting to WeChat Auth:', url); // 打印跳转链接,方便调试
|
||||
logger.info('Auth', 'Redirecting to WeChat Auth:', url);
|
||||
res.redirect(url);
|
||||
});
|
||||
|
||||
// Step 2: 微信回调,获取 code -> access_token -> userinfo -> 返回 HTML 给 web-view
|
||||
app.get('/auth/oa/callback', async (req, res) => {
|
||||
const code = req.query.code;
|
||||
// console.log('WeChat Callback Code:', code); // 打印回调 Code
|
||||
logger.info('Auth', 'WeChat Callback Code:', code);
|
||||
|
||||
if (!code) return res.send('Auth failed, no code');
|
||||
|
||||
try {
|
||||
// 1. 获取 access_token
|
||||
const tokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${config.officialAccount.appId}&secret=${config.officialAccount.appSecret}&code=${code}&grant_type=authorization_code`;
|
||||
// console.log('Requesting Token URL:', tokenUrl); // 打印 Token 请求链接 (注意:生产环境不要打印 Secret)
|
||||
logger.debug('Auth', 'Requesting Token URL (Secret Hidden)');
|
||||
|
||||
const tokenRes = await axios.get(tokenUrl);
|
||||
// console.log('Token Response:', tokenRes.data); // 打印 Token 响应结果
|
||||
logger.debug('Auth', 'Token Response:', tokenRes.data);
|
||||
|
||||
if (tokenRes.data.errcode) {
|
||||
return res.send('Token Error: ' + JSON.stringify(tokenRes.data));
|
||||
}
|
||||
|
||||
const { access_token, openid } = tokenRes.data;
|
||||
|
||||
// 2. 获取用户信息 (包含永久头像 headimgurl)
|
||||
const infoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`;
|
||||
const infoRes = await axios.get(infoUrl);
|
||||
// console.log('User Info Response:', infoRes.data); // 打印用户信息响应
|
||||
logger.success('Auth', 'User Info Retrieved:', infoRes.data.nickname);
|
||||
|
||||
const userInfo = infoRes.data; // { headimgurl, nickname, unionid, ... }
|
||||
|
||||
// 3. 返回一个 HTML,利用 JSSDK 把数据传回小程序
|
||||
// 注意:wx.miniProgram.postMessage 只有在页面后退、销毁、分享时才会触发小程序的 bindmessage
|
||||
// 所以这里我们发送数据后,立即调用 navigateBack
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>正在同步...</title>
|
||||
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h3 style="text-align:center; margin-top: 50px;">正在同步微信头像...</h3>
|
||||
<script>
|
||||
function sendAndBack() {
|
||||
// 发送消息给小程序
|
||||
wx.miniProgram.postMessage({
|
||||
data: ${JSON.stringify(userInfo)}
|
||||
});
|
||||
// 跳回小程序上一页
|
||||
wx.miniProgram.navigateBack();
|
||||
}
|
||||
|
||||
if (window.WeixinJSBridge) {
|
||||
sendAndBack();
|
||||
} else {
|
||||
document.addEventListener('WeixinJSBridgeReady', sendAndBack, false);
|
||||
}
|
||||
// 保底策略
|
||||
setTimeout(sendAndBack, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
res.send(html);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.send('Auth error: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
// 获取 AccessToken (简单实现,实际生产环境需要缓存 AccessToken)
|
||||
async function getAccessToken() {
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.miniProgram.appId}&secret=${config.miniProgram.appSecret}`;
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
if (response.data.errcode) {
|
||||
throw new Error(`获取 AccessToken 失败: ${response.data.errmsg}`);
|
||||
}
|
||||
return response.data.access_token;
|
||||
} catch (error) {
|
||||
console.error('Get AccessToken Error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 登录接口:换取 OpenID 和 UnionID
|
||||
app.post('/api/login', async (req, res) => {
|
||||
const { code } = req.body;
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: 'Code is required' });
|
||||
}
|
||||
|
||||
// 如果用户没有配置 AppID,返回模拟数据
|
||||
if (config.miniProgram.appId === 'YOUR_APP_ID') {
|
||||
// console.log('未配置 AppID,返回模拟登录数据');
|
||||
return res.json({
|
||||
openid: 'mock_openid_' + Date.now(),
|
||||
unionid: 'mock_unionid_' + Date.now(),
|
||||
session_key: 'mock_session_key'
|
||||
});
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${config.miniProgram.appId}&secret=${config.miniProgram.appSecret}&js_code=${code}&grant_type=authorization_code`;
|
||||
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
if (response.data.errcode) {
|
||||
return res.status(500).json({ error: response.data.errmsg });
|
||||
}
|
||||
// 返回 openid, unionid (如果有), session_key
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 获取手机号接口
|
||||
app.post('/api/getPhoneNumber', async (req, res) => {
|
||||
const { code } = req.body;
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: 'Code is required' });
|
||||
}
|
||||
|
||||
// 如果用户没有配置 AppID,返回模拟数据
|
||||
if (config.miniProgram.appId === 'YOUR_APP_ID') {
|
||||
// console.log('未配置 AppID,返回模拟手机号');
|
||||
return res.json({
|
||||
phoneNumber: '13800138000',
|
||||
purePhoneNumber: '13800138000',
|
||||
countryCode: '86',
|
||||
watermark: { timestamp: Date.now(), appid: config.miniProgram.appId }
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const accessToken = await getAccessToken();
|
||||
const url = `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${accessToken}`;
|
||||
|
||||
const response = await axios.post(url, { code });
|
||||
|
||||
if (response.data.errcode === 0) {
|
||||
// 成功获取
|
||||
res.json(response.data.phone_info);
|
||||
} else {
|
||||
res.status(500).json({ error: response.data.errmsg, errcode: response.data.errcode });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get Phone Number Error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 缓存 game_server_http
|
||||
let cachedGameServerHttp = null;
|
||||
|
||||
// 监听远程配置更新
|
||||
remoteConfig.onUpdate(() => {
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
const newValue = remoteConfig.getParaValue('game_server_http', agentid, gameid, channelid, marketid);
|
||||
|
||||
if (newValue) {
|
||||
cachedGameServerHttp = newValue;
|
||||
// logger.info('Config', 'Updated cached game_server_http:', cachedGameServerHttp);
|
||||
} else {
|
||||
logger.warn('Config', 'game_server_http not found in new config');
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 文件上传接口 (本地存储)
|
||||
// 确保存储目录存在
|
||||
const uploadDir = path.join(__dirname, 'uploads');
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, uploadDir)
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
const fileExtension = path.extname(file.originalname) || '.jpg';
|
||||
const fileName = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}${fileExtension}`;
|
||||
cb(null, fileName)
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (config.enableAvatarUpload === false) {
|
||||
// 如果配置关闭了上传,拒绝文件
|
||||
return cb(null, false);
|
||||
}
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/upload', upload.single('file'), (req, res) => {
|
||||
if (config.enableAvatarUpload === false) {
|
||||
return res.status(403).json({ error: 'Avatar upload is disabled' });
|
||||
}
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
try {
|
||||
// 构造访问链接
|
||||
const protocol = req.protocol;
|
||||
const host = req.get('host');
|
||||
const fileUrl = `${protocol}://${host}/uploads/${req.file.filename}`;
|
||||
|
||||
logger.success('Upload', 'File uploaded successfully:', fileUrl);
|
||||
res.json({ url: fileUrl });
|
||||
} catch (error) {
|
||||
logger.error('Upload', 'Upload Error:', error);
|
||||
res.status(500).json({ error: 'Upload failed: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 提交用户信息接口 (真实保存到游戏服务器)
|
||||
app.post('/api/saveUserInfo', async (req, res) => {
|
||||
const userInfo = req.body;
|
||||
logger.divider('Save User Info');
|
||||
logger.info('API', 'Received User Info:', userInfo.nickName);
|
||||
|
||||
try {
|
||||
// 1. 使用缓存的 game_server_http
|
||||
if (!cachedGameServerHttp) {
|
||||
// 如果缓存为空(例如启动时首次获取尚未完成),尝试手动获取一次
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
cachedGameServerHttp = remoteConfig.getParaValue('game_server_http', agentid, gameid, channelid, marketid);
|
||||
}
|
||||
|
||||
if (!cachedGameServerHttp) {
|
||||
logger.error('Config', 'game_server_http not found');
|
||||
return res.status(500).json({ error: 'Configuration error: game_server_http not found' });
|
||||
}
|
||||
|
||||
// 确保 URL 格式正确
|
||||
const targetUrl = cachedGameServerHttp.startsWith('http') ? cachedGameServerHttp : `http://${cachedGameServerHttp}`;
|
||||
|
||||
// 2. 构造请求数据
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
const payload = {
|
||||
"app": "youle",
|
||||
"route": "agent",
|
||||
"rpc": "bind_player_wechat",
|
||||
"data": {
|
||||
"agentid": agentid,
|
||||
"channelid": channelid,
|
||||
"marketid": marketid,
|
||||
"gameid": gameid,
|
||||
"openid": userInfo.openid,
|
||||
"unionid": userInfo.unionid,
|
||||
"nickname": userInfo.nickName,
|
||||
"avatar": userInfo.avatarUrl,
|
||||
"sex": userInfo.gender,
|
||||
"province": userInfo.province,
|
||||
"city": userInfo.city,
|
||||
"telphone": userInfo.phoneNumber
|
||||
}
|
||||
};
|
||||
|
||||
// --- Debug: 生成可粘贴到浏览器的 GET 链接 ---
|
||||
// try {
|
||||
// const debugQuery = `data=${encodeURIComponent(JSON.stringify(payload))}`;
|
||||
// const debugUrl = `${targetUrl}?${debugQuery}`;
|
||||
// console.log('\n[Debug] Request URL for Browser:');
|
||||
// console.log(debugUrl);
|
||||
// console.log('');
|
||||
// } catch (e) {
|
||||
// console.error('[Debug] Failed to generate debug URL:', e);
|
||||
// }
|
||||
// -------------------------------------------
|
||||
|
||||
// 3. 发送请求
|
||||
const bodyData = 'data=' + JSON.stringify(payload);
|
||||
|
||||
logger.info('GameServer', 'Sending bind_player_wechat request...');
|
||||
logger.debug('GameServer', 'Payload:', payload);
|
||||
|
||||
const response = await axios.post(targetUrl, bodyData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
});
|
||||
logger.info('GameServer', 'Response received');
|
||||
logger.debug('GameServer', 'Response Data:', response.data);
|
||||
|
||||
const gameResult = response.data?.data?.result;
|
||||
const isSuccess = gameResult !== -1;
|
||||
res.json({ success: isSuccess, message: response.data?.data?.msg, data: response.data });
|
||||
|
||||
} catch (error) {
|
||||
logger.error('API', 'Save User Info Error:', error.message);
|
||||
res.status(500).json({ success: false, message: '用户信息保存失败: ' + error.message, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 玩家登录接口
|
||||
app.post('/api/playerLogin', async (req, res) => {
|
||||
const userInfo = req.body;
|
||||
logger.divider('Player Login');
|
||||
logger.info('API', 'Received Login Request:', userInfo.nickName);
|
||||
|
||||
try {
|
||||
if (!cachedGameServerHttp) {
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
cachedGameServerHttp = remoteConfig.getParaValue('game_server_http', agentid, gameid, channelid, marketid);
|
||||
}
|
||||
|
||||
if (!cachedGameServerHttp) {
|
||||
return res.status(500).json({ error: 'Configuration error: game_server_http not found' });
|
||||
}
|
||||
|
||||
const targetUrl = cachedGameServerHttp.startsWith('http') ? cachedGameServerHttp : `http://${cachedGameServerHttp}`;
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
|
||||
const payload = {
|
||||
"app": "youle",
|
||||
"route": "agent",
|
||||
"rpc": "player_login",
|
||||
"data": {
|
||||
"agentid": agentid,
|
||||
"channelid": channelid,
|
||||
"marketid": marketid,
|
||||
"gameid": gameid,
|
||||
"openid": userInfo.openid,
|
||||
"unionid": userInfo.unionid,
|
||||
"nickname": userInfo.nickName,
|
||||
"avatar": userInfo.avatarUrl,
|
||||
"sex": userInfo.gender,
|
||||
"province": userInfo.province,
|
||||
"city": userInfo.city,
|
||||
"version": userInfo.version,
|
||||
"telphone": userInfo.phoneNumber,
|
||||
"smmcode": userInfo.verificationCode,
|
||||
"telphoneAuto": userInfo.telphoneAuto,
|
||||
"playerid": userInfo.playerid,
|
||||
}
|
||||
};
|
||||
|
||||
// --- Debug: 生成可粘贴到浏览器的 GET 链接 ---
|
||||
// try {
|
||||
// const debugQuery = `data=${encodeURIComponent(JSON.stringify(payload))}`;
|
||||
// const debugUrl = `${targetUrl}?${debugQuery}`;
|
||||
// console.log('\n[Debug] Request URL for Browser:');
|
||||
// console.log(debugUrl);
|
||||
// console.log('');
|
||||
// } catch (e) {
|
||||
// console.error('[Debug] Failed to generate debug URL:', e);
|
||||
// }
|
||||
// -------------------------------------------
|
||||
|
||||
const bodyData = 'data=' + JSON.stringify(payload);
|
||||
logger.info('GameServer', 'Sending player_login request...');
|
||||
logger.debug('GameServer', 'Payload:', payload);
|
||||
|
||||
const response = await axios.post(targetUrl, bodyData, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
});
|
||||
logger.info('GameServer', 'Login Response received');
|
||||
logger.debug('GameServer', 'Response Data:', response.data);
|
||||
|
||||
res.json(response.data);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('API', 'Player Login Error:', error.message);
|
||||
res.status(500).json({ error: '登录失败: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 6. 获取验证码接口
|
||||
app.post('/api/getPhoneCode', async (req, res) => {
|
||||
const { phonenum } = req.body;
|
||||
logger.divider('Get Phone Code');
|
||||
logger.info('API', 'Received GetCode Request:', phonenum);
|
||||
|
||||
try {
|
||||
if (!cachedGameServerHttp) {
|
||||
const { agentid, gameid, channelid, marketid } = config.remoteConfig;
|
||||
cachedGameServerHttp = remoteConfig.getParaValue('game_server_http', agentid, gameid, channelid, marketid);
|
||||
}
|
||||
|
||||
if (!cachedGameServerHttp) {
|
||||
return res.status(500).json({ error: 'Configuration error: game_server_http not found' });
|
||||
}
|
||||
|
||||
const targetUrl = cachedGameServerHttp.startsWith('http') ? cachedGameServerHttp : `http://${cachedGameServerHttp}`;
|
||||
const { agentid } = config.remoteConfig;
|
||||
|
||||
const payload = {
|
||||
"app": "youle",
|
||||
"route": "agent",
|
||||
"rpc": "send_phone_code_wechat",
|
||||
"data": {
|
||||
"agentid": agentid,
|
||||
"phonenum": phonenum
|
||||
}
|
||||
};
|
||||
|
||||
// --- Debug: 生成可粘贴到浏览器的 GET 链接 ---
|
||||
// try {
|
||||
// const debugQuery = `data=${encodeURIComponent(JSON.stringify(payload))}`;
|
||||
// const debugUrl = `${targetUrl}?${debugQuery}`;
|
||||
// console.log('\n[Debug] Request URL for Browser:');
|
||||
// console.log(debugUrl);
|
||||
// console.log('');
|
||||
// } catch (e) {
|
||||
// console.error('[Debug] Failed to generate debug URL:', e);
|
||||
// }
|
||||
// -------------------------------------------
|
||||
|
||||
const bodyData = 'data=' + JSON.stringify(payload);
|
||||
logger.info('GameServer', 'Sending get_phone_code_wechat request...');
|
||||
|
||||
const response = await axios.post(targetUrl, bodyData, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
});
|
||||
logger.info('GameServer', 'GetCode Response received');
|
||||
logger.debug('GameServer', 'Response Data:', response.data);
|
||||
|
||||
res.json(response.data);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('API', 'Get Phone Code Error:', error.message);
|
||||
res.status(500).json({ error: '获取随机数失败: ' + error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 启动远程配置更新
|
||||
remoteConfig.start();
|
||||
|
||||
app.listen(port, () => {
|
||||
logger.divider();
|
||||
logger.success('System', `Server running at http://localhost:${port}`);
|
||||
logger.info('System', 'Press Ctrl+C to stop');
|
||||
});
|
||||
2655
codes/minipro/wxserver/package-lock.json
generated
2655
codes/minipro/wxserver/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "calculation-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend for WeChat Mini Program Calculator",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.958.0",
|
||||
"@aws-sdk/lib-storage": "^3.958.0",
|
||||
"axios": "^1.6.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"multer": "^2.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
cYmJAn4NiUsc7oAH
|
||||
@@ -1,317 +0,0 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const config = require('../config/index');
|
||||
|
||||
class RemoteConfig {
|
||||
constructor() {
|
||||
this.data = null;
|
||||
|
||||
// 使用 Gitee/GitHub Raw URL
|
||||
if (config.remoteConfig.configUrl && config.remoteConfig.configUrl.startsWith('http')) {
|
||||
this.url = config.remoteConfig.configUrl;
|
||||
//console.log('RemoteConfig: Using direct configUrl:', this.url);
|
||||
} else {
|
||||
this.url = "";
|
||||
console.warn('RemoteConfig: Missing configUrl.');
|
||||
}
|
||||
|
||||
this.timer = null;
|
||||
this.localPath = path.join(__dirname, '../config/update_jsonv2.txt');
|
||||
this.updateCallbacks = [];
|
||||
}
|
||||
|
||||
onUpdate(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.updateCallbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
triggerUpdate() {
|
||||
this.updateCallbacks.forEach(cb => {
|
||||
try {
|
||||
cb();
|
||||
} catch (e) {
|
||||
console.error('Error in remote config update callback:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start() {
|
||||
this.fetchConfig();
|
||||
// 定时更新
|
||||
const interval = config.remoteConfig.interval || 30000;
|
||||
this.timer = setInterval(() => {
|
||||
this.fetchConfig();
|
||||
}, interval);
|
||||
}
|
||||
|
||||
async fetchConfig() {
|
||||
try {
|
||||
// 1. 尝试从远程 URL 获取配置
|
||||
// console.log('Fetching remote config from:', this.url);
|
||||
const response = await axios.get(this.url + "?" + Date.now(), { timeout: 5000 }); // 添加超时控制
|
||||
|
||||
if (response.data) {
|
||||
this.parseData(response.data);
|
||||
// console.log('Remote config updated from URL.');
|
||||
|
||||
// 可选:更新成功后,可以把最新的配置写入本地文件作为缓存
|
||||
// fs.writeFileSync(this.localPath, typeof response.data === 'string' ? response.data : JSON.stringify(response.data, null, 4));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching remote config:', error.message);
|
||||
|
||||
// 2. 远程获取失败,降级读取本地文件
|
||||
if (fs.existsSync(this.localPath)) {
|
||||
try {
|
||||
const fileContent = fs.readFileSync(this.localPath, 'utf-8');
|
||||
this.parseData(fileContent);
|
||||
//console.log('Fallback: Loaded config from local file:', this.localPath);
|
||||
} catch(e) {
|
||||
console.error('Fallback failed:', e);
|
||||
}
|
||||
} else {
|
||||
console.warn('No local config file found for fallback.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseData(data) {
|
||||
let parsedData = null;
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
parsedData = JSON.parse(data.trim());
|
||||
} catch (e) {
|
||||
console.error('Failed to parse config JSON:', e);
|
||||
}
|
||||
} else {
|
||||
parsedData = data;
|
||||
}
|
||||
|
||||
if (parsedData) {
|
||||
this.data = parsedData;
|
||||
this.triggerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
getParaValue(paraname, agentid, gameid, channelid, marketid) {
|
||||
let paravalue = null;
|
||||
|
||||
if (!this.data) {
|
||||
return paravalue;
|
||||
}
|
||||
|
||||
// 1. Root level
|
||||
if (this.data[paraname]) {
|
||||
paravalue = this.data[paraname];
|
||||
}
|
||||
|
||||
// Helper to find item in list and update paravalue
|
||||
const findAndCheck = (list, key, value) => {
|
||||
if (!list || !Array.isArray(list)) return null;
|
||||
for (const item of list) {
|
||||
if (item[key] == value) {
|
||||
if (item[paraname]) {
|
||||
paravalue = item[paraname];
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 2. Agent level
|
||||
const agent = findAndCheck(this.data.agentlist, 'agentid', agentid);
|
||||
if (!agent) return paravalue;
|
||||
|
||||
// 3. Game level
|
||||
const game = findAndCheck(agent.gamelist, 'gameid', gameid);
|
||||
if (!game) return paravalue;
|
||||
|
||||
// 4. Channel level
|
||||
const channel = findAndCheck(game.channellist, 'channelid', channelid);
|
||||
if (!channel) return paravalue;
|
||||
|
||||
// 5. Market level
|
||||
const market = findAndCheck(channel.marketlist, 'marketid', marketid);
|
||||
|
||||
return paravalue;
|
||||
}
|
||||
|
||||
// 获取游戏列表_下载页面
|
||||
getGameListDownHtml(agentid, channelid) {
|
||||
const gamelist = [];
|
||||
if (!this.data || !this.data.agentlist) {
|
||||
return gamelist;
|
||||
}
|
||||
|
||||
for (const agent of this.data.agentlist) {
|
||||
if (agent.agentid == agentid) {
|
||||
if (agent.gamelist) {
|
||||
for (const game of agent.gamelist) {
|
||||
const o_game = {
|
||||
name: game.gamename,
|
||||
image: game.game_down_image,
|
||||
state: game.game_down_state,
|
||||
memo: game.game_down_memo
|
||||
};
|
||||
|
||||
if (game.channellist) {
|
||||
for (const channel of game.channellist) {
|
||||
if (channel.channelid == channelid) {
|
||||
const _ios_marketid = channel.ios_defdownload_marketid;
|
||||
const _and_marketid = channel.and_defdownload_marketid;
|
||||
|
||||
if (channel.marketlist) {
|
||||
for (const market of channel.marketlist) {
|
||||
if (market.marketid == _ios_marketid) {
|
||||
o_game.ios_down = market.app_download;
|
||||
o_game.ios_size = market.app_size;
|
||||
o_game.ios_marketid = _ios_marketid;
|
||||
}
|
||||
if (market.marketid == _and_marketid) {
|
||||
o_game.android_down = market.app_download;
|
||||
o_game.android_size = market.app_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (o_game.ios_down && o_game.android_down) {
|
||||
gamelist.push(o_game);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return gamelist;
|
||||
}
|
||||
|
||||
// 获取游戏列表_游戏大厅
|
||||
getGameListGameHall(agentid, channelid, marketid) {
|
||||
const gamelist = [];
|
||||
if (!this.data || !this.data.agentlist) {
|
||||
return gamelist;
|
||||
}
|
||||
|
||||
for (const agent of this.data.agentlist) {
|
||||
if (agent.agentid == agentid) {
|
||||
if (agent.gamelist) {
|
||||
for (const game of agent.gamelist) {
|
||||
if (game.gameid != "G2hw0ubng0zcoI0r4mx3H2yr4GejidwO") {
|
||||
const o_game = {
|
||||
gameid: game.gameid,
|
||||
gamename: game.gamename,
|
||||
gamedir: game.game_hall_dir,
|
||||
gameimage: game.game_hall_image,
|
||||
gameversion: game.game_version,
|
||||
gamezip: game.game_zip,
|
||||
zipsize: game.game_size
|
||||
};
|
||||
|
||||
if (game.channellist) {
|
||||
for (const channel of game.channellist) {
|
||||
if (channel.channelid == channelid) {
|
||||
if (channel.game_version > 0) {
|
||||
o_game.gameversion = channel.game_version;
|
||||
}
|
||||
if (channel.game_zip) {
|
||||
o_game.gamezip = channel.game_zip;
|
||||
}
|
||||
if (channel.game_size) {
|
||||
o_game.zipsize = channel.game_size;
|
||||
}
|
||||
|
||||
if (channel.marketlist) {
|
||||
for (const market of channel.marketlist) {
|
||||
if (market.marketid == marketid) {
|
||||
if (market.game_version > 0) {
|
||||
o_game.gameversion = market.game_version;
|
||||
}
|
||||
if (market.game_zip) {
|
||||
o_game.gamezip = market.game_zip;
|
||||
}
|
||||
if (market.game_size) {
|
||||
o_game.zipsize = market.game_size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
gamelist.push(o_game);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return gamelist;
|
||||
}
|
||||
|
||||
// 获取代理城市列表
|
||||
getAgentList(agentid, channelid) {
|
||||
const agentlist = [];
|
||||
if (!this.data || !this.data.agentlist) {
|
||||
return agentlist;
|
||||
}
|
||||
|
||||
for (const agent of this.data.agentlist) {
|
||||
if (agent.agentid == agentid || agent.relagentid == agentid) {
|
||||
const o_agent = {
|
||||
agentid: agent.agentid,
|
||||
name: agent.agentname,
|
||||
// 注意:这里假设 gamelist 和 channellist 存在且非空,参考原逻辑
|
||||
channelid: (agent.gamelist && agent.gamelist[0] && agent.gamelist[0].channellist && agent.gamelist[0].channellist[0]) ? agent.gamelist[0].channellist[0].channelid : ''
|
||||
};
|
||||
agentlist.push(o_agent);
|
||||
}
|
||||
}
|
||||
return agentlist;
|
||||
}
|
||||
|
||||
// 获取子游戏服务器列表
|
||||
getGameServerList(agentid) {
|
||||
const iplist = [];
|
||||
const paraname = "game_server_http";
|
||||
|
||||
const doPushToIpList = (ip) => {
|
||||
if (ip && !iplist.includes(ip)) {
|
||||
iplist.push(ip);
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.data || !this.data.agentlist) {
|
||||
return iplist;
|
||||
}
|
||||
|
||||
const agent = this.data.agentlist.find(a => a.agentid === agentid);
|
||||
if (!agent) {
|
||||
return iplist;
|
||||
}
|
||||
|
||||
if (this.data[paraname]) {
|
||||
doPushToIpList(this.data[paraname]);
|
||||
}
|
||||
if (agent[paraname]) {
|
||||
doPushToIpList(agent[paraname]);
|
||||
}
|
||||
if (agent.gamelist) {
|
||||
for (const game of agent.gamelist) {
|
||||
if (game[paraname]) {
|
||||
doPushToIpList(game[paraname]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return iplist;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new RemoteConfig();
|
||||
@@ -1 +0,0 @@
|
||||
"idx" "play_agentid" "play_playerid" "play_channelid" "play_openid" "play_unionid" "play_nickname" "play_avatar" "play_sex" "play_province" "play_city" "play_roomcard" "play_bean" "play_regtime" "play_lasttime" "play_logindate" "play_usecard" "play_taskaward" "play_type" "play_score" "play_a_country" "play_a_province" "play_a_city" "play_a_citycode" "play_a_district" "play_a_street" "play_a_address" "play_longitude" "play_latitude" "play_invitecode" "play_inviteid" "play_state" "play_advanced" "play_shortcode" "play_roomcodes" "play_desone" "play_destwo" "play_whitelist" "play_limit" "play_notice" "play_bankpower" "play_bank" "play_bankpwd" "play_tel" "play_wechat" "play_marketid" "play_phoneinfo" "play_sign"
|
||||
@@ -1,7 +0,0 @@
|
||||
@echo off
|
||||
title BaibaoxiangServer
|
||||
cd /d "%~dp0"
|
||||
echo Starting Baibaoxiang Server...
|
||||
echo Server will run on port 3000 (or configured port)
|
||||
node index.js
|
||||
pause
|
||||
@@ -1,12 +0,0 @@
|
||||
@echo off
|
||||
title WeChat Server - Running
|
||||
color 0A
|
||||
cls
|
||||
|
||||
:: 切换到脚本所在目录,确保 node 能找到 index.js
|
||||
cd /d "%~dp0"
|
||||
|
||||
echo Starting WeChat Server...
|
||||
echo.
|
||||
node index.js
|
||||
pause
|
||||
@@ -1,21 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
set PORT=3000
|
||||
echo Looking for process on port %PORT%...
|
||||
|
||||
:: 查找占用端口 3000 的进程 PID
|
||||
set PID=
|
||||
for /f "tokens=5" %%a in ('netstat -aon ^| findstr ":%PORT% " ^| findstr "LISTENING"') do (
|
||||
set PID=%%a
|
||||
)
|
||||
|
||||
if defined PID (
|
||||
echo Found process with PID: %PID%
|
||||
echo Killing process...
|
||||
taskkill /F /PID %PID%
|
||||
echo Server stopped successfully.
|
||||
) else (
|
||||
echo No server found running on port %PORT%.
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -1,101 +0,0 @@
|
||||
const util = require('util');
|
||||
const config = require('../config');
|
||||
|
||||
// ANSI 颜色代码
|
||||
const colors = {
|
||||
reset: "\x1b[0m",
|
||||
bright: "\x1b[1m",
|
||||
dim: "\x1b[2m",
|
||||
underscore: "\x1b[4m",
|
||||
blink: "\x1b[5m",
|
||||
reverse: "\x1b[7m",
|
||||
hidden: "\x1b[8m",
|
||||
|
||||
fg: {
|
||||
black: "\x1b[30m",
|
||||
red: "\x1b[31m",
|
||||
green: "\x1b[32m",
|
||||
yellow: "\x1b[33m",
|
||||
blue: "\x1b[34m",
|
||||
magenta: "\x1b[35m",
|
||||
cyan: "\x1b[36m",
|
||||
white: "\x1b[37m",
|
||||
gray: "\x1b[90m",
|
||||
},
|
||||
bg: {
|
||||
black: "\x1b[40m",
|
||||
red: "\x1b[41m",
|
||||
green: "\x1b[42m",
|
||||
yellow: "\x1b[43m",
|
||||
blue: "\x1b[44m",
|
||||
magenta: "\x1b[45m",
|
||||
cyan: "\x1b[46m",
|
||||
white: "\x1b[47m",
|
||||
}
|
||||
};
|
||||
|
||||
function formatTime() {
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const ms = String(now.getMilliseconds()).padStart(3, '0');
|
||||
return `${timeStr}.${ms}`;
|
||||
}
|
||||
|
||||
const logger = {
|
||||
// 普通信息 - 青色标签
|
||||
info: (category, message, ...args) => {
|
||||
if (!config.enableLog) return;
|
||||
const time = `${colors.fg.gray}[${formatTime()}]${colors.reset}`;
|
||||
const tag = `${colors.fg.cyan}[${category}]${colors.reset}`;
|
||||
console.log(`${time} ${tag} ${message}`, ...args);
|
||||
},
|
||||
|
||||
// 成功信息 - 绿色标签
|
||||
success: (category, message, ...args) => {
|
||||
if (!config.enableLog) return;
|
||||
const time = `${colors.fg.gray}[${formatTime()}]${colors.reset}`;
|
||||
const tag = `${colors.fg.green}[${category}]${colors.reset}`;
|
||||
console.log(`${time} ${tag} ${message}`, ...args);
|
||||
},
|
||||
|
||||
// 警告信息 - 黄色标签
|
||||
warn: (category, message, ...args) => {
|
||||
if (!config.enableLog) return;
|
||||
const time = `${colors.fg.gray}[${formatTime()}]${colors.reset}`;
|
||||
const tag = `${colors.fg.yellow}[${category}]${colors.reset}`;
|
||||
console.warn(`${time} ${tag} ${message}`, ...args);
|
||||
},
|
||||
|
||||
// 错误信息 - 红色标签
|
||||
error: (category, message, ...args) => {
|
||||
if (!config.enableLog) return;
|
||||
const time = `${colors.fg.gray}[${formatTime()}]${colors.reset}`;
|
||||
const tag = `${colors.fg.red}[${category}]${colors.reset}`;
|
||||
console.error(`${time} ${tag} ${message}`, ...args);
|
||||
},
|
||||
|
||||
// 调试信息 - 洋红色标签 (对象会自动展开)
|
||||
debug: (category, message, ...args) => {
|
||||
if (!config.enableLog) return;
|
||||
const time = `${colors.fg.gray}[${formatTime()}]${colors.reset}`;
|
||||
const tag = `${colors.fg.magenta}[${category}]${colors.reset}`;
|
||||
// 使用 util.inspect 格式化对象,使其带有颜色且深度无限
|
||||
const formattedArgs = args.map(arg =>
|
||||
(typeof arg === 'object' && arg !== null)
|
||||
? '\n' + util.inspect(arg, { colors: true, depth: null, breakLength: 80 })
|
||||
: arg
|
||||
);
|
||||
console.log(`${time} ${tag} ${message}`, ...formattedArgs);
|
||||
},
|
||||
|
||||
// 分割线 - 用于区分请求
|
||||
divider: (title) => {
|
||||
if (!config.enableLog) return;
|
||||
console.log(`${colors.fg.gray}----------------------------------------------------------------${colors.reset}`);
|
||||
if (title) {
|
||||
console.log(`${colors.fg.white}${colors.bright}👉 ${title}${colors.reset}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user