1Panel OpenResty 按省份/城市识别访客 IP:从陕西专用到全国通用(完整方案)

之前站点限制只有陕西 IP 能访问,用的是 $is_shaanxi + shaanxi.geo 一套专用配置。数据只有陕西,想加广东、北京甚至城市级判断就得再维护一份,很蠢。今天把整套方案升级成全国省份 + 城市通用体系:基于 ip2region 自动生成 34 个省级 + 数百个城市级 IP 段,OpenResty 用一个 geo 块就能识别 $china_region(SN=陕西 / GD=广东 / BJ=北京 …)和 $china_city(西安市 / 深圳市 …),以后任何地区限制都不需要再单独维护数据库,数据还能每天自动更新。这篇文章给出完整可落地的全套代码,按顺序执行即可。

总体架构

ip2region 仓库
     ↓  ipv4_source.txt / ipv6_source.txt
build-china-region.py   (生成器,按省/市归并 CIDR)

┌────────────────────┬────────────────────┐
│ china-region.geo   │ china-city.geo     │
│  省级: CIDR SN;    │  城市: CIDR "西安市";│
└────────────────────┴────────────────────┘
     ↓  include 进 geo 块
OpenResty

$china_region = SN        $china_city = 西安市

关键点:生成器把 ip2region 的原始地址段ipaddress.collapse_addresses 归并成最小的 CIDR 集合,同一个省/市的段合并后体量小很多;全国总表 + 分省目录(china_regions/SN.geo)+ 分城市目录(china_cities/SN/西安市.geo)一次性生成,想用哪个用哪个。

第 1 步:清理旧陕西专用配置

先检查有没有站点还引用旧变量:

grep -RniE '\$is_shaanxi|shaanxi\.geo' /opt/1panel/www/conf.d || true

如果某个网站里还有:

if ($is_shaanxi = 0) {
    return 403;
}

以后改成(继续只允许陕西,但改用全国通用变量):

if ($china_region != SN) {
    return 403;
}

然后删除旧的陕西专用文件:

rm -f /opt/1panel/www/conf.d/00-shaanxi-geo.conf
rm -f /opt/1panel/www/conf.d/shaanxi.geo
rm -f /usr/local/bin/build-shaanxi-ip.py
rm -f /usr/local/bin/update-shaanxi-ip.sh

再次确认:

grep -RniE '\$is_shaanxi|shaanxi\.geo' /opt/1panel/www/conf.d || true

最好没有输出。

第 2 步:安装全国省份 + 城市生成器

创建 build-china-region.py(完整源码):

cat >/usr/local/bin/build-china-region.py <<'PY'
#!/usr/bin/env python3

import ipaddress
import os
import re
import shutil
import tempfile
from collections import defaultdict


# ==========================================================
# 配置
# ==========================================================

SOURCES = [
    "/opt/ip2region/data/ipv4_source.txt",
    "/opt/ip2region/data/ipv6_source.txt",
]

BASE_DIR = "/opt/1panel/www/conf.d"

REGION_FILE = os.path.join(BASE_DIR, "china-region.geo")
CITY_FILE = os.path.join(BASE_DIR, "china-city.geo")

REGION_DIR = os.path.join(BASE_DIR, "china_regions")
CITY_DIR = os.path.join(BASE_DIR, "china_cities")


# ==========================================================
# 中国省级行政区代码
# ==========================================================

REGION_CODES = {
    "北京市": "BJ", "天津市": "TJ", "河北省": "HE", "山西省": "SX",
    "内蒙古自治区": "NM", "辽宁省": "LN", "吉林省": "JL", "黑龙江省": "HL",
    "上海市": "SH", "江苏省": "JS", "浙江省": "ZJ", "安徽省": "AH",
    "福建省": "FJ", "江西省": "JX", "山东省": "SD", "河南省": "HA",
    "湖北省": "HB", "湖南省": "HN", "广东省": "GD", "广西壮族自治区": "GX",
    "海南省": "HI", "重庆市": "CQ", "四川省": "SC", "贵州省": "GZ",
    "云南省": "YN", "西藏自治区": "XZ", "陕西省": "SN", "甘肃省": "GS",
    "青海省": "QH", "宁夏回族自治区": "NX", "新疆维吾尔自治区": "XJ",
    "台湾省": "TW", "香港特别行政区": "HK", "澳门特别行政区": "MO",
}

# ==========================================================
# ip2region 省份字段兼容
# ==========================================================

