Compare commits

...

2 Commits

Author SHA1 Message Date
cheney
789505fb34 fix
All checks were successful
Docker Build and Push / build-image (push) Successful in 3m23s
2026-08-04 09:33:52 +08:00
Cheney
3d84e8d8f7 停止追踪 dist 目录 2026-08-04 09:33:31 +08:00
14 changed files with 148 additions and 184 deletions

2
.env
View File

@ -1 +1 @@
DATABASE_URL="mysql://root:fullstack@192.168.111.111:3306/iboard" DATABASE_URL="mysql://root:sunyard123@114.134.187.170:3306/iboard"

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ lerna-debug.log*
*.swp *.swp
*.swo *.swo
*~ *~
dist/*

View File

@ -1,6 +1,7 @@
# =============================================================== # ===============================================================
# iBoard 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程 # iBoard 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程
# 构建: docker build --build-arg DATABASE_URL=... -t iboard:latest . # 构建: docker build --build-arg DATABASE_URL=... -t iboard:latest .
# 可选 --build-arg VITE_API_BASE=https://api.example.com 让前端走绝对地址
# 运行: docker run -d --name iboard -p 3001:3001 -e DATABASE_URL=... iboard:latest # 运行: docker run -d --name iboard -p 3001:3001 -e DATABASE_URL=... iboard:latest
# =============================================================== # ===============================================================
FROM node:22-alpine AS builder FROM node:22-alpine AS builder
@ -24,6 +25,9 @@ COPY . .
# 构建前端 (输出到 dist/) 与 Prisma 客户端 # 构建前端 (输出到 dist/) 与 Prisma 客户端
RUN npx prisma generate RUN npx prisma generate
# build 时把 VITE_API_BASE 写进 bundle, 默认空 = 走相对路径 /api (同源 Express)
ARG VITE_API_BASE=
ENV VITE_API_BASE=$VITE_API_BASE
RUN npm run build RUN npm run build
# 运行时阶段 # 运行时阶段

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/favicon.svg vendored
View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#1f2937"/><text x="16" y="22" text-anchor="middle" font-family="Arial" font-size="16" font-weight="bold" fill="#fff">i</text></svg>

Before

Width:  |  Height:  |  Size: 233 B

15
dist/index.html vendored
View File

@ -1,15 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#1f2937" />
<title>iBoard - 经济数据与策略</title>
<script type="module" crossorigin src="/assets/index-z5hTmGIR.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DU437yrj.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# iBoard Android APK 打包 # iBoard Android APK 打包
# 用法: bash scripts/wsl/build-apk.sh [debug|release] # 用法: bash scripts/wsl/build-apk.sh [debug|release]
# release 模式自动构建 4 个 ABI 并出 universal APK
set -euo pipefail set -euo pipefail
WIN_PROJECT='/mnt/d/workbench/iboard' WIN_PROJECT='/mnt/d/workbench/iboard'
@ -12,7 +13,15 @@ echo "构建变体: $VARIANT"
# 环境变量 # 环境变量
export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}" export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}"
export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}" export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH" # 关键: 把 NDK toolchain bin 加进 PATH, .cargo/config.toml 里
# linker = "aarch64-linux-android24-clang" 这种短名才能被解析到
NDK_TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
if [ ! -d "$NDK_TOOLCHAIN_BIN" ]; then
echo "ERROR: NDK toolchain bin 不存在: $NDK_TOOLCHAIN_BIN"
echo "请确认 ANDROID_NDK_HOME 正确指向 NDK 27.2.x 目录"
exit 1
fi
export PATH="$NDK_TOOLCHAIN_BIN:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH"
# 0. 首次需要 android 工程 # 0. 首次需要 android 工程
cd src-tauri cd src-tauri
@ -22,6 +31,10 @@ if [ ! -d "gen/android" ]; then
fi fi
cd "$WIN_PROJECT" cd "$WIN_PROJECT"
# Android 后端地址, 写到前端 bundle 里 (覆盖默认的 /api 相对路径)
export VITE_API_BASE="${VITE_API_BASE:-http://bh.vps.honor3.com:3001}"
echo "VITE_API_BASE=$VITE_API_BASE"
# 1. 先构建前端, 让 Tauri android 工程能拿到 dist/ # 1. 先构建前端, 让 Tauri android 工程能拿到 dist/
echo "=== 1. 构建前端 ===" echo "=== 1. 构建前端 ==="
npm run build npm run build
@ -36,30 +49,34 @@ RUST_BUILD_TASKS=(
":app:rustBuildX86_64Release" ":app:rustBuildX86_64Release"
":app:rustBuildI686Release" ":app:rustBuildI686Release"
) )
RUST_BUILD_TASKS_DBG=(
":app:rustBuildArm64Debug"
":app:rustBuildArmv7Debug"
":app:rustBuildX86_64Debug"
":app:rustBuildI686Debug"
)
SKIP_ARGS=() SKIP_ARGS=()
for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done
if [ "$VARIANT" = "release" ]; then # 4 个 Android 目标的 cargo target triple
echo "[release] 手工 cargo 编 lib, gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)" ANDROID_TARGETS=(
"aarch64-linux-android"
"armv7-linux-androideabi"
"x86_64-linux-android"
"i686-linux-android"
)
echo " [cargo] build lib release ..." if [ "$VARIANT" = "release" ]; then
cargo build \ echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)"
--target aarch64-linux-android \
--features custom-protocol \ for target in "${ANDROID_TARGETS[@]}"; do
--lib \ echo " [cargo] build lib release for $target ..."
--release cargo build \
--target "$target" \
--features custom-protocol \
--lib \
--release
done
echo " [gradle] assembleRelease -x rustBuild* ..." echo " [gradle] assembleRelease -x rustBuild* ..."
cd gen/android cd gen/android
./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}" ./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}"
APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/release" APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release"
echo echo
echo "=== 产物 (未签名) ===" echo "=== 产物 (未签名) ==="
ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)" ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)"
@ -73,7 +90,12 @@ if [ "$VARIANT" = "release" ]; then
echo " IBOARD_UPLOAD_KEY_ALIAS=iboard" echo " IBOARD_UPLOAD_KEY_ALIAS=iboard"
echo " IBOARD_UPLOAD_STORE_PASSWORD=..." echo " IBOARD_UPLOAD_STORE_PASSWORD=..."
echo " IBOARD_UPLOAD_KEY_PASSWORD=..." echo " IBOARD_UPLOAD_KEY_PASSWORD=..."
echo "3. 重跑本脚本" echo "3. 重跑本脚本 (Gradle 读 gradle.properties 里的 IBOARD_UPLOAD_* 自动用 releaseSigning)"
echo
echo "=== 临时用 debug keystore 手动签 (仅测试) ==="
echo " \$ANDROID_HOME/build-tools/*/apksigner sign --ks ~/.android/debug.keystore \\"
echo " --ks-pass pass:android --key-pass pass:android \\"
echo " $APK_DIR/app-universal-release-unsigned.apk"
else else
echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走" echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走"
npx tauri android build --apk npx tauri android build --apk

