diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18a148bb..b0b36a9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,41 @@
+
+# [v1.13.4](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.13.4) - 2026-04-28
+
+## What's Changed
+* Update changelog for v1.13.3 by [@wu-clan](https://github.com/wu-clan) in [#1149](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1149)
+* Update the architecture description in README by [@wu-clan](https://github.com/wu-clan) in [#1150](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1150)
+* Fix app pytest client fixture scope by [@IAseven](https://github.com/IAseven) in [#1152](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1152)
+* Remove cleanup operations after plugin install by [@wu-clan](https://github.com/wu-clan) in [#1154](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1154)
+* Fix mysql enum data type mismatch by [@wu-clan](https://github.com/wu-clan) in [#1155](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1155)
+* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#1156](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1156)
+* Add plugin depends on and lifecycle ordering by [@AH-Toby](https://github.com/AH-Toby) in [#1153](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1153)
+* Update plugin README content format by [@wu-clan](https://github.com/wu-clan) in [#1158](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1158)
+* Update Grafana dashboard and datasource config by [@wu-clan](https://github.com/wu-clan) in [#1159](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1159)
+* Add database and queue observability by [@wu-clan](https://github.com/wu-clan) in [#1160](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1160)
+* Fix Grafana tempo and observability config by [@wu-clan](https://github.com/wu-clan) in [#1161](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1161)
+* Add the snowflake ID enabled config by [@wu-clan](https://github.com/wu-clan) in [#1162](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1162)
+* Fix the add plugin CLI command config by [@wu-clan](https://github.com/wu-clan) in [#1163](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1163)
+* Optimize the core code ordering of plugins by [@wu-clan](https://github.com/wu-clan) in [#1164](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1164)
+* Add uninstall plugin operation checks by [@wu-clan](https://github.com/wu-clan) in [#1165](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1165)
+* Fix multi level cache storage logic by [@wu-clan](https://github.com/wu-clan) in [#1166](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1166)
+* Fix typing warnings for return methods by [@wu-clan](https://github.com/wu-clan) in [#1167](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1167)
+* Bump dependencies and pre-commits by [@wu-clan](https://github.com/wu-clan) in [#1168](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1168)
+* Update the version number to 1.13.4 by [@wu-clan](https://github.com/wu-clan) in [#1169](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1169)
+
+## New Contributors
+* [@AH-Toby](https://github.com/AH-Toby) made their first contribution in [#1153](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1153)
+
+**Full Changelog**: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.3...v1.13.4
+
+## Contributors
+
+
+
+
+
+[Changes][v1.13.4]
+
+
# [v1.13.3](https://github.com/fastapi-practices/fastapi-best-architecture/releases/tag/v1.13.3) - 2026-04-08
@@ -1473,6 +1511,7 @@
[Changes][v1.0.0]
+[v1.13.4]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.3...v1.13.4
[v1.13.3]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.2...v1.13.3
[v1.13.2]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.1...v1.13.2
[v1.13.1]: https://github.com/fastapi-practices/fastapi-best-architecture/compare/v1.13.0...v1.13.1
diff --git a/backend/static/ip2region_v4.xdb b/backend/static/ip2region_v4.xdb
index 6f86c7d9..45edd141 100644
Binary files a/backend/static/ip2region_v4.xdb and b/backend/static/ip2region_v4.xdb differ
diff --git a/backend/utils/request_parse.py b/backend/utils/request_parse.py
index 4939e933..5a6f308c 100644
--- a/backend/utils/request_parse.py
+++ b/backend/utils/request_parse.py
@@ -1,7 +1,8 @@
import httpx
+import ip2region.searcher as ip2region_xdb
+import ip2region.util as ip2region_util
from fastapi import Request
-from ip2loc import XdbSearcher
from user_agents import parse
from backend.common.dataclasses import IpInfo, UserAgentInfo
@@ -53,8 +54,9 @@ async def get_location_online(ip: str) -> dict | None:
return None
-# 离线 IP 搜索器单例(数据将缓存到内存,缓存大小取决于 IP 数据文件大小)
-__xdb_searcher = XdbSearcher(contentBuff=XdbSearcher.loadContentFromFile(dbfile=STATIC_DIR / 'ip2region_v4.xdb'))
+# 离线 IP 搜索器(数据将缓存到内存,缓存大小取决于 IP 数据文件大小)
+__c_buffer: bytes = ip2region_util.load_content_from_file(STATIC_DIR / 'ip2region_v4.xdb')
+__xdb_searcher: ip2region_xdb.Searcher = ip2region_xdb.new_with_buffer(ip2region_util.IPv4, __c_buffer)
def get_location_offline(ip: str) -> dict | None:
@@ -66,15 +68,16 @@ def get_location_offline(ip: str) -> dict | None:
"""
try:
data = __xdb_searcher.search(ip)
- data = data.split('|')
- return {
- 'country': data[0] if data[0] != '0' else None,
- 'regionName': data[1] if data[1] != '0' else None,
- 'city': data[2] if data[2] != '0' else None,
- }
+ country, region_name, city, *_ = data.split('|')
except Exception as e:
- log.error(f'离线获取 IP 地址属地失败,错误信息:{e}')
+ log.error(f'离线获取 IP 地址属地失败:{e}')
return None
+ else:
+ return {
+ 'country': country if country != '0' else None,
+ 'regionName': region_name if region_name != '0' else None,
+ 'city': city if city != '0' else None,
+ }
async def parse_ip_info(request: Request) -> IpInfo:
diff --git a/pyproject.toml b/pyproject.toml
index b06f5950..c8f90861 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,7 +28,6 @@ dependencies = [
"flower>=2.0.1",
"gevent>=25.9.1",
"granian>=2.7.4",
- "ip2loc>=1.0.0",
"itsdangerous>=2.2.0",
"jinja2>=3.1.6",
"loguru>=0.7.3",
@@ -47,6 +46,7 @@ dependencies = [
# https://github.com/fastapi-practices/fastapi_best_architecture/issues/887
"psycopg[binary]==3.2.10",
"pwdlib>=0.3.0",
+ "py-ip2region>=3.0.4",
"pydantic>=2.13.3",
"pydantic-settings>=2.14.0",
"pymysql>=1.1.2",
diff --git a/requirements.txt b/requirements.txt
index 198a171d..4908fb7a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -138,8 +138,6 @@ importlib-metadata==8.7.1
# via opentelemetry-api
iniconfig==2.3.0
# via pytest
-ip2loc==1.0.0
- # via fastapi-best-architecture
itsdangerous==2.2.0
# via fastapi-best-architecture
jinja2==3.1.6
@@ -260,6 +258,8 @@ psycopg-binary==3.2.10 ; implementation_name != 'pypy'
# via psycopg
pwdlib==0.3.0
# via fastapi-best-architecture
+py-ip2region==3.0.4
+ # via fastapi-best-architecture
pyasn1==0.6.3
# via
# python-jose
diff --git a/uv.lock b/uv.lock
index 65338900..a7d07fe7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2252,7 +2252,6 @@ dependencies = [
{ name = "flower" },
{ name = "gevent" },
{ name = "granian" },
- { name = "ip2loc" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "loguru" },
@@ -2270,6 +2269,7 @@ dependencies = [
{ name = "psutil" },
{ name = "psycopg", extra = ["binary"] },
{ name = "pwdlib" },
+ { name = "py-ip2region" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pymysql" },
@@ -2325,7 +2325,6 @@ requires-dist = [
{ name = "flower", specifier = ">=2.0.1" },
{ name = "gevent", specifier = ">=25.9.1" },
{ name = "granian", specifier = ">=2.7.4" },
- { name = "ip2loc", specifier = ">=1.0.0" },
{ name = "itsdangerous", specifier = ">=2.2.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "loguru", specifier = ">=0.7.3" },
@@ -2346,6 +2345,7 @@ requires-dist = [
{ name = "psutil", specifier = ">=7.2.2" },
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.10" },
{ name = "pwdlib", specifier = ">=0.3.0" },
+ { name = "py-ip2region", specifier = ">=3.0.4" },
{ name = "pydantic", specifier = ">=2.13.3" },
{ name = "pydantic-settings", specifier = ">=2.14.0" },
{ name = "pymysql", specifier = ">=1.1.2" },
@@ -3998,21 +3998,6 @@ wheels = [
},
]
-[[package]]
-name = "ip2loc"
-version = "1.0.0"
-source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
-sdist = {
- url = "https://mirrors.aliyun.com/pypi/packages/80/74/b6f68d92571a11274c2a9d68c1c434f82a6c48a362e03386d356d8f6535b/ip2loc-1.0.0.tar.gz",
- hash = "sha256:6707400f1f8580386708e6072c82293ccbad496368b9bf2fea13cfc274ab1ea7"
-}
-wheels = [
- {
- url = "https://mirrors.aliyun.com/pypi/packages/55/9f/dd1a683eb6c58fcd37cce62ef2c910948fd14bf5e6398f15c03c6f9308ad/ip2loc-1.0.0-py3-none-any.whl",
- hash = "sha256:8094e071acfb3ce65c588d61da50de0eb26b21d3dc068cee85ff01823c3bc947"
- },
-]
-
[[package]]
name = "itsdangerous"
version = "2.2.0"
@@ -6819,6 +6804,21 @@ wheels = [
},
]
+[[package]]
+name = "py-ip2region"
+version = "3.0.4"
+source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
+sdist = {
+ url = "https://mirrors.aliyun.com/pypi/packages/60/b8/c8b934effe827e5d159b51912dc8366f5a490d7383943e982a801f5c3591/py_ip2region-3.0.4.tar.gz",
+ hash = "sha256:a8b818ed502a17feb998bdcf7c7748466aff368bcc75592c443aedf4eb6987f7"
+}
+wheels = [
+ {
+ url = "https://mirrors.aliyun.com/pypi/packages/86/04/ef28263025ef08ec581875468f7ba08df6803fc729a0d464c3ad2161c1c7/py_ip2region-3.0.4-py3-none-any.whl",
+ hash = "sha256:7668e3756bc941045cf317a2116fe8359b0a70d88cee326decb836caa14bd77d"
+ },
+]
+
[[package]]
name = "pyasn1"
version = "0.6.3"