ALIASES = {
    "北京": "北京市", "天津": "天津市", "河北": "河北省", "山西": "山西省",
    "内蒙古": "内蒙古自治区", "辽宁": "辽宁省", "吉林": "吉林省", "黑龙江": "黑龙江省",
    "上海": "上海市", "江苏": "江苏省", "浙江": "浙江省", "安徽": "安徽省",
    "福建": "福建省", "江西": "江西省", "山东": "山东省", "河南": "河南省",
    "湖北": "湖北省", "湖南": "湖南省", "广东": "广东省", "广西": "广西壮族自治区",
    "海南": "海南省", "重庆": "重庆市", "四川": "四川省", "贵州": "贵州省",
    "云南": "云南省", "西藏": "西藏自治区", "陕西": "陕西省", "甘肃": "甘肃省",
    "青海": "青海省", "宁夏": "宁夏回族自治区", "新疆": "新疆维吾尔自治区",
    "台湾": "台湾省", "香港": "香港特别行政区", "澳门": "澳门特别行政区",
    # ip2region 当前数据中的异常省份字段
    "广州": "广东省", "广州市": "广东省", "武汉": "湖北省", "武汉市": "湖北省",
}

# ==========================================================
# 数据容器
# ==========================================================

region_v4 = defaultdict(list)
region_v6 = defaultdict(list)
city_v4 = defaultdict(list)
city_v6 = defaultdict(list)
region_raw_count = defaultdict(int)
city_raw_count = defaultdict(int)
unknown_regions = set()
total_region_ranges = 0
total_city_ranges = 0

# ==========================================================
# 工具函数
# ==========================================================

def normalize_region(name):
    name = name.strip()
    if name in REGION_CODES:
        return name
    return ALIASES.get(name)

def valid_city(city):
    city = city.strip()
    if not city:
        return False
    invalid = {"0", "未知", "Unknown", "unknown", "NULL", "null", "-"}
    return city not in invalid

def safe_filename(name):
    name = re.sub(r'[^\w\u4e00-\u9fff\-]+', "_", name)
    return name.strip("_") or "unknown"

def nginx_quote(value):
    value = value.replace("\\", "\\\\")
    value = value.replace('"', '\\"')
    return f'"{value}"'

def atomic_write(path, content):
    directory = os.path.dirname(path)
    os.makedirs(directory, exist_ok=True)
    fd, tmp = tempfile.mkstemp(prefix=".tmp-", dir=directory, text=True)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            f.write(content)
        os.replace(tmp, path)
    except Exception:
        try:
            os.unlink(tmp)
        except FileNotFoundError:
            pass
        raise


# ==========================================================
# 读取 ip2region
# 格式: start|end|country|province|city|isp|country_code
# ==========================================================

for source in SOURCES:
    print(f"读取: {source}")
    if not os.path.exists(source):
        print(f"ERROR: 数据文件不存在: {source}")
        raise SystemExit(1)
    with open(source, "r", encoding="utf-8") as f:
        for lineno, raw in enumerate(f, 1):
            line = raw.strip()
            if not line or line.startswith("#"):
                continue
            fields = line.split("|")
            if len(fields) < 5:
                continue
            start_ip = fields[0].strip()
            end_ip = fields[1].strip()
            country = fields[2].strip()
            region_raw = fields[3].strip()
            city = fields[4].strip()
            # 这里只处理中国
            if country != "中国":
                continue
            if not region_raw or region_raw == "0":
                continue
            region_name = normalize_region(region_raw)
            if not region_name:
                unknown_regions.add(region_raw)
                continue
            region_code = REGION_CODES[region_name]
            try:
                start = ipaddress.ip_address(start_ip)
                end = ipaddress.ip_address(end_ip)
            except ValueError as e:
                print(f"忽略非法 IP: {source}:{lineno}: {e}")
                continue
            if start.version != end.version:
                continue
            networks = list(ipaddress.summarize_address_range(start, end))
            if start.version == 4:
                region_v4[region_code].extend(networks)
            else:
                region_v6[region_code].extend(networks)
            region_raw_count[region_code] += 1
            total_region_ranges += 1
            if valid_city(city):
                key = (region_code, city)
                if start.version == 4:
                    city_v4[key].extend(networks)
                else:
                    city_v6[key].extend(networks)
                city_raw_count[key] += 1
                total_city_ranges += 1


# ==========================================================
# 合并省级 CIDR
# ==========================================================

