feat(sae): add Java MVP backend and Taobao connectivity page

Spring Boot 3.5.6 / Java 17 JAR with cloud gateway envelope and ping/whoami/echo; Taobao gateway signature verification matching the Node implementation; /pages/mvp page, splash dev entry and local transport contract test; SAE + 聚石塔 runbook and portable JDK/Maven setup; Windows 11 wmic shim for Taobao DevTools.
This commit is contained in:
xsl
2026-10-31 18:35:04 +08:00
parent 459e21dad4
commit ac0b7ca42b
25 changed files with 1334 additions and 2 deletions
+2 -1
View File
@@ -4,7 +4,8 @@
"pages/profile/profile",
"pages/camera/camera",
"pages/report/report",
"pages/history/history"
"pages/history/history",
"pages/mvp/mvp"
],
"window": {
"defaultTitle": "1'me在线肌肤测试",
+4
View File
@@ -8,6 +8,10 @@ module.exports = {
cloudAppId: '',
cloudEnv: 'online', // External/empty cloud applications use online
basePath: '', // E.g. '/state' behind an existing reverse proxy
// SAE/Java MVP page only: force the local Java backend without changing the main app config.
// Set false and edit transport/baseUrl/cloudAppId above when testing the cloud gateway.
mvpLocal: true,
mvpBaseUrl: 'http://127.0.0.1:8080',
mock: false,
productItemIds: {} // Optional real Taobao item IDs keyed by tier 1/2/3
};
+91
View File
@@ -0,0 +1,91 @@
.mvp {
min-height: 100vh;
box-sizing: border-box;
padding: 48rpx 32rpx 64rpx;
background: #101823;
color: #F5F5F5;
}
.mvp-head {
margin-bottom: 32rpx;
}
.mvp-kicker {
display: block;
color: #F5F078;
font-size: 24rpx;
letter-spacing: 2rpx;
margin-bottom: 12rpx;
}
.mvp-title {
display: block;
font-size: 44rpx;
font-weight: 700;
line-height: 1.3;
}
.mvp-sub {
display: block;
color: #9AA4B2;
font-size: 26rpx;
margin-top: 10rpx;
}
.mvp-card {
background: #1B2634;
border: 1rpx solid #2A3748;
border-radius: 20rpx;
padding: 28rpx;
margin-bottom: 28rpx;
}
.mvp-label {
color: #9AA4B2;
font-size: 24rpx;
margin-bottom: 16rpx;
}
.mvp-code {
display: block;
color: #E8EDF4;
font-family: Menlo, Consolas, monospace;
font-size: 24rpx;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
.mvp-actions {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 28rpx;
}
.mvp-btn {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
background: #243244;
color: #E8EDF4;
font-size: 28rpx;
border: none;
}
.mvp-btn::after {
border: none;
}
.mvp-btn-primary {
background: #F5F078;
color: #101823;
font-weight: 600;
}
.mvp-tip {
color: #7F8A99;
font-size: 24rpx;
line-height: 1.7;
padding: 0 8rpx;
}
+28
View File
@@ -0,0 +1,28 @@
<view class="mvp">
<view class="mvp-head">
<text class="mvp-kicker">SAE + 淘宝云应用</text>
<text class="mvp-title">最小连通性验证</text>
<text class="mvp-sub">Java 后端 / 云网关统一响应</text>
</view>
<view class="mvp-card">
<view class="mvp-label">当前 config.js</view>
<text class="mvp-code">{{configText}}</text>
</view>
<view class="mvp-actions">
<button class="mvp-btn mvp-btn-primary" onTap="onPing" loading="{{loading}}">POST /api/taobao/ping</button>
<button class="mvp-btn" onTap="onWhoami" loading="{{loading}}">POST /api/taobao/whoami</button>
<button class="mvp-btn" onTap="onEcho" loading="{{loading}}">POST /api/taobao/echo</button>
<button class="mvp-btn" onTap="onBack">返回首页</button>
</view>
<view class="mvp-card">
<view class="mvp-label">返回(已由 transport.unwrap 解包)</view>
<text class="mvp-code">{{resultText}}</text>
</view>
<view class="mvp-tip">
本地调试:config.transport = direct,baseUrl 指向 127.0.0.1。云端:transport = cloud,baseUrl 必须是 HTTPS,并填写 cloudAppId/cloudEnv。
</view>
</view>
+63
View File
@@ -0,0 +1,63 @@
var config = require('../../config');
var transport = require('../../utils/transport');
var original = { transport: config.transport, baseUrl: config.baseUrl };
if (config.mvpLocal) {
config.transport = 'direct';
config.baseUrl = config.mvpBaseUrl;
}
Page({
data: {
configText: '',
resultText: '',
loading: false
},
onLoad: function () {
this.setData({
configText: JSON.stringify({
environment: config.environment,
transport: config.transport,
baseUrl: config.baseUrl,
cloudAppId: config.cloudAppId || '(empty)',
cloudEnv: config.cloudEnv
}, null, 2)
});
this.onPing();
},
call: function (path, body) {
var that = this;
this.setData({ loading: true, resultText: '请求中...' });
transport.request(path, body || {}).then(function (data) {
that.setData({ loading: false, resultText: JSON.stringify(data, null, 2) });
}).catch(function (err) {
that.setData({
loading: false,
resultText: 'ERROR: ' + (err && err.message ? err.message : String(err))
});
});
},
onPing: function () {
this.call('/api/taobao/ping', {});
},
onWhoami: function () {
this.call('/api/taobao/whoami', {});
},
onEcho: function () {
this.call('/api/taobao/echo', { hello: 'world', at: Date.now() });
},
onBack: function () {
my.navigateBack();
},
onUnload: function () {
config.transport = original.transport;
config.baseUrl = original.baseUrl;
}
});
+5
View File
@@ -0,0 +1,5 @@
{
"defaultTitle": "SAE 连通性 MVP",
"titleBarColor": "#101823",
"backgroundColor": "#101823"
}
+1
View File
@@ -142,3 +142,4 @@
.brand-row { justify-content: center; }
.brand-logo { width: 450rpx; height: 52rpx; margin: 0; }
.camera-debug-entry { position: absolute; z-index: 2; top: 24rpx; right: 24rpx; padding: 16rpx 24rpx; border-radius: 16rpx; color: #fff; background: rgba(0,0,0,.65); font-size: 24rpx; }
.mvp-debug-entry { top: 100rpx; }
+1
View File
@@ -1,6 +1,7 @@
<!-- ═════ 01 启动页 ═════ -->
<view class="splash">
<view class="camera-debug-entry" a:if="{{showCameraDebug}}" onTap="onCameraDebug">开发调试 · 相机验收</view>
<view class="camera-debug-entry mvp-debug-entry" a:if="{{showMvpDebug}}" onTap="onMvpDebug">SAE MVP 连通性</view>
<image class="splash-bg" src="{{bgUrl}}" mode="aspectFill" />
<view class="splash-slogan">
<text>1'me,</text>
+7 -1
View File
@@ -1,6 +1,7 @@
/* ═════ 启动页:品牌底图 + Slogan + 隐私授权弹窗(对齐 H5 #s-splash + #p-privacy) ═════ */
var api = require('../../utils/api.js');
var development = require('../../utils/development');
var config = require('../../config');
Page({
data: {
@@ -8,7 +9,8 @@ Page({
logoUrl: api.assetUrl('img/ui-20261012/brand-lockup.png'),
showPrivacy: false, // 隐私授权弹窗
agreed: false, // 隐私协议勾选
showPolicy: false, busy: false, signedIn: false, showCameraDebug: development.cameraDebugEnabled()
showPolicy: false, busy: false, signedIn: false, showCameraDebug: development.cameraDebugEnabled(),
showMvpDebug: config.mvpLocal === true
},
onShow: function () {
var that = this;
@@ -65,6 +67,10 @@ Page({
if (!development.cameraDebugEnabled()) return;
my.navigateTo({ url: '/pages/camera/camera?debug=camera' });
},
onMvpDebug: function () {
if (!config.mvpLocal) return;
my.navigateTo({ url: '/pages/mvp/mvp' });
},
noop: function () {} // 弹窗卡片内点击拦截冒泡用
});
+83
View File
@@ -0,0 +1,83 @@
'use strict';
// 不依赖淘宝 IDE 的本地契约测试:
// 用 Node 模拟 my.request,加载真实的 utils/transport.js,直连 Java 后端。
// 运行前先启动 backend-java,然后:node scripts/mvp-local-check.cjs
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const BASE = process.env.MVP_BASE_URL || 'http://127.0.0.1:8080';
const config = {
environment: 'development',
transport: 'direct',
baseUrl: BASE,
assetBase: BASE + '/assets/',
cloudAppId: '',
cloudEnv: 'test',
basePath: '',
mock: false
};
function httpRequest(url, options, body) {
return new Promise((resolve, reject) => {
const req = http.request(url, options, (res) => {
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
const text = Buffer.concat(chunks).toString('utf8');
let data;
try {
data = JSON.parse(text);
} catch (e) {
reject(new Error('non-JSON response: ' + text.slice(0, 200)));
return;
}
resolve({ status: res.statusCode, statusCode: res.statusCode, data });
});
});
req.on('error', reject);
req.end(body);
});
}
const my = {
request(options) {
httpRequest(options.url, { method: options.method, headers: options.headers }, options.data)
.then((res) => options.success(res))
.catch((err) => options.fail(err));
}
};
const transportModule = { exports: {} };
vm.runInNewContext(fs.readFileSync(path.join(__dirname, '../utils/transport.js'), 'utf8'), {
module: transportModule,
Promise,
my,
require(name) {
return name === '../config' ? config : {};
}
});
const transport = transportModule.exports;
(async () => {
const ping = await transport.request('/api/taobao/ping', {});
assert.equal(ping.server, 'java', 'ping.server should be java');
assert.equal(ping.pong, true, 'ping.pong should be true');
const echo = await transport.request('/api/taobao/echo', { hello: 'mvp' });
assert.equal(echo.body.hello, 'mvp', 'echo.body.hello should round-trip');
const whoami = await transport.request('/api/taobao/whoami', {});
assert.equal(whoami.ok, true, 'whoami.ok should be true');
console.log('PASS');
console.log(JSON.stringify({ ping, echo, whoami }, null, 2));
})().catch((err) => {
console.error('FAIL:', err && err.stack ? err.stack : err);
process.exit(1);
});