Parcourir la source

Merge remote-tracking branch 'origin/uat' into produ

# Conflicts:
#	Jenkinsfile
#	alien_gateway/config.py
dujian il y a 2 semaines
Parent
commit
588f6b2395

+ 31 - 0
.dockerignore

@@ -0,0 +1,31 @@
+# 避免误打入容器的本地/敏感文件
+.env
+.env.local
+.env.*.local
+
+# Python 缓存
+__pycache__/
+*.pyc
+*.pyo
+*.pyd
+.pytest_cache/
+
+# 虚拟环境
+.venv/
+venv/
+
+# IDE / Git
+.git/
+.gitignore
+.idea/
+.vscode/
+
+# 日志和临时文件
+*.log
+common/logs/
+*.tmp
+
+# 镜像构建无需的文件
+README.md
+docs/
+tests/

+ 26 - 0
.env.uat

@@ -0,0 +1,26 @@
+SECRET_KEY="your-super-secret-key-change-me"
+ALGORITHM="HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES=10080
+ # 数据库配置
+DB_USER="root"
+DB_PASSWORD="Alien123456"
+DB_HOST="ailiendb.mysql.rds.aliyuncs.com"
+DB_PORT=53306
+DB_NAME="alien_uat"
+
+# redis高可用配置
+REDIS_SENTINELS=192.168.2.251:36379,192.168.2.252:36379,192.168.2.253:36379
+REDIS_MASTER_NAME=mymaster
+REDIS_PASSWORD=my_password_123
+#REDIS_SENTINEL_USERNAME=default
+REDIS_SENTINEL_PASSWORD=
+
+# 下游服务地址(容器部署时由 docker run -e 覆盖为容器名:容器端口)
+STORE_BASE_URL="http://127.0.0.1:8001"# alien_store 服务地址(本地开发用)
+CONTRACT_BASE_URL="http://127.0.0.1:8002"# alien_contract 服务地址(本地开发用)
+
+# 阿里云短信配置
+ALIYUN_SMS_SIGN_NAME_CONTRACT="爱丽恩严大连商务科技"
+ALIYUN_SMS_TEMPLATE_CODE_CONTRACT="SMS_501820309"
+ALIYUN_ACCESS_KEY_ID="LTAI5t77CS9gD7JMkMAjD2vF"
+ALIYUN_ACCESS_KEY_SECRET="jLYGPpaJuc7NqmRdLvu1ObAS9CJFB8"

+ 9 - 0
Jenkinsfile

@@ -1,6 +1,8 @@
 /**
  * alien_py_cloud production deploy (repo root Jenkinsfile)
  * Jenkins: 39.106.135.88 | Target: 39.105.153.68 | Script Path: Jenkinsfile
+ *
+ * 容器均使用 --restart unless-stopped,ECS/宿主机重启后 Docker 会自动拉起。
  */
 
 def DEFAULT_PROD_SSH_TARGET = 'alien_store@39.105.153.68'
@@ -142,32 +144,39 @@ wait_http() {
   return 1
 }
 
+# 1) store(下游)
 sudo docker run -d --name ${CONTAINER_NAME_STORE} \\
   --network ${DOCKER_NET} \\
   --env-file ${ENV_FILE_REMOTE} \\
   -v ${ENV_FILE_REMOTE}:/app/.env.produ:ro \\
   -v ${LOG_ROOT_REMOTE}/store:/app/common/logs/alien_store \\
+  -e APP_ENV=produ \\
   -e SNOWFLAKE_WORKER_IP=127.0.0.1 \\
   --restart unless-stopped \\
   ${IMAGE_STORE}
 
+# 2) contract(下游)
 sudo docker run -d --name ${CONTAINER_NAME_CONTRACT} \\
   --network ${DOCKER_NET} \\
   -p ${PORT_CONTRACT_HOST}:${PORT_CONTRACT_INTERNAL} \\
   --env-file ${ENV_FILE_REMOTE} \\
   -v ${ENV_FILE_REMOTE}:/app/.env.produ:ro \\
   -v ${LOG_ROOT_REMOTE}/contract:/app/common/logs/alien_contract \\