collapsed_region_v4 = {}
collapsed_region_v6 = {}
all_region_codes = set(region_v4.keys()) | set(region_v6.keys())
for code in all_region_codes:
    collapsed_region_v4[code] = list(ipaddress.collapse_addresses(region_v4[code]))
    collapsed_region_v6[code] = list(ipaddress.collapse_addresses(region_v6[code]))

# ==========================================================
# 合并城市级 CIDR
# ==========================================================

collapsed_city_v4 = {}
collapsed_city_v6 = {}
all_city_keys = set(city_v4.keys()) | set(city_v6.keys())
for key in all_city_keys:
    collapsed_city_v4[key] = list(ipaddress.collapse_addresses(city_v4[key]))
    collapsed_city_v6[key] = list(ipaddress.collapse_addresses(city_v6[key]))

# ==========================================================
# 清理旧分拆目录
# ==========================================================

if os.path.exists(REGION_DIR):
    shutil.rmtree(REGION_DIR)
if os.path.exists(CITY_DIR):
    shutil.rmtree(CITY_DIR)
os.makedirs(REGION_DIR, exist_ok=True)
os.makedirs(CITY_DIR, exist_ok=True)

# ==========================================================
# 每个省单独生成(SN.geo / GD.geo ...)
# ==========================================================

for region_name, code in REGION_CODES.items():
    v4 = collapsed_region_v4.get(code, [])
    v6 = collapsed_region_v6.get(code, [])
    lines = [f"# {region_name} / {code}\n", "# Generated from ip2region\n", "# DO NOT EDIT MANUALLY\n\n", "# IPv4\n"]
    for net in v4:
        lines.append(f"{net} 1;\n")
    lines.append("\n# IPv6\n")
    for net in v6:
        lines.append(f"{net} 1;\n")
    atomic_write(os.path.join(REGION_DIR, f"{code}.geo"), "".join(lines))

# ==========================================================
# 全国省份总表
# 输出: 113.140.64.0/18 SN;
# ==========================================================

region_entries_v4 = []
region_entries_v6 = []
for code in all_region_codes:
    for net in collapsed_region_v4.get(code, []):
        region_entries_v4.append((int(net.network_address), net, code))
    for net in collapsed_region_v6.get(code, []):
        region_entries_v6.append((int(net.network_address), net, code))
region_entries_v4.sort(key=lambda x: (x[0], x[1].prefixlen))
region_entries_v6.sort(key=lambda x: (x[0], x[1].prefixlen))

lines = ["# ==========================================\n", "# China province/region IP database\n", "# Generated from ip2region\n", "# DO NOT EDIT MANUALLY\n", "# ==========================================\n\n", "# IPv4\n"]
for _, net, code in region_entries_v4:
    lines.append(f"{net} {code};\n")
lines.append("\n# IPv6\n")
for _, net, code in region_entries_v6:
    lines.append(f"{net} {code};\n")
atomic_write(REGION_FILE, "".join(lines))

# ==========================================================
# 每个城市单独生成(china_cities/SN/西安市.geo ...)
# ==========================================================

for region_code, city in sorted(all_city_keys):
    key = (region_code, city)
    v4 = collapsed_city_v4.get(key, [])
    v6 = collapsed_city_v6.get(key, [])
    province_dir = os.path.join(CITY_DIR, region_code)
    os.makedirs(province_dir, exist_ok=True)
    lines = [f"# Region: {region_code}\n", f"# City: {city}\n", "# Generated from ip2region\n", "# DO NOT EDIT MANUALLY\n\n", "# IPv4\n"]
    for net in v4:
        lines.append(f"{net} 1;\n")
    lines.append("\n# IPv6\n")
    for net in v6:
        lines.append(f"{net} 1;\n")
    filename = safe_filename(city) + ".geo"
    atomic_write(os.path.join(province_dir, filename), "".join(lines))

# ==========================================================
# 全国城市总表
# 输出: 113.140.64.0/18 "西安市";
# ==========================================================

city_entries_v4 = []
city_entries_v6 = []
for region_code, city in all_city_keys:
    key = (region_code, city)
    for net in collapsed_city_v4.get(key, []):
        city_entries_v4.append((int(net.network_address), net, region_code, city))
    for net in collapsed_city_v6.get(key, []):
        city_entries_v6.append((int(net.network_address), net, region_code, city))
city_entries_v4.sort(key=lambda x: (x[0], x[1].prefixlen))
city_entries_v6.sort(key=lambda x: (x[0], x[1].prefixlen))

