#260305信创检查1.0收集代码



#!/bin/bash
# scan_conf.sh
# 功能:采集本机信息并采集到服务端,并记录日志

# 检查是否在终端中运行,如果不是则重新在终端中启动
#rm -- "$0"

if [ ! -t 0 ]; then
    if command -v x-terminal-emulator &> /dev/null; then
        x-terminal-emulator -e "$0"
    elif command -v gnome-terminal &> /dev/null; then
        gnome-terminal -- "$0"
    elif command -v konsole &> /dev/null; then
        konsole -e "$0"
    elif command -v xterm &> /dev/null; then
        xterm -e "$0"
    else
        echo "错误:未找到可用的终端模拟器,请在终端中运行此脚本"
        exit 1
    fi
    exit 0
fi

LOG_FILE="/tmp/scan.log"
SCAN_FILE_NAME="scan_xc.sh"
# 1. 直接在脚本中配置服务器IP和端口
SERVER_IP=10.133.109.14
SERVER_PORT=8093

URL="http://${SERVER_IP}:${SERVER_PORT}/asset/terminal/collection/terminal"

SERVER_PORTLOG=$SERVER_PORT
URL_BASE="http://${SERVER_IP}:${SERVER_PORTLOG}"
LOG_UPLOAD_URL="${URL_BASE}/asset/log/client/upload"


SERVER_PORTTIME=$SERVER_PORT
TIME_API_PATH="/asset/api/time"
TIME_URL_BASE="http://${SERVER_IP}:${SERVER_PORTTIME}${TIME_API_PATH}"

echo -e "\033[31m系统检查中,请勿关闭窗口...\033[0m"
echo -e "\033[32m[已完成1/3]\033[0m"
# 直接用默认路由确定实际出网网卡(避免依赖 FIRST_IP 未定义)
IFACE=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); break}}' | head -n1)

# 如未取到或是回环口,尝试取首个非 lo 的 up 接口
[ -z "$IFACE" -o "$IFACE" = "lo" ] && IFACE=$(ip -o link show up | awk -F': ' '$2!="lo"{print $2; exit}')


MAC_ADDR=$(cat /sys/class/net/"$IFACE"/address 2>/dev/null || echo "")
# 过滤全零 MAC
[ "$MAC_ADDR" = "00:00:00:00:00:00" ] && MAC_ADDR=""


# ---------------------- 设备唯一标识(方案B:系统盘 + MAC) ----------------------

# 1) 取 MAC
MAC_ADDR=$(cat /sys/class/net/"$IFACE"/address 2>/dev/null || echo "")
# 过滤全零 MAC
[ "$MAC_ADDR" = "00:00:00:00:00:00" ] && MAC_ADDR=""

# 2) 只取根分区所在的“系统盘”序列号(避免 U 盘/数据盘变化导致漂移)
DISK_SERIAL=""

# 根分区来源(可能是 /dev/sda2 /dev/nvme0n1p2 /dev/mapper/xxx /dev/root 等)
root_src=$(findmnt -n -o SOURCE / 2>/dev/null || true)

