diff --git a/.gitignore b/.gitignore index 2d16bcb..acc59a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ **.vscode **/node_modules __pycache__/ -*.py[cod] \ No newline at end of file +*.py[cod] +logs +*.idea \ No newline at end of file diff --git a/backend/.env b/backend/.env index d2cc8e2..66a424b 100644 --- a/backend/.env +++ b/backend/.env @@ -13,7 +13,7 @@ JWT_SECRET_KEY=dev_mb_project_2025_xyz123 # 开发环境可随意,生产环 SECRET_KEY=dev_mb_2025_abc789 # 数据库配置 -DATABASE_URL=mysql+pymysql://root:Tsystems_2026@150.158.121.95:3306/incident?charset=utf8mb4 +DATABASE_URL=mysql+pymysql://root:Tsystems_2026@150.158.121.95:3306/incid_db?charset=utf8mb4 # oauth2配置 CLIENT_ID=A17336FC-A8D7-4CEA-8777-384BFEABABC1 @@ -24,6 +24,7 @@ SS0_HOST = https://ssoalpha.dvb.corpinter.net.cn/ REDIRECT_URL = http://localhost:5221/report # 用户登录成功后,最终重定向到的前端页面地址 DOMAIN = http://localhost:3001 +IMAGE_BASE_URL=http://localhost:5221 # 前端请求头的key HEADERS_SECRET_KEY=mbincid-server12 \ No newline at end of file diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 49b20b4..d4c2cbf 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -74,7 +74,7 @@ def create_app(): domain_prefix='accidentportal/api' - app.register_blueprint(auth_bp, url_prefix=f"/{domain_prefix}/api/auth") + app.register_blueprint(auth_bp, url_prefix=f"/{domain_prefix}/auth") app.register_blueprint(login_bp, url_prefix=f"/{domain_prefix}/user") app.register_blueprint(incident_gen5_bp, url_prefix=f"/{domain_prefix}/gen5") app.register_blueprint(incident_gen6_bp, url_prefix=f"/{domain_prefix}/gen6") diff --git a/backend/app/blueprints/oauth/routes.py b/backend/app/blueprints/oauth/routes.py index 4f6d8e4..3991dcd 100644 --- a/backend/app/blueprints/oauth/routes.py +++ b/backend/app/blueprints/oauth/routes.py @@ -84,7 +84,7 @@ def authorized(): decoded_jwt = jwt.decode(access_token, options={"verify_signature": False}) # 5. 验证和清理 state 参数 (防止开放重定向攻击) - safe_state = "/callback" + safe_state = current_app.config["FRONTEND_CALLBACK_PATH"] print(f"Redirecting to: {DOMAIN}{safe_state}") # 6. 设置 Cookie @@ -227,7 +227,7 @@ def authorized_test(): decoded_jwt = jwt.decode(access_token, options={"verify_signature": False}) # 5. 验证和清理 state 参数 (防止开放重定向攻击) - safe_state = "/callback" + safe_state = current_app.config["FRONTEND_CALLBACK_PATH"] print(f"Redirecting to: {DOMAIN}{safe_state}") # 6. 设置 Cookie diff --git a/backend/app/config.py b/backend/app/config.py index 2ef670b..ca31b47 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -51,6 +51,8 @@ class DevelopmentConfig(Config): SS0_HOST = os.getenv("SS0_HOST","https://ssoalpha.dvb.corpinter.net.cn/") REDIRECT_URL = os.getenv("REDIRECT_URL","http://localhost:5221/report") DOMAIN = os.getenv("DOMAIN","http://localhost:3001") + # 前端回调路由(dev 根路径) + FRONTEND_CALLBACK_PATH = os.getenv("FRONTEND_CALLBACK_PATH", "/callback") # 2. 开发模式开关 DEBUG = True TESTING = True @@ -69,8 +71,10 @@ class ProductionConfig(Config): CLIENT_ID = os.getenv("CLIENT_ID","A17336FC-A8D7-4CEA-8777-384BFEABABC1") CLIENT_PASSWORD = os.getenv("CLIENT_PASSWORD","U7-C1BZQdL8V5c.z~qA_64SO2l9F3J0o") SS0_HOST = os.getenv("SS0_HOST","https://ssoalpha.dvb.corpinter.net.cn/") - REDIRECT_URL = os.getenv("REDIRECT_URL","http://localhost:5221/report") - DOMAIN = os.getenv("DOMAIN","http://localhost:3001") + REDIRECT_URL = os.getenv("REDIRECT_URL","https://cadb.dvb.corpinter.net.cn/accidentportal/api/incident/authorized") + DOMAIN = os.getenv("DOMAIN","https://cadb.dvb.corpinter.net.cn") + # 前端回调路由(prod 带域名前缀) + FRONTEND_CALLBACK_PATH = os.getenv("FRONTEND_CALLBACK_PATH", "/accidentportal/callback") DEBUG = False # 禁用调试(关键!避免生产环境暴露敏感信息) TESTING = False diff --git a/backend/prod.env b/backend/prod.env index b9829fd..2cf0363 100644 --- a/backend/prod.env +++ b/backend/prod.env @@ -1,32 +1,40 @@ -# 集群配置 -DATABRICKS_TOKEN=dapi188523cfb1cf34ad5a605fd75c5c0d31 -CLUSTER_ID=1203-054146-wkjf95i0 -DATABRICKS_HOST=https://adb-3035612432650494.2.databricks.azure.cn +# 运行环境(决定加载 ProductionConfig;容器必须以环境变量注入) +FLASK_ENV=production +FLASK_APP=run:app -# gen5 spark表的配置 +# Databricks(注意:entrypoint.sh 用的是 DATABRICKS_CLUSTER_ID,config.py 用的是 CLUSTER_ID,两个都给) +DATABRICKS_TOKEN=dapi188523cfb1cf34ad5a605fd75c5c0d31 +DATABRICKS_HOST=https://adb-3035612432650494.2.databricks.azure.cn +DATABRICKS_CLUSTER_ID=1203-054146-wkjf95i0 +CLUSTER_ID=1203-054146-wkjf95i0 + +# gen5 spark 表 GEN5_TABLE_NAME=hive_metastore.taf_level_two_plus.accident_report_data -# JWT配置 -JWT_SECRET_KEY=dev_mb_project_2025_xyz123 # 开发环境可随意,生产环境需复杂密钥 +# 安全密钥(生产请换成强随机值) +JWT_SECRET_KEY=dev_mb_project_2025_xyz123 SECRET_KEY=dev_mb_2025_abc789 -# 数据库配置 +# 数据库 DATABASE_URL=mysql+pymysql://root:V2OuCHPL%40changeit@10.200.10.193:3306/incid_db?charset=utf8mb4 -# oauth2配置 +# OAuth2 / SSO CLIENT_ID=B7E86ADC-A8F7-4462-87FC-A04DFDF3C58F CLIENT_PASSWORD=AWd408ETS_V~j-yt.763P1D9ovC25fbe - SS0_HOST=https://ssoalpha.dvb.corpinter.net.cn/ -# 授权成功后,SSO 服务器重定向回你的应用的地址 (必须与 SSO 服务器上配置的一致) -REDIRECT_URL=http://150.158.236.152:5221/incident/authorized -# 用户登录成功后,最终重定向到的前端页面地址 -DOMAIN=http://150.158.236.152:5000 -# 图片上传的配置 +# SSO 回调地址(必须与 SSO 平台登记的完全一致;经 nginx 反代到后端 /incident/authorized) +REDIRECT_URL=https://cadb.dvb.corpinter.net.cn/accidentportal/api/incident/authorized + +# 登录成功后浏览器最终跳转的前端域名 +DOMAIN=https://cadb.dvb.corpinter.net.cn +# 前端回调路由(带前缀) +FRONTEND_CALLBACK_PATH=/accidentportal/callback + +# 图片上传 UPLOAD_FOLDER=/app/uploads MAX_CONTENT_LENGTH=5242880 -IMAGE_BASE_URL=http://150.158.236.152:5221 +IMAGE_BASE_URL=https://cadb.dvb.corpinter.net.cn/accidentportal -# 前端请求头的key +# 前端请求头 key HEADERS_SECRET_KEY=mbincid-server12 \ No newline at end of file diff --git a/default.conf b/default.conf index 6757e5a..9bf3257 100644 --- a/default.conf +++ b/default.conf @@ -1,6 +1,6 @@ server { listen 80; - server_name 150.158.236.152; + server_name cadb.dvb.corpinter.net.cn; client_max_body_size 100M; # root /usr/share/nginx/html; include /etc/nginx/mime.types; @@ -39,7 +39,7 @@ server { proxy_busy_buffers_size 256k; proxy_max_temp_file_size 1024m; - proxy_pass http://172.17.0.1:5221/accidentportal/api/; + proxy_pass https://cadb.dvb.corpinter.net.cn/accidentportal/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -53,6 +53,12 @@ server { # 关键:Cookie处理 proxy_cookie_path / "/; SameSite=Lax"; } + + # 图片/上传(IMAGE_BASE_URL + file_path 拼成 /uploads/...) + location /accidentportal/uploads/ { + proxy_pass https://cadb.dvb.corpinter.net.cn/accidentportal/uploads/; + proxy_set_header Host $host; + } } diff --git a/frontend/.env.development b/frontend/.env.development index 08b0e4b..19f9b22 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -13,7 +13,7 @@ VITE_GLOB_APP_TITLE="RDCA_Incidents Data Portal" VITE_ENV=test # 登录跳转 -VITE_LOGIN_URL = http://localhost:5221/accidentportal/user/login +VITE_LOGIN_URL = /api/user/login # 请求头的密钥 VITE_APP_CHECK_KEY = 'mbincid-server12' diff --git a/frontend/.env.production b/frontend/.env.production index 3b64fdd..9931cad 100644 --- a/frontend/.env.production +++ b/frontend/.env.production @@ -1,14 +1,14 @@ +# 接口前缀(相对路径,走 nginx 反代) VITE_APP_BASE_API=/accidentportal/api -VITE_APP_BASE_URL=http://150.158.236.152:5221 -# The title of your application (string) +# 站点域名 +VITE_APP_BASE_URL=https://cadb.dvb.corpinter.net.cn + VITE_GLOB_APP_TITLE="RDCA_Incidents Data Portal" -# 登录跳转 -# VITE_LOGIN_URL = http://150.158.236.152:5221/login -VITE_LOGIN_URL = http://150.158.236.152:5221/accidentportal/api/user/login +# 登录入口:整页跳到后端 SSO 入口(经 nginx /accidentportal/api 反代) +VITE_LOGIN_URL=/accidentportal/api/user/login -# 请求头的密钥 -VITE_APP_CHECK_KEY = 'mbincid-server12' +VITE_APP_CHECK_KEY='mbincid-server12' -# 前端配置域名加的前缀名 -VITE_APP_DOMAIN_PREFIX = 'accidentportal' \ No newline at end of file +# 前端路由/资源前缀 +VITE_APP_DOMAIN_PREFIX='accidentportal' \ No newline at end of file diff --git a/frontend/src/pages/common/callback.vue b/frontend/src/pages/common/callback.vue index 0f190b2..337668c 100644 --- a/frontend/src/pages/common/callback.vue +++ b/frontend/src/pages/common/callback.vue @@ -53,7 +53,7 @@ onMounted(async () => { // console.log(`Redirecting to: ${redirectPath}`); // router.push({name:"report"}); // } - window.location.href = 'https://cadb.dvb.corpinter.net.cn/accidentportal/oauth/report' + window.location.href = `${import.meta.env.BASE_URL}oauth/report` } else { console.error("No authentication cookies found"); error.value = "Authentication failed: missing cookies"; diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index f0b00ae..f94f68f 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -37,7 +37,7 @@ const router = createRouter({ ...staticRoutes, ...routes ], - history: createWebHistory(import.meta.env.VITE_APP_DOMAIN_PREFIX), //accidentportal + history: createWebHistory(import.meta.env.BASE_URL), // dev: '/' prod: '/accidentportal/' // history: createWebHashHistory('/accidentportal/'), //accidentportal }) diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index 58b25da..b84cc3f 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -28,9 +28,8 @@ async function requestHandler(config: InternalAxiosRequestConfig & RequestConfig import.meta.env.DEV && import.meta.env.VITE_APP_BASE_API_DEV && import.meta.env.VITE_APP_BASE_URL_DEV - && config.customDev ) { - // 替换url的请求前缀baseUrl + // 开发环境统一使用 /api 前缀(由 vite 代理重写为后端真实前缀) config.baseURL = import.meta.env.VITE_APP_BASE_API_DEV } // console.log('Request URL:', config.baseURL) diff --git a/frontend/types/components.d.ts b/frontend/types/components.d.ts index 7460744..e51725d 100644 --- a/frontend/types/components.d.ts +++ b/frontend/types/components.d.ts @@ -11,32 +11,19 @@ declare module 'vue' { AAlert: typeof import('ant-design-vue/es')['Alert'] AApp: typeof import('ant-design-vue/es')['App'] AAvatar: typeof import('ant-design-vue/es')['Avatar'] - AAvatarGroup: typeof import('ant-design-vue/es')['AvatarGroup'] ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] AButton: typeof import('ant-design-vue/es')['Button'] ACard: typeof import('ant-design-vue/es')['Card'] - ACardGrid: typeof import('ant-design-vue/es')['CardGrid'] - ACardMeta: typeof import('ant-design-vue/es')['CardMeta'] Access: typeof import('./../src/components/access/index.vue')['default'] - ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] - ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] ACol: typeof import('ant-design-vue/es')['Col'] AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] - ADatePicker: typeof import('ant-design-vue/es')['DatePicker'] - ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] - ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] ADivider: typeof import('ant-design-vue/es')['Divider'] ADrawer: typeof import('ant-design-vue/es')['Drawer'] ADropdown: typeof import('ant-design-vue/es')['Dropdown'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] - AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest'] - AImage: typeof import('ant-design-vue/es')['Image'] - AImagePreviewGroup: typeof import('ant-design-vue/es')['ImagePreviewGroup'] AInput: typeof import('ant-design-vue/es')['Input'] - AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] - AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] ALayout: typeof import('ant-design-vue/es')['Layout'] ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] @@ -49,36 +36,19 @@ declare module 'vue' { AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] - AModal: typeof import('ant-design-vue/es')['Modal'] - APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] - AProgress: typeof import('ant-design-vue/es')['Progress'] - ARadio: typeof import('ant-design-vue/es')['Radio'] - ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] - ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] - AResult: typeof import('ant-design-vue/es')['Result'] ARow: typeof import('ant-design-vue/es')['Row'] ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASpace: typeof import('ant-design-vue/es')['Space'] ASpin: typeof import('ant-design-vue/es')['Spin'] - AStatistic: typeof import('ant-design-vue/es')['Statistic'] - AStatisticCountdown: typeof import('ant-design-vue/es')['StatisticCountdown'] - AStep: typeof import('ant-design-vue/es')['Step'] - ASteps: typeof import('ant-design-vue/es')['Steps'] ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] ATabPane: typeof import('ant-design-vue/es')['TabPane'] ATabs: typeof import('ant-design-vue/es')['Tabs'] ATag: typeof import('ant-design-vue/es')['Tag'] - ATextarea: typeof import('ant-design-vue/es')['Textarea'] - ATimePicker: typeof import('ant-design-vue/es')['TimePicker'] ATooltip: typeof import('ant-design-vue/es')['Tooltip'] - ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect'] - ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph'] - ATypographyText: typeof import('ant-design-vue/es')['TypographyText'] - AUpload: typeof import('ant-design-vue/es')['Upload'] AWatermark: typeof import('ant-design-vue/es')['Watermark'] BaseLoading: typeof import('./../src/components/base-loading/index.vue')['default'] CarbonLanguage: typeof import('./../src/components/icons/carbon-language.vue')['default'] @@ -101,7 +71,6 @@ declare module 'vue' { RouterView: typeof import('vue-router')['RouterView'] SelectLang: typeof import('./../src/components/select-lang/index.vue')['default'] TokenProvider: typeof import('./../src/components/token-provider/index.vue')['default'] - Tooltip: typeof import('ant-design-vue/es')['Tooltip'] UserAvatar: typeof import('./../src/components/user-avatar/index.vue')['default'] VirtualList: typeof import('./../src/components/virtual-list/index.vue')['default'] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 56c5e05..152db75 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -22,16 +22,17 @@ export default ({ mode }: ConfigEnv): UserConfig => { // } // } - if (mode === 'development' && env.VITE_APP_BASE_API && env.VITE_APP_BASE_URL) { - proxyObj[env.VITE_APP_BASE_API] = { - target: env.VITE_APP_BASE_URL, + if (mode === 'development' && env.VITE_APP_BASE_API_DEV && env.VITE_APP_BASE_URL_DEV) { + proxyObj[env.VITE_APP_BASE_API_DEV] = { + target: env.VITE_APP_BASE_URL_DEV, changeOrigin: true, - rewrite: path => path.replace(new RegExp(`^${env.VITE_APP_BASE_API}`), ''), + // 浏览器请求 /api/xxx,转发给后端时补上域名前缀 -> /accidentportal/api/xxx + rewrite: path => path.replace(new RegExp(`^${env.VITE_APP_BASE_API_DEV}`), '/accidentportal/api'), } } return { - base: '/accidentportal/', + base: mode === 'development' ? '/' : '/accidentportal/', plugins: createVitePlugins(env), resolve: { alias: [