lines = ["# ==========================================\n", "# China city IP database\n", "# Generated from ip2region\n", "# DO NOT EDIT MANUALLY\n", "# ==========================================\n\n", "# IPv4\n"]
for _, net, region_code, city in city_entries_v4:
    lines.append(f"{net} {nginx_quote(city)};\n")
lines.append("\n# IPv6\n")
for _, net, region_code, city in city_entries_v6:
    lines.append(f"{net} {nginx_quote(city)};\n")
atomic_write(CITY_FILE, "".join(lines))

# ==========================================================
# 输出统计
# ==========================================================

reverse_names = {code: name for name, code in REGION_CODES.items()}
print()
print("========================================")
print("全国 IP 地区库生成完成")
print("========================================")
print(f"省级原始地址段: {total_region_ranges}")
print(f"城市有效地址段: {total_city_ranges}")
print()
print(f"省级总表: {REGION_FILE}")
print(f"城市总表: {CITY_FILE}")
print(f"分省目录: {REGION_DIR}")
print(f"分城市目录: {CITY_DIR}")
print()
print(f"{'代码':<6}{'地区':<12}{'IPv4':>10}{'IPv6':>10}{'城市数':>10}")
print("-" * 58)
for code in sorted(REGION_CODES.values()):
    region_name = reverse_names[code]
    cities = {city for rc, city in all_city_keys if rc == code}
    print(f"{code:<6}{region_name:<12}{len(collapsed_region_v4.get(code, [])):>10}{len(collapsed_region_v6.get(code, [])):>10}{len(cities):>10}")
if unknown_regions:
    print()
    print("WARNING: 发现未识别的省级名称:")
    for name in sorted(unknown_regions):
        print(f"  - {name}")
    print()
    print("请把这些名称加入 ALIASES。")
PY

chmod +x /usr/local/bin/build-china-region.py

核心逻辑:读取 ip2region 的 ipv4/ipv6 source → normalize 省份别名(ip2region 里有些字段是”广州""武汉”这种异常写法,映射回广东省/湖北省)→ 分省/分城归集 → ipaddress.collapse_addresses 合并 → 原子写出 china-region.geo(格式 113.140.64.0/18 SN;)+ china-city.geo(格式 113.140.64.0/18 "西安市";),并生成分省目录 china_regions/SN.geo 与分城市目录 china_cities/SN/西安市.geo

先运行一次:

/usr/local/bin/build-china-region.py

第 3 步:1Panel OpenResty 全局配置

覆盖写入 /opt/1panel/www/conf.d/00-china-region.conf:

cat >/opt/1panel/www/conf.d/00-china-region.conf <<'EOF'
# ==========================================================
# 中国 IP 省份 / 城市识别
#
# 变量:
#
# $china_region
#   SN = 陕西
#   SX = 山西
#   BJ = 北京
#   GD = 广东
#   XX = 未识别 / 非中国
#
# $china_city
#   西安市
#   深圳市
#   北京市
#   Unknown = 无城市数据
#
# ==========================================================


# ----------------------------------------------------------
# 省级定位
# ----------------------------------------------------------

geo $china_region {

    default XX;

    # 本机
    127.0.0.1/32 LOCAL;
    ::1/128       LOCAL;

    include /usr/local/openresty/nginx/conf/conf.d/china-region.geo;
}


# ----------------------------------------------------------
# 城市级定位
# ----------------------------------------------------------

geo $china_city {

    default Unknown;

    # 本机
    127.0.0.1/32 Localhost;
    ::1/128       Localhost;

    include /usr/local/openresty/nginx/conf/conf.d/china-city.geo;
}
EOF

最终结构应该是:

/opt/1panel/www/conf.d/
├── 00-china-region.conf
├── china-region.geo
├── china-city.geo
├── china_regions/
│   ├── BJ.geo
│   ├── GD.geo
│   ├── SN.geo
│   ├── SX.geo
│   └── ...
└── china_cities/
    ├── SN/
    │   ├── 西安市.geo
    │   ├── 宝鸡市.geo
    │   ├── 咸阳市.geo
    │   └── ...
    ├── GD/
    │   ├── 广州市.geo
    │   ├── 深圳市.geo
    │   └── ...
    └── ...

第 4 步:检查 OpenResty 能否读取并 reload

OPENRESTY=$(
    docker ps --format '{{.Names}}' |
    grep -i openresty |
    head -1
)

echo "$OPENRESTY"

确认两个数据文件在容器里(注意 geo 文件在容器内挂载路径是 /usr/local/openresty/nginx/conf/conf.d/):

