update backend
This commit is contained in:
@ -17,7 +17,7 @@ func main() {
|
||||
|
||||
// 1) brand 3100 的走秀档案数(only_with_articles 子查询依赖这个)
|
||||
var artCount int64
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runway WHERE is_deleted=0 AND brand_id=3100`).Scan(&artCount)
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runways WHERE is_deleted=0 AND brand_id=3100`).Scan(&artCount)
|
||||
fmt.Printf("brand 3100 走秀档案数 = %d\n", artCount)
|
||||
|
||||
// 2) 复现后端完整搜索:only_with_articles=1 + keyword=猿人头
|
||||
@ -30,9 +30,9 @@ func main() {
|
||||
}
|
||||
var rows []Row
|
||||
db.Raw(`
|
||||
SELECT id, name, name_en, name_cn FROM brand
|
||||
SELECT id, name, name_en, name_cn FROM brands
|
||||
WHERE is_deleted=0
|
||||
AND id IN (SELECT DISTINCT brand_id FROM brand_runway WHERE is_deleted=0)
|
||||
AND id IN (SELECT DISTINCT brand_id FROM brand_runways WHERE is_deleted=0)
|
||||
AND (name_en LIKE ? OR name_cn LIKE ?)`,
|
||||
kw, kw).Scan(&rows)
|
||||
fmt.Printf("\n[搜索 keyword=猿人头, only_with_articles=1] 命中 %d 条:\n", len(rows))
|
||||
@ -43,7 +43,7 @@ func main() {
|
||||
// 3) 对比:only_with_articles=0(不过滤档案)时的命中
|
||||
var rows2 []Row
|
||||
db.Raw(`
|
||||
SELECT id, name, name_en, name_cn FROM brand
|
||||
SELECT id, name, name_en, name_cn FROM brands
|
||||
WHERE is_deleted=0
|
||||
AND (name_en LIKE ? OR name_cn LIKE ?)`,
|
||||
kw, kw).Scan(&rows2)
|
||||
|
||||
@ -26,7 +26,7 @@ func main() {
|
||||
var rows []Row
|
||||
db.Raw(`
|
||||
SELECT brand_id, COALESCE(SUM(image_count),0) AS images, COUNT(*) AS shows
|
||||
FROM brand_runway
|
||||
FROM brand_runways
|
||||
WHERE is_deleted = 0
|
||||
GROUP BY brand_id
|
||||
ORDER BY images DESC
|
||||
@ -36,24 +36,24 @@ func main() {
|
||||
fmt.Println("== FeaturedIDs(images, 30) 原始排名 ==")
|
||||
for i, r := range rows {
|
||||
var name string
|
||||
db.Raw(`SELECT name_en FROM brand WHERE id = ?`, r.BrandID).Scan(&name)
|
||||
db.Raw(`SELECT name_en FROM brands WHERE id = ?`, r.BrandID).Scan(&name)
|
||||
fmt.Printf("%2d. brand_id=%-6d images=%-6d shows=%-4d %s\n", i+1, r.BrandID, r.Images, r.Shows, name)
|
||||
}
|
||||
|
||||
// 2) image_count 分布:有多少品牌有图、多少归零
|
||||
var zeroBrands, hasImgBrands int64
|
||||
var totalRows, zeroRows int64
|
||||
db.Raw(`SELECT COUNT(*) FROM (SELECT brand_id, SUM(image_count) s FROM brand_runway WHERE is_deleted=0 GROUP BY brand_id) t WHERE s = 0`).Scan(&zeroBrands)
|
||||
db.Raw(`SELECT COUNT(*) FROM (SELECT brand_id, SUM(image_count) s FROM brand_runway WHERE is_deleted=0 GROUP BY brand_id) t WHERE s > 0`).Scan(&hasImgBrands)
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runway WHERE is_deleted=0`).Scan(&totalRows)
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runway WHERE is_deleted=0 AND image_count=0`).Scan(&zeroRows)
|
||||
db.Raw(`SELECT COUNT(*) FROM (SELECT brand_id, SUM(image_count) s FROM brand_runways WHERE is_deleted=0 GROUP BY brand_id) t WHERE s = 0`).Scan(&zeroBrands)
|
||||
db.Raw(`SELECT COUNT(*) FROM (SELECT brand_id, SUM(image_count) s FROM brand_runways WHERE is_deleted=0 GROUP BY brand_id) t WHERE s > 0`).Scan(&hasImgBrands)
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runways WHERE is_deleted=0`).Scan(&totalRows)
|
||||
db.Raw(`SELECT COUNT(*) FROM brand_runways WHERE is_deleted=0 AND image_count=0`).Scan(&zeroRows)
|
||||
fmt.Printf("\n== image_count 分布 ==\n有图品牌=%d 零图品牌=%d | 走秀总行=%d 其中 image_count=0 的行=%d\n", hasImgBrands, zeroBrands, totalRows, zeroRows)
|
||||
|
||||
// 3) 对比口径:按走秀数(shows) DESC 前 10
|
||||
var byShows []Row
|
||||
db.Raw(`
|
||||
SELECT brand_id, COALESCE(SUM(image_count),0) AS images, COUNT(*) AS shows
|
||||
FROM brand_runway
|
||||
FROM brand_runways
|
||||
WHERE is_deleted = 0
|
||||
GROUP BY brand_id
|
||||
ORDER BY shows DESC
|
||||
@ -62,7 +62,7 @@ func main() {
|
||||
fmt.Println("\n== 若按 shows DESC 前 10 ==")
|
||||
for i, r := range byShows {
|
||||
var name string
|
||||
db.Raw(`SELECT name_en FROM brand WHERE id = ?`, r.BrandID).Scan(&name)
|
||||
db.Raw(`SELECT name_en FROM brands WHERE id = ?`, r.BrandID).Scan(&name)
|
||||
fmt.Printf("%2d. brand_id=%-6d images=%-6d shows=%-4d %s\n", i+1, r.BrandID, r.Images, r.Shows, name)
|
||||
}
|
||||
}
|
||||
|
||||
3
scripts/pgvector/initdb/01-extensions.sql
Normal file
3
scripts/pgvector/initdb/01-extensions.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- 首次初始化(数据卷为空时由 postgres 官方 entrypoint 自动执行)即启用 pgvector 扩展。
|
||||
-- 用 IF NOT EXISTS 保证重复执行幂等。
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
@ -0,0 +1,49 @@
|
||||
-- 03-migrate-phash-bigint-to-vector.sql
|
||||
-- 兼容旧库:phash 早期以 bigint 存储 64-bit dHash 指纹(uint64),现模型改为 vector(64)。
|
||||
-- pgvector 不支持 bigint -> vector 直接转换,需逐位拆成 0/1 向量(位 i -> 第 i 维,
|
||||
-- 与 Go 侧 phash.ToVectorBits 完全一致,保证旧数据与新写入向量在同一语义空间可比)。
|
||||
--
|
||||
-- 用法(任选其一):
|
||||
-- 1) 后端启动时由 AutoMigrate 自动执行(已内置,幂等,无需手动);
|
||||
-- 2) 手动执行: psql "postgres://fashion:fashion_dev_2026@localhost:5432/fashion" -f 03-migrate-phash-bigint-to-vector.sql
|
||||
--
|
||||
-- 已为 vector/text 类型的列自动跳过;可重复执行。
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
t text;
|
||||
dt text;
|
||||
BEGIN
|
||||
FOR t IN
|
||||
SELECT tablename FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename IN ('brand_runway_images','brand_runway_draft_images','street_snap_images','street_snap_draft_images')
|
||||
LOOP
|
||||
SELECT data_type INTO dt
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = t AND column_name = 'phash';
|
||||
|
||||
IF dt = 'bigint' THEN
|
||||
-- bigint 即 64-bit dHash 指纹;pgvector 不支持 bigint->vector 直接转换,且 ALTER ... USING 的
|
||||
-- transform 表达式不允许子查询(0A000)。改用纯表达式:bigint::bit(64) 取 64 位模式(MSB 在左),
|
||||
-- reverse 成 LSB 在左(与 phash.ToVectorBits 维度顺序一致:第 i 维 = bit i),插逗号后 text::vector(64)。
|
||||
-- NULL 自然透传。
|
||||
EXECUTE format($e$
|
||||
ALTER TABLE %I ALTER COLUMN phash TYPE vector(64)
|
||||
USING (
|
||||
('[' ||
|
||||
rtrim(regexp_replace(reverse(phash::bit(64)::text), '(.)', '\1,', 'g'), ',') ||
|
||||
']')::vector(64)
|
||||
)
|
||||
$e$, t);
|
||||
RAISE NOTICE 'migrated phash (bigint -> vector) on %', t;
|
||||
ELSIF dt IN ('character varying','text') THEN
|
||||
EXECUTE format($e$
|
||||
ALTER TABLE %I ALTER COLUMN phash TYPE vector(64) USING phash::vector(64)
|
||||
$e$, t);
|
||||
RAISE NOTICE 'migrated phash (text -> vector) on %', t;
|
||||
ELSE
|
||||
RAISE NOTICE 'phash on % already type %, skipped', t, dt;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$;
|
||||
164
scripts/setup_pg_wsl.sh
Normal file
164
scripts/setup_pg_wsl.sh
Normal file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# setup_pg_wsl.sh — WSL (Ubuntu/Debian) 下一键装 PostgreSQL + pgvector 开发环境
|
||||
#
|
||||
# 用法(在 WSL 终端里,不要用 Windows 的 CMD/PowerShell 跑):
|
||||
# sudo bash setup_pg_wsl.sh
|
||||
#
|
||||
# 三态幂等:
|
||||
# 1) 未安装 -> 安装 + 启动
|
||||
# 2) 已安装未运行 -> 仅启动
|
||||
# 3) 已安装且运行 -> 完全不动(不重装 / 不重启;仅幂等确保库/用户/扩展存在)
|
||||
#
|
||||
# 仅用于本地开发 / 迁移验证,生产环境请改强密码并单独评审配置。
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ============================ 配置(按需修改)============================
|
||||
PG_VERSION=16 # PostgreSQL 大版本,pgvector HNSW 在 13+ 均可用
|
||||
DB_NAME=fashion # 业务库名(替换成你的项目库)
|
||||
DB_USER=fashion # 应用账号
|
||||
DB_PASS="fashion_dev_2026" # 仅本地开发;生产务必换成强密码
|
||||
# =========================================================================
|
||||
|
||||
log() { echo -e "\033[32m==>\033[0m $*"; }
|
||||
|
||||
# 需要 root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "请以 root 运行: sudo bash $0"; exit 1
|
||||
fi
|
||||
|
||||
# 发行版探测(仅提示,不强制)
|
||||
if [ -r /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
log "检测到发行版: ${PRETTY_NAME:-unknown}"
|
||||
case "${ID:-}" in
|
||||
ubuntu|debian) ;;
|
||||
*) echo "⚠️ 本脚本面向 Ubuntu/Debian WSL,其他发行版请自行调整 apt 命令";;
|
||||
esac
|
||||
fi
|
||||
|
||||
# PG 是否在运行(用超级用户连 postgres 库试探,对活实例零副作用)
|
||||
pg_is_running() { sudo -u postgres psql -d postgres -tAc "SELECT 1" >/dev/null 2>&1; }
|
||||
|
||||
# 1) 已装则跳过安装(不重装)
|
||||
if command -v psql >/dev/null 2>&1; then
|
||||
log "PostgreSQL 已安装 ($(psql --version 2>/dev/null || echo unknown)),跳过安装"
|
||||
else
|
||||
log "安装依赖并添加 PostgreSQL 官方 apt 源"
|
||||
# 关键:先清掉旧的 pgdg 源与坏 key,否则下面第一次 apt-get update 会因旧 .asc key
|
||||
# 报 "not signed" 返回非零,被 set -e 直接退出,导致修 key 步骤永远没机会执行。
|
||||
rm -f /etc/apt/sources.list.d/pgdg.list
|
||||
rm -f /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
|
||||
apt-get update -y
|
||||
apt-get install -y curl ca-certificates gnupg lsb-release
|
||||
# 加 pgdg 源(官方标准:armored key -> dearmor 成 .gpg,signed-by 指向 .gpg)
|
||||
install -d /usr/share/postgresql-common/pgdg
|
||||
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc -o /tmp/pgdg-key.asc
|
||||
gpg --dearmor -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg /tmp/pgdg-key.asc
|
||||
chmod 644 /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
|
||||
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
|
||||
> /etc/apt/sources.list.d/pgdg.list
|
||||
apt-get update -y
|
||||
apt-get install -y "postgresql-$PG_VERSION" "postgresql-client-$PG_VERSION"
|
||||
fi
|
||||
|
||||
# 2) pgvector(已可用则跳过,不重装)
|
||||
if sudo -u postgres psql -d postgres -tAc "SELECT 1 FROM pg_available_extensions WHERE name='vector'" 2>/dev/null | grep -q 1; then
|
||||
log "pgvector 已可用,跳过"
|
||||
else
|
||||
log "安装 pgvector 扩展"
|
||||
if apt-get install -y "postgresql-$PG_VERSION-pgvector"; then
|
||||
:
|
||||
else
|
||||
echo "apt 无 pgvector 包,改用源码编译..."
|
||||
apt-get install -y "build-essential" "postgresql-server-dev-$PG_VERSION" git
|
||||
tmp=$(mktemp -d)
|
||||
git clone --depth 1 https://github.com/pgvector/pgvector.git "$tmp/pgvector"
|
||||
make -C "$tmp/pgvector"
|
||||
make -C "$tmp/pgvector" install
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3) 仅在未运行时启动;已在运行则完全不动
|
||||
if pg_is_running; then
|
||||
log "PostgreSQL 已在运行,跳过启动 / 重启"
|
||||
else
|
||||
log "启动 PostgreSQL"
|
||||
pg_ctlcluster "$PG_VERSION" main start 2>/dev/null \
|
||||
|| service postgresql start 2>/dev/null \
|
||||
|| systemctl start postgresql
|
||||
# 等实例就绪(最多 15s)
|
||||
for i in $(seq 1 15); do
|
||||
if pg_is_running; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
if ! pg_is_running; then
|
||||
echo "❌ PostgreSQL 启动失败,请查日志: journalctl -u postgresql 或 /var/log/postgresql/"; exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 4) 幂等确保库/用户/扩展(对运行中实例无副作用,不重启)
|
||||
log "确保用户、数据库与 pgvector 扩展存在(幂等)"
|
||||
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '$DB_PASS';"
|
||||
sudo -u postgres psql -c "CREATE ROLE $DB_USER LOGIN PASSWORD '$DB_PASS';" 2>/dev/null \
|
||||
|| echo " 角色 $DB_USER 已存在,跳过"
|
||||
sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 \
|
||||
|| sudo -u postgres createdb -O "$DB_USER" "$DB_NAME"
|
||||
sudo -u postgres psql -d "$DB_NAME" -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||
|
||||
# 5) 本地访问配置:仅当 listen 还不是 localhost 才改;运行中走在线 reload,绝不重启
|
||||
PGCONF="/etc/postgresql/$PG_VERSION/main/postgresql.conf"
|
||||
if [ -f "$PGCONF" ]; then
|
||||
if grep -q "^listen_addresses = 'localhost'" "$PGCONF"; then
|
||||
: # 已配置,无需改动
|
||||
else
|
||||
log "调整 listen_addresses=localhost(仅本地)"
|
||||
sed -i "s/^#*listen_addresses = .*/listen_addresses = 'localhost'/" "$PGCONF"
|
||||
if pg_is_running; then
|
||||
pg_ctlcluster "$PG_VERSION" main reload 2>/dev/null \
|
||||
|| sudo -u postgres psql -d postgres -c "SELECT pg_reload_conf();" >/dev/null 2>&1 \
|
||||
|| echo " ⚠️ reload 失败,配置将在下次重启生效"
|
||||
else
|
||||
log "(实例未运行,配置将在下次启动时生效)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 6) 验证
|
||||
log "验证安装"
|
||||
sudo -u postgres psql -d "$DB_NAME" \
|
||||
-c "SELECT version();" \
|
||||
-c "SELECT extname, extversion FROM pg_extension WHERE extname='vector';"
|
||||
|
||||
cat <<EOF
|
||||
|
||||
✅ PostgreSQL $PG_VERSION + pgvector 就绪(WSL)
|
||||
连接串 : postgres://$DB_USER:$DB_PASS@localhost:5432/$DB_NAME
|
||||
命令行 : psql -h localhost -U $DB_USER -d $DB_NAME
|
||||
数据目录: /var/lib/postgresql/$PG_VERSION/main
|
||||
|
||||
⚠️ WSL 注意事项
|
||||
- WSL 重启后 PG 不会自动起,手动: sudo service postgresql start
|
||||
- 想开机自启:在 Windows 侧建 .bat 执行 wsl -u root service postgresql start
|
||||
或在 /etc/wsl.conf 加 [boot] command="service postgresql start"
|
||||
- 本脚本密码为弱密码,仅本地开发;生产请更换并 review pg_hba.conf
|
||||
|
||||
(可选)CLIP / DINOv2 embedding 表示例:
|
||||
CREATE TABLE image_embedding (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
image_uid VARCHAR(32) NOT NULL, -- 对应你现有的 hashid uid
|
||||
kind SMALLINT NOT NULL, -- 0=runway 1=street
|
||||
embedding vector(512), -- 512 维特征(CLIP ViT-B/32 或 DINOv2)
|
||||
created_at INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX image_embedding_vec_idx
|
||||
ON image_embedding USING hnsw (embedding vector_cosine_ops);
|
||||
-- 去重查询:找最近邻
|
||||
SELECT image_uid, 1 - (embedding <=> '[...]'::vector) AS sim
|
||||
FROM image_embedding
|
||||
WHERE kind = 0
|
||||
ORDER BY embedding <=> '[...]'::vector
|
||||
LIMIT 20;
|
||||
EOF
|
||||
Reference in New Issue
Block a user