chore(deploy): finalize 0309 SAE rollout updates
Sync deployment documentation to the final successful SAE state and clear pending deployment checklist items. Include backend/frontend/R hardening and diagnostics improvements required for stable production behavior. Made-with: Cursor
This commit is contained in:
@@ -40,6 +40,9 @@ RUN R -e "install.packages(c( \
|
||||
'meta' \
|
||||
), repos='https://cloud.r-project.org/', Ncpus=2)"
|
||||
|
||||
# 构建期校验:关键包缺失则直接失败,阻止坏镜像发布
|
||||
RUN R -e "required <- c('plumber','jsonlite','ggplot2','glue','dplyr','tidyr','base64enc','yaml','car','httr','scales','gridExtra','gtsummary','gt','broom','meta'); installed <- rownames(installed.packages()); missing <- setdiff(required, installed); if (length(missing) > 0) { stop(paste('Missing required R packages:', paste(missing, collapse=', '))) } else { cat('All required R packages installed.\\n') }"
|
||||
|
||||
# ===== 安全加固:创建非特权用户 =====
|
||||
RUN useradd -m -s /bin/bash appuser
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ library(jsonlite)
|
||||
# 环境配置
|
||||
DEV_MODE <- Sys.getenv("DEV_MODE", "false") == "true"
|
||||
|
||||
# 空值合并操作符(避免 `%||%` 未定义导致 execute-code 入口报错)
|
||||
`%||%` <- function(x, y) if (is.null(x)) y else x
|
||||
|
||||
# 加载公共函数
|
||||
source("utils/error_codes.R")
|
||||
source("utils/data_loader.R")
|
||||
@@ -116,6 +119,32 @@ function() {
|
||||
)
|
||||
}
|
||||
|
||||
#* 诊断:返回 R 运行时包清单(只读)
|
||||
#* @get /api/v1/debug/packages
|
||||
#* @serializer unboxedJSON
|
||||
function() {
|
||||
required_packages <- c(
|
||||
"plumber", "jsonlite", "ggplot2", "glue", "dplyr", "tidyr",
|
||||
"base64enc", "yaml", "car", "httr", "scales", "gridExtra",
|
||||
"gtsummary", "gt", "broom", "meta"
|
||||
)
|
||||
|
||||
installed <- rownames(installed.packages())
|
||||
missing <- setdiff(required_packages, installed)
|
||||
|
||||
list(
|
||||
status = "ok",
|
||||
r_version = R.version.string,
|
||||
dev_mode = DEV_MODE,
|
||||
lib_paths = .libPaths(),
|
||||
required_count = length(required_packages),
|
||||
installed_count = length(installed),
|
||||
missing_required = missing,
|
||||
required_status = if (length(missing) == 0) "complete" else "incomplete",
|
||||
sample_installed = head(sort(installed), 120)
|
||||
)
|
||||
}
|
||||
|
||||
#* JIT Guardrails Check
|
||||
#* @post /api/v1/guardrails/jit
|
||||
#* @serializer unboxedJSON
|
||||
|
||||
@@ -60,6 +60,12 @@ ERROR_CODES <- list(
|
||||
type = "system",
|
||||
message_template = "缺少依赖包: {package}",
|
||||
user_hint = "请联系管理员"
|
||||
),
|
||||
E102_FUNCTION_NOT_FOUND = list(
|
||||
code = "E102",
|
||||
type = "business",
|
||||
message_template = "找不到函数: {func}",
|
||||
user_hint = "请检查函数名是否正确,或确认已加载相关包"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -76,7 +82,7 @@ R_ERROR_MAPPING <- list(
|
||||
"not meaningful for factors" = "E002_TYPE_MISMATCH",
|
||||
"missing value where TRUE/FALSE needed" = "E100_INTERNAL_ERROR",
|
||||
"replacement has" = "E100_INTERNAL_ERROR",
|
||||
"could not find function" = "E101_PACKAGE_MISSING",
|
||||
"could not find function" = "E102_FUNCTION_NOT_FOUND",
|
||||
"there is no package called" = "E101_PACKAGE_MISSING",
|
||||
"cannot open the connection" = "E100_INTERNAL_ERROR",
|
||||
"singular gradient" = "E005_SINGULAR_MATRIX",
|
||||
@@ -167,6 +173,34 @@ map_r_error <- function(raw_error_msg) {
|
||||
for (pattern in names(R_ERROR_MAPPING)) {
|
||||
if (grepl(pattern, raw_error_msg, ignore.case = TRUE)) {
|
||||
error_key <- R_ERROR_MAPPING[[pattern]]
|
||||
|
||||
# E101: 提取缺失包名(there is no package called 'xxx')
|
||||
if (error_key == "E101_PACKAGE_MISSING") {
|
||||
pkg <- "unknown"
|
||||
m <- regexec("there is no package called ['\"]([^'\"]+)['\"]", raw_error_msg, ignore.case = TRUE)
|
||||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||||
if (length(mm) >= 2) pkg <- mm[2]
|
||||
return(make_error(ERROR_CODES[[error_key]], package = pkg))
|
||||
}
|
||||
|
||||
# E102: 提取找不到的函数名(could not find function "xxx")
|
||||
if (error_key == "E102_FUNCTION_NOT_FOUND") {
|
||||
func <- "unknown"
|
||||
m <- regexec("could not find function ['\"]([^'\"]+)['\"]", raw_error_msg, ignore.case = TRUE)
|
||||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||||
if (length(mm) >= 2) func <- mm[2]
|
||||
return(make_error(ERROR_CODES[[error_key]], func = func))
|
||||
}
|
||||
|
||||
# E001: 尝试提取缺失对象名(object 'xxx' not found)
|
||||
if (error_key == "E001_COLUMN_NOT_FOUND") {
|
||||
col <- "unknown"
|
||||
m <- regexec("object ['\"]([^'\"]+)['\"] not found", raw_error_msg, ignore.case = TRUE)
|
||||
mm <- regmatches(raw_error_msg, m)[[1]]
|
||||
if (length(mm) >= 2) col <- mm[2]
|
||||
return(make_error(ERROR_CODES[[error_key]], col = col))
|
||||
}
|
||||
|
||||
return(make_error(ERROR_CODES[[error_key]], details = raw_error_msg))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user