docker exec "$OPENRESTY" \
    ls -lh \
    /usr/local/openresty/nginx/conf/conf.d/china-region.geo \
    /usr/local/openresty/nginx/conf/conf.d/china-city.geo

检查配置:

docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -t

成功后 reload:

docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -s reload

临时测试接口

在 1Panel 某个网站的 server {} 里临时加:

location = /__geo_test {

    default_type text/plain;

    return 200
        "ip=$remote_addr\n"
        "region=$china_region\n"
        "city=$china_city\n";
}

你之前的:

113.140.84.102

应该类似:

ip=113.140.84.102
region=SN
city=西安市

如果某个陕西 IP 没有城市信息:

region=SN
city=Unknown

这是正常的。

第 5 步:用法示例

只允许陕西

以前的:

if ($is_shaanxi = 0) {
    return 403;
}

全部不要了,现在直接:

if ($china_region != SN) {
    return 403;
}

只允许西安

if ($china_city != "西安市") {
    return 403;
}

不过更严谨的是同时判断省+市。全局配置中增加:

map "$china_region:$china_city" $allow_xian {
    default 0;

    "SN:西安市" 1;
}

站点:

if ($allow_xian = 0) {
    return 403;
}

第 6 步:自动更新脚本(每天凌晨)

创建 /usr/local/bin/update-china-region.sh(完整源码):

cat >/usr/local/bin/update-china-region.sh <<'SH'
#!/bin/bash

set -Eeuo pipefail


REPO="/opt/ip2region"

BUILD_SCRIPT="/usr/local/bin/build-china-region.py"

REGION_FILE="/opt/1panel/www/conf.d/china-region.geo"
CITY_FILE="/opt/1panel/www/conf.d/china-city.geo"


echo
echo "========================================"
echo "$(date '+%F %T') 开始更新中国 IP 数据"
echo "========================================"
echo


# ==========================================================
# 检查仓库
# ==========================================================

if [ ! -d "$REPO/.git" ]; then

    echo "ERROR: ip2region 仓库不存在:"
    echo "$REPO"

    exit 1

fi


cd "$REPO"


OLD_HEAD=$(git rev-parse HEAD)


echo "当前版本:"
echo "$OLD_HEAD"
echo


# ==========================================================
# 拉取最新数据
# ==========================================================

echo "检查 ip2region 更新..."

git pull --ff-only

NEW_HEAD=$(git rev-parse HEAD)


echo

if [ "$OLD_HEAD" = "$NEW_HEAD" ]; then

    echo "ip2region 仓库没有变化"

else

    echo "ip2region 已更新:"
    echo
    echo "$OLD_HEAD"
    echo "↓"
    echo "$NEW_HEAD"

fi


# ==========================================================
# 备份当前运行中的 geo 文件
# ==========================================================

TMP_DIR=$(mktemp -d)

trap 'rm -rf "$TMP_DIR"' EXIT


HAD_REGION=0
HAD_CITY=0


if [ -f "$REGION_FILE" ]; then

    cp "$REGION_FILE" "$TMP_DIR/china-region.geo"
    HAD_REGION=1

fi


if [ -f "$CITY_FILE" ]; then

    cp "$CITY_FILE" "$TMP_DIR/china-city.geo"
    HAD_CITY=1

fi


# ==========================================================
# 重新生成
# ==========================================================

echo
echo "重新生成全国省份 / 城市数据..."
echo


"$BUILD_SCRIPT"


# ==========================================================
# 判断运行文件有没有变化
# ==========================================================

REGION_CHANGED=1
CITY_CHANGED=1


if [ "$HAD_REGION" -eq 1 ] &&
   cmp -s \
       "$TMP_DIR/china-region.geo" \
       "$REGION_FILE"
then

    REGION_CHANGED=0

fi


if [ "$HAD_CITY" -eq 1 ] &&
   cmp -s \
       "$TMP_DIR/china-city.geo" \
       "$CITY_FILE"
then

    CITY_CHANGED=0

fi


echo

if [ "$REGION_CHANGED" -eq 0 ] &&
   [ "$CITY_CHANGED" -eq 0 ]
then

    echo "省份和城市 IP 数据均没有变化"
    echo "无需 reload OpenResty"

    echo
    echo "========================================"
    echo "更新完成"
    echo "========================================"

    exit 0

fi


if [ "$REGION_CHANGED" -eq 1 ]; then
    echo "省份 IP 数据发生变化"
fi


