Compare commits
No commits in common. "4b817b320c2f79c3671431c4b45b7bd0b5dae934" and "ca2fe80adb28353d8beb387c411c5127764e4466" have entirely different histories.
4b817b320c
...
ca2fe80adb
313
backend/dev.sh
313
backend/dev.sh
@ -1,313 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Hot-Reload Dev Script for TopFans Backend
|
|
||||||
# Usage: ./dev.sh
|
|
||||||
#
|
|
||||||
# Requires: fswatch (Mac: brew install fswatch) or inotifywait (Linux)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 信号捕获:优雅退出
|
|
||||||
cleanup() {
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🛑 收到停止信号,正在关闭所有服务...${NC}"
|
|
||||||
|
|
||||||
# 先杀所有 watcher 和 debounce 进程(防止继续触发重启)
|
|
||||||
if [ -f /tmp/dev_sh_watchers.tmp ]; then
|
|
||||||
while read watcher_pid; do
|
|
||||||
kill "$watcher_pid" 2>/dev/null || true
|
|
||||||
done < /tmp/dev_sh_watchers.tmp
|
|
||||||
rm -f /tmp/dev_sh_watchers.tmp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理所有 PID 文件并杀服务进程
|
|
||||||
for service in gateway activityService galleryService socialService assetService userService; do
|
|
||||||
pkill -9 -f "$service" 2>/dev/null || true
|
|
||||||
rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart"
|
|
||||||
echo -e "${YELLOW} 🛑 $service 已停止${NC}"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo -e "${GREEN}✅ 所有服务已关闭${NC}"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup SIGINT SIGTERM
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
|
|
||||||
# Detect platform
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
WATCHER_TOOL="fswatch"
|
|
||||||
WATCHER_CMD="fswatch -r"
|
|
||||||
elif [[ "$(uname)" == "Linux" ]]; then
|
|
||||||
WATCHER_TOOL="inotifywait"
|
|
||||||
WATCHER_CMD="inotifywait -r -m -e modify,create,write"
|
|
||||||
else
|
|
||||||
echo -e "${RED}不支持的平台${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v "$WATCHER_TOOL" &> /dev/null; then
|
|
||||||
echo -e "${RED}缺少工具: $WATCHER_TOOL${NC}"
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
echo "安装方法: brew install fswatch"
|
|
||||||
else
|
|
||||||
echo "安装方法: sudo apt install inotify-tools (Debian/Ubuntu)"
|
|
||||||
echo " sudo yum install inotify-tools (CentOS/RHEL)"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ENV_FILE="$SCRIPT_DIR/.env"
|
|
||||||
if [ -f "$ENV_FILE" ]; then
|
|
||||||
echo -e "${GREEN}📄 加载 .env 文件...${NC}"
|
|
||||||
set -a
|
|
||||||
source "$ENV_FILE"
|
|
||||||
set +a
|
|
||||||
fi
|
|
||||||
|
|
||||||
DB_HOST="${DB_HOST:-localhost}"
|
|
||||||
DB_PORT="${DB_PORT:-5432}"
|
|
||||||
DB_USER="${DB_USER:-haihuizhu}"
|
|
||||||
DB_PASSWORD="${DB_PASSWORD:-admin}"
|
|
||||||
DB_NAME="${DB_NAME:-top-fans}"
|
|
||||||
DB_ARGS=(-db-host="$DB_HOST" -db-port="$DB_PORT" -db-user="$DB_USER" -db-password="$DB_PASSWORD" -db-name="$DB_NAME")
|
|
||||||
|
|
||||||
# 启动一个服务
|
|
||||||
# 用法: start_service name binary port use_db
|
|
||||||
start_service() {
|
|
||||||
local name=$1
|
|
||||||
local binary=$2
|
|
||||||
local port=$3
|
|
||||||
local use_db=$4
|
|
||||||
|
|
||||||
echo -e "${GREEN}🚀 启动 $name...${NC}"
|
|
||||||
|
|
||||||
if [ "$use_db" = "1" ]; then
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
|
|
||||||
else
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
|
|
||||||
fi
|
|
||||||
local pid=$!
|
|
||||||
# 保存 PID 到文件
|
|
||||||
echo $pid > "/tmp/dev_sh_${name}.pid"
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ps -p $pid > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ $name 已启动 (PID: $pid, 端口: $port)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $name 启动失败${NC}"
|
|
||||||
echo -e "${YELLOW}查看日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 构建一个服务(在服务目录下执行 go build)
|
|
||||||
# 用法: build_service name dir binary
|
|
||||||
build_service() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
|
|
||||||
if [ ! -d "$SCRIPT_DIR/$dir" ]; then
|
|
||||||
echo -e "${RED}❌ $dir 目录不存在${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$SCRIPT_DIR/$dir"
|
|
||||||
if go build -o "$SCRIPT_DIR/$binary" . 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ $name 编译成功${NC}"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $name 编译失败${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 重建并重启单个服务(构建成功后才杀旧进程)
|
|
||||||
# 用法: restart_service name dir binary port use_db
|
|
||||||
restart_service() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
local port=$4
|
|
||||||
local use_db=$5
|
|
||||||
local pid_file="/tmp/dev_sh_${name}.pid"
|
|
||||||
|
|
||||||
# Step 1: 编译(先编译,编译成功才杀旧进程)
|
|
||||||
if [ ! -d "$SCRIPT_DIR/$dir" ]; then
|
|
||||||
echo -e "${RED}❌ $dir 目录不存在,跳过${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$SCRIPT_DIR/$dir"
|
|
||||||
if ! go build -o "$SCRIPT_DIR/$binary" . 2>&1; then
|
|
||||||
echo -e "${RED}❌ [$name] 编译失败,旧进程保持运行${NC}"
|
|
||||||
echo -e "${RED} 查看详细日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ [$name] 编译成功${NC}"
|
|
||||||
|
|
||||||
# Step 2: 编译成功后,杀旧进程(从 PID 文件读取)
|
|
||||||
if [ -f "$pid_file" ]; then
|
|
||||||
local old_pid=$(cat "$pid_file")
|
|
||||||
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
|
|
||||||
kill "$old_pid" 2>/dev/null || true
|
|
||||||
echo -e "${YELLOW}🛑 [$name] 旧进程 (PID: $old_pid) 已终止${NC}"
|
|
||||||
fi
|
|
||||||
rm -f "$pid_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: 启动新进程
|
|
||||||
sleep 1
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
if [ "$use_db" = "1" ]; then
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
|
|
||||||
else
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
|
|
||||||
fi
|
|
||||||
local new_pid=$!
|
|
||||||
echo $new_pid > "$pid_file"
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ps -p $new_pid > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ [$name] 已重启 (PID: $new_pid)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ [$name] 重启失败,查看日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 启动文件监听器
|
|
||||||
# 用法: start_watcher name dir binary port use_db
|
|
||||||
start_watcher() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
local port=$4
|
|
||||||
local use_db=$5
|
|
||||||
local watch_path="$SCRIPT_DIR/$dir"
|
|
||||||
local restart_marker="/tmp/dev_sh_${name}_restart"
|
|
||||||
|
|
||||||
if [ ! -d "$watch_path" ]; then
|
|
||||||
echo -e "${RED}❌ 监听目录不存在: $watch_path${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理可能遗留的重启标记
|
|
||||||
rm -f "$restart_marker"
|
|
||||||
|
|
||||||
(
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
fswatch -r "$watch_path" --exclude='\.git' --exclude='_test\.go$'
|
|
||||||
else
|
|
||||||
inotifywait -r -m -e modify,create,write "$watch_path" --exclude='\.git' --exclude='_test\.go$'
|
|
||||||
fi | while read event; do
|
|
||||||
# 时间戳防抖:每次事件更新标记文件
|
|
||||||
date +%s%N > "$restart_marker"
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
local watcher_pid=$!
|
|
||||||
echo "$watcher_pid" >> /tmp/dev_sh_watchers.tmp
|
|
||||||
echo -e "${GREEN}👁️ [$name] 文件监听器已启动 (PID: $watcher_pid)${NC}"
|
|
||||||
|
|
||||||
# 后台防抖循环:每 300ms 检查一次是否需要重启
|
|
||||||
(
|
|
||||||
while true; do
|
|
||||||
sleep 0.3
|
|
||||||
if [ ! -f "$restart_marker" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
local now=$(date +%s%N)
|
|
||||||
local last_time=$(cat "$restart_marker" 2>/dev/null || echo 0)
|
|
||||||
local elapsed=$((now - last_time))
|
|
||||||
if (( elapsed >= 300000000 )); then
|
|
||||||
# 距上次事件已过 300ms,执行重启
|
|
||||||
rm -f "$restart_marker"
|
|
||||||
restart_service "$name" "$dir" "$binary" "$port" "$use_db"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
local debounce_pid=$!
|
|
||||||
echo "$debounce_pid" >> /tmp/dev_sh_watchers.tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo -e "${GREEN} TopFans Backend 热更新开发模式${NC}"
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}数据库: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}${NC}"
|
|
||||||
echo -e "${YELLOW}文件监听器: $WATCHER_TOOL${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 初始化监听器 PID 列表
|
|
||||||
> /tmp/dev_sh_watchers.tmp
|
|
||||||
|
|
||||||
# 清理残留 PID 文件(上次非正常退出可能留下)
|
|
||||||
for service in activityService galleryService socialService assetService userService gateway; do
|
|
||||||
rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 停止现有服务(清理环境)
|
|
||||||
echo -e "${YELLOW}🛑 停止现有服务...${NC}"
|
|
||||||
for service in gateway userService socialService assetService galleryService activityService; do
|
|
||||||
pkill -9 -f "$service" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# 先构建所有服务
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🔨 预编译所有服务...${NC}"
|
|
||||||
build_service "gateway" "gateway" "gateway/gateway"
|
|
||||||
build_service "userService" "services/userService" "services/userService/userService"
|
|
||||||
build_service "assetService" "services/assetService" "services/assetService/assetService"
|
|
||||||
build_service "socialService" "services/socialService" "services/socialService/socialService"
|
|
||||||
build_service "galleryService" "services/galleryService" "services/galleryService/galleryService"
|
|
||||||
build_service "activityService" "services/activityService" "services/activityService/activityService"
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
|
|
||||||
# 启动所有服务
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🚀 启动所有服务...${NC}"
|
|
||||||
start_service "userService" "services/userService/userService" 20000 1
|
|
||||||
start_service "assetService" "services/assetService/assetService" 20003 1
|
|
||||||
start_service "socialService" "services/socialService/socialService" 20002 1
|
|
||||||
start_service "galleryService" "services/galleryService/galleryService" 20004 1
|
|
||||||
start_service "activityService" "services/activityService/activityService" 20005 1
|
|
||||||
start_service "gateway" "gateway/gateway" 8080 0
|
|
||||||
|
|
||||||
# 启动所有文件监听器
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}👁️ 启动所有文件监听器...${NC}"
|
|
||||||
start_watcher "gateway" "gateway" "gateway/gateway" 8080 0
|
|
||||||
start_watcher "userService" "services/userService" "services/userService/userService" 20000 1
|
|
||||||
start_watcher "assetService" "services/assetService" "services/assetService/assetService" 20003 1
|
|
||||||
start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1
|
|
||||||
start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1
|
|
||||||
start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo -e "${GREEN} 热更新开发模式已启动!${NC}"
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}服务地址:${NC}"
|
|
||||||
echo " - Gateway: http://localhost:8080"
|
|
||||||
echo " - Swagger UI: http://localhost:8080/swagger/index.html"
|
|
||||||
echo " - User Service: tri://localhost:20000"
|
|
||||||
echo " - Social Service: tri://localhost:20002"
|
|
||||||
echo " - Asset Service: tri://localhost:20003"
|
|
||||||
echo " - Gallery Service: tri://localhost:20004"
|
|
||||||
echo " - Activity Service: tri://localhost:20005"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}按 Ctrl+C 停止所有服务${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 保持脚本运行
|
|
||||||
wait
|
|
||||||
@ -315,30 +315,7 @@ func (ctrl *ActivityController) PurchaseItem(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 非 active 阶段:通过 message 信号返回 200 + activity_status
|
// 转换响应
|
||||||
activityStatusMap := map[string]string{
|
|
||||||
"activity:expired": "expired",
|
|
||||||
"activity:pending": "pending",
|
|
||||||
"activity:completed": "completed",
|
|
||||||
"activity:incomplete": "incomplete",
|
|
||||||
"activity:active": "active",
|
|
||||||
}
|
|
||||||
activityMessageMap := map[string]string{
|
|
||||||
"expired": "活动已结束",
|
|
||||||
"pending": "活动未开始",
|
|
||||||
"completed": "活动已完成",
|
|
||||||
"incomplete": "活动未完成",
|
|
||||||
"active": "活动进行中",
|
|
||||||
}
|
|
||||||
if status, ok := activityStatusMap[resp.Base.Message]; ok {
|
|
||||||
response.Success(c, map[string]interface{}{
|
|
||||||
"activity_status": status,
|
|
||||||
"message": activityMessageMap[status],
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 正常购买成功
|
|
||||||
data := convertPurchaseResponse(resp)
|
data := convertPurchaseResponse(resp)
|
||||||
response.Success(c, data)
|
response.Success(c, data)
|
||||||
}
|
}
|
||||||
@ -438,41 +415,27 @@ func (ctrl *ActivityController) GetContributionRanking(c *gin.Context) {
|
|||||||
response.Success(c, data)
|
response.Success(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatTimestamp 将时间戳转换为 YYYY-MM-DD HH:mm:ss 格式
|
|
||||||
// 毫秒级时间戳除以1000转换为秒级
|
|
||||||
func formatTimestamp(timestamp int64) string {
|
|
||||||
if timestamp <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// 毫秒级时间戳(13位以上,如1773411298000)除以1000转为秒级
|
|
||||||
if timestamp >= 1e12 {
|
|
||||||
timestamp = timestamp / 1000
|
|
||||||
}
|
|
||||||
return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertActivityListResponse 转换活动列表响应
|
// convertActivityListResponse 转换活动列表响应
|
||||||
func convertActivityListResponse(resp *pbActivity.GetActivityListResponse) map[string]interface{} {
|
func convertActivityListResponse(resp *pbActivity.GetActivityListResponse) map[string]interface{} {
|
||||||
activities := make([]map[string]interface{}, 0, len(resp.Activities))
|
activities := make([]map[string]interface{}, 0, len(resp.Activities))
|
||||||
for _, activity := range resp.Activities {
|
for _, activity := range resp.Activities {
|
||||||
activities = append(activities, map[string]interface{}{
|
activities = append(activities, map[string]interface{}{
|
||||||
"id": activity.Id,
|
"id": activity.Id,
|
||||||
"activity_type": activity.ActivityType,
|
"activity_type": activity.ActivityType,
|
||||||
"title": activity.Title,
|
"title": activity.Title,
|
||||||
"theme": activity.Theme,
|
"theme": activity.Theme,
|
||||||
"description": activity.Description,
|
"description": activity.Description,
|
||||||
"star_id": activity.StarId,
|
"star_id": activity.StarId,
|
||||||
"start_time": formatTimestamp(activity.StartTime),
|
"start_time": activity.StartTime,
|
||||||
"end_time": formatTimestamp(activity.EndTime),
|
"end_time": activity.EndTime,
|
||||||
"overall_end_time": formatTimestamp(activity.OverallEndTime),
|
"target_progress": activity.TargetProgress,
|
||||||
"target_progress": activity.TargetProgress,
|
"current_progress": activity.CurrentProgress,
|
||||||
"current_progress": activity.CurrentProgress,
|
"status": activity.Status,
|
||||||
"status": activity.Status,
|
"current_stage": activity.CurrentStage,
|
||||||
"current_stage": activity.CurrentStage,
|
"cover_image": activity.CoverImage,
|
||||||
"cover_image": activity.CoverImage,
|
"banner_image": activity.BannerImage,
|
||||||
"banner_image": activity.BannerImage,
|
"current_stage_background": activity.CurrentStageBackground,
|
||||||
"current_stage_background": activity.CurrentStageBackground,
|
"current_stage_title": activity.CurrentStageTitle,
|
||||||
"current_stage_title": activity.CurrentStageTitle,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,24 +462,23 @@ func convertActivityResponse(activity *pbActivity.Activity) map[string]interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"id": activity.Id,
|
"id": activity.Id,
|
||||||
"activity_type": activity.ActivityType,
|
"activity_type": activity.ActivityType,
|
||||||
"title": activity.Title,
|
"title": activity.Title,
|
||||||
"theme": activity.Theme,
|
"theme": activity.Theme,
|
||||||
"description": activity.Description,
|
"description": activity.Description,
|
||||||
"star_id": activity.StarId,
|
"star_id": activity.StarId,
|
||||||
"start_time": formatTimestamp(activity.StartTime),
|
"start_time": activity.StartTime,
|
||||||
"end_time": formatTimestamp(activity.EndTime),
|
"end_time": activity.EndTime,
|
||||||
"overall_end_time": formatTimestamp(activity.OverallEndTime),
|
"target_progress": activity.TargetProgress,
|
||||||
"target_progress": activity.TargetProgress,
|
"current_progress": activity.CurrentProgress,
|
||||||
"current_progress": activity.CurrentProgress,
|
"status": activity.Status,
|
||||||
"status": activity.Status,
|
"current_stage": activity.CurrentStage,
|
||||||
"current_stage": activity.CurrentStage,
|
"cover_image": activity.CoverImage,
|
||||||
"cover_image": activity.CoverImage,
|
"banner_image": activity.BannerImage,
|
||||||
"banner_image": activity.BannerImage,
|
"current_stage_background": activity.CurrentStageBackground,
|
||||||
"current_stage_background": activity.CurrentStageBackground,
|
"current_stage_title": activity.CurrentStageTitle,
|
||||||
"current_stage_title": activity.CurrentStageTitle,
|
"items": items,
|
||||||
"items": items,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,7 +509,7 @@ func convertProgressResponse(resp *pbActivity.GetProgressResponse) map[string]in
|
|||||||
"current_progress": resp.CurrentProgress,
|
"current_progress": resp.CurrentProgress,
|
||||||
"target_progress": resp.TargetProgress,
|
"target_progress": resp.TargetProgress,
|
||||||
"current_stage": resp.CurrentStage,
|
"current_stage": resp.CurrentStage,
|
||||||
"end_time": formatTimestamp(resp.EndTime),
|
"end_time": resp.EndTime,
|
||||||
"status": resp.Status,
|
"status": resp.Status,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,8 @@ type Activity struct {
|
|||||||
Theme string `json:"theme" gorm:"size:100"` // 中文主题
|
Theme string `json:"theme" gorm:"size:100"` // 中文主题
|
||||||
Description string `json:"description" gorm:"type:text"`
|
Description string `json:"description" gorm:"type:text"`
|
||||||
StarID int64 `json:"star_id" gorm:"not null"`
|
StarID int64 `json:"star_id" gorm:"not null"`
|
||||||
StartTime int64 `json:"start_time" gorm:"not null"` // 毫秒时间戳
|
StartTime int64 `json:"start_time" gorm:"not null"` // 毫秒时间戳
|
||||||
EndTime int64 `json:"end_time" gorm:"not null"` // 毫秒时间戳
|
EndTime int64 `json:"end_time" gorm:"not null"` // 毫秒时间戳
|
||||||
OverallEndTime int64 `json:"overall_end_time" gorm:"default:0"` // 整体活动结束时间(毫秒时间戳)
|
|
||||||
TargetProgress int64 `json:"target_progress" gorm:"default:1000"`
|
TargetProgress int64 `json:"target_progress" gorm:"default:1000"`
|
||||||
CurrentProgress int64 `json:"current_progress" gorm:"default:0"`
|
CurrentProgress int64 `json:"current_progress" gorm:"default:0"`
|
||||||
Status string `json:"status" gorm:"size:20;default:pending"` // pending/active/completed/expired
|
Status string `json:"status" gorm:"size:20;default:pending"` // pending/active/completed/expired
|
||||||
|
|||||||
@ -43,7 +43,6 @@ type Activity struct {
|
|||||||
BannerImage string `protobuf:"bytes,14,opt,name=banner_image,json=bannerImage,proto3" json:"banner_image,omitempty"` // 横幅图
|
BannerImage string `protobuf:"bytes,14,opt,name=banner_image,json=bannerImage,proto3" json:"banner_image,omitempty"` // 横幅图
|
||||||
CurrentStageBackground string `protobuf:"bytes,15,opt,name=current_stage_background,json=currentStageBackground,proto3" json:"current_stage_background,omitempty"` // 当前阶段背景图
|
CurrentStageBackground string `protobuf:"bytes,15,opt,name=current_stage_background,json=currentStageBackground,proto3" json:"current_stage_background,omitempty"` // 当前阶段背景图
|
||||||
CurrentStageTitle string `protobuf:"bytes,16,opt,name=current_stage_title,json=currentStageTitle,proto3" json:"current_stage_title,omitempty"` // 当前阶段标题
|
CurrentStageTitle string `protobuf:"bytes,16,opt,name=current_stage_title,json=currentStageTitle,proto3" json:"current_stage_title,omitempty"` // 当前阶段标题
|
||||||
OverallEndTime int64 `protobuf:"varint,18,opt,name=overall_end_time,json=overallEndTime,proto3" json:"overall_end_time,omitempty"` // 整体活动结束时间
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -197,13 +196,6 @@ func (x *Activity) GetCurrentStageTitle() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Activity) GetOverallEndTime() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.OverallEndTime
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 活动道具
|
// 活动道具
|
||||||
type ActivityItem struct {
|
type ActivityItem struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
@ -1108,7 +1100,7 @@ var File_activity_proto protoreflect.FileDescriptor
|
|||||||
|
|
||||||
const file_activity_proto_rawDesc = "" +
|
const file_activity_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x0eactivity.proto\x12\x10topfans.activity\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xff\x04\n" +
|
"\x0eactivity.proto\x12\x10topfans.activity\x1a\x12proto/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xd5\x04\n" +
|
||||||
"\bActivity\x12\x0e\n" +
|
"\bActivity\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12#\n" +
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12#\n" +
|
||||||
"\ractivity_type\x18\x02 \x01(\tR\factivityType\x12\x14\n" +
|
"\ractivity_type\x18\x02 \x01(\tR\factivityType\x12\x14\n" +
|
||||||
@ -1129,8 +1121,7 @@ const file_activity_proto_rawDesc = "" +
|
|||||||
"coverImage\x12!\n" +
|
"coverImage\x12!\n" +
|
||||||
"\fbanner_image\x18\x0e \x01(\tR\vbannerImage\x128\n" +
|
"\fbanner_image\x18\x0e \x01(\tR\vbannerImage\x128\n" +
|
||||||
"\x18current_stage_background\x18\x0f \x01(\tR\x16currentStageBackground\x12.\n" +
|
"\x18current_stage_background\x18\x0f \x01(\tR\x16currentStageBackground\x12.\n" +
|
||||||
"\x13current_stage_title\x18\x10 \x01(\tR\x11currentStageTitle\x12(\n" +
|
"\x13current_stage_title\x18\x10 \x01(\tR\x11currentStageTitle\"\xc7\x01\n" +
|
||||||
"\x10overall_end_time\x18\x12 \x01(\x03R\x0eoverallEndTime\"\xc7\x01\n" +
|
|
||||||
"\fActivityItem\x12\x0e\n" +
|
"\fActivityItem\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1b\n" +
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x1b\n" +
|
||||||
"\titem_type\x18\x02 \x01(\tR\bitemType\x12\x1b\n" +
|
"\titem_type\x18\x02 \x01(\tR\bitemType\x12\x1b\n" +
|
||||||
|
|||||||
@ -28,7 +28,6 @@ message Activity {
|
|||||||
string banner_image = 14; // 横幅图
|
string banner_image = 14; // 横幅图
|
||||||
string current_stage_background = 15; // 当前阶段背景图
|
string current_stage_background = 15; // 当前阶段背景图
|
||||||
string current_stage_title = 16; // 当前阶段标题
|
string current_stage_title = 16; // 当前阶段标题
|
||||||
int64 overall_end_time = 18; // 整体活动结束时间
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 活动道具
|
// 活动道具
|
||||||
|
|||||||
@ -1,499 +0,0 @@
|
|||||||
# 热更新开发脚本实现计划 (dev.sh)
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**目标:** 创建 `dev.sh` 脚本,监听后端各服务目录的 `.go` 文件变更,自动重新编译并重启对应服务。
|
|
||||||
|
|
||||||
**架构:** 每个服务一个独立的文件监听器进程,监控该服务目录下的所有 `.go` 文件。检测到变更后,通过 300ms 防抖机制避免频繁重建,编译成功后才终止旧进程并启动新进程,确保服务不中断。
|
|
||||||
|
|
||||||
**技术栈:** Bash 脚本 + fswatch (Mac) / inotifywait (Linux)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
└── dev.sh # 新增:热更新开发脚本
|
|
||||||
```
|
|
||||||
|
|
||||||
`dev.sh` 是唯一新增的文件,所有逻辑都在这个脚本中实现。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 任务总览
|
|
||||||
|
|
||||||
| 任务 | 内容 |
|
|
||||||
|------|------|
|
|
||||||
| Task 1 | 编写 `dev.sh` 主框架(检测依赖工具、加载 .env、解析参数) |
|
|
||||||
| Task 2 | 实现服务启动函数(build + start + log) |
|
|
||||||
| Task 3 | 实现文件监听器函数(fswatch/inotifywait + 防抖 + 重启逻辑) |
|
|
||||||
| Task 4 | 实现主启动流程(按顺序启动所有服务 + 启动所有监听器) |
|
|
||||||
| Task 5 | 实现信号捕获与优雅退出(trap SIGINT/SIGTERM) |
|
|
||||||
| Task 6 | 测试脚本是否能正常运行 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: dev.sh 主框架
|
|
||||||
|
|
||||||
**文件:**
|
|
||||||
- 新增: `backend/dev.sh`
|
|
||||||
|
|
||||||
- [ ] **Step 1: 创建 `backend/dev.sh` 文件头部**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Hot-Reload Dev Script for TopFans Backend
|
|
||||||
# Usage: ./dev.sh
|
|
||||||
#
|
|
||||||
# Requires: fswatch (Mac: brew install fswatch) or inotifywait (Linux)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: 检测依赖工具**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Detect platform
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
WATCHER_TOOL="fswatch"
|
|
||||||
WATCHER_CMD="fswatch -r"
|
|
||||||
elif [[ "$(uname)" == "Linux" ]]; then
|
|
||||||
WATCHER_TOOL="inotifywait"
|
|
||||||
WATCHER_CMD="inotifywait -r -m -e modify,create,write"
|
|
||||||
else
|
|
||||||
echo -e "${RED}不支持的平台${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v "$WATCHER_TOOL" &> /dev/null; then
|
|
||||||
echo -e "${RED}缺少工具: $WATCHER_TOOL${NC}"
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
echo "安装方法: brew install fswatch"
|
|
||||||
else
|
|
||||||
echo "安装方法: sudo apt install inotify-tools (Debian/Ubuntu)"
|
|
||||||
echo " sudo yum install inotify-tools (CentOS/RHEL)"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: 加载 .env 文件(同 start.sh)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ENV_FILE="$SCRIPT_DIR/.env"
|
|
||||||
if [ -f "$ENV_FILE" ]; then
|
|
||||||
echo -e "${GREEN}📄 加载 .env 文件...${NC}"
|
|
||||||
set -a
|
|
||||||
source "$ENV_FILE"
|
|
||||||
set +a
|
|
||||||
fi
|
|
||||||
|
|
||||||
DB_HOST="${DB_HOST:-localhost}"
|
|
||||||
DB_PORT="${DB_PORT:-5432}"
|
|
||||||
DB_USER="${DB_USER:-haihuizhu}"
|
|
||||||
DB_PASSWORD="${DB_PASSWORD:-admin}"
|
|
||||||
DB_NAME="${DB_NAME:-top-fans}"
|
|
||||||
DB_ARGS=(-db-host="$DB_HOST" -db-port="$DB_PORT" -db-user="$DB_USER" -db-password="$DB_PASSWORD" -db-name="$DB_NAME")
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "feat: add dev.sh main framework header"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: 服务构建与启动函数
|
|
||||||
|
|
||||||
- [ ] **Step 1: 添加 `start_service` 函数**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动一个服务
|
|
||||||
# 用法: start_service name binary port use_db
|
|
||||||
start_service() {
|
|
||||||
local name=$1
|
|
||||||
local binary=$2
|
|
||||||
local port=$3
|
|
||||||
local use_db=$4
|
|
||||||
|
|
||||||
echo -e "${GREEN}🚀 启动 $name...${NC}"
|
|
||||||
|
|
||||||
if [ "$use_db" = "1" ]; then
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
|
|
||||||
else
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
|
|
||||||
fi
|
|
||||||
local pid=$!
|
|
||||||
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ps -p $pid > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ $name 已启动 (PID: $pid, 端口: $port)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $name 启动失败${NC}"
|
|
||||||
echo -e "${YELLOW}查看日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: 添加 `build_service` 函数**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建一个服务(在服务目录下执行 go build)
|
|
||||||
# 用法: build_service name dir binary
|
|
||||||
build_service() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
|
|
||||||
if [ ! -d "$SCRIPT_DIR/$dir" ]; then
|
|
||||||
echo -e "${RED}❌ $dir 目录不存在${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$SCRIPT_DIR/$dir"
|
|
||||||
if go build -o "$SCRIPT_DIR/$binary" . 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ $name 编译成功${NC}"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ $name 编译失败${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "feat: add build_service and start_service functions"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: 文件监听器函数
|
|
||||||
|
|
||||||
- [ ] **Step 1: 修改 `start_service` 函数(增加 PID 文件写入)**
|
|
||||||
|
|
||||||
在 `start_service` 函数的 `local pid=$!` 之后,添加:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 保存 PID 到文件
|
|
||||||
echo $pid > "/tmp/dev_sh_${name}.pid"
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: 添加 `restart_service` 函数(单服务重建重启,PID 文件追踪)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 重建并重启单个服务(构建成功后才杀旧进程)
|
|
||||||
# 用法: restart_service name dir binary port use_db
|
|
||||||
restart_service() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
local port=$4
|
|
||||||
local use_db=$5
|
|
||||||
local pid_file="/tmp/dev_sh_${name}.pid"
|
|
||||||
|
|
||||||
# Step 1: 编译(先编译,编译成功才杀旧进程)
|
|
||||||
if [ ! -d "$SCRIPT_DIR/$dir" ]; then
|
|
||||||
echo -e "${RED}❌ $dir 目录不存在,跳过${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$SCRIPT_DIR/$dir"
|
|
||||||
if ! go build -o "$SCRIPT_DIR/$binary" . 2>&1; then
|
|
||||||
echo -e "${RED}❌ [$name] 编译失败,旧进程保持运行${NC}"
|
|
||||||
echo -e "${RED} 查看详细日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}✅ [$name] 编译成功${NC}"
|
|
||||||
|
|
||||||
# Step 2: 编译成功后,杀旧进程(从 PID 文件读取)
|
|
||||||
if [ -f "$pid_file" ]; then
|
|
||||||
local old_pid=$(cat "$pid_file")
|
|
||||||
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
|
|
||||||
kill "$old_pid" 2>/dev/null || true
|
|
||||||
echo -e "${YELLOW}🛑 [$name] 旧进程 (PID: $old_pid) 已终止${NC}"
|
|
||||||
fi
|
|
||||||
rm -f "$pid_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: 启动新进程
|
|
||||||
sleep 1
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
if [ "$use_db" = "1" ]; then
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port "${DB_ARGS[@]}" > "/tmp/${name}.log" 2>&1 &
|
|
||||||
else
|
|
||||||
"$SCRIPT_DIR/$binary" -port=$port > "/tmp/${name}.log" 2>&1 &
|
|
||||||
fi
|
|
||||||
local new_pid=$!
|
|
||||||
echo $new_pid > "$pid_file"
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
if ps -p $new_pid > /dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}✅ [$name] 已重启 (PID: $new_pid)${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ [$name] 重启失败,查看日志: tail -f /tmp/${name}.log${NC}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: 添加 `start_watcher` 函数(时间戳防抖,无 pipe race)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动文件监听器
|
|
||||||
# 用法: start_watcher name dir binary port use_db
|
|
||||||
start_watcher() {
|
|
||||||
local name=$1
|
|
||||||
local dir=$2
|
|
||||||
local binary=$3
|
|
||||||
local port=$4
|
|
||||||
local use_db=$5
|
|
||||||
local watch_path="$SCRIPT_DIR/$dir"
|
|
||||||
local restart_marker="/tmp/dev_sh_${name}_restart"
|
|
||||||
|
|
||||||
if [ ! -d "$watch_path" ]; then
|
|
||||||
echo -e "${RED}❌ 监听目录不存在: $watch_path${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理可能遗留的重启标记
|
|
||||||
rm -f "$restart_marker"
|
|
||||||
|
|
||||||
(
|
|
||||||
if [[ "$(uname)" == "Darwin" ]]; then
|
|
||||||
fswatch -r "$watch_path" --exclude='\.git' --exclude='_test\.go$'
|
|
||||||
else
|
|
||||||
inotifywait -r -m -e modify,create,write "$watch_path" --exclude='\.git' --exclude='_test\.go$'
|
|
||||||
fi | while read event; do
|
|
||||||
# 时间戳防抖:每次事件更新标记文件,restart_trigger.sh 负责延迟执行
|
|
||||||
date +%s%N > "$restart_marker"
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
local watcher_pid=$!
|
|
||||||
echo "$watcher_pid" >> /tmp/dev_sh_watchers.tmp
|
|
||||||
echo -e "${GREEN}👁️ [$name] 文件监听器已启动 (PID: $watcher_pid)${NC}"
|
|
||||||
|
|
||||||
# 后台防抖循环:每 300ms 检查一次是否需要重启
|
|
||||||
(
|
|
||||||
while true; do
|
|
||||||
sleep 0.3
|
|
||||||
if [ ! -f "$restart_marker" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
local now=$(date +%s%N)
|
|
||||||
local last_time=$(cat "$restart_marker" 2>/dev/null || echo 0)
|
|
||||||
local elapsed=$((now - last_time))
|
|
||||||
if (( elapsed >= 300000000 )); then
|
|
||||||
# 距上次事件已过 300ms,执行重启
|
|
||||||
rm -f "$restart_marker"
|
|
||||||
restart_service "$name" "$dir" "$binary" "$port" "$use_db"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
local debounce_pid=$!
|
|
||||||
echo "$debounce_pid" >> /tmp/dev_sh_watchers.tmp
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "feat: add restart_service and start_watcher functions"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: 主启动流程
|
|
||||||
|
|
||||||
- [ ] **Step 1: 添加主启动流程**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo -e "${GREEN} TopFans Backend 热更新开发模式${NC}"
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}数据库: ${DB_USER}@${DB_HOST}:${DB_PORT}/${DB_NAME}${NC}"
|
|
||||||
echo -e "${YELLOW}文件监听器: $WATCHER_TOOL${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 初始化监听器 PID 列表
|
|
||||||
> /tmp/dev_sh_watchers.tmp
|
|
||||||
|
|
||||||
# 清理残留 PID 文件(上次非正常退出可能留下)
|
|
||||||
for service in activityService galleryService socialService assetService userService gateway; do
|
|
||||||
rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 停止现有服务(清理环境)
|
|
||||||
echo -e "${YELLOW}🛑 停止现有服务...${NC}"
|
|
||||||
for service in gateway userService socialService assetService galleryService activityService; do
|
|
||||||
pkill -9 -f "$service" 2>/dev/null || true
|
|
||||||
done
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# 先构建所有服务
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🔨 预编译所有服务...${NC}"
|
|
||||||
build_service "gateway" "gateway" "gateway/gateway"
|
|
||||||
build_service "userService" "services/userService" "services/userService/userService"
|
|
||||||
build_service "assetService" "services/assetService" "services/assetService/assetService"
|
|
||||||
build_service "socialService" "services/socialService" "services/socialService/socialService"
|
|
||||||
build_service "galleryService" "services/galleryService" "services/galleryService/galleryService"
|
|
||||||
build_service "activityService" "services/activityService" "services/activityService/activityService"
|
|
||||||
cd "$SCRIPT_DIR"
|
|
||||||
|
|
||||||
# 启动所有服务
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🚀 启动所有服务...${NC}"
|
|
||||||
start_service "userService" "services/userService/userService" 20000 1
|
|
||||||
start_service "assetService" "services/assetService/assetService" 20003 1
|
|
||||||
start_service "socialService" "services/socialService/socialService" 20002 1
|
|
||||||
start_service "galleryService" "services/galleryService/galleryService" 20004 1
|
|
||||||
start_service "activityService" "services/activityService/activityService" 20005 1
|
|
||||||
start_service "gateway" "gateway/gateway" 8080 0
|
|
||||||
|
|
||||||
# 启动所有文件监听器
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}👁️ 启动所有文件监听器...${NC}"
|
|
||||||
start_watcher "gateway" "gateway" "gateway/gateway" 8080 0
|
|
||||||
start_watcher "userService" "services/userService" "services/userService/userService" 20000 1
|
|
||||||
start_watcher "assetService" "services/assetService" "services/assetService/assetService" 20003 1
|
|
||||||
start_watcher "socialService" "services/socialService" "services/socialService/socialService" 20002 1
|
|
||||||
start_watcher "galleryService" "services/galleryService" "services/galleryService/galleryService" 20004 1
|
|
||||||
start_watcher "activityService" "services/activityService" "services/activityService/activityService" 20005 1
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo -e "${GREEN} 热更新开发模式已启动!${NC}"
|
|
||||||
echo -e "${GREEN}========================================${NC}"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}服务地址:${NC}"
|
|
||||||
echo " - Gateway: http://localhost:8080"
|
|
||||||
echo " - Swagger UI: http://localhost:8080/swagger/index.html"
|
|
||||||
echo " - User Service: tri://localhost:20000"
|
|
||||||
echo " - Social Service: tri://localhost:20002"
|
|
||||||
echo " - Asset Service: tri://localhost:20003"
|
|
||||||
echo " - Gallery Service: tri://localhost:20004"
|
|
||||||
echo " - Activity Service: tri://localhost:20005"
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}按 Ctrl+C 停止所有服务${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 保持脚本运行
|
|
||||||
wait
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "feat: add main startup flow in dev.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: 信号捕获与优雅退出
|
|
||||||
|
|
||||||
- [ ] **Step 1: 在脚本头部(set -e 之后)添加 trap**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 信号捕获:优雅退出
|
|
||||||
cleanup() {
|
|
||||||
echo ""
|
|
||||||
echo -e "${YELLOW}🛑 收到停止信号,正在关闭所有服务...${NC}"
|
|
||||||
|
|
||||||
# 先杀所有 watcher 和 debounce 进程(防止继续触发重启)
|
|
||||||
if [ -f /tmp/dev_sh_watchers.tmp ]; then
|
|
||||||
while read watcher_pid; do
|
|
||||||
kill "$watcher_pid" 2>/dev/null || true
|
|
||||||
done < /tmp/dev_sh_watchers.tmp
|
|
||||||
rm -f /tmp/dev_sh_watchers.tmp
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 清理所有 PID 文件并杀服务进程
|
|
||||||
for service in activityService galleryService socialService assetService userService gateway; do
|
|
||||||
pkill -9 -f "$service" 2>/dev/null || true
|
|
||||||
rm -f "/tmp/dev_sh_${service}.pid" "/tmp/dev_sh_${service}_restart"
|
|
||||||
echo -e "${YELLOW} 🛑 $service 已停止${NC}"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo -e "${GREEN}✅ 所有服务已关闭${NC}"
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup SIGINT SIGTERM
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: 将 trap 代码移到 `set -e` 之后(Task 1 的 Step 1 中添加)**
|
|
||||||
|
|
||||||
在文件头部的 `set -e` 之后、`SCRIPT_DIR=` 之前插入 trap 代码。
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "feat: add signal trap and graceful shutdown to dev.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 6: 整体测试
|
|
||||||
|
|
||||||
- [ ] **Step 1: 添加执行权限并检查脚本语法**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
chmod +x backend/dev.sh
|
|
||||||
bash -n backend/dev.sh # 语法检查,输出为空表示无语法错误
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: 检查 fswatch 是否可用(如未安装给出提示)**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
if command -v fswatch &> /dev/null; then
|
|
||||||
echo "fswatch 已安装: $(fswatch --version | head -1)"
|
|
||||||
else
|
|
||||||
echo "fswatch 未安装,Mac 用户请运行: brew install fswatch"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add backend/dev.sh
|
|
||||||
git commit -m "chore: add execute permission and validation to dev.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 完成后验证
|
|
||||||
|
|
||||||
脚本创建完成后,运行以下命令验证:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 语法检查
|
|
||||||
bash -n backend/dev.sh
|
|
||||||
|
|
||||||
# 2. 查看帮助信息
|
|
||||||
./backend/dev.sh --help 2>&1 | head -20
|
|
||||||
|
|
||||||
# 3. (如果有 fswatch)后台启动并检查进程
|
|
||||||
./backend/dev.sh &
|
|
||||||
sleep 5
|
|
||||||
ps aux | grep -E "(dev.sh|fswatch|gateway|userService)" | grep -v grep
|
|
||||||
```
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
# 热更新开发脚本设计 (dev.sh)
|
|
||||||
|
|
||||||
## 1. 概述
|
|
||||||
|
|
||||||
`dev.sh` 是一个开发脚本,用于启动所有后端微服务并监听文件变化,实现热更新。当某个服务的 `.go` 文件发生变更时,只重新编译和重启该服务。
|
|
||||||
|
|
||||||
## 2. 架构
|
|
||||||
|
|
||||||
```
|
|
||||||
dev.sh
|
|
||||||
├── 加载 .env 文件(与 start.sh 相同)
|
|
||||||
├── 按依赖顺序启动所有服务(后台运行)
|
|
||||||
└── 并发运行多个文件监听器(后台运行)
|
|
||||||
├── watcher(gateway) → 监听 gateway/ → 重启 gateway
|
|
||||||
├── watcher(userService) → 监听 services/userService/ → 重启 userService
|
|
||||||
├── watcher(assetService) → 监听 services/assetService/ → 重启 assetService
|
|
||||||
├── watcher(socialService) → 监听 services/socialService/ → 重启 socialService
|
|
||||||
├── watcher(galleryService) → 监听 services/galleryService/ → 重启 galleryService
|
|
||||||
└── watcher(activityService) → 监听 services/activityService/ → 重启 activityService
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 服务启动顺序
|
|
||||||
|
|
||||||
与 `start.sh` 保持一致:
|
|
||||||
1. userService (端口 20000)
|
|
||||||
2. assetService (端口 20003)
|
|
||||||
3. socialService (端口 20002)
|
|
||||||
4. galleryService (端口 20004)
|
|
||||||
5. activityService (端口 20005)
|
|
||||||
6. gateway (端口 8080)
|
|
||||||
|
|
||||||
## 4. 监听器行为
|
|
||||||
|
|
||||||
每个服务对应一个独立的监听器:
|
|
||||||
- **监听范围**: `<service_dir>/**/*.go`
|
|
||||||
- **触发条件**: 任意 `.go` 文件的创建/修改/写入事件
|
|
||||||
- **防抖**: 300ms 冷却期 —— 触发重启后,300ms 内的后续事件会被忽略,避免频繁重建
|
|
||||||
- **操作步骤**(在服务自己的模块目录下执行,与 `start.sh` 一致):
|
|
||||||
1. 重新编译: `cd <service_dir> && go build -o <binary> .`
|
|
||||||
2. **构建失败安全**: 若编译失败,记录错误日志,保持旧进程继续运行
|
|
||||||
3. 仅在编译成功后: 终止旧进程
|
|
||||||
4. 使用与 `start.sh` 相同的参数启动新进程
|
|
||||||
5. 在控制台输出热更新事件(青色)
|
|
||||||
|
|
||||||
## 5. 依赖工具
|
|
||||||
|
|
||||||
- **Mac**: `fswatch`(通过 `brew install fswatch` 安装)
|
|
||||||
- **Linux**: `inotifywait`(通过系统包管理器安装)
|
|
||||||
|
|
||||||
若所需工具未安装,脚本打印安装说明后退出。
|
|
||||||
|
|
||||||
## 6. 信号处理
|
|
||||||
|
|
||||||
- **Ctrl+C / SIGINT / SIGTERM**: 使用 `trap` 捕获信号,停止所有服务和监听器后退出
|
|
||||||
- 退出时按启动相反顺序(先 gateway,再各个服务)杀灭所有进程
|
|
||||||
|
|
||||||
## 7. 日志
|
|
||||||
|
|
||||||
- 各服务日志写入 `/tmp/<service_name>.log`(与 start.sh 相同)
|
|
||||||
- 控制台显示变更的文件路径和被重启的服务
|
|
||||||
- 热更新事件以青色输出
|
|
||||||
|
|
||||||
## 8. 文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
backend/
|
|
||||||
├── dev.sh # 热更新开发脚本(新增)
|
|
||||||
└── start.sh # 生产环境启动脚本(现有)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 9. 边界情况
|
|
||||||
|
|
||||||
- **构建失败**: 旧进程保持运行,错误同时记录到控制台和 `/tmp/<service>.log`
|
|
||||||
- **监听目录不存在**: 脚本退出并报错
|
|
||||||
- **工具未安装**: 打印安装说明后退出
|
|
||||||
- **文件快速连续变更**: 300ms 防抖机制确保每批变更最多触发一次重启
|
|
||||||
|
|
||||||
## 10. 不修改现有文件
|
|
||||||
|
|
||||||
`dev.sh` 不修改任何现有文件,仅参考 `start.sh` 的实现逻辑,并新增一个文件。
|
|
||||||
@ -340,7 +340,7 @@ const fetchActivityList = async () => {
|
|||||||
try {
|
try {
|
||||||
isLoadingActivities.value = true;
|
isLoadingActivities.value = true;
|
||||||
activityLoadError.value = null;
|
activityLoadError.value = null;
|
||||||
const response = await getActivityListApi(starId, 1, 20);
|
const response = await getActivityListApi(starId, 'active', 1, 20);
|
||||||
|
|
||||||
if (response && response.code === 200 && response.data) {
|
if (response && response.code === 200 && response.data) {
|
||||||
activityList.value = response.data.activities || [];
|
activityList.value = response.data.activities || [];
|
||||||
|
|||||||
@ -257,7 +257,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onShow } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import { onReady } from "@dcloudio/uni-app";
|
import { onReady } from "@dcloudio/uni-app";
|
||||||
import Header from '../components/Header.vue';
|
import Header from '../components/Header.vue';
|
||||||
@ -1216,19 +1216,9 @@ onReady(() => {
|
|||||||
// 不再自动触发引导,由用户从引导列表选择后触发
|
// 不再自动触发引导,由用户从引导列表选择后触发
|
||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onMounted(() => {
|
||||||
// 每次页面显示时都刷新用户信息并通知 Header 更新余额
|
// 每次进入页面都从API刷新用户信息
|
||||||
fetchUserInfo().then(() => {
|
fetchUserInfo();
|
||||||
const cachedUserStr = uni.getStorageSync('user');
|
|
||||||
if (cachedUserStr) {
|
|
||||||
try {
|
|
||||||
const cachedUser = JSON.parse(cachedUserStr);
|
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: cachedUser.crystal_balance || 0 });
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析缓存用户信息失败:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -242,11 +242,9 @@ async function validateBalance(cost) {
|
|||||||
try {
|
try {
|
||||||
if (!userInfo.value) {
|
if (!userInfo.value) {
|
||||||
const userStr = uni.getStorageSync('user')
|
const userStr = uni.getStorageSync('user')
|
||||||
if (userStr) {
|
if (userStr) userInfo.value = JSON.parse(userStr)
|
||||||
userInfo.value = typeof userStr === 'string' ? JSON.parse(userStr) : { ...userStr }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const balance = Number(userInfo.value?.crystal_balance) || 0
|
const balance = userInfo.value?.crystal_balance || 0
|
||||||
return balance >= cost
|
return balance >= cost
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('验证余额失败:', error)
|
console.error('验证余额失败:', error)
|
||||||
@ -260,12 +258,6 @@ async function contributeItem(item, isRetry = false) {
|
|||||||
// 使用 activity-config.js 中的 purchaseItem 函数
|
// 使用 activity-config.js 中的 purchaseItem 函数
|
||||||
const result = await purchaseItem(props.activityId, item.type, 1)
|
const result = await purchaseItem(props.activityId, item.type, 1)
|
||||||
|
|
||||||
// 检查购买结果
|
|
||||||
if (!result.success) {
|
|
||||||
showResultToast('', result.message || '活动不在进行中,无法购买')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 成功:触发反馈动画(重试时静默,不触发)
|
// 成功:触发反馈动画(重试时静默,不触发)
|
||||||
if (!isRetry) {
|
if (!isRetry) {
|
||||||
feedbackItem.value = item.type
|
feedbackItem.value = item.type
|
||||||
@ -332,18 +324,8 @@ function deductLocalBalance(cost) {
|
|||||||
try {
|
try {
|
||||||
const userStr = uni.getStorageSync('user')
|
const userStr = uni.getStorageSync('user')
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
// 确保正确解析用户对象
|
const user = typeof userStr === 'string' ? JSON.parse(userStr) : userStr
|
||||||
let user
|
user.crystal_balance = Math.max(0, (user.crystal_balance || 0) - cost)
|
||||||
if (typeof userStr === 'string') {
|
|
||||||
user = JSON.parse(userStr)
|
|
||||||
} else {
|
|
||||||
user = { ...userStr }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扣除余额,确保不为负数
|
|
||||||
user.crystal_balance = Math.max(0, (Number(user.crystal_balance) || 0) - cost)
|
|
||||||
|
|
||||||
// 保存回存储
|
|
||||||
uni.setStorageSync('user', JSON.stringify(user))
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
userInfo.value = user
|
userInfo.value = user
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
||||||
@ -358,21 +340,10 @@ function refundLocalBalance(cost) {
|
|||||||
try {
|
try {
|
||||||
const userStr = uni.getStorageSync('user')
|
const userStr = uni.getStorageSync('user')
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
// 确保正确解析用户对象
|
const user = typeof userStr === 'string' ? JSON.parse(userStr) : userStr
|
||||||
let user
|
user.crystal_balance = (user.crystal_balance || 0) + cost
|
||||||
if (typeof userStr === 'string') {
|
|
||||||
user = JSON.parse(userStr)
|
|
||||||
} else {
|
|
||||||
user = { ...userStr }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 退还余额
|
|
||||||
user.crystal_balance = (Number(user.crystal_balance) || 0) + cost
|
|
||||||
|
|
||||||
// 保存回存储
|
|
||||||
uni.setStorageSync('user', JSON.stringify(user))
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
userInfo.value = user
|
userInfo.value = user
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('退还本地余额失败:', error)
|
console.error('退还本地余额失败:', error)
|
||||||
@ -382,22 +353,11 @@ async function updateLocalBalanceFromResult(newBalance) {
|
|||||||
try {
|
try {
|
||||||
const userStr = uni.getStorageSync('user')
|
const userStr = uni.getStorageSync('user')
|
||||||
if (userStr) {
|
if (userStr) {
|
||||||
// 确保正确解析用户对象
|
const user = typeof userStr === 'string' ? JSON.parse(userStr) : userStr
|
||||||
let user
|
user.crystal_balance = newBalance
|
||||||
if (typeof userStr === 'string') {
|
|
||||||
user = JSON.parse(userStr)
|
|
||||||
} else {
|
|
||||||
// 如果已经是对象,创建一个新副本避免引用问题
|
|
||||||
user = { ...userStr }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新余额,确保是数字类型
|
|
||||||
user.crystal_balance = Number(newBalance) || 0
|
|
||||||
|
|
||||||
// 保存回存储,确保是字符串格式
|
|
||||||
uni.setStorageSync('user', JSON.stringify(user))
|
uni.setStorageSync('user', JSON.stringify(user))
|
||||||
userInfo.value = user
|
userInfo.value = user
|
||||||
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
|
uni.$emit('balanceUpdated', { crystal_balance: newBalance })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新本地余额失败:', error)
|
console.error('更新本地余额失败:', error)
|
||||||
|
|||||||
@ -122,32 +122,18 @@ export async function fetchActivityProgress(activityId) {
|
|||||||
export async function purchaseItem(activityId, itemType, quantity = 1) {
|
export async function purchaseItem(activityId, itemType, quantity = 1) {
|
||||||
try {
|
try {
|
||||||
const response = await purchaseActivityItemApi(activityId, itemType, quantity)
|
const response = await purchaseActivityItemApi(activityId, itemType, quantity)
|
||||||
// 检查活动状态,只有 active 才能购买
|
|
||||||
if (response.code === 200 && response.data) {
|
if (response.code === 200 && response.data) {
|
||||||
if (response.data.activity_status && response.data.activity_status !== 'active') {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: response.data.message || '活动不在进行中,无法购买'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
|
||||||
totalCrystalSpent: response.data.total_crystal_spent,
|
totalCrystalSpent: response.data.total_crystal_spent,
|
||||||
totalContribution: response.data.total_contribution,
|
totalContribution: response.data.total_contribution,
|
||||||
currentProgress: response.data.current_progress,
|
currentProgress: response.data.current_progress,
|
||||||
remainingBalance: response.data.remaining_balance
|
remainingBalance: response.data.remaining_balance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
throw new Error(response.message || '购买道具失败')
|
||||||
success: false,
|
|
||||||
message: response.message || '购买道具失败'
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('purchaseItem error:', error)
|
console.error('purchaseItem error:', error)
|
||||||
return {
|
throw error
|
||||||
success: false,
|
|
||||||
message: error.message || '购买道具失败'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -486,9 +486,9 @@ export function getOriginalRankingApi(dimension = 'total', starId = null, page =
|
|||||||
// 获取活动列表
|
// 获取活动列表
|
||||||
export function getActivityListApi(starId, status = '', page = 1, pageSize = 10) {
|
export function getActivityListApi(starId, status = '', page = 1, pageSize = 10) {
|
||||||
let url = `/api/v1/activities?star_id=${starId}&page=${page}&page_size=${pageSize}`
|
let url = `/api/v1/activities?star_id=${starId}&page=${page}&page_size=${pageSize}`
|
||||||
// if (status) {
|
if (status) {
|
||||||
// url += `&status=${status}`
|
url += `&status=${status}`
|
||||||
// }
|
}
|
||||||
return request({
|
return request({
|
||||||
url: url,
|
url: url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user