feat:新增docker密钥登录部署

This commit is contained in:
zerosaturation 2026-04-24 18:04:55 +08:00
parent e761bde30b
commit f6e1caad8b
17 changed files with 1052 additions and 410 deletions

View File

@ -98,10 +98,11 @@ func (r *rankingRepository) getHotRankingByDimension(starID int64, dimension str
now := time.Now().UnixMilli()
startOfMonth := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Local).UnixMilli()
// 构建基础查询JOIN 用户表和粉丝档案表获取昵称和头像(一次查询,避免 N+1 问题)
// 构建基础查询JOIN 用户表和粉丝档案表获取昵称和头像
// 使用 GROUP BY 去重,并用聚合函数获取 owner 信息
// is_original 是布尔值,需要转为 int 再取 MAX
db := r.db.Model(&models.Asset{}).
Select("assets.id as asset_id, assets.name as asset_name, assets.cover_url, assets.owner_uid, assets.like_count, assets.is_original, fp.nickname as owner_nickname, fp.avatar_url as owner_avatar").
Select("assets.id as asset_id, assets.name as asset_name, assets.cover_url, assets.owner_uid, MAX(assets.like_count) as like_count, MAX(assets.is_original::int) as is_original, MAX(fp.nickname) as owner_nickname, MAX(fp.avatar_url) as owner_avatar").
Joins("LEFT JOIN fan_profiles fp ON fp.user_id = assets.owner_uid AND fp.star_id = ?", starID).
Where("assets.star_id = ? AND assets.is_active = ? AND assets.status = ?", starID, true, models.AssetStatusActive)
@ -112,20 +113,21 @@ func (r *rankingRepository) getHotRankingByDimension(starID int64, dimension str
// 根据维度添加条件
switch dimension {
case "displaying":
// 展示中:关联 Exhibition 表,筛选未过期的且是当前star的展馆
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Joins("INNER JOIN fan_profiles ON fan_profiles.id = exhibitions.host_profile_id").
// 展示中:关联 Exhibition 表,筛选未过期的、未删除的且是当前star的展品
// occupier_star_id 表示展品所属的明星
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("fan_profiles.star_id = ?", starID)
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的(未下架或本月内下架的
// 本月:本月内展览过的藏品(包括已下架的,只要 expire_at 在本月内即可
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth)
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
case "total":
// 全部:直接使用 assets 表的 like_count无需额外条件
}
// 统计总数
// 统计总数(先查询 ID 列表再 Count避免 DISTINCT 干扰)
var total int64
countDB := r.db.Model(&models.Asset{}).
Select("assets.id").
@ -136,22 +138,23 @@ func (r *rankingRepository) getHotRankingByDimension(starID int64, dimension str
}
switch dimension {
case "displaying":
countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id").
countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("host_fp.star_id = ?", starID)
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的(未下架或本月内下架的)
// 本月:本月内展览过的藏品(包括已下架的)
countDB = countDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth)
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
if err := countDB.Count(&total).Error; err != nil {
return nil, 0, err
}
// 查询列表
// 查询列表(使用 GROUP BY 去重,按点赞数排序)
var results []*RankingItem
err := db.Order("assets.like_count DESC, assets.id ASC").
err := db.Group("assets.id").
Order("MAX(assets.like_count) DESC, assets.id ASC").
Limit(limit).
Offset(offset).
Scan(&results).Error
@ -187,15 +190,15 @@ func (r *rankingRepository) GetMyBestRanking(userID, starID int64, dimension str
// 根据维度添加条件,与 getHotRankingByDimension 保持一致
switch dimension {
case "displaying":
// 展示中:关联 Exhibition 表筛选未过期的且是当前star的展馆
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id").
// 展示中:关联 Exhibition 表筛选未过期的、未删除的且是当前star的展品
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("host_fp.star_id = ?", starID)
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的(未下架或本月内下架的)
// 本月:本月内展览过的藏品(包括已下架的)
db = db.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth)
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
// 获取用户在该star下点赞数最高的藏品
@ -231,14 +234,14 @@ func (r *rankingRepository) GetMyBestRanking(userID, starID int64, dimension str
switch dimension {
case "displaying":
rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Joins("INNER JOIN fan_profiles AS host_fp ON host_fp.id = exhibitions.host_profile_id").
rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id AND exhibitions.deleted_at IS NULL").
Where("exhibitions.expire_at > ?", now).
Where("host_fp.star_id = ?", starID)
Where("exhibitions.occupier_star_id = ?", starID)
case "month":
// 本月:本月内展览过的(未下架或本月内下架的)
// 本月:本月内展览过的藏品(包括已下架的)
rankingDB = rankingDB.Joins("INNER JOIN exhibitions ON exhibitions.asset_id = assets.id").
Where("exhibitions.expire_at >= ?", startOfMonth)
Where("exhibitions.expire_at >= ?", startOfMonth).
Where("exhibitions.occupier_star_id = ?", starID)
}
if err := rankingDB.Count(&rank).Error; err != nil {

View File

@ -42,7 +42,13 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
echo "Built galleryservice" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /tmp/activityservice services/activityService/main.go && \
echo "Built activityservice"
echo "Built activityservice" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /tmp/taskservice services/taskService/main.go && \
echo "Built taskservice" && \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /tmp/starbookservice services/starbookService/main.go && \
echo "Built starbookservice"
# ---- Runtime Stage: Gateway ----
FROM --platform=linux/amd64 alpine:3.19 AS gateway
@ -135,3 +141,33 @@ HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:20005 || exit 1
ENTRYPOINT ["/app/activityservice"]
# ---- Runtime Stage: TaskService ----
FROM --platform=linux/amd64 alpine:3.19 AS taskservice
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /tmp/taskservice /app/taskservice
EXPOSE 20006
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:21006 || exit 1
ENTRYPOINT ["/app/taskservice"]
# ---- Runtime Stage: StarbookService ----
FROM --platform=linux/amd64 alpine:3.19 AS starbookservice
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
COPY --from=builder /tmp/starbookservice /app/starbookservice
EXPOSE 20007
HEALTHCHECK --interval=10s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:21007 || exit 1
ENTRYPOINT ["/app/starbookservice"]

View File

@ -77,7 +77,7 @@ while [[ $# -gt 0 ]]; do
echo ""
echo "服务名 (可选):"
echo " gateway, userService, socialService, assetService,"
echo " galleryService, activityService"
echo " galleryService, activityService, taskService, starbookService"
echo ""
echo "示例:"
echo " $0 # 构建所有服务"
@ -96,6 +96,8 @@ while [[ $# -gt 0 ]]; do
asset|assetService) SERVICES+=("assetService") ;;
gallery|galleryService) SERVICES+=("galleryService") ;;
activity|activityService) SERVICES+=("activityService") ;;
task|taskService) SERVICES+=("taskService") ;;
starbook|starbookService) SERVICES+=("starbookService") ;;
all)
# all 关键字,构建所有服务
SERVICES=()
@ -114,7 +116,7 @@ done
# ==================== 服务列表 ====================
# 所有可用服务及其配置(使用小写 target 名)
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice")
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" "taskservice" "starbookservice")
# 确定要构建的服务
if [ ${#SERVICES[@]} -eq 0 ]; then
@ -201,12 +203,16 @@ main() {
assetservice) docker_target="assetservice" ;;
galleryservice) docker_target="galleryservice" ;;
activityservice) docker_target="activityservice" ;;
taskservice) docker_target="taskservice" ;;
starbookservice) docker_target="starbookservice" ;;
# 兼容旧的大写服务名
userService) docker_target="userservice" ;;
socialService) docker_target="socialservice" ;;
assetService) docker_target="assetservice" ;;
galleryService) docker_target="galleryservice" ;;
activityService) docker_target="activityservice" ;;
taskService) docker_target="taskservice" ;;
starbookService) docker_target="starbookservice" ;;
*) docker_target="$service" ;;
esac

View File

