feat: 实现减脂体重管理App完整功能
- 实现拍照识别食物功能(集成大语言模型视觉能力) - 实现智能对话功能(集成大语言模型流式输出) - 实现食物记录和卡路里管理功能 - 实现体重记录和统计功能 - 实现健康数据管理页面 - 配置数据库表结构(用户、食物记录、体重记录) - 实现Express后端API路由 - 配置Tab导航和前端页面 - 采用健康运动配色方案
This commit is contained in:
46
.cozeproj/scripts/dev_build.sh
Normal file
46
.cozeproj/scripts/dev_build.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
if [ -z "${BASH_VERSION:-}" ]; then exec /usr/bin/env bash "$0" "$@"; fi
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(pwd)"
|
||||
PREVIEW_DIR="${COZE_PREVIEW_DIR:-/source/preview}"
|
||||
|
||||
# ==================== 配置项 ====================
|
||||
SERVER_DIR="app"
|
||||
EXPO_DIR="expo"
|
||||
CHECK_HASH_SCRIPT="$ROOT_DIR/check_hash.py"
|
||||
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
echo "error:命令 $1 未找到,请先安装"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==================== 开始构建 ===================="
|
||||
|
||||
echo "检查根目录 pre_install.py"
|
||||
if [ -f "$PREVIEW_DIR/pre_install.py" ]; then
|
||||
echo "执行:python $PREVIEW_DIR/pre_install.py"
|
||||
python "$PREVIEW_DIR/pre_install.py" || echo "pre_install.py 执行失败"
|
||||
fi
|
||||
|
||||
echo "开始执行构建脚本(build_dev.sh)..."
|
||||
echo "正在检查依赖命令是否存在..."
|
||||
# 检查核心命令
|
||||
check_command "pnpm"
|
||||
check_command "npm"
|
||||
|
||||
echo "==================== 安装项目依赖 ===================="
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo "项目目录下无 package.json,不是合法的 Node.js 项目"
|
||||
fi
|
||||
# 步骤 2.1/2.2:安装项目依赖
|
||||
pnpm install --registry=https://registry.npmmirror.com || echo "Expo 项目依赖安装失败(pnpm 执行出错)"
|
||||
|
||||
echo "检查根目录 post_install.py"
|
||||
if [ -f "$PREVIEW_DIR/post_install.py" ]; then
|
||||
echo "执行:python $PREVIEW_DIR/post_install.py"
|
||||
python "$PREVIEW_DIR/post_install.py" || echo "post_install.py 执行失败"
|
||||
fi
|
||||
|
||||
echo "==================== 依赖安装完成!====================\n"
|
||||
echo "下一步:执行 ./deploy_run.sh 启动服务"
|
||||
228
.cozeproj/scripts/dev_run.sh
Normal file
228
.cozeproj/scripts/dev_run.sh
Normal file
@@ -0,0 +1,228 @@
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
PREVIEW_DIR="${COZE_PREVIEW_DIR:-/source/preview}"
|
||||
LOG_DIR="${COZE_LOG_DIR:-$ROOT_DIR/logs}"
|
||||
LOG_FILE="$LOG_DIR/app.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
# ==================== 配置项 ====================
|
||||
# Server 服务配置
|
||||
SERVER_HOST="0.0.0.0"
|
||||
SERVER_PORT="9091"
|
||||
# Expo 项目配置
|
||||
EXPO_HOST="0.0.0.0"
|
||||
EXPO_DIR="expo"
|
||||
EXPO_PORT="5000"
|
||||
WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
|
||||
ASSUME_YES="1"
|
||||
EXPO_PUBLIC_BACKEND_BASE_URL="${EXPO_PUBLIC_BACKEND_BASE_URL:-$WEB_URL}"
|
||||
EXPO_PUBLIC_COZE_PROJECT_ID="${COZE_PROJECT_ID:-}"
|
||||
|
||||
EXPO_PACKAGER_PROXY_URL="${EXPO_PUBLIC_BACKEND_BASE_URL}"
|
||||
export EXPO_PUBLIC_BACKEND_BASE_URL EXPO_PACKAGER_PROXY_URL EXPO_PUBLIC_COZE_PROJECT_ID
|
||||
# 运行时变量(为避免 set -u 的未绑定错误,预置为空)
|
||||
SERVER_PID=""
|
||||
EXPO_PID=""
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
echo "error:命令 $1 未找到,请先安装"
|
||||
fi
|
||||
}
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-y|--yes)
|
||||
ASSUME_YES="1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
is_port_free() {
|
||||
! lsof -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1
|
||||
}
|
||||
choose_next_free_port() {
|
||||
local start=$1
|
||||
local p=$start
|
||||
while ! is_port_free "$p"; do
|
||||
p=$((p+1))
|
||||
done
|
||||
echo "$p"
|
||||
}
|
||||
ensure_port() {
|
||||
local var_name=$1
|
||||
local port_val=$2
|
||||
if is_port_free "$port_val"; then
|
||||
echo "端口未占用:$port_val"
|
||||
eval "$var_name=$port_val"
|
||||
else
|
||||
echo "端口已占用:$port_val"
|
||||
local choice
|
||||
if [ "$ASSUME_YES" = "1" ]; then choice="Y"; else read -r -p "是否关闭该端口的进程?[Y/n] " choice || choice="Y"; fi
|
||||
if [ -z "$choice" ] || [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
|
||||
if command -v lsof &> /dev/null; then
|
||||
local pids
|
||||
pids=$(lsof -t -i tcp:"$port_val" -sTCP:LISTEN 2>/dev/null || true)
|
||||
if [ -n "$pids" ]; then
|
||||
echo "正在关闭进程:$pids"
|
||||
kill -9 $pids 2>/dev/null || echo "关闭进程失败:$pids"
|
||||
eval "$var_name=$port_val"
|
||||
else
|
||||
echo "未获取到占用该端口的进程"
|
||||
eval "$var_name=$port_val"
|
||||
fi
|
||||
else
|
||||
echo "缺少 lsof,无法自动关闭进程"
|
||||
eval "$var_name=$port_val"
|
||||
fi
|
||||
else
|
||||
local new_port
|
||||
new_port=$(choose_next_free_port "$port_val")
|
||||
info "使用新的端口:$new_port"
|
||||
eval "$var_name=$new_port"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
pipe_to_log() {
|
||||
local source="${1:-CLIENT}"
|
||||
local raw_log="${2:-}"
|
||||
local line timestamp ts msg record
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [ -n "$raw_log" ]; then
|
||||
echo "$line" >> "$raw_log"
|
||||
fi
|
||||
line=$(echo "[$source] $line" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g')
|
||||
msg="${line}"
|
||||
echo "$msg"
|
||||
done
|
||||
}
|
||||
|
||||
wait_port_connectable() {
|
||||
local host=$1 port=$2 retries=${3:-10}
|
||||
for _ in $(seq 1 "$retries"); do
|
||||
nc -z -w 1 "$host" "$port" >/dev/null 2>&1 && return 0
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
start_expo() {
|
||||
local offline="${1:-0}"
|
||||
|
||||
pushd "$ROOT_DIR/client"
|
||||
|
||||
if [ "$offline" = "1" ]; then
|
||||
( EXPO_OFFLINE=1 EXPO_NO_DEPENDENCY_VALIDATION=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" EXPO_PUBLIC_COZE_PROJECT_ID="$EXPO_PUBLIC_COZE_PROJECT_ID" \
|
||||
nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$ROOT_DIR/logs/client.log" ) &
|
||||
else
|
||||
( EXPO_NO_DEPENDENCY_VALIDATION=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" EXPO_PUBLIC_COZE_PROJECT_ID="$EXPO_PUBLIC_COZE_PROJECT_ID" \
|
||||
nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$ROOT_DIR/logs/client.log" ) &
|
||||
fi
|
||||
EXPO_PID=$!
|
||||
disown $EXPO_PID 2>/dev/null || true
|
||||
|
||||
popd
|
||||
}
|
||||
|
||||
detect_expo_fetch_failed() {
|
||||
local timeout="${1:-8}"
|
||||
local waited=0
|
||||
local log_file="$ROOT_DIR/logs/client.log"
|
||||
while [ "$waited" -lt "$timeout" ]; do
|
||||
if [ -f "$log_file" ] && grep -q "TypeError: fetch failed" "$log_file" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
waited=$((waited+1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ==================== 前置检查 ====================
|
||||
# 关掉nginx进程
|
||||
ps -ef | grep nginx | grep -v grep | awk '{print $2}' | xargs -r kill -9
|
||||
|
||||
echo "检查根目录 pre_install.py"
|
||||
if [ -f "$PREVIEW_DIR/pre_install.py" ]; then
|
||||
echo "执行:python $PREVIEW_DIR/pre_install.py"
|
||||
python "$PREVIEW_DIR/pre_install.py" || echo "pre_install.py 执行失败"
|
||||
fi
|
||||
|
||||
echo "检查根目录 post_install.py"
|
||||
if [ -f "$PREVIEW_DIR/post_install.py" ]; then
|
||||
echo "执行:python $PREVIEW_DIR/post_install.py"
|
||||
python "$PREVIEW_DIR/post_install.py" || echo "post_install.py 执行失败"
|
||||
fi
|
||||
|
||||
echo "==================== 开始启动 ===================="
|
||||
echo "开始执行服务启动脚本(start_dev.sh)..."
|
||||
echo "正在检查依赖命令和目录是否存在..."
|
||||
# 检查核心命令
|
||||
check_command "npm"
|
||||
check_command "pnpm"
|
||||
check_command "lsof"
|
||||
check_command "bash"
|
||||
|
||||
echo "准备日志目录:$ROOT_DIR/logs"
|
||||
mkdir -p "$ROOT_DIR/logs"
|
||||
# 端口占用预检查与处理
|
||||
ensure_port SERVER_PORT "$SERVER_PORT"
|
||||
ensure_port EXPO_PORT "$EXPO_PORT"
|
||||
|
||||
echo "==================== 启动 server 服务 ===================="
|
||||
echo "正在执行:pnpm run dev (server)"
|
||||
( pushd "$ROOT_DIR/server" > /dev/null && SERVER_PORT="$SERVER_PORT" nohup pnpm run dev; popd > /dev/null ) &
|
||||
SERVER_PID=$!
|
||||
disown $SERVER_PID 2>/dev/null || true
|
||||
if [ -z "${SERVER_PID}" ]; then
|
||||
echo "无法获取 server 后台进程 PID"
|
||||
fi
|
||||
echo "server 服务已启动,进程 ID:${SERVER_PID:-unknown}"
|
||||
|
||||
echo "==================== 启动 Expo 项目 ===================="
|
||||
echo "开始启动 Expo 服务,端口 ${EXPO_PORT}"
|
||||
start_expo 0
|
||||
if detect_expo_fetch_failed 8; then
|
||||
echo "Expo 启动检测到网络错误:TypeError: fetch failed,启用离线模式重试"
|
||||
if [ -n "${EXPO_PID}" ]; then kill -9 "$EXPO_PID" 2>/dev/null || true; fi
|
||||
: > "$ROOT_DIR/logs/client.log"
|
||||
start_expo 1
|
||||
fi
|
||||
# 输出以下环境变量,确保 Expo 项目能正确连接到 Server 服务
|
||||
echo "Expo 环境变量配置:"
|
||||
echo "EXPO_PUBLIC_BACKEND_BASE_URL=${EXPO_PUBLIC_BACKEND_BASE_URL}"
|
||||
echo "EXPO_PACKAGER_PROXY_URL=${EXPO_PACKAGER_PROXY_URL}"
|
||||
echo "EXPO_PUBLIC_COZE_PROJECT_ID=${EXPO_PUBLIC_COZE_PROJECT_ID}"
|
||||
if [ -z "${EXPO_PID}" ]; then
|
||||
echo "无法获取 Expo 后台进程 PID"
|
||||
fi
|
||||
|
||||
echo "所有服务已启动。Server PID: ${SERVER_PID}, Expo PID: ${EXPO_PID}"
|
||||
|
||||
echo "检查 Server 服务端口:$SERVER_HOST:$SERVER_PORT"
|
||||
if wait_port_connectable "$SERVER_HOST" "$SERVER_PORT" 10 2; then
|
||||
echo "端口可连接:$SERVER_HOST:$SERVER_PORT"
|
||||
else
|
||||
echo "端口不可连接:$SERVER_HOST:$SERVER_PORT 10 次)"
|
||||
fi
|
||||
|
||||
echo "检查 Expo 服务端口:$EXPO_HOST:$EXPO_PORT"
|
||||
if wait_port_connectable "$EXPO_HOST" "$EXPO_PORT" 10 2; then
|
||||
echo "端口可连接:$EXPO_HOST:$EXPO_PORT"
|
||||
else
|
||||
echo "端口不可连接:$EXPO_HOST:$EXPO_PORT(已尝试 10 次)"
|
||||
fi
|
||||
|
||||
echo "服务端口检查完成"
|
||||
|
||||
echo "检查根目录 post_run.py"
|
||||
if [ -f "$ROOT_DIR/post_run.py" ]; then
|
||||
echo "启动检查中"
|
||||
python "$ROOT_DIR/post_run.py" --port "$EXPO_PORT" || echo "post_run.py 执行失败"
|
||||
echo "启动检查结束"
|
||||
fi
|
||||
|
||||
echo "==================== 服务启动完成 ===================="
|
||||
47
.cozeproj/scripts/prod_build.sh
Normal file
47
.cozeproj/scripts/prod_build.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
if [ -z "${BASH_VERSION:-}" ]; then exec /usr/bin/env bash "$0" "$@"; fi
|
||||
set -euo pipefail
|
||||
ROOT_DIR="$(pwd)"
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
info() {
|
||||
echo "[INFO] $1"
|
||||
}
|
||||
warn() {
|
||||
echo "[WARN] $1"
|
||||
}
|
||||
error() {
|
||||
echo "[ERROR] $1"
|
||||
exit 1
|
||||
}
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
error "命令 $1 未找到,请先安装"
|
||||
fi
|
||||
}
|
||||
|
||||
info "==================== 开始构建 ===================="
|
||||
info "开始执行构建脚本(build_prod.sh)..."
|
||||
info "正在检查依赖命令是否存在..."
|
||||
# 检查核心命令
|
||||
check_command "pnpm"
|
||||
check_command "npm"
|
||||
|
||||
# ==================== 安装 Node 依赖 ====================
|
||||
info "==================== 安装 Node 依赖 ===================="
|
||||
info "开始安装 Node 依赖"
|
||||
if [ -f "$ROOT_DIR/package.json" ]; then
|
||||
info "进入目录:$ROOT_DIR"
|
||||
info "正在执行:pnpm install"
|
||||
(cd "$ROOT_DIR" && pnpm install --registry=https://registry.npmmirror.com) || error "Node 依赖安装失败"
|
||||
else
|
||||
warn "未找到 $ROOT_DIR/package.json 文件,请检查路径是否正确"
|
||||
fi
|
||||
info "==================== 依赖安装完成!====================\n"
|
||||
|
||||
info "==================== dist打包 ===================="
|
||||
info "开始执行:pnpm run build (server)"
|
||||
(pushd "$ROOT_DIR/server" > /dev/null && pnpm run build; popd > /dev/null) || error "dist打包失败"
|
||||
info "==================== dist打包完成!====================\n"
|
||||
|
||||
info "下一步:执行 ./prod_run.sh 启动服务"
|
||||
34
.cozeproj/scripts/prod_run.sh
Normal file
34
.cozeproj/scripts/prod_run.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# 产物部署使用
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(pwd)"
|
||||
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
PORT="${PORT:-5000}"
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
info() {
|
||||
echo "[INFO] $1"
|
||||
}
|
||||
warn() {
|
||||
echo "[WARN] $1"
|
||||
}
|
||||
error() {
|
||||
echo "[ERROR] $1"
|
||||
exit 1
|
||||
}
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
error "命令 $1 未找到,请先安装"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============== 启动服务 ======================
|
||||
# 检查核心命令
|
||||
check_command "pnpm"
|
||||
check_command "npm"
|
||||
|
||||
info "开始执行:pnpm run start (server)"
|
||||
(pushd "$ROOT_DIR/server" > /dev/null && PORT="$PORT" pnpm run start; popd > /dev/null) || error "服务启动失败"
|
||||
info "服务启动完成!\n"
|
||||
45
.cozeproj/scripts/server_dev_run.sh
Normal file
45
.cozeproj/scripts/server_dev_run.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
SERVER_DIR="$ROOT_DIR/server"
|
||||
LOG_DIR="$ROOT_DIR/logs"
|
||||
LOG_FILE="$LOG_DIR/server.log"
|
||||
SERVER_PORT="${SERVER_PORT:-9091}"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
pipe_to_log() {
|
||||
local source="${1:-SERVER}"
|
||||
local raw_log="${2:-}"
|
||||
local line
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
if [ -n "$raw_log" ]; then
|
||||
echo "$line" >> "$raw_log"
|
||||
fi
|
||||
line=$(echo "[$source] $line" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g')
|
||||
echo "$line"
|
||||
done
|
||||
}
|
||||
|
||||
kill_old_server() {
|
||||
if command -v lsof &> /dev/null; then
|
||||
local pids
|
||||
pids=$(lsof -t -i tcp:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
|
||||
if [ -n "$pids" ]; then
|
||||
echo "正在关闭旧的 server 进程:$pids"
|
||||
kill -9 $pids 2>/dev/null || echo "关闭进程失败:$pids"
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==================== Server Dev Run ===================="
|
||||
echo "Server 目录:$SERVER_DIR"
|
||||
echo "Server 端口:$SERVER_PORT"
|
||||
echo "日志文件:$LOG_FILE"
|
||||
|
||||
kill_old_server
|
||||
|
||||
echo "启动 server 服务..."
|
||||
cd "$SERVER_DIR"
|
||||
NODE_ENV=development PORT="$SERVER_PORT" npx tsx ./src/index.ts 2>&1 | pipe_to_log "SERVER" "$LOG_FILE"
|
||||
Reference in New Issue
Block a user