+  -e APP_ENV=produ \\
   -e SNOWFLAKE_WORKER_IP=127.0.0.1 \\
   --restart unless-stopped \\
   ${IMAGE_CONTRACT}
 
+# 3) gateway(依赖 store/contract;对外 33333)
 sudo docker run -d --name ${CONTAINER_NAME_GATEWAY} \\
   --network ${DOCKER_NET} \\
   -p ${PORT_GATEWAY_HOST}:${PORT_GATEWAY_INTERNAL} \\
   --env-file ${ENV_FILE_REMOTE} \\
   -v ${ENV_FILE_REMOTE}:/app/.env.produ:ro \\
   -v ${LOG_ROOT_REMOTE}/gateway:/app/common/logs/alien_gateway \\
+  -e APP_ENV=produ \\
   -e STORE_BASE_URL=http://${CONTAINER_NAME_STORE}:${PORT_STORE_INTERNAL} \\
+  -e CONTRACT_BASE_URL=http://${CONTAINER_NAME_CONTRACT}:${PORT_CONTRACT_INTERNAL} \\
   -e SNOWFLAKE_WORKER_IP=127.0.0.1 \\
   --restart unless-stopped \\
   ${IMAGE_GATEWAY}

+ 1 - 1
alien_contract/Dockerfile

@@ -17,6 +17,6 @@ RUN poetry source add --priority=primary tsinghua https://pypi.tuna.tsinghua.edu
 
 COPY . .
 
-EXPOSE 8005
+EXPOSE 8002
 
 CMD ["uvicorn", "alien_contract.main:app", "--host", "0.0.0.0", "--port", "8002"]

+ 1 - 1
alien_contract/infrastructure/esign/esign_config.py

@@ -21,6 +21,6 @@ class Config:
             "alipay_auth": "U店在这-支付宝授权函",
             "wechat_pay_commitment": "U店在这-微信支付承诺函",
         }
-        self.callback_url = os.getenv("ESIGN_CALLBACK_URL", "http://120.26.186.130:33333/api/contract/esign/callback")
+        self.callback_url = os.getenv("ESIGN_CALLBACK_URL", "https://prod.ailien.shop/api/contract/esign/callback")
         self.developer_callback_url = os.getenv("ESIGN_DEVELOPER_CALLBACK_URL", self.callback_url)
         self.redirect_url = os.getenv("ESIGN_REDIRECT_URL", "https://www.esign.cn/")

+ 1 - 1
alien_contract/main.py

@@ -19,4 +19,4 @@ async def health():
 if __name__ == "__main__":
     import uvicorn
 
-    uvicorn.run(app, host="0.0.0.0", port=8005)
+    uvicorn.run(app, host="0.0.0.0", port=8002)

+ 6 - 12
alien_gateway/config.py

@@ -5,8 +5,9 @@ from dotenv import load_dotenv
 from urllib.parse import quote
 import os
 
-# Docker/Jenkins 挂载 /app/.env.produ;本地开发可同路径或环境变量覆盖
-load_dotenv(".env.produ")
+# APP_ENV: dev / uat / produ(produ 分支默认 produ;UAT Jenkins 设 APP_ENV=uat)
+_ENV_FILE = f".env.{os.getenv('APP_ENV', 'produ')}"
+load_dotenv(_ENV_FILE)
 
 
 def _strip_env_quotes(value: str | None) -> str | None:
@@ -45,6 +46,7 @@ class Settings(BaseSettings):
     REDIS_SOCKET_TIMEOUT: float = 0.5
     REDIS_CONNECT_TIMEOUT: float = 1.0
     STORE_BASE_URL: str = ""
+    CONTRACT_BASE_URL: str = ""
 
     ALIYUN_SMS_SIGN_NAME_CONTRACT: str = ""
     ALIYUN_SMS_TEMPLATE_CODE_CONTRACT: str = ""
@@ -58,6 +60,7 @@ class Settings(BaseSettings):
         "DB_PASSWORD",
         "SECRET_KEY",
         "STORE_BASE_URL",
+        "CONTRACT_BASE_URL",
         mode="before",
     )
     @classmethod
@@ -66,8 +69,6 @@ class Settings(BaseSettings):
             return _strip_env_quotes(value) or ""
         return value
 