@ -76,6 +76,8 @@ SERVICES=(
"assetservice"
"galleryservice"
"activityservice"
"taskservice"
"starbookservice"
)
# ==================== 服务器配置 ====================
@ -83,19 +85,32 @@ SERVICES=(
SERVER_HOST="101.132.250.62" # 服务器 IP 或域名
SERVER_PORT="22" # SSH 端口
SERVER_USER="root" # SSH 用户名
SERVER_PASSWORD=">n73qBnCja-,#VF+Wq" # 服务器密码
SERVER_PASSWORD="" # 服务器密码(仅在未配置 SSH 密钥时使用)
SERVER_PATH="/opt/topfans/docker" # 服务器上 docker 目录路径
SSH_KEY_PATH="$HOME/.ssh/id_rsa" # SSH 密钥路径,默认使用 ~/.ssh/id_rsa
# ==================== SSH 别名 ====================
# 使用 sshpass 执行 SSH 命令
# 优先使用 SSH 密钥,如果失败则使用密码
ssh_cmd() {
if [ -n "$SERVER_PASSWORD" ]; then
sshpass -p "$SERVER_PASSWORD" ssh -o StrictHostKeyChecking=no -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" "$@"
else
ssh -o StrictHostKeyChecking=no -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" "$@"
fi
}
ssh_cmd_batch() {
if [ -n "$SERVER_PASSWORD" ]; then
sshpass -p "$SERVER_PASSWORD" ssh -o StrictHostKeyChecking=no -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" "$@"
else
ssh -o StrictHostKeyChecking=no -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" "$@"
fi
}
scp_cmd() {
sshpass -p "$SERVER_PASSWORD" scp -P "$SERVER_PORT" "$@"
if [ -n "$SERVER_PASSWORD" ]; then
sshpass -p "$SERVER_PASSWORD" scp -o StrictHostKeyChecking=no -P "$SERVER_PORT" "$@"
else
scp -o StrictHostKeyChecking=no -P "$SERVER_PORT" "$@"
fi
}
# ==================== 打印函数 ====================
@ -148,7 +163,11 @@ ${YELLOW}示例:${NC}
${YELLOW}前提准备:${NC}
1. 修改 SERVER_HOST 为你的服务器 IP
2. 配置服务器 SSH 免密登录(建议)
2. 配置 SSH 密钥登录(推荐):
- 生成密钥ssh-keygen -t rsa
- 上传公钥ssh-copy-id -i ~/.ssh/id_rsa.pub root@你的服务器IP
- 设置 SSH_KEY_PATH="~/.ssh/id_rsa"
3. 或使用密码登录:设置 SERVER_PASSWORD
EOF
}
@ -341,10 +360,11 @@ ENDSSH
# 上传配置文件
print_step "📤 上传配置文件"
print_msg "$YELLOW" "上传 docker-compose.prod.yml 和 .env.prod..."
print_msg "$YELLOW" "上传 docker-compose.prod.yml, .env.prod, init-db.sql..."
scp_cmd "${SCRIPT_DIR}/docker-compose.prod.yml" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/"
scp_cmd "${SCRIPT_DIR}/.env.prod" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/"
scp_cmd "${SCRIPT_DIR}/init-db.sql" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/"
scp_cmd -r "${SCRIPT_DIR}/sql" "${SERVER_USER}@${SERVER_HOST}:${SERVER_PATH}/"
print_msg "$GREEN" "✅ 配置文件上传完成"
# 从 tar 文件加载镜像

View File

@ -192,6 +192,70 @@ services:
reservations:
memory: 256M
taskservice:
image: topfans/taskservice:latest
build:
context: ..
dockerfile: docker/Dockerfile.services
target: taskservice
container_name: topfans-taskservice
restart: unless-stopped
environment:
<<: *common-env
PORT: 20006
USER_SERVICE_URL: tri://userservice:20000
depends_on:
userservice:
condition: service_healthy
networks:
- topfans-net
extra_hosts:
- "host.docker.internal:host-gateway"
expose:
- "20006"
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 20006 || exit 1"]
<<: *healthcheck
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
starbookservice:
image: topfans/starbookservice:latest
build:
context: ..
dockerfile: docker/Dockerfile.services
target: starbookservice
container_name: topfans-starbookservice
restart: unless-stopped
environment:
<<: *common-env
PORT: 20007
ASSET_SERVICE_URL: tri://assetservice:20003
depends_on:
userservice:
condition: service_healthy
assetservice:
condition: service_healthy
networks:
- topfans-net
extra_hosts:
- "host.docker.internal:host-gateway"
expose:
- "20007"
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 20007 || exit 1"]
<<: *healthcheck
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# ==================== API Gateway ====================
gateway:
image: topfans/gateway:latest
@ -210,6 +274,8 @@ services:
DUBBO_ASSET_SERVICE_URL: tri://assetservice:20003
DUBBO_GALLERY_SERVICE_URL: tri://galleryservice:20004
DUBBO_ACTIVITY_SERVICE_URL: tri://activityservice:20005
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20007
depends_on:
userservice:
condition: service_healthy
@ -221,6 +287,10 @@ services:
condition: service_healthy
activityservice:
condition: service_healthy
taskservice:
condition: service_healthy
starbookservice:
condition: service_healthy
networks:
- topfans-net
ports:

View File

@ -44,7 +44,6 @@ services:
<<: *postgres-env
volumes:
- postgres_data:/var/lib/postgresql
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
ports:
- "5432:5432"
networks:
@ -59,6 +58,27 @@ services:
reservations:
memory: 128M
# ==================== Flyway Migration ====================
flyway:
image: flyway/flyway:10
container_name: topfans-flyway
restart: "no"
depends_on:
postgres:
condition: service_healthy
environment:
- FLYWAY_URL=jdbc:postgresql://postgres:5432/topfans
- FLYWAY_USER=postgres
- FLYWAY_PASSWORD=${DB_PASSWORD:-postgres123}
- FLYWAY_SCHEMAS=public
- FLYWAY_PLACEHOLDER_REPLACEMENT=true
volumes:
- ./sql/migrations:/flyway/sql
- flyway_data:/flyway/data
command: migrate -baselineOnMigrate=true
networks:
- topfans-net
# ==================== Dubbo Services ====================
userservice:
image: topfans/userservice:latest
@ -77,8 +97,8 @@ services:
DB_PASSWORD: ${DB_PASSWORD:-postgres123}
DB_NAME: topfans
depends_on:
postgres:
condition: service_started
flyway:
condition: service_completed_successfully
networks:
- topfans-net
expose:
@ -253,6 +273,80 @@ services:
memory: 32M
cpus: '0.25'
taskservice:
image: topfans/taskservice:latest
build:
context: ..
dockerfile: docker/Dockerfile.services
target: taskservice
container_name: topfans-taskservice
restart: always
environment:
<<: *common-env
PORT: 20006
DB_HOST: postgres
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: ${DB_PASSWORD:-postgres123}
DB_NAME: topfans
USER_SERVICE_URL: tri://userservice:20000
depends_on:
userservice:
condition: service_started
networks:
- topfans-net
expose:
- "20006"
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:21006 || exit 1"]
<<: *healthcheck
deploy:
resources:
limits:
memory: 150M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.25'
starbookservice:
image: topfans/starbookservice:latest
build:
context: ..
dockerfile: docker/Dockerfile.services
target: starbookservice
container_name: topfans-starbookservice
restart: always
environment:
<<: *common-env
PORT: 20007
DB_HOST: postgres
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: ${DB_PASSWORD:-postgres123}
DB_NAME: topfans
ASSET_SERVICE_URL: tri://assetservice:20003
depends_on:
userservice:
condition: service_started
assetservice:
condition: service_started
networks:
- topfans-net
expose:
- "20007"
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:21007 || exit 1"]
<<: *healthcheck
deploy:
resources:
limits:
memory: 150M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.25'
# ==================== API Gateway ====================
gateway:
image: topfans/gateway:latest
@ -273,6 +367,8 @@ services:
DUBBO_ASSET_SERVICE_URL: tri://assetservice:20003
DUBBO_GALLERY_SERVICE_URL: tri://galleryservice:20001
DUBBO_ACTIVITY_SERVICE_URL: tri://activityservice:20004
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20007
depends_on:
userservice:
condition: service_started
@ -284,6 +380,10 @@ services:
condition: service_started
activityservice:
condition: service_started
taskservice:
condition: service_started
starbookservice:
condition: service_started
networks:
- topfans-net
ports:
@ -300,10 +400,11 @@ services:
memory: 64M
cpus: '0.25'
volumes:
postgres_data:
flyway_data:
networks:
topfans-net:
driver: bridge
external: true
volumes:
postgres_data:

View File

