286 lines
9.2 KiB
JavaScript
286 lines
9.2 KiB
JavaScript
/**
|
|
* 后端 API 调用助手(Node.js HTTP 直接调用,含 HMAC 签名)
|
|
* 支持带签名和不带签名两种模式
|
|
*/
|
|
const http = require('http');
|
|
const crypto = require('crypto');
|
|
|
|
const BASE_URL = 'http://192.168.110.64:8084';
|
|
const API_BASE = '/api';
|
|
const SIGNATURE_SECRET = 'NovalonManageSystemSecretKey2026';
|
|
const ENABLE_SIGNATURE = true; // HMAC 签名
|
|
|
|
function generateSignature(method, path, body, timestamp, nonce) {
|
|
const stringToSign = [method, path, '', body || '', String(timestamp), nonce].join('\n');
|
|
return crypto.createHmac('sha256', SIGNATURE_SECRET).update(stringToSign).digest('base64');
|
|
}
|
|
|
|
function generateNonce() {
|
|
return Date.now().toString(36) + '-' + Math.random().toString(36).substring(2, 15);
|
|
}
|
|
|
|
function makeRequest(method, url, body, token) {
|
|
return new Promise((resolve, reject) => {
|
|
const urlObj = new URL(url, BASE_URL);
|
|
const path = urlObj.pathname + urlObj.search;
|
|
const bodyStr = body ? JSON.stringify(body) : '';
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
|
|
if (ENABLE_SIGNATURE) {
|
|
const timestamp = Date.now();
|
|
const nonce = generateNonce();
|
|
const signature = generateSignature(method.toUpperCase(), path, bodyStr, timestamp, nonce);
|
|
headers['X-Signature'] = signature;
|
|
headers['X-Timestamp'] = String(timestamp);
|
|
headers['X-Nonce'] = nonce;
|
|
}
|
|
|
|
if (token) {
|
|
headers['Authorization'] = token.startsWith('Bearer ') ? token : 'Bearer ' + token;
|
|
}
|
|
|
|
const options = {
|
|
hostname: urlObj.hostname,
|
|
port: urlObj.port,
|
|
path: path,
|
|
method: method.toUpperCase(),
|
|
headers: headers
|
|
};
|
|
|
|
const req = http.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => { data += chunk; });
|
|
res.on('end', () => {
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
resolve({ status: res.statusCode, data: parsed });
|
|
} catch (e) {
|
|
resolve({ status: res.statusCode, data: data });
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (err) => reject(err));
|
|
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Request timeout')); });
|
|
|
|
if (bodyStr) req.write(bodyStr);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function isSuccess(response, expectedStatus = 200) {
|
|
return response.status === expectedStatus;
|
|
}
|
|
|
|
async function adminLogin(password) {
|
|
console.log('[API] 管理员登录...');
|
|
const res = await makeRequest('POST', `${API_BASE}/system/auth/login`, {
|
|
username: 'admin',
|
|
password: password || 'admin'
|
|
});
|
|
console.log(`[API] 登录响应: ${res.status}`);
|
|
if (res.data && res.data.data && res.data.data.token) {
|
|
return res.data.data.token;
|
|
}
|
|
if (res.data && res.data.token) {
|
|
return res.data.token;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function searchCourses(keyword, token) {
|
|
console.log(`[API] 搜索课程: "${keyword}"`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/page`, {
|
|
page: 0, size: 50
|
|
}, token);
|
|
console.log(`[API] 搜索结果: status=${res.status}, total=${res.data?.data?.totalElements || res.data?.totalElements || '?'}`);
|
|
return res;
|
|
}
|
|
|
|
async function searchByKeyword(keyword, token) {
|
|
console.log(`[API] 按关键词搜索: "${keyword}"`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/page`, {
|
|
page: 0, size: 50
|
|
}, token);
|
|
console.log(`[API] 搜索结果: status=${res.status}, total=${res.data?.data?.totalElements || res.data?.totalElements || '?'}`);
|
|
return res;
|
|
}
|
|
|
|
async function getCourseDetail(courseId, token) {
|
|
console.log(`[API] 获取课程详情: id=${courseId}`);
|
|
const res = await makeRequest('GET', `${API_BASE}/groupCourse/${courseId}/detail`, null, token);
|
|
console.log(`[API] 课程详情: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
async function getCourseTypes() {
|
|
console.log('[API] 获取课程类型...');
|
|
const res = await makeRequest('GET', `${API_BASE}/groupCourse/types`);
|
|
console.log(`[API] 课程类型: status=${res.status}, count=${Array.isArray(res.data) ? res.data.length : '?'}`);
|
|
return res;
|
|
}
|
|
|
|
async function getActiveRecommendations() {
|
|
console.log('[API] 获取活跃推荐课程...');
|
|
const res = await makeRequest('GET', `${API_BASE}/groupCourse/recommend/active`);
|
|
console.log(`[API] 推荐课程: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
// ── 会员端 API ──
|
|
|
|
/** 会员小程序登录,返回 {memberId, accessToken} */
|
|
async function memberLogin() {
|
|
console.log('[API] 会员登录 (miniappLogin)...');
|
|
const res = await makeRequest('POST', `${API_BASE}/member/auth/miniapp/login`, {
|
|
code: 'dev-test-code-' + Date.now()
|
|
});
|
|
console.log(`[API] 会员登录响应: status=${res.status}`);
|
|
if (res.status === 200 && res.data) {
|
|
return { memberId: res.data.memberId, accessToken: res.data.accessToken };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** 预约课程 */
|
|
async function bookCourse(courseId, memberId, token) {
|
|
console.log(`[API] 预约课程: courseId=${courseId}, memberId=${memberId}`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/book`, {
|
|
courseId, memberId
|
|
}, token);
|
|
console.log(`[API] 预约结果: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 获取会员的预约列表 */
|
|
async function getMemberBookings(memberId, token) {
|
|
console.log(`[API] 获取会员预约: memberId=${memberId}`);
|
|
const res = await makeRequest('GET', `${API_BASE}/groupCourse/bookings/member/${memberId}`, null, token);
|
|
console.log(`[API] 预约列表: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 扫码签到 */
|
|
async function signIn(courseId, memberId, token) {
|
|
console.log(`[API] 签到: courseId=${courseId}, memberId=${memberId}`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/signin/${memberId}`, {
|
|
courseId
|
|
}, token);
|
|
console.log(`[API] 签到结果: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
// ── 品牌定制 API ──
|
|
|
|
/** 获取品牌配置 (需auth) */
|
|
async function getBrandConfig(token) {
|
|
console.log('[API] 获取品牌配置...');
|
|
const res = await makeRequest('GET', `${API_BASE}/brand`, null, token);
|
|
console.log(`[API] 品牌配置: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
// ── 通知/消息 API ──
|
|
|
|
/** 获取活跃Banner列表 */
|
|
async function getActiveBanners(token) {
|
|
console.log('[API] 获取活跃Banners...');
|
|
const res = await makeRequest('GET', `${API_BASE}/banners/active`, null, token);
|
|
console.log(`[API] Banners: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 获取系统通知列表 */
|
|
async function getSystemNotices(token) {
|
|
console.log('[API] 获取系统通知...');
|
|
const res = await makeRequest('GET', `${API_BASE}/notices`, null, token);
|
|
console.log(`[API] 通知列表: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 获取用户消息列表 */
|
|
async function getUserMessages(userId, token) {
|
|
console.log(`[API] 获取用户消息: userId=${userId}`);
|
|
const res = await makeRequest('GET', `${API_BASE}/messages/user/${userId}`, null, token);
|
|
console.log(`[API] 用户消息: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 获取用户未读消息数 */
|
|
async function getUnreadMessageCount(userId, token) {
|
|
console.log(`[API] 获取未读消息数: userId=${userId}`);
|
|
const res = await makeRequest('GET', `${API_BASE}/messages/user/${userId}/unread`, null, token);
|
|
console.log(`[API] 未读数: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
/** 标记单条消息已读 */
|
|
async function markMessageAsRead(messageId, token) {
|
|
console.log(`[API] 标记消息已读: id=${messageId}`);
|
|
const res = await makeRequest('PUT', `${API_BASE}/messages/${messageId}/read`, null, token);
|
|
console.log(`[API] 标记已读: status=${res.status}`);
|
|
return res;
|
|
}
|
|
|
|
// ── 团课高级搜索 API ──
|
|
|
|
/** 按课程类型筛选 */
|
|
async function searchCoursesByType(typeId, token) {
|
|
console.log(`[API] 按类型搜索: typeId=${typeId}`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/page`, {
|
|
page: 0, size: 50, courseTypeId: typeId
|
|
}, token);
|
|
console.log(`[API] 类型筛选: status=${res.status}, total=${res.data?.data?.totalElements || '?'}`);
|
|
return res;
|
|
}
|
|
|
|
/** 按标签筛选 */
|
|
async function searchCoursesByLabel(labelId, token) {
|
|
console.log(`[API] 按标签搜索: labelId=${labelId}`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/page`, {
|
|
page: 0, size: 50, labelId: labelId
|
|
}, token);
|
|
console.log(`[API] 标签筛选: status=${res.status}, total=${res.data?.data?.totalElements || '?'}`);
|
|
return res;
|
|
}
|
|
|
|
/** 复筛: 类型+标签 */
|
|
async function searchCoursesCombined(typeId, labelId, token) {
|
|
console.log(`[API] 组合搜索: typeId=${typeId}, labelId=${labelId}`);
|
|
const res = await makeRequest('POST', `${API_BASE}/groupCourse/page`, {
|
|
page: 0, size: 50, courseTypeId: typeId, labelId: labelId
|
|
}, token);
|
|
console.log(`[API] 组合筛选: status=${res.status}, total=${res.data?.data?.totalElements || '?'}`);
|
|
return res;
|
|
}
|
|
|
|
module.exports = {
|
|
makeRequest,
|
|
adminLogin,
|
|
searchCourses,
|
|
searchByKeyword,
|
|
getCourseDetail,
|
|
getCourseTypes,
|
|
getActiveRecommendations,
|
|
memberLogin,
|
|
bookCourse,
|
|
getMemberBookings,
|
|
signIn,
|
|
getBrandConfig,
|
|
getActiveBanners,
|
|
getSystemNotices,
|
|
getUserMessages,
|
|
getUnreadMessageCount,
|
|
markMessageAsRead,
|
|
searchCoursesByType,
|
|
searchCoursesByLabel,
|
|
searchCoursesCombined,
|
|
isSuccess,
|
|
BASE_URL,
|
|
API_BASE
|
|
};
|
|
|