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
*.swo
*~
dist/*

View File

@ -1,6 +1,7 @@
# ===============================================================
# iBoard 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程
# 构建: 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
# ===============================================================
FROM node:22-alpine AS builder
@ -24,6 +25,9 @@ COPY . .
# 构建前端 (输出到 dist/) 与 Prisma 客户端
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
# 运行时阶段

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
# iBoard Android APK 打包
# 用法: bash scripts/wsl/build-apk.sh [debug|release]
# release 模式自动构建 4 个 ABI 并出 universal APK
set -euo pipefail
WIN_PROJECT='/mnt/d/workbench/iboard'
@ -12,7 +13,15 @@ echo "构建变体: $VARIANT"
# 环境变量
export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}"
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 工程
cd src-tauri
@ -22,6 +31,10 @@ if [ ! -d "gen/android" ]; then
fi
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/
echo "=== 1. 构建前端 ==="
npm run build
@ -36,30 +49,34 @@ RUST_BUILD_TASKS=(
":app:rustBuildX86_64Release"
":app:rustBuildI686Release"
)
RUST_BUILD_TASKS_DBG=(
":app:rustBuildArm64Debug"
":app:rustBuildArmv7Debug"
":app:rustBuildX86_64Debug"
":app:rustBuildI686Debug"
)
SKIP_ARGS=()
for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done
if [ "$VARIANT" = "release" ]; then
echo "[release] 手工 cargo 编 lib, gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)"
# 4 个 Android 目标的 cargo target triple
ANDROID_TARGETS=(
"aarch64-linux-android"
"armv7-linux-androideabi"
"x86_64-linux-android"
"i686-linux-android"
)
echo " [cargo] build lib release ..."
if [ "$VARIANT" = "release" ]; then
echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)"
for target in "${ANDROID_TARGETS[@]}"; do
echo " [cargo] build lib release for $target ..."
cargo build \
--target aarch64-linux-android \
--target "$target" \
--features custom-protocol \
--lib \
--release
done
echo " [gradle] assembleRelease -x rustBuild* ..."
cd gen/android
./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 "=== 产物 (未签名) ==="
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_STORE_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
echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走"
npx tauri android build --apk

View File

@ -6,7 +6,32 @@ VARIANT="${1:-debug}"
WIN_PROJECT='/mnt/d/workbench/iboard'
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=$(ls -1 "$APK_DIR"/*.apk | head -1)
echo "安装: $APK"
# 候选 APK 路径 (按优先级, 找到第一个就用)
CANDIDATES=(
# 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"

View File

@ -13,9 +13,7 @@ sudo apt update
sudo apt install -y \
build-essential curl wget file libssl-dev pkg-config \
unzip zip git \
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
openjdk-17-jdk-headless
echo "=== 2. Rust ==="
if ! command -v cargo >/dev/null 2>&1; then
@ -24,30 +22,33 @@ if ! command -v cargo >/dev/null 2>&1; then
fi
rustc --version
cargo --version
# Tauri 2 + Android 需要 rustc 1.77+, 当前默认 toolchain 即可
echo "=== 3. Android SDK + NDK ==="
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 "$HOME/android-sdk/cmdline-tools"
cd /tmp
if [ ! -d "$HOME/android-sdk/cmdline-tools/latest" ]; then
export ANDROID_HOME="$HOME/android-sdk"
mkdir -p "$ANDROID_HOME/cmdline-tools"
if [ ! -d "$ANDROID_HOME/cmdline-tools/latest" ]; then
cd /tmp
echo "下载 Android cmdline-tools..."
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O 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
export PATH="$HOME/android-sdk/cmdline-tools/latest/bin:$PATH"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
yes | sdkmanager --licenses >/dev/null
sdkmanager --install "platforms;android-34" "build-tools;34.0.0" "platform-tools" \
"ndk;26.1.10909125" "cmake;3.22.1"
yes | sdkmanager --licenses >/dev/null || true
# 与 src-tauri/gen/android 中 Tauri 生成的配置对齐: compileSdk=36, NDK 27.2
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_NDK_HOME=$ANDROID_NDK_HOME"
echo "=== 4. Node 依赖 (与 Windows 共用 node_modules 也行, 这里重新装) ==="
cd "$WIN_PROJECT"
# 跳过 Windows 二进制不兼容的 sharp 等, npm 会自动 rebuild
npm install
echo "=== 5. Tauri Android 工程初始化 (首次执行) ==="
@ -60,9 +61,18 @@ echo
echo "=== 搭建完成 ==="
echo "接下来:"
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 "如要永久生效, 把下面加进 ~/.bashrc:"
echo " export ANDROID_HOME=$ANDROID_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": {
"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": {

View File

@ -2,6 +2,12 @@
const TOKEN_KEY = 'iboard_token'
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() {
return localStorage.getItem(TOKEN_KEY) || ''
}
@ -20,9 +26,13 @@ export function setRememberFlag(v) {
else localStorage.removeItem(REMEMBER_KEY)
}
export function getApiBase() {
return API_BASE
}
export async function api(path, { method = 'GET', body, headers = {} } = {}) {
const token = getToken()
const res = await fetch('/api' + path, {
const res = await fetch(API_PREFIX + path, {
method,
headers: {
'Content-Type': 'application/json',