mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-01-31 19:53:11 +08:00
Compare commits
5 Commits
v1.3.7-dev
...
v1.3.10-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f287f18134 | ||
|
|
de27230b7a | ||
|
|
15a6295189 | ||
|
|
674acdac66 | ||
|
|
c59152bedf |
176
.github/workflows/docker-build.yml
vendored
176
.github/workflows/docker-build.yml
vendored
@@ -19,7 +19,8 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
# AMD64 构建(原生 x64 runner)
|
||||||
|
build-amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -27,43 +28,30 @@ jobs:
|
|||||||
- image: xingrin-server
|
- image: xingrin-server
|
||||||
dockerfile: docker/server/Dockerfile
|
dockerfile: docker/server/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
- image: xingrin-frontend
|
- image: xingrin-frontend
|
||||||
dockerfile: docker/frontend/Dockerfile
|
dockerfile: docker/frontend/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64 # ARM64 构建时 Next.js 在 QEMU 下会崩溃
|
|
||||||
- image: xingrin-worker
|
- image: xingrin-worker
|
||||||
dockerfile: docker/worker/Dockerfile
|
dockerfile: docker/worker/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
- image: xingrin-nginx
|
- image: xingrin-nginx
|
||||||
dockerfile: docker/nginx/Dockerfile
|
dockerfile: docker/nginx/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
- image: xingrin-agent
|
- image: xingrin-agent
|
||||||
dockerfile: docker/agent/Dockerfile
|
dockerfile: docker/agent/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
- image: xingrin-postgres
|
- image: xingrin-postgres
|
||||||
dockerfile: docker/postgres/Dockerfile
|
dockerfile: docker/postgres/Dockerfile
|
||||||
context: docker/postgres
|
context: docker/postgres
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Free disk space (for large builds like worker)
|
- name: Free disk space
|
||||||
run: |
|
run: |
|
||||||
echo "=== Before cleanup ==="
|
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
|
||||||
df -h
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /usr/local/lib/android
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
|
||||||
sudo docker image prune -af
|
sudo docker image prune -af
|
||||||
echo "=== After cleanup ==="
|
|
||||||
df -h
|
|
||||||
|
|
||||||
- name: Generate SSL certificates for nginx build
|
- name: Generate SSL certificates for nginx build
|
||||||
if: matrix.image == 'xingrin-nginx'
|
if: matrix.image == 'xingrin-nginx'
|
||||||
@@ -73,10 +61,6 @@ jobs:
|
|||||||
-keyout docker/nginx/ssl/privkey.pem \
|
-keyout docker/nginx/ssl/privkey.pem \
|
||||||
-out docker/nginx/ssl/fullchain.pem \
|
-out docker/nginx/ssl/fullchain.pem \
|
||||||
-subj "/CN=localhost"
|
-subj "/CN=localhost"
|
||||||
echo "SSL certificates generated for CI build"
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -87,7 +71,120 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Get version from git tag
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "VERSION=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push AMD64
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ${{ matrix.context }}
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:${{ steps.version.outputs.VERSION }}-amd64
|
||||||
|
build-args: IMAGE_TAG=${{ steps.version.outputs.VERSION }}
|
||||||
|
cache-from: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache-amd64
|
||||||
|
cache-to: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache-amd64,mode=max
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
# ARM64 构建(原生 ARM64 runner)
|
||||||
|
build-arm64:
|
||||||
|
runs-on: ubuntu-22.04-arm
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- image: xingrin-server
|
||||||
|
dockerfile: docker/server/Dockerfile
|
||||||
|
context: .
|
||||||
|
- image: xingrin-frontend
|
||||||
|
dockerfile: docker/frontend/Dockerfile
|
||||||
|
context: .
|
||||||
|
- image: xingrin-worker
|
||||||
|
dockerfile: docker/worker/Dockerfile
|
||||||
|
context: .
|
||||||
|
- image: xingrin-nginx
|
||||||
|
dockerfile: docker/nginx/Dockerfile
|
||||||
|
context: .
|
||||||
|
- image: xingrin-agent
|
||||||
|
dockerfile: docker/agent/Dockerfile
|
||||||
|
context: .
|
||||||
|
- image: xingrin-postgres
|
||||||
|
dockerfile: docker/postgres/Dockerfile
|
||||||
|
context: docker/postgres
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate SSL certificates for nginx build
|
||||||
|
if: matrix.image == 'xingrin-nginx'
|
||||||
|
run: |
|
||||||
|
mkdir -p docker/nginx/ssl
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||||
|
-keyout docker/nginx/ssl/privkey.pem \
|
||||||
|
-out docker/nginx/ssl/fullchain.pem \
|
||||||
|
-subj "/CN=localhost"
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "VERSION=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push ARM64
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ${{ matrix.context }}
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:${{ steps.version.outputs.VERSION }}-arm64
|
||||||
|
build-args: IMAGE_TAG=${{ steps.version.outputs.VERSION }}
|
||||||
|
cache-from: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache-arm64
|
||||||
|
cache-to: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache-arm64,mode=max
|
||||||
|
provenance: false
|
||||||
|
sbom: false
|
||||||
|
|
||||||
|
# 合并多架构 manifest
|
||||||
|
merge-manifests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build-amd64, build-arm64]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
image:
|
||||||
|
- xingrin-server
|
||||||
|
- xingrin-frontend
|
||||||
|
- xingrin-worker
|
||||||
|
- xingrin-nginx
|
||||||
|
- xingrin-agent
|
||||||
|
- xingrin-postgres
|
||||||
|
steps:
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
@@ -98,28 +195,27 @@ jobs:
|
|||||||
echo "IS_RELEASE=false" >> $GITHUB_OUTPUT
|
echo "IS_RELEASE=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push
|
- name: Create and push multi-arch manifest
|
||||||
uses: docker/build-push-action@v5
|
run: |
|
||||||
with:
|
VERSION=${{ steps.version.outputs.VERSION }}
|
||||||
context: ${{ matrix.context }}
|
IMAGE=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}
|
||||||
file: ${{ matrix.dockerfile }}
|
|
||||||
platforms: ${{ matrix.platforms }}
|
docker manifest create ${IMAGE}:${VERSION} \
|
||||||
push: true
|
${IMAGE}:${VERSION}-amd64 \
|
||||||
tags: |
|
${IMAGE}:${VERSION}-arm64
|
||||||
${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:${{ steps.version.outputs.VERSION }}
|
docker manifest push ${IMAGE}:${VERSION}
|
||||||
${{ steps.version.outputs.IS_RELEASE == 'true' && format('{0}/{1}:latest', env.IMAGE_PREFIX, matrix.image) || '' }}
|
|
||||||
build-args: |
|
if [[ "${{ steps.version.outputs.IS_RELEASE }}" == "true" ]]; then
|
||||||
IMAGE_TAG=${{ steps.version.outputs.VERSION }}
|
docker manifest create ${IMAGE}:latest \
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache
|
${IMAGE}:${VERSION}-amd64 \
|
||||||
cache-to: type=registry,ref=${{ env.IMAGE_PREFIX }}/${{ matrix.image }}:cache,mode=max
|
${IMAGE}:${VERSION}-arm64
|
||||||
provenance: false
|
docker manifest push ${IMAGE}:latest
|
||||||
sbom: false
|
fi
|
||||||
|
|
||||||
# 所有镜像构建成功后,更新 VERSION 文件
|
# 更新 VERSION 文件
|
||||||
# 根据 tag 所在的分支更新对应分支的 VERSION 文件
|
|
||||||
update-version:
|
update-version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: merge-manifests
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
|
|||||||
@@ -1,106 +1,6 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AssetConfig(AppConfig):
|
class AssetConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'apps.asset'
|
name = 'apps.asset'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
# 导入所有模型以确保Django发现并注册
|
|
||||||
from . import models
|
|
||||||
|
|
||||||
# 启用 pg_trgm 扩展(用于文本模糊搜索索引)
|
|
||||||
# 用于已有数据库升级场景
|
|
||||||
self._ensure_pg_trgm_extension()
|
|
||||||
|
|
||||||
# 验证 pg_ivm 扩展是否可用(用于 IMMV 增量维护)
|
|
||||||
self._verify_pg_ivm_extension()
|
|
||||||
|
|
||||||
def _ensure_pg_trgm_extension(self):
|
|
||||||
"""
|
|
||||||
确保 pg_trgm 扩展已启用。
|
|
||||||
该扩展用于 response_body 和 response_headers 字段的 GIN 索引,
|
|
||||||
支持高效的文本模糊搜索。
|
|
||||||
"""
|
|
||||||
from django.db import connection
|
|
||||||
|
|
||||||
# 检查是否为 PostgreSQL 数据库
|
|
||||||
if connection.vendor != 'postgresql':
|
|
||||||
logger.debug("跳过 pg_trgm 扩展:当前数据库不是 PostgreSQL")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;")
|
|
||||||
logger.debug("pg_trgm 扩展已启用")
|
|
||||||
except Exception as e:
|
|
||||||
# 记录错误但不阻止应用启动
|
|
||||||
# 常见原因:权限不足(需要超级用户权限)
|
|
||||||
logger.warning(
|
|
||||||
"无法创建 pg_trgm 扩展: %s。"
|
|
||||||
"这可能导致 response_body 和 response_headers 字段的 GIN 索引无法正常工作。"
|
|
||||||
"请手动执行: CREATE EXTENSION IF NOT EXISTS pg_trgm;",
|
|
||||||
str(e)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _verify_pg_ivm_extension(self):
|
|
||||||
"""
|
|
||||||
验证 pg_ivm 扩展是否可用。
|
|
||||||
pg_ivm 用于 IMMV(增量维护物化视图),是系统必需的扩展。
|
|
||||||
如果不可用,将记录错误并退出。
|
|
||||||
"""
|
|
||||||
from django.db import connection
|
|
||||||
|
|
||||||
# 检查是否为 PostgreSQL 数据库
|
|
||||||
if connection.vendor != 'postgresql':
|
|
||||||
logger.debug("跳过 pg_ivm 验证:当前数据库不是 PostgreSQL")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 跳过某些管理命令(如 migrate、makemigrations)
|
|
||||||
import sys
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1] in ('migrate', 'makemigrations', 'collectstatic', 'check'):
|
|
||||||
logger.debug("跳过 pg_ivm 验证:当前为管理命令")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
# 检查 pg_ivm 扩展是否已安装
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT COUNT(*) FROM pg_extension WHERE extname = 'pg_ivm'
|
|
||||||
""")
|
|
||||||
count = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
if count > 0:
|
|
||||||
logger.info("✓ pg_ivm 扩展已启用")
|
|
||||||
else:
|
|
||||||
# 尝试创建扩展
|
|
||||||
try:
|
|
||||||
cursor.execute("CREATE EXTENSION IF NOT EXISTS pg_ivm;")
|
|
||||||
logger.info("✓ pg_ivm 扩展已创建并启用")
|
|
||||||
except Exception as create_error:
|
|
||||||
logger.error(
|
|
||||||
"=" * 60 + "\n"
|
|
||||||
"错误: pg_ivm 扩展未安装\n"
|
|
||||||
"=" * 60 + "\n"
|
|
||||||
"pg_ivm 是系统必需的扩展,用于增量维护物化视图。\n\n"
|
|
||||||
"请在 PostgreSQL 服务器上安装 pg_ivm:\n"
|
|
||||||
" curl -sSL https://raw.githubusercontent.com/yyhuni/xingrin/main/docker/scripts/install-pg-ivm.sh | sudo bash\n\n"
|
|
||||||
"或手动安装:\n"
|
|
||||||
" 1. apt install build-essential postgresql-server-dev-15 git\n"
|
|
||||||
" 2. git clone https://github.com/sraoss/pg_ivm.git && cd pg_ivm && make && make install\n"
|
|
||||||
" 3. 在 postgresql.conf 中添加: shared_preload_libraries = 'pg_ivm'\n"
|
|
||||||
" 4. 重启 PostgreSQL\n"
|
|
||||||
"=" * 60
|
|
||||||
)
|
|
||||||
# 在生产环境中退出,开发环境中仅警告
|
|
||||||
from django.conf import settings
|
|
||||||
if not settings.DEBUG:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"pg_ivm 扩展验证失败: {e}")
|
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# 1. 确保 pg_ivm 扩展已启用
|
# 1. 确保 pg_trgm 扩展已启用(用于文本模糊搜索索引)
|
||||||
|
migrations.RunSQL(
|
||||||
|
sql="CREATE EXTENSION IF NOT EXISTS pg_trgm;",
|
||||||
|
reverse_sql="-- pg_trgm extension kept for other uses"
|
||||||
|
),
|
||||||
|
|
||||||
|
# 2. 确保 pg_ivm 扩展已启用(用于 IMMV 增量维护)
|
||||||
migrations.RunSQL(
|
migrations.RunSQL(
|
||||||
sql="CREATE EXTENSION IF NOT EXISTS pg_ivm;",
|
sql="CREATE EXTENSION IF NOT EXISTS pg_ivm;",
|
||||||
reverse_sql="-- pg_ivm extension kept for other uses"
|
reverse_sql="-- pg_ivm extension kept for other uses"
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Optional, List, Dict, Any, Tuple, Literal
|
import uuid
|
||||||
|
from typing import Optional, List, Dict, Any, Tuple, Literal, Iterator
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
|
||||||
@@ -400,7 +401,7 @@ class AssetSearchService:
|
|||||||
query: str,
|
query: str,
|
||||||
asset_type: AssetType = 'website',
|
asset_type: AssetType = 'website',
|
||||||
batch_size: int = 1000
|
batch_size: int = 1000
|
||||||
):
|
) -> Iterator[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
流式搜索资产(使用服务端游标,内存友好)
|
流式搜索资产(使用服务端游标,内存友好)
|
||||||
|
|
||||||
@@ -425,9 +426,12 @@ class AssetSearchService:
|
|||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 生成唯一的游标名称,避免并发请求冲突
|
||||||
|
cursor_name = f'export_cursor_{uuid.uuid4().hex[:8]}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 使用服务端游标,避免一次性加载所有数据到内存
|
# 使用服务端游标,避免一次性加载所有数据到内存
|
||||||
with connection.cursor(name='export_cursor') as cursor:
|
with connection.cursor(name=cursor_name) as cursor:
|
||||||
cursor.itersize = batch_size
|
cursor.itersize = batch_size
|
||||||
cursor.execute(sql, params)
|
cursor.execute(sql, params)
|
||||||
columns = [col[0] for col in cursor.description]
|
columns = [col[0] for col in cursor.description]
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from rest_framework import status
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.db import connection
|
from django.db import connection, transaction
|
||||||
|
|
||||||
from apps.common.response_helpers import success_response, error_response
|
from apps.common.response_helpers import success_response, error_response
|
||||||
from apps.common.error_codes import ErrorCodes
|
from apps.common.error_codes import ErrorCodes
|
||||||
@@ -286,6 +286,9 @@ class AssetSearchExportView(APIView):
|
|||||||
|
|
||||||
Response:
|
Response:
|
||||||
CSV 文件流(使用服务端游标,支持大数据量导出)
|
CSV 文件流(使用服务端游标,支持大数据量导出)
|
||||||
|
|
||||||
|
注意:使用 @transaction.non_atomic_requests 装饰器,
|
||||||
|
因为服务端游标不能在事务块内使用。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -312,6 +315,7 @@ class AssetSearchExportView(APIView):
|
|||||||
|
|
||||||
return headers, formatters
|
return headers, formatters
|
||||||
|
|
||||||
|
@transaction.non_atomic_requests
|
||||||
def get(self, request: Request):
|
def get(self, request: Request):
|
||||||
"""导出搜索结果为 CSV(流式导出,无数量限制)"""
|
"""导出搜索结果为 CSV(流式导出,无数量限制)"""
|
||||||
from apps.common.utils import generate_csv_rows
|
from apps.common.utils import generate_csv_rows
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ REST_FRAMEWORK = {
|
|||||||
# 允许所有来源(前后端分离项目,安全性由认证系统保障)
|
# 允许所有来源(前后端分离项目,安全性由认证系统保障)
|
||||||
CORS_ALLOW_ALL_ORIGINS = os.getenv('CORS_ALLOW_ALL_ORIGINS', 'True').lower() == 'true'
|
CORS_ALLOW_ALL_ORIGINS = os.getenv('CORS_ALLOW_ALL_ORIGINS', 'True').lower() == 'true'
|
||||||
CORS_ALLOW_CREDENTIALS = True
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
# 暴露额外的响应头给前端(Content-Disposition 用于文件下载获取文件名)
|
||||||
|
CORS_EXPOSE_HEADERS = ['Content-Disposition']
|
||||||
|
|
||||||
# ==================== CSRF 配置 ====================
|
# ==================== CSRF 配置 ====================
|
||||||
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://127.0.0.1:3000').split(',')
|
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://127.0.0.1:3000').split(',')
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# 第一阶段:使用 Go 官方镜像编译工具
|
# 第一阶段:使用 Go 官方镜像编译工具
|
||||||
# 锁定 digest 避免上游更新导致缓存失效
|
FROM golang:1.24 AS go-builder
|
||||||
FROM golang:1.24@sha256:7e050c14ae9ca5ae56408a288336545b18632f51402ab0ec8e7be0e649a1fc42 AS go-builder
|
|
||||||
|
|
||||||
ENV GOPROXY=https://goproxy.cn,direct
|
ENV GOPROXY=https://goproxy.cn,direct
|
||||||
# Naabu 需要 CGO 和 libpcap
|
# Naabu 需要 CGO 和 libpcap
|
||||||
@@ -37,8 +36,7 @@ RUN CGO_ENABLED=0 go install -v github.com/owasp-amass/amass/v5/cmd/amass@main
|
|||||||
RUN go install github.com/hahwul/dalfox/v2@latest
|
RUN go install github.com/hahwul/dalfox/v2@latest
|
||||||
|
|
||||||
# 第二阶段:运行时镜像
|
# 第二阶段:运行时镜像
|
||||||
# 锁定 digest 避免上游更新导致缓存失效
|
FROM ubuntu:24.04
|
||||||
FROM ubuntu:24.04@sha256:4fdf0125919d24aec972544669dcd7d6a26a8ad7e6561c73d5549bd6db258ac2
|
|
||||||
|
|
||||||
# 避免交互式提示
|
# 避免交互式提示
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|||||||
Reference in New Issue
Block a user