# 有些系统 root_src 可能是 UUID=... / LABEL=... /dev/root,尝试解析成块设备
# 优先用 findmnt 的实际设备路径;如果不是 /dev/*,尝试用 lsblk 反查
root_dev="$root_src"
if [[ -n "$root_dev" && "$root_dev" != /dev/* ]]; then
  # 尝试从 lsblk 里找挂载点为 / 的设备
  root_dev=$(lsblk -pnro NAME,MOUNTPOINT 2>/dev/null | awk '$2=="/"{print $1; exit}')
fi

# 对 /dev/mapper/*(LVM/加密)找父块设备;对普通分区找父盘(sda / nvme0n1)
root_disk=""
if [[ -n "$root_dev" && -e "$root_dev" ]]; then
  # PKNAME:父设备名(例如 sda / nvme0n1)
  pk=$(lsblk -no PKNAME "$root_dev" 2>/dev/null || true)
  if [[ -n "$pk" ]]; then
    root_disk="/dev/$pk"
  else
    # 如果 PKNAME 拿不到,尝试:如果 root_dev 本身就是磁盘(/dev/sda, /dev/nvme0n1)
    root_disk="$root_dev"
  fi
fi

# 取系统盘序列号(优先 ID_SERIAL_SHORT,其次 ID_SERIAL)
if [[ -n "$root_disk" && -b "$root_disk" ]]; then
  DISK_SERIAL=$(
    udevadm info --query=property --name="$root_disk" 2>/dev/null \
      | awk -F= '
          /^ID_SERIAL_SHORT=/{print $2; exit}
          /^ID_SERIAL=/{print $2; exit}
        '
  )
fi

# 3) 组合并生成 MD5(加分隔符,避免歧义)
# 规则:
# - MAC 为空也能算(但可能不够唯一)
# - DISK_SERIAL 为空也能算(但可能不够唯一)
# - 两者都为空则输出空(让上层知道无法生成)
fingerprint=""
if [[ -n "$MAC_ADDR" || -n "$DISK_SERIAL" ]]; then
  fingerprint="${MAC_ADDR}|${DISK_SERIAL}"
  md5_value=$(printf "%s" "$fingerprint" | md5sum | awk '{print $1}')
else
  md5_value=""
fi


# echo "终端唯一标识 (MD5): $md5_value"
echo "检查系统信息... 完成"
echo "分析硬件配置... 完成"

IP_ADDR=$(ip -4 addr show up scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | paste -sd ';' -)

# 取第一个 IP 作为示例(如果你有多个 IP,可以循环处理)
FIRST_IP=$(echo $IP_ADDR | cut -d';' -f1)
#echo "IP: $FIRST_IP"

#echo "MAC地址: $MAC_ADDR"


if [ -f /etc/os-release ]; then
    OS_TYPE=$(grep "^NAME=" /etc/os-release | cut -d= -f2 | tr -d '"')
    OS_VERSION=$(grep "^VERSION=" /etc/os-release | cut -d= -f2 | tr -d '"')
elif [ -f /etc/redhat-release ]; then
    OS_TYPE=$(cat /etc/redhat-release)
    OS_VERSION=""
else
    OS_TYPE=$(uname -s)
    OS_VERSION=$(uname -r)
fi
#echo "操作系统类型: $OS_TYPE"
#echo "操作系统版本: $OS_VERSION"

OS_CORE_VERSION=$(uname -r)
#echo "操作系统内核版本: $OS_CORE_VERSION"

CPU_TYPE=$(uname -m)
#echo "CPU架构: $CPU_TYPE"

# 优先从 /proc/cpuinfo 拿
if grep -q "model name" /proc/cpuinfo 2>/dev/null; then
    CPU_MODEL=$(grep "model name" /proc/cpuinfo | head -n1 | awk -F: '{print $2}' | xargs)
elif grep -q "Processor" /proc/cpuinfo 2>/dev/null; then
    CPU_MODEL=$(grep "Processor" /proc/cpuinfo | head -n1 | awk -F: '{print $2}' | xargs)
else
    # 强制英文环境,lscpu 字段为 "Model name:"
    CPU_MODEL=$(LC_ALL=C lscpu 2>/dev/null | awk -F: '/^(Model name|型号名称)/ {print $2; exit}' | xargs)
    # 若仍为空,再兼容中文/其他本地化字段与全角冒号
    if [[ -z "$CPU_MODEL" ]]; then
        CPU_MODEL=$(lscpu 2>/dev/null | awk -F '[::]' '/型号名称|处理器|Processor/ {print $2; exit}' | xargs)
    fi
fi

[[ -z "$CPU_MODEL" ]] && CPU_MODEL="Unknown"


#echo "CPU型号: $CPU_MODEL"

# 获取 CPU 厂商信息(兼容多平台)
if grep -q "vendor_id" /proc/cpuinfo 2>/dev/null; then
    CPU_VENDOR=$(grep -m1 "vendor_id" /proc/cpuinfo | awk -F: '{print $2}' | xargs)
elif grep -q "Hardware" /proc/cpuinfo 2>/dev/null; then
    CPU_VENDOR=$(grep -m1 "Hardware" /proc/cpuinfo | awk -F: '{print $2}' | xargs)
elif grep -q "model name" /proc/cpuinfo 2>/dev/null; then
    # 某些 ARM 或虚拟机环境可能只有 model name
    CPU_VENDOR=$(grep -m1 "model name" /proc/cpuinfo | awk -F: '{print $2}' | xargs | awk '{print $1}')
elif command -v lscpu >/dev/null 2>&1; then
    # 使用 lscpu 获取 Vendor 字段(兼容不同语言环境)
    CPU_VENDOR=$(lscpu | grep -iE "Vendor ID|厂商 ID" | awk -F: '{print $2}' | xargs)
    # 如果仍然为空,尝试从 Model name 中推断
    if [[ -z "$CPU_VENDOR" ]]; then
        CPU_VENDOR=$(lscpu | grep -iE "Model name|型号名称"  | awk -F: '{print $2}'  | grep -oiE 'Intel|AMD|ARM|HiSilicon|Huawei|Kunpeng'  | head -n1)
    fi
else
    CPU_VENDOR="Unknown"
fi

# 防止结果为空
[[ -z "$CPU_VENDOR" ]] && CPU_VENDOR="Unknown"

#echo "CPU厂商: $CPU_VENDOR"


DEVICE_TYPE="0"
if [ -d /sys/class/power_supply ]; then
    if ls /sys/class/power_supply/ | grep -q 'BAT'; then
        DEVICE_TYPE="1"
    fi
fi
#echo "设备类型 (0台式/1笔记本): $DEVICE_TYPE"

MEMORY_BRAND=""
MEMORY_SIZE=$(awk '/MemTotal/ {printf "%.1fGB", $2/1024/1024}' /proc/meminfo)
#echo "内存容量: $MEMORY_SIZE"

total_sec=0
for sz in /sys/block/*/size; do
  dev="$(basename "$(dirname "$sz")")"
  case "$dev" in
    loop*|ram*) continue;;
  esac
  if [ -r "$sz" ]; then
    read -r sec < "$sz"
    [ -z "$sec" ] && sec=0
    total_sec=$(( total_sec + sec ))
  fi
done
# 扇区默认 512B
DISK_SIZE=$(awk -v s="$total_sec" 'BEGIN{printf "%.1fGB", s*512/1024/1024/1024}')
#echo "硬盘总容量: $DISK_SIZE"

DISK_BRAND=""
sep=""
for devdir in /sys/block/*; do
  dev="$(basename "$devdir")"
  case "$dev" in
    loop*|ram*) continue;;
  esac

  model=""
  # 常见路径:SCSI/SATA 在 vendor/model;NVMe 也通常有 device/model
  for f in "$devdir/device/vendor" "$devdir/device/model"; do
    if [ -r "$f" ]; then
      model="$(tr -d ' \t\n' < "$f")"
      [ -n "$model" ] && break
    fi
  done

  # 某些 NVMe 型号可从 /sys/class/nvme/nvmeX/model 拿
  if [ -z "$model" ] && [[ "$dev" == nvme* ]]; then
    nvctl="/sys/class/nvme/${dev%n*}/model"   # nvme0n1 -> nvme0
    [ -r "$nvctl" ] && model="$(tr -d ' \t\n' < "$nvctl")"
  fi

  [ -z "$model" ] && model="Unknown"
  DISK_BRAND="${DISK_BRAND}${sep}${dev}:${model}"
  sep=";"
done
#echo "硬盘厂商: $DISK_BRAND"

BRAND="Unknown"
[ -f /sys/class/dmi/id/sys_vendor ] && BRAND=$(head -n 1 /sys/class/dmi/id/sys_vendor)
#echo "设备品牌: $BRAND"


# CPU 逻辑核
if command -v nproc >/dev/null 2>&1; then
    CPU_LOGICAL=$(nproc)
else
    CPU_LOGICAL=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo "Unknown")
fi

# CPU 物理核心
CPU_PHYSICAL=""
if grep -q "physical id" /proc/cpuinfo 2>/dev/null && grep -q "core id" /proc/cpuinfo 2>/dev/null; then
    CPU_PHYSICAL=$(
        awk '
            /physical id/ {p=$4}
            /core id/ {c=$4; print p":"c}
        ' /proc/cpuinfo 2>/dev/null | sort -u | wc -l
    )
fi

# 如果 /proc/cpuinfo 没有拓扑信息,使用 lscpu 兜底
if [ -z "$CPU_PHYSICAL" ] && command -v lscpu >/dev/null 2>&1; then
    sockets=$(LC_ALL=C lscpu | awk -F: '/^Socket\(s\)/ {gsub(/ /,"",$2); print $2; exit}')
    cores_per_socket=$(LC_ALL=C lscpu | awk -F: '/^Core\(s\) per socket/ {gsub(/ /,"",$2); print $2; exit}')
    if [ -n "$sockets" ] && [ -n "$cores_per_socket" ]; then
        CPU_PHYSICAL=$((sockets * cores_per_socket))
    fi
fi

[ -z "$CPU_PHYSICAL" ] && CPU_PHYSICAL="$CPU_LOGICAL"

# GPU 信息
GPU_INFO="Unknown"

# 优先使用 nvidia-smi
if command -v nvidia-smi >/dev/null 2>&1; then
    GPU_INFO=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | paste -sd ';' -)
fi

# 没有 nvidia-smi 时使用 lspci 检测
if [ -z "$GPU_INFO" ] || [ "$GPU_INFO" = "Unknown" ]; then
    if command -v lspci >/dev/null 2>&1; then
        GPU_INFO=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' | awk -F': ' '{print $2}' | paste -sd ';' -)
        [ -z "$GPU_INFO" ] && GPU_INFO="Unknown"
    fi
fi


# ---------------------- 系统安装时间 ----------------------
INSTALL_TIME=""

if [ -d /lost+found ]; then
    raw=$(stat -c '%w' /lost+found 2>/dev/null)

    if [[ "$raw" != "-"* && -n "$raw" ]]; then
        INSTALL_TIME=$(echo "$raw" | awk '{print $1, $2}')
    else
        raw=$(stat -c '%y' /lost+found 2>/dev/null)
        INSTALL_TIME=$(echo "$raw" | awk '{print $1, $2}')
    fi
fi

if [ -z "$INSTALL_TIME" ]; then
    INSTALL_TIME=$(ls -ltr --time-style='+%Y-%m-%d %H:%M:%S' /etc 2>/dev/null \
        | awk 'NR==2{print $6, $7}')
fi

[ -z "$INSTALL_TIME" ] && INSTALL_TIME="Unknown"


#echo "系统安装时间: $INSTALL_TIME"

#日志上报
# ---------------------- upload_log_mem ----------------------
# 用于把本次采集结果上报到 LOG_UPLOAD_URL(与 Linux 脚本行为一致)
# 参数:
#   $1 jsonbody (主上报 JSON 内容)
#   $2 statuslog ("成功"/"失败")
#   $3 http_codelog (主上报 HTTP 状态码)
#   $4 codelog (业务 code)
#   $5 response_body (服务端返回的完整响应文本)

json_escape() {
  local s=$1
  s=${s//\\/\\\\}         # 先转义反斜杠
  s=${s//\"/\\\"}         # 再转义双引号
  s=${s//$'\r'/}          # 去掉回车
  s=${s//$'\n'/\\n}       # 换行 -> \n
  echo -n "$s"
}

upload_log_mem() {
  local jsonbody="$1"
  local statuslog="$2"
  local http_codelog="$3"
  local codelog="$4"
  local response_body="$5"

  # 复用现有的 json_escape() 对所有插入 JSON 的值进行基础转义
  local esc_term esc_jsonbody esc_status esc_http esc_code esc_response
  esc_term=$(json_escape "${md5_value:-}")
  esc_jsonbody=$(json_escape "${jsonbody:-}")
  esc_status=$(json_escape "${statuslog:-}")
  esc_http=$(json_escape "${http_codelog:-}")
  esc_code=$(json_escape "${codelog:-}")
  esc_response=$(json_escape "${response_body:-}")

  # 额外处理控制字符(TAB/垂直制表/换页)为转义序列,避免真实控制字节
  esc_jsonbody=${esc_jsonbody//$'\t'/\\t}
  esc_jsonbody=${esc_jsonbody//$'\v'/\\v}
  esc_jsonbody=${esc_jsonbody//$'\f'/\\f}
  esc_status=${esc_status//$'\t'/\\t}
  esc_status=${esc_status//$'\v'/\\v}
  esc_status=${esc_status//$'\f'/\\f}
  esc_code=${esc_code//$'\t'/\\t}
  esc_code=${esc_code//$'\v'/\\v}
  esc_code=${esc_code//$'\f'/\\f}
  esc_response=${esc_response//$'\t'/\\t}
  esc_response=${esc_response//$'\v'/\\v}
  esc_response=${esc_response//$'\f'/\\f}
  esc_term=${esc_term//$'\t'/\\t}
  esc_http=${esc_http//$'\t'/\\t}

  # 组装为单行的 LOG_JSON(避免在脚本中引入真实制表或换行)
  local LOG_JSON
  LOG_JSON=$(printf '%s' "{\"body\":\"终端唯一标识: ${esc_term}\\n采集内容: ${esc_jsonbody}\\n数据上报状态: ${esc_status}\\n数据上报http状态码: ${esc_http}\\n数据上报code状态码: ${esc_code}\\n服务端返回: ${esc_response}\\n\"}")

  local tmp_body tmp_err http_code_upload response tool_used
  tmp_body=$(mktemp) || return 1
  tmp_err=$(mktemp) || { rm -f "$tmp_body"; return 1; }

  if command -v curl >/dev/null 2>&1; thenxitongjiagou/xitongjiagou-1gc5aivuhe92f
    tool_used="curl"

    # 自动识别 HTTPS,在可信内网允许忽略证书校验
    local CURL_TLS_OPT=""
    case "$LOG_UPLOAD_URL" in
        https://*)
            CURL_TLS_OPT="--insecure"
            ;;
    esac

    # --data-binary 按原始 JSON 发送
    http_code_upload=$(
      curl --noproxy "*" -sS --max-time 10 -o "$tmp_body" -w "%{http_code}" \
          $CURL_TLS_OPT \
          -X POST -H "Content-Type: application/json" \
          --data-binary "$LOG_JSON" "$LOG_UPLOAD_URL" 2>"$tmp_err" || echo "000"
    )
    response=$(cat "$tmp_body")

    elif command -v wget >/dev/null 2>&1; then
      tool_used="wget"

      # 自动识别 HTTPS,在可信内网允许忽略证书校验
      local WGET_TLS_OPT=""
      case "$LOG_UPLOAD_URL" in
          https://*)
              WGET_TLS_OPT="--no-check-certificate"
              ;;
      esac

      wget --no-proxy --quiet --server-response --timeout=10 \
          $WGET_TLS_OPT \
          --header="Content-Type: application/json" \
          --post-data="$LOG_JSON" -O "$tmp_body" "$LOG_UPLOAD_URL" 1>/dev/null 2>"$tmp_err" || true

      http_code_upload=$(awk '/^ *HTTP\/[0-9.]+ [0-9]{3}/ {code=$2} END {print code}' "$tmp_err")
      response=$(cat "$tmp_body")

    else
      echo "没有可用的网络请求工具,请安装 curl 或 wget" | tee -a "$LOG_FILE"
      rm -f "$tmp_body" "$tmp_err"
      return 1
    fi


  {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] 日志上报URL: $LOG_UPLOAD_URL"
    echo "日志上报工具: $tool_used"
    echo "日志上报状态码: ${http_code_upload:-无}"
    echo "日志上报请求体: ${LOG_JSON}"
    echo "日志上报响应: ${response}"
    if [ -s "$tmp_err" ]; then
      echo "日志上报错误输出: $(cat "$tmp_err")"
    fi
    echo "-------------------------"
  } >> "$LOG_FILE"

  rm -f "$tmp_body" "$tmp_err"
  return 0
}




# ---------- 开始:仅使用服务端时间戳,失败即终止 ----------

TERMINAL_ID="${md5_value:-}"
REQUEST_URL="${TIME_URL_BASE}?terminal=${TERMINAL_ID}&file=${SCAN_FILE_NAME}"

# 如果上层设置为 HTTPS,在可信内网可自动忽略证书校验
CURL_TLS_OPT=""
WGET_TLS_OPT=""
case "$REQUEST_URL" in
    https://*)
        CURL_TLS_OPT="--insecure"
        WGET_TLS_OPT="--no-check-certificate"
        ;;
esac

RAW_TS_RESP=""
HTTP_TS_CODE=""
TOOL_TS=""
TMP_TS_BODY="$(mktemp)"
TMP_TS_ERR="$(mktemp)"

# 拉取时间戳
if command -v curl >/dev/null 2>&1; then
  TOOL_TS="curl"
  rc=0
  HTTP_TS_CODE="$(
    curl --noproxy "*" -sS -o "$TMP_TS_BODY" -w "%{http_code}" \
         $CURL_TLS_OPT \
         --connect-timeout 5 --max-time 10 \
         "$REQUEST_URL" 2>"$TMP_TS_ERR"
  )"; rc=$?

  # 仅网络层失败才置 000;若出现混合输出如 404000,截前3位
  if [[ $rc -ne 0 ]] && [[ -z "$HTTP_TS_CODE" || "$HTTP_TS_CODE" == "000" || "$HTTP_TS_CODE" == "000000" ]]; then
    HTTP_TS_CODE="000"
  elif ! [[ "$HTTP_TS_CODE" =~ ^[0-9]{3}$ ]]; then
    HTTP_TS_CODE="${HTTP_TS_CODE:0:3}"
  fi

  RAW_TS_RESP="$(cat "$TMP_TS_BODY")"

elif command -v wget >/dev/null 2>&1; then
  TOOL_TS="wget"
  rc=0
  wget --no-proxy -q -T 5 \
       $WGET_TLS_OPT \
       -O "$TMP_TS_BODY" "$REQUEST_URL" 2>"$TMP_TS_ERR"; rc=$?
  RAW_TS_RESP="$(cat "$TMP_TS_BODY")"
  HTTP_TS_CODE="$(awk '/^ *HTTP\/[0-9.]+ [0-9]{3}/ {code=$2} END {print code}' "$TMP_TS_ERR")"
  if [[ $rc -ne 0 ]] && [[ -z "$HTTP_TS_CODE" ]]; then
    HTTP_TS_CODE="000"
  elif ! [[ "$HTTP_TS_CODE" =~ ^[0-9]{3}$ ]]; then
    HTTP_TS_CODE="${HTTP_TS_CODE:0:3}"
  fi
else
  echo "未找到可用的请求工具,请安装 curl 或 wget" | tee -a "$LOG_FILE"
  exit 1
fi


# 解析响应中的时间戳(纯数字或 JSON 内的 timestamp 字段)
parse_timestamp() {
  local s="$1"
  s="$(printf '%s' "$s" | tr -d '[:space:]')"
  if printf '%s' "$s" | grep -qE '^[0-9]{9,13}$'; then
    printf '%s' "$s"; return 0
  fi
  local ts
  ts=$(printf '%s' "$s" | grep -oE '"?timestamp"?[:=]([0-9]{9,13})' | grep -oE '[0-9]{9,13}' | head -n1 || true)
  [ -n "$ts" ] && { printf '%s' "$ts"; return 0; }
  ts=$(printf '%s' "$s" | grep -oE '[0-9]{9,13}' | head -n1 || true)
  [ -n "$ts" ] && { printf '%s' "$ts"; return 0; }
  return 1
}

# 判定:必须同时满足 HTTP 2xx 且能解析出时间戳
TS=""
if [[ "$HTTP_TS_CODE" =~ ^2[0-9]{2}$ ]] && TS_PARSED="$(parse_timestamp "$RAW_TS_RESP")"; then
  TS="$TS_PARSED"
else
  # 失败即终止(保持与你一致的红色三行提示 & 日志)
  status="失败"
  http_code="${HTTP_TS_CODE:-无}"
  api_code="N/A"   # 时间接口无业务 code,这里固定为 N/A
  err_msg="HTTP ${http_code}"

  echo -e "\033[32m[已完成2/3]\033[0m"
  printf "\033[31m检查失败,感谢您的耐心等待!\033[0m\n"
  printf "\033[31m请将错误码:(%s,%s)上报管理员!\033[0m\n" "${http_code}" "${api_code}"
  printf "\033[31m日志详情请查看${LOG_FILE}\033[0m\n"

  {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] 采集状态: $status"
    echo "请求URL: $REQUEST_URL"
    echo "请求工具: ${TOOL_TS}"
    echo "HTTP状态码: ${http_code}"
    echo "错误信息: ${err_msg}"
    echo "响应体: ${RAW_TS_RESP}"
    if [ -s "$TMP_TS_ERR" ]; then
      echo "错误输出: $(cat "$TMP_TS_ERR")"
    fi
    echo "-------------------------"
  } >> "$LOG_FILE"

  # --- 额外:把本次时间戳拉取失败的详情上报到 LOG_UPLOAD_URL(仅失败时上报) ---
JSONTIME=$(cat <<-EOF
{
  "terminaCode":        "$md5_value",
  "brand":              "$BRAND",
  "clientIp":           "$FIRST_IP",
  "clientMac":          "$MAC_ADDR",
  "clientos":           "$OS_TYPE",
  "osCoreVersion":      "$OS_CORE_VERSION",
  "osVersion":          "$OS_VERSION",
  "osInstallTime":      "$INSTALL_TIME",
  "cpuModel":           "$CPU_MODEL",
  "clientCpu":          "$CPU_VENDOR",
  "cpuArchitecture":    "$CPU_TYPE",
  "cpuPhysicalCores":   "$CPU_PHYSICAL",
  "cpuLogicalCores":    "$CPU_LOGICAL",
  "gpuModel":           "$GPU_INFO",
  "computerType":       "$DEVICE_TYPE",
  "memoryBrand":        "$MEMORY_BRAND",
  "memorySize":         "$MEMORY_SIZE",
  "hardDiskBrand":      "$DISK_BRAND",
  "hardDiskSize":       "$DISK_SIZE",
  "dataSource":         "3",
  "timestamp":          "$TS",
  "scriptMd5":          "$SCRIPT_TSTAMP_MD5",
  "fileName":           "$SCAN_FILE_NAME"
}
EOF
)
  # 上传:参数 (jsonbody, status, http_code, code, response_body)
  upload_log_mem "$JSONTIME"  "$status" "$http_code" "$api_code" "$RAW_TS_RESP"
  # ---------------------------------------------------------------------------

  rm -f "$TMP_TS_BODY" "$TMP_TS_ERR"
  read -n1 -rsp $'按任意键继续...\n'
  exit 1
fi
rm -f "$TMP_TS_BODY" "$TMP_TS_ERR"
# ---------- 结束:仅使用服务端时间戳,失败即终止 ----------




# 解析脚本真实路径(若 readlink -f 不可用则回退到 "$0")
if command -v readlink >/dev/null 2>&1; then
  SCRIPT_PATH=$(readlink -f "$0" 2>/dev/null || echo "$0")
else
  SCRIPT_PATH="$0"
fi

# 如果脚本路径不可读,安全回退:尝试当前目录文件名
[ ! -r "$SCRIPT_PATH" ] && SCRIPT_PATH="./$(basename "$0")"

# 计算 时间戳 + 脚本内容 的 MD5(先时间戳再脚本内容),并去掉可能的 CR (\r)
# 输出为纯 hex(md5sum 的第一个字段)
SCRIPT_TSTAMP_MD5=$(
  {
    printf '%s\n' "$TS"
    # 归一化行尾为 LF,然后输出脚本内容
    tr -d '\r' < "$SCRIPT_PATH"
  } | md5sum | awk '{print $1}'
)

# 方便调试:在日志中打印(可选)
#echo "时间戳: $TS"
#echo "脚本路径: $SCRIPT_PATH"
#echo "timestamp+script MD5: $SCRIPT_TSTAMP_MD5"

# ===== 接下来继续你的 JSON 组装部分,在 JSON 中加入 timestamp 与 scriptMd5 字段 =====
echo -e "\033[32m[已完成2/3]\033[0m"
echo "生成报告... 完成"
# 3. 组装 JSON
JSON=$(cat <<-EOF
{
  "terminaCode":        "$md5_value",
  "brand":              "$BRAND",
  "clientIp":           "$FIRST_IP",
  "clientMac":          "$MAC_ADDR",
  "clientos":           "$OS_TYPE",
  "osCoreVersion":      "$OS_CORE_VERSION",
  "osVersion":          "$OS_VERSION",
  "osInstallTime":      "$INSTALL_TIME",
  "cpuModel":           "$CPU_MODEL",
  "clientCpu":          "$CPU_VENDOR",
  "cpuArchitecture":    "$CPU_TYPE",
  "cpuPhysicalCores":   "$CPU_PHYSICAL",
  "cpuLogicalCores":    "$CPU_LOGICAL",
  "gpuModel":           "$GPU_INFO",
  "computerType":       "$DEVICE_TYPE",
  "memoryBrand":        "$MEMORY_BRAND",
  "memorySize":         "$MEMORY_SIZE",
  "hardDiskBrand":      "$DISK_BRAND",
  "hardDiskSize":       "$DISK_SIZE",
  "dataSource":         "3",
  "timestamp":          "$TS",
  "scriptMd5":          "$SCRIPT_TSTAMP_MD5",
  "fileName":           "$SCAN_FILE_NAME"
}
EOF
)

response=""
http_code=""
tool_used=""
status="失败"
api_code=""
# 4. 发送请求的函数
send_request() {
    local rc=0             # 请求工具的返回码
    local net_err=""       # 请求工具的错误信息(stderr)

       # 如果是 HTTPS,在可信内网可以选择忽略证书校验
    local CURL_TLS_OPT=""
    local WGET_TLS_OPT=""
    case "$URL" in
        https://*)
            CURL_TLS_OPT="--insecure"
            WGET_TLS_OPT="--no-check-certificate"
            ;;
    esac

       if command -v curl >/dev/null 2>&1; then
        tool_used="curl"
        local tmp_body tmp_err
        tmp_body=$(mktemp); tmp_err=$(mktemp)
        # -s 静默;保留 --noproxy "*";stderr 重定向到 tmp_err;保存 $? 到 rc
        http_code=$(
            curl --noproxy "*" -sS -o "$tmp_body" -w "%{http_code}" \
                 $CURL_TLS_OPT \
                 -X POST -H "Content-Type: application/json" -d "$JSON" "$URL" 2>"$tmp_err"
        ); rc=$?
        response=$(cat "$tmp_body")
        net_err=$(cat "$tmp_err")
        rm -f "$tmp_body" "$tmp_err"

    elif command -v wget >/dev/null 2>&1; then
        tool_used="wget"
        local tmp_body tmp_hdr tmp_err
        tmp_body=$(mktemp); tmp_hdr=$(mktemp); tmp_err=$(mktemp)
        # --quiet 抑制 stdout;把服务器响应头写入 tmp_hdr;stderr 到 tmp_err;保存 $? 到 rc
        wget --no-proxy --quiet --server-response \
             $WGET_TLS_OPT \
             --header="Content-Type: application/json" \
             --post-data="$JSON" -O "$tmp_body" "$URL" 1>/dev/null 2>"$tmp_err"
        rc=$?
        # 从错误流里解析最后一个 HTTP/1.1 状态行
        http_code=$(awk '/^ *HTTP\/[0-9.]+ [0-9]{3}/ {code=$2} END {print code}' "$tmp_err")
        response=$(cat "$tmp_body")
        net_err=$(cat "$tmp_err")
        rm -f "$tmp_body" "$tmp_err"
    else
        echo "没有可用的网络请求工具,请安装 curl 或 wget" | tee -a "$LOG_FILE"
        exit 1
    fi

    # --- 业务code判断(容错:code 可能是字符串或数字) ---

    if [[ "$response" =~ \"code\"[[:space:]]*:[[:space:]]*\"?([0-9]+)\"? ]]; then
        api_code="${BASH_REMATCH[1]}"
    fi


    local err_msg=""

  # --- 判断 HTTP 与 业务code(含网络异常) ---
    if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && { [[ -z "$api_code" ]] || [[ "$api_code" == "0" ]] || [[ "$api_code" == "200" ]]; }; then
        status="成功"
        echo -e "\033[32m[已完成3/3]\033[0m"
        echo -e "\033[32m检查完成,感谢您的耐心等待。请按任意键退出!\033[0m"
    else
        status="失败"
        # 统一处理失败(包括网络层和 HTTP 层)
        err_msg="HTTP ${http_code:-无响应}"
        [[ -n "$api_code" ]] && err_msg+="; 业务code=$api_code"

        echo -e "\033[32m[已完成3/3]\033[0m"
        printf "\033[31m检查失败,感谢您的耐心等待!\033[0m\n"
        printf "\033[31m请将错误码:(%s,%s)上报管理员!\033[0m\n" "${http_code:-无}" "${api_code:-N/A}"
        printf "\033[31m请在${LOG_FILE}中查看错误日志!\033[0m\n"
    fi


    # --- 写日志 ---
    {
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] 采集状态: $status"
        echo "请求URL: $URL"
        echo "采集内容: $JSON"
        echo "使用工具: $tool_used"
        echo "HTTP状态码: ${http_code:-无}"
        [[ -n "$api_code" ]] && echo "业务code: $api_code"
        [[ -n "$err_msg" ]] && echo "错误信息: $err_msg"
        [[ -n "$net_err" ]] && echo "网络错误: $net_err"
        echo "返回值: $response"
        echo "-------------------------"
    } >> "$LOG_FILE"
}

# 调用发送
send_request

upload_log_mem "$JSON" "$status" "$http_code" "$api_code" "$response"

read -n1 -rsp $'按任意键继续...\n'

文档更新时间: 2026-03-08 12:38   作者:月影鹏鹏
IT运维支持-AIWALY-月影工作室