if [ "$CITY_CHANGED" -eq 1 ]; then
    echo "城市 IP 数据发生变化"
fi


# ==========================================================
# 找 1Panel OpenResty 容器
# ==========================================================

echo
echo "查找 OpenResty 容器..."


OPENRESTY=$(
    docker ps --format '{{.Names}}' |
    grep -i openresty |
    head -1 || true
)


if [ -z "$OPENRESTY" ]; then

    echo "ERROR: 没找到运行中的 OpenResty 容器"

    exit 1

fi


echo "OpenResty: $OPENRESTY"


# ==========================================================
# nginx -t
# ==========================================================

echo
echo "检查 OpenResty 配置..."


if ! docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -t
then

    echo
    echo "ERROR: 新 IP 数据导致 OpenResty 配置检查失败"
    echo "恢复旧 IP 数据..."


    if [ "$HAD_REGION" -eq 1 ]; then

        cp \
            "$TMP_DIR/china-region.geo" \
            "$REGION_FILE"

    else

        rm -f "$REGION_FILE"

    fi


    if [ "$HAD_CITY" -eq 1 ]; then

        cp \
            "$TMP_DIR/china-city.geo" \
            "$CITY_FILE"

    else

        rm -f "$CITY_FILE"

    fi


    echo
    echo "旧数据已恢复"


    if docker exec "$OPENRESTY" \
        /usr/local/openresty/nginx/sbin/nginx -t
    then

        echo "恢复后的 OpenResty 配置正常"

    else

        echo "WARNING: 恢复后 nginx -t 仍失败"
        echo "请手动检查其他 OpenResty 配置"

    fi


    exit 1

fi


# ==========================================================
# reload
# ==========================================================

echo
echo "配置检查成功"
echo "重新加载 OpenResty..."


docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -s reload


echo
echo "OpenResty reload 成功"

echo
echo "========================================"
echo "$(date '+%F %T') 更新完成"
echo "========================================"
echo
SH

chmod +x /usr/local/bin/update-china-region.sh

这个版本的特性:

  • git pull --ff-only 拉最新 ip2region;
  • 自动重新生成省市数据库;
  • cmp -s 比较是否真的发生变化:没变化不 reload;
  • 有变化先 nginx -t;配置正确才 reload;
  • 如果 nginx -t 失败,会自动恢复旧数据库,再试一次,仍失败就报警告;
  • 不生成日志,全部直接打印。

手动测试一次

/usr/local/bin/update-china-region.sh

如果仓库和数据库都没变化,会类似:

========================================
2026-09-01 12:00:00 开始更新中国 IP 数据
========================================

检查 ip2region 更新...
Already up to date.

ip2region 仓库没有变化

重新生成全国省份 / 城市数据...

...

省份和城市 IP 数据均没有变化
无需 reload OpenResty

========================================
更新完成
========================================

如果数据变化:

省份 IP 数据发生变化
城市 IP 数据发生变化

查找 OpenResty 容器...
OpenResty: 1Panel-openresty-xxxx

检查 OpenResty 配置...
nginx: configuration file ... test is successful

配置检查成功
重新加载 OpenResty...

OpenResty reload 成功

1Panel 定时任务

1Panel → 计划任务 → 创建计划任务 → Shell 脚本,命令:

/usr/local/bin/update-china-region.sh

建议每天一次,例如每天凌晨 04:17。因为不写日志、直接打印,1Panel 自己的任务执行记录里就能看到结果。

第 7 步:最后检查旧配置是否彻底消失

grep -RniE '\$is_shaanxi|shaanxi\.geo' \
    /opt/1panel/www/conf.d || true

如果没有输出,就说明已经彻底清理。再检查新的变量:

grep -RniE '\$china_region|\$china_city' \
    /opt/1panel/www/conf.d

最后:

OPENRESTY=$(
    docker ps --format '{{.Names}}' |
    grep -i openresty |
    head -1
)

docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -t

成功后:

docker exec "$OPENRESTY" \
    /usr/local/openresty/nginx/sbin/nginx -s reload

这样最终你只有一套通用体系:

ip2region

ipv4_source.txt
ipv6_source.txt

build-china-region.py

┌──────────────────┬──────────────────┐
│ china-region.geo │ china-city.geo   │
│      省份         │      城市        │
└──────────────────┴──────────────────┘

      OpenResty

$china_region = SN
$china_city   = 西安市

以后陕西、广东、北京、西安、深圳等都不需要再单独维护数据库。