-
-
     @property
     def SQLALCHEMY_DATABASE_URI(self) -> str:
         return f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
@@ -96,8 +97,6 @@ class Settings(BaseSettings):
 
     @property
     def REDIS_SENTINEL_URL(self) -> str:
-        # 标准 Sentinel URL:
-        # redis+sentinel://[username[:password]@]host1:port1,host2:port2/db?sentinel_master=mymaster
         if self.REDIS_SENTINEL_USERNAME:
             user = quote(self.REDIS_SENTINEL_USERNAME, safe="")
             pwd = quote(self.REDIS_PASSWORD, safe="")
@@ -113,11 +112,6 @@ class Settings(BaseSettings):
 
     @property
     def REDIS_CELERY_SENTINEL_URL(self) -> str:
-        # Celery/Kombu Sentinel URL 格式:
-        # sentinel://:redis_password@sentinel_host:port/db;...
-        # URL 中的 auth 密码 = Redis 主从节点密码
-        # (Kombu 通过 Sentinel 发现主节点地址后,用此密码向 Redis 主节点认证)
-        # Sentinel 自身的密码(如有)通过 broker_transport_options["sentinel_kwargs"]["password"] 传递
         auth = f":{quote(self.REDIS_PASSWORD, safe='')}@" if self.REDIS_PASSWORD else ""
         return ";".join(
             f"sentinel://{auth}{host}:{port}/{self.REDIS_DB}"
@@ -135,7 +129,7 @@ class Settings(BaseSettings):
 
     model_config = SettingsConfigDict(
         case_sensitive=True,
-        env_file=".env.produ",
+        env_file=_ENV_FILE,
         extra="ignore",
     )
 

+ 21 - 13
alien_gateway/main.py

@@ -89,18 +89,10 @@ def _clean_headers(headers):
     return {k: v for k, v in headers.items() if k.lower() not in HOP_BY_HOP_HEADERS}
 
 
-@app.api_route("/api/store/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
-async def proxy_to_store(full_path: str, request: Request):
-    """
-    简易网关:监听 33333 端口,将 /api/store/* 转发到 alien_store 服务。
-    """
-    target_url = f"{settings.STORE_BASE_URL}/api/store/{full_path}"
+async def _proxy(request: Request, target_url: str, service_tag: str) -> Response:
+    """通用反向代理:转发请求体、头部、查询参数并回写下游响应。"""
     client_ip = request.client.host if request.client else "-"
-
-    # 读取请求体
     body = await request.body()
-
-    # 过滤头部
     headers = _clean_headers(dict(request.headers))
 
     try:
@@ -113,10 +105,12 @@ async def proxy_to_store(full_path: str, request: Request):
                 params=request.query_params,
             )
     except Exception as exc:
-        logger.error("proxy to store failed ip=%s url=%s err=%s", client_ip, target_url, exc)
+        logger.error(
+            "proxy to %s failed ip=%s url=%s err=%s",
+            service_tag, client_ip, target_url, exc,
+        )
         raise HTTPException(status_code=HTTP_502_BAD_GATEWAY, detail="Upstream unavailable")
 
-    # 返回下游响应
     return Response(
         content=resp.content,
         status_code=resp.status_code,
@@ -124,6 +118,20 @@ async def proxy_to_store(full_path: str, request: Request):
         media_type=resp.headers.get("content-type"),
     )
 
+
+@app.api_route("/api/store/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
+async def proxy_to_store(full_path: str, request: Request):
+    """监听 43333 端口,转发 /api/store/* 到 alien_store 服务。"""
+    target_url = f"{settings.STORE_BASE_URL}/api/store/{full_path}"
+    return await _proxy(request, target_url, "store")
+
+
+@app.api_route("/api/contract/{full_path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
+async def proxy_to_contract(full_path: str, request: Request):
+    """转发 /api/contract/* 到 alien_contract 服务。"""
+    target_url = f"{settings.CONTRACT_BASE_URL}/api/contract/{full_path}"
+    return await _proxy(request, target_url, "contract")
+
 if __name__ == "__main__":
     import uvicorn
-    uvicorn.run(app, host="0.0.0.0", port=33333)
+    uvicorn.run(app, host="0.0.0.0", port=43333)