@ -253,6 +253,54 @@ CREATE SEQUENCE public.asset_likes_id_seq
ALTER SEQUENCE public.asset_likes_id_seq OWNED BY public.asset_likes.id;
--
-- Name: asset_registry; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.asset_registry (
id bigint NOT NULL,
asset_id bigint NOT NULL,
asset_type character varying(20) NOT NULL,
owner_uid bigint NOT NULL,
star_id bigint NOT NULL,
grade integer,
collection_category character varying(50),
activity_id bigint,
activity_type character varying(50),
status integer DEFAULT 0 NOT NULL,
like_count integer DEFAULT 0 NOT NULL,
display_status integer DEFAULT 0 NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
--
-- Name: TABLE asset_registry; Type: COMMENT; Schema: public; Owner: -
--
COMMENT ON TABLE public.asset_registry IS '资产注册表(星册体系)';
--
-- Name: asset_registry_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.asset_registry_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: asset_registry_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.asset_registry_id_seq OWNED BY public.asset_registry.id;
--
-- Name: assets; Type: TABLE; Schema: public; Owner: -
--
@ -408,7 +456,8 @@ CREATE TABLE public.exhibitions (
start_time bigint NOT NULL,
expire_at bigint NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
updated_at bigint NOT NULL,
deleted_at bigint
);
@ -845,6 +894,13 @@ ALTER TABLE ONLY public.activity_user_stats ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY public.asset_likes ALTER COLUMN id SET DEFAULT nextval('public.asset_likes_id_seq'::regclass);
--
-- Name: asset_registry id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry ALTER COLUMN id SET DEFAULT nextval('public.asset_registry_id_seq'::regclass);
--
-- Name: assets id; Type: DEFAULT; Schema: public; Owner: -
--
@ -852,6 +908,13 @@ ALTER TABLE ONLY public.asset_likes ALTER COLUMN id SET DEFAULT nextval('public.
ALTER TABLE ONLY public.assets ALTER COLUMN id SET DEFAULT nextval('public.assets_id_seq'::regclass);
--
-- Name: asset_registry id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry ALTER COLUMN id SET DEFAULT nextval('public.asset_registry_id_seq'::regclass);
--
-- Name: booth_slots slot_id; Type: DEFAULT; Schema: public; Owner: -
--
@ -969,6 +1032,14 @@ ALTER TABLE ONLY public.asset_likes
ADD CONSTRAINT asset_likes_pkey PRIMARY KEY (id);
--
-- Name: asset_registry asset_registry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry
ADD CONSTRAINT asset_registry_pkey PRIMARY KEY (id);
--
-- Name: assets assets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@ -1180,6 +1251,13 @@ CREATE INDEX idx_asset_likes_asset ON public.asset_likes USING btree (asset_id);
CREATE INDEX idx_asset_likes_user_star ON public.asset_likes USING btree (user_id, star_id);
--
-- Name: idx_registry_owner_star; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx_registry_owner_star ON public.asset_registry USING btree (owner_uid, star_id);
--
-- Name: idx_assets_created_at; Type: INDEX; Schema: public; Owner: -
--
@ -1549,6 +1627,30 @@ ALTER TABLE ONLY public.asset_likes
ADD CONSTRAINT fk_asset_likes_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
--
-- Name: asset_registry fk_registry_asset; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry
ADD CONSTRAINT fk_registry_asset FOREIGN KEY (asset_id) REFERENCES public.assets(id) ON DELETE CASCADE;
--
-- Name: asset_registry fk_registry_owner; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry
ADD CONSTRAINT fk_registry_owner FOREIGN KEY (owner_uid) REFERENCES public.users(id) ON DELETE CASCADE;
--
-- Name: asset_registry fk_registry_star; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.asset_registry
ADD CONSTRAINT fk_registry_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
--
-- Name: assets fk_assets_owner; Type: FK CONSTRAINT; Schema: public; Owner: -
--

View File

@ -0,0 +1,491 @@
-- V1__init_schema.sql
-- 初始表结构
-- postgres 扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- =====================================================
-- 用户相关表
-- =====================================================
-- 用户表
CREATE TABLE public.users (
id bigint NOT NULL,
mobile character varying(11) NOT NULL,
password_hash character varying(255) NOT NULL,
access_token text,
token_expires_at bigint,
avatar_url character varying(500),
global_wallet_address character varying(100),
is_active boolean DEFAULT true NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
deleted_at bigint
);
CREATE SEQUENCE public.users_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
ALTER TABLE ONLY public.users ADD CONSTRAINT users_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_users_mobile ON public.users USING btree (mobile);
CREATE INDEX idx_users_deleted_at ON public.users USING btree (deleted_at);
ALTER TABLE ONLY public.users ADD CONSTRAINT fk_users FOREIGN KEY (owner_uid) REFERENCES public.users(id) ON DELETE CASCADE;
-- 明星信息表
CREATE TABLE public.stars (
star_id bigint NOT NULL,
name character varying(100) NOT NULL,
tag character varying(100),
name_en character varying(100),
pic_url character varying(500),
description text,
identity_id character varying(50) NOT NULL,
is_active boolean DEFAULT true NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.stars_star_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.stars ALTER COLUMN star_id SET DEFAULT nextval('public.stars_star_id_seq'::regclass);
ALTER TABLE ONLY public.stars ADD CONSTRAINT stars_pkey PRIMARY KEY (star_id);
CREATE UNIQUE INDEX uk_stars_identity_id ON public.stars USING btree (identity_id);
-- 粉丝档案表
CREATE TABLE public.fan_profiles (
id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
nickname character varying(50) NOT NULL,
level integer DEFAULT 1 NOT NULL,
times integer DEFAULT 1 NOT NULL,
social integer DEFAULT 0 NOT NULL,
experience bigint DEFAULT 0 NOT NULL,
coin_balance bigint DEFAULT 0 NOT NULL,
crystal_balance bigint DEFAULT 0 NOT NULL,
tags jsonb,
starbook_limit integer DEFAULT 3 NOT NULL,
slot_limit integer DEFAULT 3 NOT NULL,
assets_count integer DEFAULT 0 NOT NULL,
chain_address character varying(100),
is_active boolean DEFAULT true NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
avatar_url character varying(500)
);
CREATE SEQUENCE public.fan_profiles_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.fan_profiles ALTER COLUMN id SET DEFAULT nextval('public.fan_profiles_id_seq'::regclass);
ALTER TABLE ONLY public.fan_profiles ADD CONSTRAINT fan_profiles_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_fan_profiles_user_star ON public.fan_profiles USING btree (user_id, star_id);
CREATE UNIQUE INDEX uk_fan_profiles_star_nickname ON public.fan_profiles USING btree (star_id, nickname);
CREATE INDEX idx_fan_profiles_user_id ON public.fan_profiles USING btree (user_id);
CREATE INDEX idx_fan_profiles_star_id ON public.fan_profiles USING btree (star_id);
ALTER TABLE ONLY public.fan_profiles ADD CONSTRAINT fk_fan_profiles_user FOREIGN KEY (user_id) REFERENCES public.users(id);
ALTER TABLE ONLY public.fan_profiles ADD CONSTRAINT fk_fan_profiles_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id);
-- =====================================================
-- 藏品相关表
-- =====================================================
-- 资产表(藏品)
CREATE TABLE public.assets (
id bigint NOT NULL,
owner_uid bigint NOT NULL,
star_id bigint NOT NULL,
name character varying(100) NOT NULL,
cover_url character varying(500) NOT NULL,
material_url character varying(500),
description text,
rarity integer,
tags jsonb,
visibility character varying(20) DEFAULT 'private'::character varying,
status integer DEFAULT 0 NOT NULL,
tx_hash character varying(100),
block_number bigint,
like_count integer DEFAULT 0 NOT NULL,
is_original boolean DEFAULT false NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
minted_at bigint,
deleted_at bigint,
is_active boolean DEFAULT true NOT NULL,
info text
);
CREATE SEQUENCE public.assets_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.assets ALTER COLUMN id SET DEFAULT nextval('public.assets_id_seq'::regclass);
ALTER TABLE ONLY public.assets ADD CONSTRAINT assets_pkey PRIMARY KEY (id);
CREATE INDEX idx_assets_created_at ON public.assets USING btree (created_at DESC);
CREATE INDEX idx_assets_deleted_at ON public.assets USING btree (deleted_at);
CREATE INDEX idx_assets_owner_star ON public.assets USING btree (owner_uid, star_id);
CREATE INDEX idx_assets_star_active ON public.assets USING btree (star_id, is_active);
CREATE INDEX idx_assets_status ON public.assets USING btree (status);
CREATE INDEX idx_assets_tx_hash ON public.assets USING btree (tx_hash);
ALTER TABLE ONLY public.assets ADD CONSTRAINT fk_assets_owner FOREIGN KEY (owner_uid) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.assets ADD CONSTRAINT fk_assets_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
-- 资产注册表(星册体系)
CREATE TABLE public.asset_registry (
id bigint NOT NULL,
asset_id bigint NOT NULL,
asset_type character varying(20) NOT NULL,
owner_uid bigint NOT NULL,
star_id bigint NOT NULL,
grade integer,
collection_category character varying(50),
activity_id bigint,
activity_type character varying(50),
status integer DEFAULT 0 NOT NULL,
like_count integer DEFAULT 0 NOT NULL,
display_status integer DEFAULT 0 NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.asset_registry_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.asset_registry ALTER COLUMN id SET DEFAULT nextval('public.asset_registry_id_seq'::regclass);
ALTER TABLE ONLY public.asset_registry ADD CONSTRAINT asset_registry_pkey PRIMARY KEY (id);
CREATE INDEX idx_registry_owner_star ON public.asset_registry USING btree (owner_uid, star_id);
ALTER TABLE ONLY public.asset_registry ADD CONSTRAINT fk_registry_asset FOREIGN KEY (asset_id) REFERENCES public.assets(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.asset_registry ADD CONSTRAINT fk_registry_owner FOREIGN KEY (owner_uid) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.asset_registry ADD CONSTRAINT fk_registry_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
-- 点赞记录表
CREATE TABLE public.asset_likes (
id bigint NOT NULL,
asset_id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
created_at bigint NOT NULL
);
CREATE SEQUENCE public.asset_likes_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.asset_likes ALTER COLUMN id SET DEFAULT nextval('public.asset_likes_id_seq'::regclass);
ALTER TABLE ONLY public.asset_likes ADD CONSTRAINT asset_likes_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_asset_likes_user_asset ON public.asset_likes USING btree (user_id, asset_id);
CREATE INDEX idx_asset_likes_asset ON public.asset_likes USING btree (asset_id);
CREATE INDEX idx_asset_likes_user_star ON public.asset_likes USING btree (user_id, star_id);
ALTER TABLE ONLY public.asset_likes ADD CONSTRAINT fk_asset_likes_asset FOREIGN KEY (asset_id) REFERENCES public.assets(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.asset_likes ADD CONSTRAINT fk_asset_likes_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
ALTER TABLE ONLY public.asset_likes ADD CONSTRAINT fk_asset_likes_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- 铸造订单表
CREATE TABLE public.mint_orders (
order_id character varying(100) NOT NULL,
user_id bigint NOT NULL,
asset_id bigint,
star_id bigint NOT NULL,
status character varying(20) DEFAULT 'PENDING'::character varying NOT NULL,
cost_crystal bigint DEFAULT 0,
error_message text,
retry_count integer DEFAULT 0,
material_url character varying(500),
name character varying(100),
description text,
material_type character varying(50),
event character varying(100),
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
minted_at bigint,
info text
);
ALTER TABLE ONLY public.mint_orders ADD CONSTRAINT mint_orders_pkey PRIMARY KEY (order_id);
CREATE INDEX idx_mint_orders_asset ON public.mint_orders USING btree (asset_id);
CREATE INDEX idx_mint_orders_created_at ON public.mint_orders USING btree (created_at DESC);
CREATE INDEX idx_mint_orders_status ON public.mint_orders USING btree (status);
CREATE INDEX idx_mint_orders_user_star ON public.mint_orders USING btree (user_id, star_id);
ALTER TABLE ONLY public.mint_orders ADD CONSTRAINT fk_mint_orders_asset FOREIGN KEY (asset_id) REFERENCES public.assets(id) ON DELETE SET NULL;
ALTER TABLE ONLY public.mint_orders ADD CONSTRAINT fk_mint_orders_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
ALTER TABLE ONLY public.mint_orders ADD CONSTRAINT fk_mint_orders_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- =====================================================
-- 展馆相关表
-- =====================================================
-- 展位表
CREATE TABLE public.booth_slots (
slot_id bigint NOT NULL,
host_profile_id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
slot_index bigint NOT NULL,
visibility character varying(20) DEFAULT 'public'::character varying,
is_enabled boolean DEFAULT false,
unlock_type character varying(20),
unlock_value bigint,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.booth_slots_slot_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.booth_slots ALTER COLUMN slot_id SET DEFAULT nextval('public.booth_slots_slot_id_seq'::regclass);
ALTER TABLE ONLY public.booth_slots ADD CONSTRAINT booth_slots_pkey PRIMARY KEY (slot_id);
CREATE UNIQUE INDEX idx_host_slot ON public.booth_slots USING btree (host_profile_id, slot_index);
CREATE INDEX idx_star_enabled ON public.booth_slots USING btree (star_id);
CREATE INDEX idx_user_star ON public.booth_slots USING btree (user_id, star_id);
ALTER TABLE ONLY public.booth_slots ADD CONSTRAINT fk_booth_slots_profile FOREIGN KEY (host_profile_id) REFERENCES public.fan_profiles(id) ON DELETE CASCADE;
-- 展品展示表
CREATE TABLE public.exhibitions (
id bigint NOT NULL,
asset_id bigint NOT NULL,
slot_id bigint NOT NULL,
host_profile_id bigint NOT NULL,
occupier_uid bigint NOT NULL,
occupier_star_id bigint NOT NULL,
start_time bigint NOT NULL,
expire_at bigint NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
deleted_at bigint
);
CREATE SEQUENCE public.exhibitions_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.exhibitions ALTER COLUMN id SET DEFAULT nextval('public.exhibitions_id_seq'::regclass);
ALTER TABLE ONLY public.exhibitions ADD CONSTRAINT exhibitions_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_asset ON public.exhibitions USING btree (asset_id);
CREATE INDEX idx_expire ON public.exhibitions USING btree (expire_at);
CREATE INDEX idx_host ON public.exhibitions USING btree (host_profile_id);
CREATE INDEX idx_occupier ON public.exhibitions USING btree (occupier_uid, occupier_star_id);
CREATE INDEX idx_slot ON public.exhibitions USING btree (slot_id);
ALTER TABLE ONLY public.exhibitions ADD CONSTRAINT fk_exhibitions_asset FOREIGN KEY (asset_id) REFERENCES public.assets(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.exhibitions ADD CONSTRAINT fk_exhibitions_slot FOREIGN KEY (slot_id) REFERENCES public.booth_slots(slot_id) ON DELETE CASCADE;
-- 展览收入记录表
CREATE TABLE public.exhibition_revenue_records (
id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
exhibition_id bigint NOT NULL,
asset_id bigint NOT NULL,
slot_id bigint NOT NULL,
slot_owner_uid bigint NOT NULL,
slot_type character varying(20) NOT NULL,
crystal_amount bigint NOT NULL,
cycle_start_time bigint NOT NULL,
cycle_end_time bigint NOT NULL,
status character varying(20) DEFAULT 'claimable'::character varying NOT NULL,
claimed_at bigint,
created_at bigint NOT NULL
);
CREATE SEQUENCE public.exhibition_revenue_records_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.exhibition_revenue_records ALTER COLUMN id SET DEFAULT nextval('public.exhibition_revenue_records_id_seq'::regclass);
ALTER TABLE ONLY public.exhibition_revenue_records ADD CONSTRAINT exhibition_revenue_records_pkey PRIMARY KEY (id);
CREATE INDEX idx_user_revenue ON public.exhibition_revenue_records USING btree (user_id, star_id);
-- =====================================================
-- 社交相关表
-- =====================================================
-- 好友请求表
CREATE TABLE public.friend_requests (
id bigint NOT NULL,
from_user_id bigint NOT NULL,
to_user_id bigint NOT NULL,
star_id bigint NOT NULL,
message character varying(200),
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
expires_at bigint,
processed_at bigint
);
CREATE SEQUENCE public.friend_requests_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.friend_requests ALTER COLUMN id SET DEFAULT nextval('public.friend_requests_id_seq'::regclass);
ALTER TABLE ONLY public.friend_requests ADD CONSTRAINT friend_requests_pkey PRIMARY KEY (id);
CREATE INDEX idx_friend_requests_expires ON public.friend_requests USING btree (expires_at);
CREATE INDEX idx_friend_requests_from_status ON public.friend_requests USING btree (from_user_id, status, created_at DESC);
CREATE INDEX idx_friend_requests_star ON public.friend_requests USING btree (star_id);
CREATE INDEX idx_friend_requests_to_status ON public.friend_requests USING btree (to_user_id, status, created_at DESC);
CREATE INDEX idx_friend_requests_users_star ON public.friend_requests USING btree (from_user_id, to_user_id, star_id, created_at DESC);
ALTER TABLE ONLY public.friend_requests ADD CONSTRAINT fk_friend_requests_from_user FOREIGN KEY (from_user_id) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.friend_requests ADD CONSTRAINT fk_friend_requests_to_user FOREIGN KEY (to_user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- 好友关系表
CREATE TABLE public.friendships (
id bigint NOT NULL,
user_id bigint NOT NULL,
friend_id bigint NOT NULL,
star_id bigint NOT NULL,
status character varying(20) DEFAULT 'accepted'::character varying NOT NULL,
remark character varying(50),
intimacy integer DEFAULT 0 NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.friendships_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.friendships ALTER COLUMN id SET DEFAULT nextval('public.friendships_id_seq'::regclass);
ALTER TABLE ONLY public.friendships ADD CONSTRAINT friendships_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_friendships_user_friend_star ON public.friendships USING btree (user_id, friend_id, star_id);
CREATE INDEX idx_friendships_friend_star ON public.friendships USING btree (friend_id);
CREATE INDEX idx_friendships_list_query ON public.friendships USING btree (user_id, friend_id, star_id, status, remark, created_at);
CREATE INDEX idx_friendships_user_star_created ON public.friendships USING btree (user_id, star_id, created_at DESC);
CREATE INDEX idx_friendships_user_star_status ON public.friendships USING btree (user_id, star_id, status);
ALTER TABLE ONLY public.friendships ADD CONSTRAINT fk_friendships_friend FOREIGN KEY (friend_id) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.friendships ADD CONSTRAINT fk_friendships_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- =====================================================
-- 任务相关表
-- =====================================================
-- 任务定义表
CREATE TABLE public.task_definitions (
id bigint NOT NULL,
task_key character varying(50) NOT NULL,
task_type character varying(20) NOT NULL,
name character varying(100) NOT NULL,
description text,
crystal_reward bigint DEFAULT 0 NOT NULL,
exp_reward bigint DEFAULT 0 NOT NULL,
sort_order integer DEFAULT 0 NOT NULL,
is_active boolean DEFAULT true NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.task_definitions_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.task_definitions ALTER COLUMN id SET DEFAULT nextval('public.task_definitions_id_seq'::regclass);
ALTER TABLE ONLY public.task_definitions ADD CONSTRAINT task_definitions_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX idx_task_definitions_task_key ON public.task_definitions USING btree (task_key);
-- 用户任务进度表
CREATE TABLE public.user_task_progress (
id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
task_key character varying(50) NOT NULL,
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
completed_at bigint,
claimed_at bigint,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.user_task_progress_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.user_task_progress ALTER COLUMN id SET DEFAULT nextval('public.user_task_progress_id_seq'::regclass);
ALTER TABLE ONLY public.user_task_progress ADD CONSTRAINT user_task_progress_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_user_task ON public.user_task_progress USING btree (user_id, star_id, task_key);
-- 用户引导状态表
CREATE TABLE public.user_onboarding_status (
id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
is_onboarding_completed boolean DEFAULT false NOT NULL,
is_onboarding_claimed boolean DEFAULT false NOT NULL,
has_friend_display_bonus boolean DEFAULT false NOT NULL,
onboarding_completed_at bigint,
onboarding_claimed_at bigint,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.user_onboarding_status_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.user_onboarding_status ALTER COLUMN id SET DEFAULT nextval('public.user_onboarding_status_id_seq'::regclass);
ALTER TABLE ONLY public.user_onboarding_status ADD CONSTRAINT user_onboarding_status_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_user_star_onboarding ON public.user_onboarding_status USING btree (user_id, star_id);
-- =====================================================
-- 活动相关表
-- =====================================================
-- 活动表
CREATE TABLE public.activities (
id bigint NOT NULL,
activity_type character varying(50) NOT NULL,
title character varying(100) NOT NULL,
description text,
star_id bigint NOT NULL,
start_time bigint NOT NULL,
end_time bigint NOT NULL,
target_progress bigint DEFAULT 1000 NOT NULL,
current_progress bigint DEFAULT 0 NOT NULL,
status character varying(20) DEFAULT 'pending'::character varying,
stage_configs jsonb,
created_at bigint NOT NULL,
updated_at bigint NOT NULL,
theme character varying(100),
overall_end_time bigint DEFAULT 0
);
CREATE SEQUENCE public.activities_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.activities ALTER COLUMN id SET DEFAULT nextval('public.activities_id_seq'::regclass);
ALTER TABLE ONLY public.activities ADD CONSTRAINT activities_pkey PRIMARY KEY (id);
CREATE INDEX idx_activities_star_id ON public.activities USING btree (star_id);
CREATE INDEX idx_activities_start_end ON public.activities USING btree (start_time, end_time);
CREATE INDEX idx_activities_status ON public.activities USING btree (status);
-- 活动道具表
CREATE TABLE public.activity_items (
id bigint NOT NULL,
activity_id bigint NOT NULL,
item_type character varying(50) NOT NULL,
item_name character varying(50) NOT NULL,
icon_url character varying(500),
crystal_cost bigint NOT NULL,
contribution_points bigint NOT NULL,
sort_order integer DEFAULT 0 NOT NULL,
is_active boolean DEFAULT true NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.activity_items_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.activity_items ALTER COLUMN id SET DEFAULT nextval('public.activity_items_id_seq'::regclass);
ALTER TABLE ONLY public.activity_items ADD CONSTRAINT activity_items_pkey PRIMARY KEY (id);
CREATE INDEX idx_activity_items_activity ON public.activity_items USING btree (activity_id);
CREATE INDEX idx_activity_items_type ON public.activity_items USING btree (item_type);
ALTER TABLE ONLY public.activity_items ADD CONSTRAINT fk_activity_items_activity FOREIGN KEY (activity_id) REFERENCES public.activities(id) ON DELETE CASCADE;
-- 活动贡献记录表
CREATE TABLE public.activity_contributions (
id bigint NOT NULL,
activity_id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
item_id bigint NOT NULL,
item_type character varying(50) NOT NULL,
quantity integer DEFAULT 1 NOT NULL,
crystal_spent bigint NOT NULL,
contribution_points bigint NOT NULL,
created_at bigint NOT NULL
);
CREATE SEQUENCE public.activity_contributions_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.activity_contributions ALTER COLUMN id SET DEFAULT nextval('public.activity_contributions_id_seq'::regclass);
ALTER TABLE ONLY public.activity_contributions ADD CONSTRAINT activity_contributions_pkey PRIMARY KEY (id);
CREATE INDEX idx_activity_contributions_activity ON public.activity_contributions USING btree (activity_id);
CREATE INDEX idx_activity_contributions_created ON public.activity_contributions USING btree (created_at DESC);
CREATE INDEX idx_activity_contributions_user_star ON public.activity_contributions USING btree (user_id, star_id);
ALTER TABLE ONLY public.activity_contributions ADD CONSTRAINT fk_activity_contributions_activity FOREIGN KEY (activity_id) REFERENCES public.activities(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.activity_contributions ADD CONSTRAINT fk_activity_contributions_item FOREIGN KEY (item_id) REFERENCES public.activity_items(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.activity_contributions ADD CONSTRAINT fk_activity_contributions_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
ALTER TABLE ONLY public.activity_contributions ADD CONSTRAINT fk_activity_contributions_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
-- 用户活动统计表
CREATE TABLE public.activity_user_stats (
id bigint NOT NULL,
activity_id bigint NOT NULL,
user_id bigint NOT NULL,
star_id bigint NOT NULL,
total_contribution bigint DEFAULT 0 NOT NULL,
total_crystal_spent bigint DEFAULT 0 NOT NULL,
total_items integer DEFAULT 0 NOT NULL,
last_contribute_at bigint NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
CREATE SEQUENCE public.activity_user_stats_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;
ALTER TABLE ONLY public.activity_user_stats ALTER COLUMN id SET DEFAULT nextval('public.activity_user_stats_id_seq'::regclass);
ALTER TABLE ONLY public.activity_user_stats ADD CONSTRAINT activity_user_stats_pkey PRIMARY KEY (id);
CREATE UNIQUE INDEX uk_activity_user_star ON public.activity_user_stats UNIQUE (activity_id, user_id, star_id);
CREATE INDEX idx_activity_user_stats_activity ON public.activity_user_stats USING btree (activity_id);
CREATE INDEX idx_activity_user_stats_contribution ON public.activity_user_stats USING btree (activity_id, total_contribution DESC);
CREATE INDEX idx_activity_user_stats_user_star ON public.activity_user_stats USING btree (user_id, star_id);
ALTER TABLE ONLY public.activity_user_stats ADD CONSTRAINT fk_activity_user_stats_activity FOREIGN KEY (activity_id) REFERENCES public.activities(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.activity_user_stats ADD CONSTRAINT fk_activity_user_stats_star FOREIGN KEY (star_id) REFERENCES public.stars(star_id) ON DELETE CASCADE;
ALTER TABLE ONLY public.activity_user_stats ADD CONSTRAINT fk_activity_user_stats_user FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;

View File

@ -0,0 +1,4 @@
-- V2__add_deleted_at_to_exhibitions.sql
-- 为 exhibitions 表添加 deleted_at 字段(软删除)
ALTER TABLE public.exhibitions ADD COLUMN IF NOT EXISTS deleted_at bigint;

View File

@ -0,0 +1,17 @@
-- V3__seed_data.sql
-- 种子数据(测试用)
-- 插入测试明星
INSERT INTO public.stars (star_id, name, tag, identity_id, is_active, created_at, updated_at) VALUES
(87, '测试明星', '测试', 'test_star_001', true, 1704067200000, 1704067200000)
ON CONFLICT DO NOTHING;
-- 插入测试用户
INSERT INTO public.users (id, mobile, password_hash, is_active, created_at, updated_at) VALUES
(1, '13800138000', '$2a$10$N9qo8uLOickgx2ZMRZoMye6e7q5R5pR6Vx9qPCr5dLq5R5pR6Vx9q', true, 1704067200000, 1704067200000)
ON CONFLICT DO NOTHING;
-- 插入测试粉丝档案
INSERT INTO public.fan_profiles (id, user_id, star_id, nickname, level, times, social, experience, coin_balance, crystal_balance, starbook_limit, slot_limit, assets_count, is_active, created_at, updated_at, avatar_url) VALUES
(1, 1, 87, '测试用户', 5, 3, 10, 1000, 500, 100, 3, 3, 5, true, 1704067200000, 1704067200000, 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/avatar/1/87/avatar.png')
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,53 @@
-- V4__seed_data.sql
-- 种子数据
-- 插入明星数据
INSERT INTO public.stars (star_id, name, tag, name_en, pic_url, description, identity_id, is_active, created_at, updated_at) VALUES
(87, '肖战', '小飞侠', 'Xiao Zhan', NULL, NULL, 'xz', true, 1773322573872, 1773322573872),
(88, '王一博', '小摩托', 'Wang Yibo', NULL, NULL, 'wyb', true, 1773322573872, 1773322573872),
(89, '杨洋', NULL, 'Yang Yang', NULL, NULL, 'yy', true, 1773322573872, 1773322573872),
(93, 'Lisa', 'BLACKPINK', 'Lalisa Manoban', 'https://example.com/lisa.jpg', 'BLACKPINK成员', 'lisa_test_001', true, 1773407317000, 1773407317000),
(94, 'Jennie', 'BLACKPINK', 'Jennie Kim', 'https://example.com/jennie.jpg', 'BLACKPINK成员', 'jennie_test_001', true, 1773407317000, 1773407317000),
(95, 'Rosé', 'BLACKPINK', 'Park Chae-young', 'https://example.com/rose.jpg', 'BLACKPINK成员', 'rose_test_001', true, 1773407317000, 1773407317000)
ON CONFLICT (star_id) DO UPDATE SET name = EXCLUDED.name, tag = EXCLUDED.tag;
-- 插入用户数据
INSERT INTO public.users (id, mobile, password_hash, access_token, token_expires_at, avatar_url, global_wallet_address, is_active, created_at, updated_at, deleted_at) VALUES
(1, '13900000001', '$2a$10$bNqpiVCSSrhxAsUelgkEp.j1untPPWMDT5208fmcUPKpsFHZlhjrW', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJzdGFyX2lkIjo4NywidXBkYXRlZF9hdCI6MTc3MzU4MTI5MiwiZXhwIjoxNzc3NTI5OTg5LCJpYXQiOjE3NzY5MjUxODl9.LooVCY-ST2y02qKud8A6GRVL1RxOy4pXVD6KC7NLs6w', 1777529989817, 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/avatar/1/87/avatar.png', NULL, true, 1773322609887, 1773581292, NULL),
(2, '17319409126', '$2a$10$bNqpiVCSSrhxAsUelgkEp.j1untPPWMDT5208fmcUPKpsFHZlhjrW', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJzdGFyX2lkIjo4NywidXBkYXRlZF9hdCI6MTc3MzMyNDEwMTU5OCwiZXhwIjoxNzc0NDIxOTIxLCJpYXQiOjE3NzM4MTcxMjF9.GnAL9exh-VcXDHqwTn1bNKVAEAg6xfMMpd6JTqkwugk', 1774421921871, NULL, NULL, true, 1773324101598, 1773324101598, NULL),
(8, '13800000001', '$2a$10$LC8XORj6Nx9nT6j5QHfN5u2nB4z8e0v2dWm7X2uF5m6W6br8NnDPS', NULL, NULL, NULL, NULL, true, 1773407317000, 1776649807972, NULL),
(9, '13800000002', '$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', NULL, NULL, NULL, NULL, true, 1773407317000, 1773407317000, NULL),
(10, '13800000003', '$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', NULL, NULL, NULL, NULL, true, 1773407317000, 1773407317000, NULL),
(11, '13800000004', '$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', NULL, NULL, NULL, NULL, true, 1773407317000, 1773407317000, NULL),
(12, '13800000005', '$2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', NULL, NULL, NULL, NULL, true, 1773407317000, 1773407317000, NULL)
ON CONFLICT (id) DO UPDATE SET access_token = EXCLUDED.access_token, updated_at = EXCLUDED.updated_at;
-- 插入粉丝档案数据
INSERT INTO public.fan_profiles (id, user_id, star_id, nickname, level, times, social, experience, coin_balance, crystal_balance, tags, starbook_limit, slot_limit, assets_count, chain_address, is_active, created_at, updated_at, avatar_url) VALUES
(1, 1, 87, '测试用户', 6, 1, 0, 1750, 0, 10919, '[]', 3, 3, 42, NULL, true, 1773322609890, 1776926969, 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/avatar/1/87/avatar.png'),
(2, 2, 87, 'topfans1', 1, 1, 0, 0, 0, 0, '[]', 3, 3, 0, NULL, true, 1773324101600, 1773324101600, NULL)
ON CONFLICT (id) DO UPDATE SET level = EXCLUDED.level, experience = EXCLUDED.experience, updated_at = EXCLUDED.updated_at;
-- 插入 Booth Slots 数据
INSERT INTO public.booth_slots (slot_id, host_profile_id, user_id, star_id, slot_index, visibility, is_enabled, unlock_type, unlock_value, created_at, updated_at) VALUES
(37, 1, 1, 87, 1, 'public', true, 'free', 0, 1773748940460, 1773748940460),
(38, 1, 1, 87, 2, 'public', true, 'free', 0, 1773748940460, 1773748940460),
(39, 1, 1, 87, 3, 'public', true, 'free', 0, 1773748940460, 1773748940460),
(40, 1, 1, 87, 4, 'private', true, 'free', 0, 1773748940460, 1773748940460),
(41, 1, 1, 87, 5, 'private', true, 'free', 0, 1773748940460, 1773748940460),
(42, 1, 1, 87, 6, 'private', true, 'free', 0, 1773748940460, 1773748940460)
ON CONFLICT (slot_id) DO NOTHING;
-- 插入 Assets 数据
INSERT INTO public.assets (id, owner_uid, star_id, name, cover_url, material_url, description, grade, tags, visibility, status, tx_hash, block_number, like_count, is_original, created_at, updated_at, minted_at, deleted_at, is_active, info) VALUES
(17, 1, 87, '1', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/covers/17_1775472017.png', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/ai_generated_1775472013603_g87iwk.png', NULL, NULL, NULL, 'private', 1, '0x1922ffb92fcd1a7e4a8c6b3347e8e0791f6107fa44cbde1734ecb1bcd861d63c', 1493537, 4, false, 1775472014756, 1775472018619, 1775472018619, NULL, true, NULL),
(18, 1, 87, '1', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/covers/18_1775539093.png', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/ai_generated_1775539090338_fwzxhs.png', NULL, NULL, NULL, 'private', 1, '0x40ca6efcf29f47731453f01b5c364cde52acbce597c1fbf8261301cc44ec4b7a', 1406147, 2, false, 1775539090589, 1775539093945, 1775539093945, NULL, true, NULL),
(19, 1, 87, '未命名藏品', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/covers/19_1775812743.png', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/ai_generated_1775812740051_gf8ew2.png', NULL, NULL, NULL, 'private', 1, '0x0e3e47c58814e52465ac1e928f3392ff1c692ea448103fcb03c4a10077141308', 1219083, 3, false, 1775812740261, 1775812743690, 1775812743690, NULL, true, '111'),
(20, 1, 87, '星卡', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/covers/20_1775812994.png', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/1775812991404.jpg', NULL, NULL, NULL, 'private', 1, '0x49d04d7463cf03374f6e622d739f69e83cd4ba84b145627f039a22340daead93', 1938046, 2, false, 1775812991507, 1775812994841, 1775812994841, NULL, true, '11'),
(21, 1, 87, '星卡', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/covers/21_1775813155.png', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/1775813152104.jpg', NULL, NULL, NULL, 'private', 1, '0x6d7c0c276d7485d17e6dea4c9d1e394315613d69114699d341c221a22e8156f8', 1118388, 2, false, 1775813152198, 1775813155515, 1775813155515, NULL, true, '2')
ON CONFLICT (id) DO UPDATE SET like_count = EXCLUDED.like_count;
-- 插入 Exhibitions 数据
INSERT INTO public.exhibitions (id, asset_id, slot_id, host_profile_id, occupier_uid, occupier_star_id, start_time, expire_at, created_at, updated_at, deleted_at) VALUES
(1, 17, 37, 1, 1, 87, 1776926400000, 1777555200000, 1776926400000, 1776926400000, NULL)
ON CONFLICT (id) DO NOTHING;

View File

@ -161,18 +161,18 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/tasks/daily-tasks",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/tasks/guide",
"style": {
"navigationStyle": "custom"
}
},
// {
// "path": "pages/tasks/daily-tasks",
// "style": {
// "navigationStyle": "custom"
// }
// },
// {
// "path": "pages/tasks/guide",
// "style": {
// "navigationStyle": "custom"
// }
// },
{
"path": "pages/tasks/revenue",
"style": {

View File

@ -29,7 +29,7 @@
<view class="user-info-with-artwork">
<image
class="user-avatar-small"
:src="item.avatar_url || '/static/avatar/1.jpeg'"
:src="item.owner_avatar || '/static/avatar/1.jpeg'"
mode="aspectFit"
/>
<text class="user-nickname">用户 :

View File

@ -66,11 +66,12 @@
>
<image
class="nft-image"
:class="{ 'nft-image-displayed': item.display_status === 1 }"
:src="item.coverUrl"
mode="aspectFill"
/>
<view class="status-badge" :class="item.display_status === 1 ? 'status-badge-active' : 'status-badge-pending'">
<text class="status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
<view v-if="item.display_status === 1" class="status-overlay">
<text class="status-text-center">已展示</text>
</view>
<view class="nft-info">
<text class="nft-name">{{ item.name }}</text>
@ -89,7 +90,7 @@
</view>
</view>
<!-- 典藏藏品 category 分组 -->
<!-- 典藏藏品: category 分组 -->
<view v-if="currentType === 'collection'">
<view v-for="group in collectionGroups" :key="group.category" class="nft-group">
<!-- 分组标题 -->
@ -108,11 +109,12 @@
>
<image
class="nft-image"
:class="{ 'nft-image-displayed': item.display_status === 1 }"
:src="item.coverUrl"
mode="aspectFill"
/>
<view class="status-badge" :class="item.display_status === 1 ? 'status-badge-active' : 'status-badge-pending'">
<text class="status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
<view v-if="item.display_status === 1" class="status-overlay">
<text class="status-text-center">已展示</text>
</view>
<view class="nft-info">
<text class="nft-name">{{ item.name }}</text>
@ -131,7 +133,7 @@
</view>
</view>
<!-- 活动藏品 activity_type 分组 -->
<!-- 活动藏品: activity_type 分组 -->
<view v-if="currentType === 'activity'">
<view v-for="group in activityGroups" :key="group.category" class="nft-group">
<!-- 分组标题 -->
@ -150,11 +152,12 @@
>
<image
class="nft-image"
:class="{ 'nft-image-displayed': item.display_status === 1 }"
:src="item.coverUrl"
mode="aspectFill"
/>
<view class="status-badge" :class="item.display_status === 1 ? 'status-badge-active' : 'status-badge-pending'">
<text class="status-text">{{ item.display_status === 1 ? '已展示' : '待展示' }}</text>
<view v-if="item.display_status === 1" class="status-overlay">
<text class="status-text-center">已展示</text>
</view>
<view class="nft-info">
<text class="nft-name">{{ item.name }}</text>
@ -540,10 +543,15 @@ watch(() => props.isActive, (newVal) => {
width: 192rpx;
height: 224rpx;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.05);
/* background: rgba(255, 255, 255, 0.05); */
display: block;
}
/* 已展示的图片 - 灰色滤镜 */
.nft-image-displayed {
filter: grayscale(23%);
}
/* 更多占位符 */
.more-placeholder {
background: rgba(255, 255, 255, 0.1);
@ -559,26 +567,45 @@ watch(() => props.isActive, (newVal) => {
text-align: center;
}
/* 展示状态标签 */
.status-badge {
/* 展示状态覆盖层 - 居中显示 */
.status-overlay {
position: absolute;
top: 8rpx;
right: 8rpx;
border-radius: 8rpx;
padding: 4rpx 8rpx;
top: 0;
left: 0;
width: 192rpx;
height: 224rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
z-index: 1;
}
.status-badge-active {
background: linear-gradient(135deg, #FFD700, #FFA500);
box-shadow: 0 0 12rpx rgba(255, 215, 0, 0.6);
}
.status-badge-pending {
background: rgba(0, 0, 0, 0.6);
}
.status-text {
font-size: 18rpx;
.status-text-center {
font-size: 24rpx;
color: #fff;
font-weight: bold;
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8);
/* 渐变:左浅橙粉 → 右柔粉红 */
background: linear-gradient(to bottom right,
#F0E4B1 0%, /* 左:浅橙粉 */
#F08399 50%,
#B94E73 100% /* 右:柔粉红 */
);
border-radius: 24rpx; /* 胶囊形 */
/* 立体感核心:多层阴影 + 内阴影模拟凸起 */
box-shadow:
/* 外层投影 - 让按钮浮起 */
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
/* 内阴影 - 模拟顶部受光 + 底部凹陷 */
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4), /* 顶部高光 */
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05); /* 底部暗部 */
padding: 16rpx;
}
.nft-name {

View File

@ -5,15 +5,7 @@
<!-- 礼盒区域 -->
<view class="gift-box">
<view class="gift-container">
<view class="gift-bow"></view>
<view class="gift-body">
<text class="gift-text">TOPFANS</text>
<view class="gift-window">
<text class="question-mark">?</text>
</view>
</view>
</view>
<image class="gift-image" src="/static/nft/lihe.png" mode="aspectFit" />
</view>
<!-- 进度条区域 -->
@ -206,11 +198,11 @@ onMounted(() => {
.gift-box {
position: absolute;
top: 20%;
top: 25%;
left: 50%;
transform: translateX(-50%);
width: 450rpx;
height: 450rpx;
width: 480rpx;
height: 480rpx;
z-index: 2;
animation: float 3s ease-in-out infinite;
}
@ -224,125 +216,9 @@ onMounted(() => {
}
}
.gift-container {
position: relative;
.gift-image {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.gift-bow {
position: absolute;
top: -30rpx;
width: 200rpx;
height: 80rpx;
background: linear-gradient(135deg, #FFB6D9 0%, #FFA8C5 50%, #FFB6D9 100%);
border-radius: 50rpx 50rpx 20rpx 20rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.4);
z-index: 3;
}
.gift-bow::before,
.gift-bow::after {
content: '';
position: absolute;
top: 10rpx;
width: 80rpx;
height: 60rpx;
background: linear-gradient(135deg, #FFB6D9 0%, #FFA8C5 100%);
border-radius: 50%;
}
.gift-bow::before {
left: -40rpx;
transform: rotate(-20deg);
}
.gift-bow::after {
right: -40rpx;
transform: rotate(20deg);
}
.gift-body {
position: relative;
width: 350rpx;
height: 350rpx;
background: linear-gradient(135deg, #FFE5F0 0%, #FFD4E8 50%, #FFC9E3 100%);
border-radius: 30rpx;
box-shadow: 0 16rpx 48rpx rgba(255, 107, 157, 0.3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.gift-body::before {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 100%;
background: linear-gradient(180deg, rgba(255, 182, 217, 0.6) 0%, rgba(255, 168, 197, 0.6) 100%);
}
.gift-body::after {
content: '';
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 100%;
height: 60rpx;
background: linear-gradient(90deg, rgba(255, 182, 217, 0.6) 0%, rgba(255, 168, 197, 0.6) 100%);
}
.gift-text {
position: absolute;
top: 40rpx;
font-size: 36rpx;
font-weight: bold;
color: #FF6B9D;
letter-spacing: 4rpx;
text-shadow: 0 2rpx 8rpx rgba(255, 107, 157, 0.3);
z-index: 2;
}
.gift-window {
position: relative;
width: 180rpx;
height: 180rpx;
background: linear-gradient(135deg, #FFF5FA 0%, #FFEBF3 100%);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 0 4rpx 12rpx rgba(255, 107, 157, 0.2);
z-index: 2;
}
.question-mark {
font-size: 120rpx;
font-weight: bold;
color: #FFB6D9;
text-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.3);
animation: questionPulse 2s ease-in-out infinite;
}
@keyframes questionPulse {
0%, 100% {
opacity: 0.8;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.05);
}
}
.progress-container {

View File

@ -49,19 +49,18 @@
<!-- 礼盒 -->
<view class="gift-box" :class="{ 'gift-opened': isGiftOpened }">
<view class="gift-container">
<view class="gift-lid">
<view class="gift-bow"></view>
<view class="gift-lid-top">
<text class="gift-text">TOPFANS</text>
</view>
</view>
<view class="gift-body">
<view class="gift-window">
<text class="question-mark">?</text>
</view>
</view>
</view>
<image
v-if="!isGiftOpened"
class="gift-image"
src="/static/nft/lihe.png"
mode="aspectFit"
/>
<image
v-else
class="gift-image gift-image-opened"
src="/static/nft/lihe_kaiqi.png"
mode="aspectFit"
/>
</view>
<!-- 底部按钮 -->
@ -1086,16 +1085,16 @@ onMounted(() => {
bottom: 20%;
left: 50%;
transform: translateX(-50%);
width: 420rpx;
height: 450rpx;
width: 480rpx;
height: 480rpx;
z-index: 3;
animation: giftFloat 3s ease-in-out infinite;
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
perspective: 1000rpx;
}
.gift-box.gift-opened {
bottom: 5%;
width: 17rem;
bottom: 15%;
animation: none;
}
@ -1108,192 +1107,29 @@ onMounted(() => {
}
}
.gift-container {
position: relative;
.gift-image {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
transition: opacity 0.5s ease-in-out;
}
.gift-lid {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 340rpx;
height: 140rpx;
z-index: 3;
transition: all 1s cubic-bezier(0.4, 0, 0.2, 1);
transform-style: preserve-3d;
transform-origin: bottom left;
.gift-image-opened {
width: 17rem;
animation: giftOpenPop 0.5s ease-out;
}
.gift-opened .gift-lid {
transform: translateX(-70%) translateY(140rpx) rotateZ(-45deg);
opacity: 0.9;
}
.gift-bow {
position: absolute;
top: -30rpx;
left: 50%;
transform: translateX(-50%);
width: 200rpx;
height: 80rpx;
z-index: 4;
}
.gift-bow::before {
content: '';
position: absolute;
top: 20rpx;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 60rpx;
background: linear-gradient(135deg, #FFB6D9 0%, #FF9DC5 100%);
border-radius: 8rpx;
box-shadow: 0 4rpx 15rpx rgba(255, 107, 157, 0.4);
}
.gift-bow::after {
content: '';
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 180rpx;
height: 50rpx;
background: linear-gradient(135deg, #FFB6D9 0%, #FF9DC5 50%, #FFB6D9 100%);
border-radius: 30rpx;
box-shadow: 0 6rpx 20rpx rgba(255, 107, 157, 0.5);
}
.gift-lid-top {
position: relative;
width: 340rpx;
height: 100rpx;
background: linear-gradient(180deg,
#FFF0F5 0%,
#FFE5F0 30%,
#FFD4E8 100%);
border-radius: 20rpx 20rpx 8rpx 8rpx;
box-shadow:
0 10rpx 40rpx rgba(255, 107, 157, 0.4),
inset 0 -5rpx 15rpx rgba(255, 182, 217, 0.3),
inset 0 5rpx 15rpx rgba(255, 255, 255, 0.5);
display: flex;
align-items: center;
justify-content: center;
overflow: visible;
border: 3rpx solid rgba(255, 255, 255, 0.6);
}
.gift-lid-top::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 280rpx;
height: 60rpx;
background: linear-gradient(90deg,
transparent 0%,
rgba(255, 182, 217, 0.4) 50%,
transparent 100%);
border-radius: 30rpx;
}
.gift-text {
position: relative;
font-size: 36rpx;
font-weight: bold;
color: #FF6B9D;
letter-spacing: 4rpx;
text-shadow:
0 2rpx 8rpx rgba(255, 107, 157, 0.5),
0 0 20rpx rgba(255, 182, 217, 0.3);
z-index: 2;
}
.gift-body {
position: relative;
width: 340rpx;
height: 280rpx;
background: linear-gradient(180deg,
#FFE5F0 0%,
#FFD4E8 50%,
#FFC9E3 100%);
border-radius: 8rpx 8rpx 20rpx 20rpx;
box-shadow:
0 15rpx 50rpx rgba(255, 107, 157, 0.4),
inset 0 5rpx 20rpx rgba(255, 255, 255, 0.3),
inset 0 -5rpx 20rpx rgba(255, 182, 217, 0.3);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
border: 3rpx solid rgba(255, 255, 255, 0.5);
border-top: none;
}
.gift-body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 8rpx;
background: linear-gradient(90deg,
rgba(255, 107, 157, 0.3) 0%,
rgba(255, 182, 217, 0.5) 50%,
rgba(255, 107, 157, 0.3) 100%);
}
.gift-body::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 90%;
background: radial-gradient(circle at center,
rgba(255, 255, 255, 0.2) 0%,
transparent 70%);
pointer-events: none;
}
.gift-window {
position: relative;
width: 180rpx;
height: 180rpx;
background: linear-gradient(135deg,
#FFF5FA 0%,
#FFEBF3 50%,
#FFE0ED 100%);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
inset 0 4rpx 15rpx rgba(255, 107, 157, 0.2),
0 4rpx 20rpx rgba(255, 182, 217, 0.3);
z-index: 2;
border: 2rpx solid rgba(255, 182, 217, 0.4);
}
.question-mark {
font-size: 120rpx;
font-weight: bold;
color: #FFB6D9;
text-shadow:
0 4rpx 15rpx rgba(255, 107, 157, 0.4),
0 0 30rpx rgba(255, 182, 217, 0.3);
@keyframes giftOpenPop {
0% {
transform: scale(0.8);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.bottom-action {

View File

@ -1,7 +1,7 @@
// API 基础配置
// const baseURL = 'http://101.132.250.62:8080'
const baseURL = 'http://101.132.250.62:8080'
// const baseURL = 'http://192.168.110.60:8080'
const baseURL = 'http://localhost:8080'
// const baseURL = 'http://localhost:8080'
// 是否使用模拟数据(开发调试时设为 true后端API准备好后改为 false
const USE_MOCK_API = false