View File

@ -6,7 +6,32 @@ VARIANT="${1:-debug}"
WIN_PROJECT='/mnt/d/workbench/iboard' WIN_PROJECT='/mnt/d/workbench/iboard'
export PATH="$HOME/android-sdk/platform-tools:$PATH" export PATH="$HOME/android-sdk/platform-tools:$PATH"
APK_DIR="$WIN_PROJECT/src-tauri/target/$( [ "$VARIANT" = "release" ] && echo release || echo debug )/bundle/android/app/build/outputs/apk/$VARIANT" # 候选 APK 路径 (按优先级, 找到第一个就用)
APK=$(ls -1 "$APK_DIR"/*.apk | head -1) CANDIDATES=(
echo "安装: $APK" # release 通用包 (脚本 build-apk.sh release 产物)
"$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release.apk"
"$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk"
# 各 ABI 单独包
"$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/arm64-v8a/release/app-arm64-v8a-release.apk"
"$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/arm64-v8a/release/app-arm64-v8a-release-unsigned.apk"
# debug 通用包
"$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk"
# Tauri CLI debug 默认路径
"$WIN_PROJECT/src-tauri/target/debug/bundle/android/app/build/outputs/apk/debug/app-debug.apk"
"$WIN_PROJECT/src-tauri/target/release/bundle/android/app/build/outputs/apk/release/app-release.apk"
)
APK=""
for p in "${CANDIDATES[@]}"; do
if [ -f "$p" ]; then APK="$p"; break; fi
done
if [ -z "$APK" ]; then
echo "ERROR: 找不到 $VARIANT APK, 请先 bash scripts/wsl/build-apk.sh $VARIANT"
echo "扫描过路径:"
for p in "${CANDIDATES[@]}"; do echo " $p"; done
exit 1
fi
echo "安装 ($VARIANT): $APK"
adb install -r "$APK" adb install -r "$APK"

View File

@ -13,9 +13,7 @@ sudo apt update
sudo apt install -y \ sudo apt install -y \
build-essential curl wget file libssl-dev pkg-config \ build-essential curl wget file libssl-dev pkg-config \
unzip zip git \ unzip zip git \
openjdk-17-jdk-headless \ openjdk-17-jdk-headless
clang libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev \
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
echo "=== 2. Rust ===" echo "=== 2. Rust ==="
if ! command -v cargo >/dev/null 2>&1; then if ! command -v cargo >/dev/null 2>&1; then
@ -24,30 +22,33 @@ if ! command -v cargo >/dev/null 2>&1; then
fi fi
rustc --version rustc --version
cargo --version cargo --version
# Tauri 2 + Android 需要 rustc 1.77+, 当前默认 toolchain 即可
echo "=== 3. Android SDK + NDK ===" echo "=== 3. Android SDK + NDK ==="
export ANDROID_HOME="$HOME/Android/Sdk" export ANDROID_HOME="$HOME/android-sdk"
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/$(ls -1 $ANDROID_HOME/ndk 2>/dev/null | sort -V | tail -1 || echo 26.1.10909125)" mkdir -p "$ANDROID_HOME/cmdline-tools"
mkdir -p "$HOME/android-sdk/cmdline-tools" if [ ! -d "$ANDROID_HOME/cmdline-tools/latest" ]; then
cd /tmp cd /tmp
if [ ! -d "$HOME/android-sdk/cmdline-tools/latest" ]; then
echo "下载 Android cmdline-tools..." echo "下载 Android cmdline-tools..."
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdline-tools.zip wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdline-tools.zip
unzip -q cmdline-tools.zip unzip -q cmdline-tools.zip
mv cmdline-tools "$HOME/android-sdk/cmdline-tools/latest" mv cmdline-tools "$ANDROID_HOME/cmdline-tools/latest"
fi fi
export PATH="$HOME/android-sdk/cmdline-tools/latest/bin:$PATH" export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
yes | sdkmanager --licenses >/dev/null yes | sdkmanager --licenses >/dev/null || true
sdkmanager --install "platforms;android-34" "build-tools;34.0.0" "platform-tools" \ # 与 src-tauri/gen/android 中 Tauri 生成的配置对齐: compileSdk=36, NDK 27.2
"ndk;26.1.10909125" "cmake;3.22.1" sdkmanager --install "platforms;android-36" "build-tools;36.0.0" "platform-tools" \
"ndk;27.2.12479018" "cmake;3.22.1"
# 默认 NDK 版本 (build-apk.sh 也用这个)
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.2.12479018"
echo "ANDROID_HOME=$ANDROID_HOME" echo "ANDROID_HOME=$ANDROID_HOME"
echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME" echo "ANDROID_NDK_HOME=$ANDROID_NDK_HOME"
echo "=== 4. Node 依赖 (与 Windows 共用 node_modules 也行, 这里重新装) ===" echo "=== 4. Node 依赖 (与 Windows 共用 node_modules 也行, 这里重新装) ==="
cd "$WIN_PROJECT" cd "$WIN_PROJECT"
# 跳过 Windows 二进制不兼容的 sharp 等, npm 会自动 rebuild
npm install npm install
echo "=== 5. Tauri Android 工程初始化 (首次执行) ===" echo "=== 5. Tauri Android 工程初始化 (首次执行) ==="
@ -60,9 +61,18 @@ echo
echo "=== 搭建完成 ===" echo "=== 搭建完成 ==="
echo "接下来:" echo "接下来:"
echo " bash scripts/wsl/build-apk.sh # 打 debug APK" echo " bash scripts/wsl/build-apk.sh # 打 debug APK"
echo " bash scripts/wsl/build-apk.sh release # 打 release APK (需要签名)" echo " bash scripts/wsl/build-apk.sh release # 打 release APK (默认未签名, 需配置 keystore 才能签名)"
echo
echo "Release 签名 (可选, 仅 release 用):"
echo " 1. keytool -genkey -v -keystore ~/iboard-release.keystore -alias iboard -keyalg RSA -keysize 2048 -validity 10000"
echo " 2. 在 ~/.gradle/gradle.properties 写:"
echo " IBOARD_UPLOAD_STORE_FILE=/home/\$USER/iboard-release.keystore"
echo " IBOARD_UPLOAD_KEY_ALIAS=iboard"
echo " IBOARD_UPLOAD_STORE_PASSWORD=..."
echo " IBOARD_UPLOAD_KEY_PASSWORD=..."
echo " 3. 重跑 build-apk.sh release 即可"
echo echo
echo "如要永久生效, 把下面加进 ~/.bashrc:" echo "如要永久生效, 把下面加进 ~/.bashrc:"
echo " export ANDROID_HOME=$ANDROID_HOME" echo " export ANDROID_HOME=$ANDROID_HOME"
echo " export ANDROID_NDK_HOME=$ANDROID_NDK_HOME" echo " export ANDROID_NDK_HOME=$ANDROID_NDK_HOME"
echo " export PATH=\$PATH:$HOME/android-sdk/cmdline-tools/latest/bin:$HOME/android-sdk/platform-tools" echo " export PATH=\$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:\$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"

View File

@ -0,0 +1,37 @@
# iBoard Android 交叉编译配置
# 让 cargo 在编译 Android 目标时强制用 NDK 提供的 clang/clang++
# 否则会回退到宿主机 ld.bfd, 报 "file in wrong format"
#
# 链接器名直接写, 路径靠 PATH 解析 (在 build-apk.sh 里 export 过 NDK toolchain bin)
[target.aarch64-linux-android]
linker = "aarch64-linux-android24-clang"
rustflags = [
"-Clink-arg=-landroid",
"-Clink-arg=-llog",
"-Clink-arg=-lOpenSLES",
]
[target.armv7-linux-androideabi]
linker = "armv7a-linux-androideabi24-clang"
rustflags = [
"-Clink-arg=-landroid",
"-Clink-arg=-llog",
"-Clink-arg=-lOpenSLES",
]
[target.x86_64-linux-android]
linker = "x86_64-linux-android24-clang"
rustflags = [
"-Clink-arg=-landroid",
"-Clink-arg=-llog",
"-Clink-arg=-lOpenSLES",
]
[target.i686-linux-android]
linker = "i686-linux-android24-clang"
rustflags = [
"-Clink-arg=-landroid",
"-Clink-arg=-llog",
"-Clink-arg=-lOpenSLES",
]

View File

@ -22,7 +22,7 @@
} }
], ],
"security": { "security": {
"csp": null "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob: asset: http://asset.localhost; connect-src 'self' ipc: http://ipc.localhost http://bh.vps.honor3.com:3001 https://bh.vps.honor3.com:3001; style-src 'self' 'unsafe-inline'; script-src 'self'"
} }
}, },
"bundle": { "bundle": {

View File

@ -2,6 +2,12 @@
const TOKEN_KEY = 'iboard_token' const TOKEN_KEY = 'iboard_token'
const REMEMBER_KEY = 'iboard_remember' const REMEMBER_KEY = 'iboard_remember'
// API 基址:
// - 默认相对路径 `/api` (web dev: Vite 代理; web prod: 同源 Express 一起托管)
// - 打包 Android 时设 VITE_API_BASE=http://bh.vps.honor3.com:3001 走外网后端
const API_BASE = (import.meta.env.VITE_API_BASE || '').replace(/\/+$/, '')
const API_PREFIX = API_BASE ? API_BASE + '/api' : '/api'
export function getToken() { export function getToken() {
return localStorage.getItem(TOKEN_KEY) || '' return localStorage.getItem(TOKEN_KEY) || ''
} }
@ -20,9 +26,13 @@ export function setRememberFlag(v) {
else localStorage.removeItem(REMEMBER_KEY) else localStorage.removeItem(REMEMBER_KEY)
} }
export function getApiBase() {
return API_BASE
}
export async function api(path, { method = 'GET', body, headers = {} } = {}) { export async function api(path, { method = 'GET', body, headers = {} } = {}) {
const token = getToken() const token = getToken()
const res = await fetch('/api' + path, { const res = await fetch(API_PREFIX + path, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',