refactor: split workspace and delivery layout
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
.git
|
||||
.DS_Store
|
||||
delivery/.env
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
49
.github/workflows/daily-sync-upstream.yml
vendored
49
.github/workflows/daily-sync-upstream.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Daily Upstream Sync
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Capture upstream commit
|
||||
id: upstream
|
||||
run: |
|
||||
echo "commit=$(git ls-remote https://github.com/KHwang9883/MobileModels.git refs/heads/master | awk '{print $1}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Sync upstream raw data
|
||||
run: |
|
||||
python3 tools/sync_upstream_mobilemodels.py --build-index --export-mysql-seed
|
||||
|
||||
- name: Commit changes
|
||||
env:
|
||||
UPSTREAM_COMMIT: ${{ steps.upstream.outputs.commit }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add brands misc README.md README_en.md CHANGELOG.md CHANGELOG_en.md LICENSE.txt dist/device_index.json dist/mobilemodels_mysql_seed.sql
|
||||
if git diff --cached --quiet; then
|
||||
echo "No upstream changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore: sync upstream raw data ${UPSTREAM_COMMIT::7}"
|
||||
|
||||
- name: Push changes
|
||||
run: git push
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
.env
|
||||
162
README.md
162
README.md
@@ -1,157 +1,37 @@
|
||||
# 手机品牌型号汇总
|
||||
# MobileModels Workspace
|
||||
|
||||
[](https://github.com/KHwang9883/MobileModels/issues)
|
||||
[](https://github.com/KHwang9883/MobileModels/pulls)
|
||||
[](https://github.com/KHwang9883/MobileModels)
|
||||
[](https://github.com/KHwang9883/MobileModels)
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
当前仓库按两层结构组织:
|
||||
|
||||
汇总各厂商上市的手机型号与对应的传播名。
|
||||
- `workspace/`
|
||||
- 工作空间
|
||||
- 存放上游原始数据、补充资料和历史变更文件
|
||||
- `delivery/`
|
||||
- 交付物
|
||||
- 存放可直接运行的 Docker Compose 项目、Web 页面、MySQL schema 和交付文档
|
||||
|
||||
[English](README_en.md)
|
||||
## 使用方式
|
||||
|
||||
## Web UI
|
||||
|
||||
项目内置了设备查询与数据管理页面,统一通过 `docker compose` 启动:
|
||||
进入交付目录启动:
|
||||
|
||||
```bash
|
||||
cd delivery
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
打开:
|
||||
页面入口:
|
||||
|
||||
- `http://127.0.0.1:8123/web/device_query.html`
|
||||
- `http://127.0.0.1:8123/web/brand_management.html`
|
||||
- `http://127.0.0.1:8123/web/device_query.html?view=docs`
|
||||
|
||||
MySQL 也会一并启动:
|
||||
## 目录说明
|
||||
|
||||
- host: `127.0.0.1`
|
||||
- port: `3306`
|
||||
- database: `mobilemodels`
|
||||
- reader user: `mobilemodels_reader`
|
||||
```text
|
||||
workspace/ 原始数据与工作空间
|
||||
delivery/ 可交付运行物
|
||||
```
|
||||
|
||||
如需自定义 MySQL 账号密码,可先复制 `.env.example` 为 `.env` 后再启动。
|
||||
原始数据、索引和 MySQL seed 运行时持久化在 Docker volume 中,不再回写本地工作区。
|
||||
更多交付说明见:
|
||||
|
||||
更多说明参见 [web/README.md](web/README.md)。
|
||||
|
||||
- ✅ 包含
|
||||
- ⏹ 仅部分包含
|
||||
- ❌ 不包含
|
||||
|
||||
| 名称 | 品牌 | 汇总范围 | codename | 海外机型 | 备注 |
|
||||
| :-: | :-: | :-: | :-: | :-: | :-: |
|
||||
| [360shouji](brands/360shouji.md) | 360 手机 | 全部 360/奇酷手机 | ❌ | ❌ | -- |
|
||||
| [apple_all](brands/apple_all.md) | Apple | 全部 iPhone/iPad/iPod touch/Apple Watch/Apple TV/Apple Vision | ✅ | ✅ | -- |
|
||||
| [apple_all_en](brands/apple_all_en.md) | Apple | 全部 iPhone/iPad/iPod touch/Apple Watch/Apple TV/Apple Vision | ✅ | ✅ | 英文版 |
|
||||
| [apple_cn](brands/apple_cn.md) | Apple | 全部国行 iPhone/iPad/iPod touch/Apple Watch/Apple Vision | ✅ | ❌ | -- |
|
||||
| [asus_cn](brands/asus_cn.md) | 华硕 (ASUS) | ROG Phone 等 | ✅ | ❌ | -- |
|
||||
| [asus_en](brands/asus_en.md) | 华硕 (ASUS) | ROG Phone/Zenfone | ✅ | ✅ | 英文版 |
|
||||
| [blackshark](brands/blackshark.md) | 黑鲨 (Black Shark) | 全部机型 | ✅ | ✅ | -- |
|
||||
| [blackshark_en](brands/blackshark_en.md) | 黑鲨 (Black Shark) | 全部机型 | ✅ | ✅ | 英文版 |
|
||||
| [coolpad](brands/coolpad.md) | 酷派 (Coolpad) | 酷派近年智能手机机型 | ❌ | ❌ | -- |
|
||||
| [google](brands/google.md) | Google | Google Pixel 手机/平板/手表 | ✅ | ✅ | 英文版 |
|
||||
| [honor_cn](brands/honor_cn.md) | 荣耀 (HONOR) | 荣耀手机/平板/笔记本电脑/智慧屏/穿戴设备,仅包含国行型号 | ⏹ | ❌ | -- |
|
||||
| [honor_global_en](brands/honor_global_en.md)| 荣耀 (HONOR) | 荣耀手机/平板,仅包含海外型号 | ⏹ | ✅ | 英文版 |
|
||||
| [huawei_cn](brands/huawei_cn.md) | 华为 (HUAWEI) | 华为 Mate/Pura/nova/G/麦芒/畅享系列、平板电脑、MateBook、智慧屏及穿戴设备,仅包含国行型号 | ⏹ | ❌ | [其他早期型号参阅此处](misc/early-huawei-models.md) |
|
||||
| [huawei_global_en](brands/huawei_global_en.md)| 华为 (HUAWEI) | 华为 Mate/Pura/nova/Y 系列及平板电脑,仅包含海外型号 | ⏹ | ⏹ | 英文版 |
|
||||
| [lenovo_cn](brands/lenovo_cn.md) | 联想 (Lenovo) | 联想品牌 2017 年起上市的机型、ZUK 全部机型 | ✅ | ❌ | -- |
|
||||
| [letv](brands/letv.md) | 乐视 (Letv) | 全部手机机型 | ❌ | ❌ | 不包含电视产品 |
|
||||
| [meizu](brands/meizu.md) | 魅族 (MEIZU) | 全部机型 | ✅ | ✅ | -- |
|
||||
| [meizu_en](brands/meizu_en.md) | 魅族 (MEIZU) | 全部机型 | ✅ | ✅ | 英文版 |
|
||||
| [mitv_cn](brands/mitv_cn.md) | 小米 (Xiaomi) | 全部国行小米/Redmi 电视、机顶盒 | ❌ | ❌ | -- |
|
||||
| [mitv_global_en](brands/mitv_global_en.md) | 小米 (Xiaomi) | 全部小米/Redmi 电视、机顶盒、智能电视棒,仅包含海外型号 | ❌ | ✅ | 英文版 |
|
||||
| [motorola_cn](brands/motorola_cn.md) | 摩托罗拉 (Motorola) | 2015 年起上市的机型 | ✅ | ❌ | -- |
|
||||
| [nokia_cn](brands/nokia_cn.md) | 诺基亚 (Nokia) | 2017 年起由 HMD Global 制造的智能手机机型 | ✅ | ❌ | -- |
|
||||
| [nothing](brands/nothing.md) | Nothing | 全部机型 | ✅ | ✅ | 英文版 |
|
||||
| [nubia](brands/nubia.md) | 努比亚 (nubia) | 全部机型 | ❌ | ⏹ | -- |
|
||||
| [oneplus](brands/oneplus.md) | 一加 (OnePlus) | 全部机型 | ✅ | ✅ | -- |
|
||||
| [oneplus_en](brands/oneplus_en.md) | 一加 (OnePlus) | 全部机型 | ✅ | ✅ | 英文版 |
|
||||
| [oppo_cn](brands/oppo_cn.md) | OPPO | 2018 年起新型号命名方式的国行机型 | ⏹ | ❌ | -- |
|
||||
| [oppo_global_en](brands/oppo_global_en.md) | OPPO | 2018 年起上市的海外机型 | ⏹ | ⏹ | 英文版 |
|
||||
| [realme_cn](brands/realme_cn.md) | 真我 (realme) | 全部国行机型 | ⏹ | ❌ | -- |
|
||||
| [realme_global_en](brands/realme_global_en.md) | 真我 (realme) | 全部海外机型 | ⏹ | ✅ | 英文版 |
|
||||
| [samsung_cn](brands/samsung_cn.md) | 三星 (Samsung) | Galaxy S/Note/A/Z/M/C/J/On/Tab/心系天下系列及个别其他机型,仅包含国行型号 | ✅ | ❌ | [其他早期型号参阅此处](misc/early-samsung-models.md) |
|
||||
| [samsung_global_en](brands/samsung_global_en.md) | 三星 (Samsung) | Galaxy S/Note/A/Z/M/F 系列,2019 年起上市的机型 | ✅ | ⏹ | 英文版 |
|
||||
| [smartisan](brands/smartisan.md) | 坚果 (Smartisan) | 全部机型 | ✅ | ❌ | -- |
|
||||
| [sony](brands/sony.md) | 索尼 (SONY) | 2015 年起上市的机型 | ✅ | ✅ | 英文版 |
|
||||
| [sony_cn](brands/sony_cn.md) | 索尼 (SONY) | 2015 年起上市的国行机型 | ✅ | ❌ | -- |
|
||||
| [vivo_cn](brands/vivo_cn.md) | vivo | 2018 年起新型号命名方式的国行机型 | ✅ | ❌ | -- |
|
||||
| [vivo_global_en](brands/vivo_global_en.md) | vivo | 2019 年起上市的海外机型 | ⏹ | ⏹ | 英文版 |
|
||||
| [xiaomi](brands/xiaomi.md) | 小米 (Xiaomi) | 小米/REDMI/POCO 手机 & 平板等 | ✅ | ✅ | -- |
|
||||
| [xiaomi_cn](brands/xiaomi_cn.md) | 小米 (Xiaomi) | 小米/REDMI 手机 & 平板等 | ✅ | ✅ | 英文版 |
|
||||
| [xiaomi_en](brands/xiaomi_en.md) | 小米 (Xiaomi) | 小米/REDMI/POCO 手机 & 平板等 | ✅ | ✅ | 英文版 |
|
||||
| [xiaomi-wear](brands/xiaomi-wear.md) | 小米 (Xiaomi) | 小米/Redmi 手表、手环、TWS 等穿戴设备 | ⏹ | ✅ | TWS 不包含外包型号;暂不含儿童手表型号 |
|
||||
| [zhixuan](brands/zhixuan.md) | 华为智选 | U-MAGIC 优畅享/电信麦芒/NZONE/Hi nova/雷鸟 FFALCON/TD Tech/WIKO | ⏹ | ❌ | -- |
|
||||
| [zte_cn](brands/zte_cn.md) | 中兴 (ZTE) | 2017 年起上市的机型 | ❌ | ❌ | -- |
|
||||
|
||||
## 更新日志
|
||||
|
||||
参见 [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
## 许可
|
||||
|
||||
<a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br />本作品采用 <a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a> 进行许可。
|
||||
|
||||
## 项目历史
|
||||
|
||||
### 2024 年 3 月
|
||||
- 将本项目 csv 及脚本迁移至 [此 repo](https://github.com/KHwang9883/MobileModels-csv),使用 GitHub Actions 自动更新。
|
||||
|
||||
### 2022 年 4 月
|
||||
- 新增 [各大 Android 厂商 BL 解锁/内核开源情况](https://github.com/KHwang9883/bootloader-kernel-source) 汇总(已停更)。
|
||||
|
||||
### 2021 年 12 月
|
||||
- 新增 [各品牌型号命名规则](misc/naming-rules.md) 汇总。
|
||||
|
||||
### 2019 年 7 月
|
||||
- 文档版停止维护。
|
||||
|
||||
### 2019 年 4 月
|
||||
- 文档版迁移至 GitHub 直链下载。
|
||||
- 新增英文版。
|
||||
|
||||
### 2019 年 3 月
|
||||
- 文档版迁移至微云。
|
||||
|
||||
### 2018 年 11 月
|
||||
- 项目同步至 GitHub。
|
||||
|
||||
### 2018 年 7 月
|
||||
- 由于小米社区帖子失效,「手机品牌型号汇总」项目公开发布,提供文档版百度网盘下载。
|
||||
|
||||
### 2016 年 3 月
|
||||
- 小米手机型号汇总发布至 [小米社区](http://bbs.xiaomi.cn/t-12641411)(帖子已失效)。
|
||||
|
||||
### 2016 年 2 月
|
||||
- 我开始汇总一些国内手机品牌的型号,「手机品牌型号汇总」的雏形诞生。
|
||||
|
||||
[](https://starchart.cc/KHwang9883/MobileModels)
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [电信设备终端网](http://zd.taf.org.cn)
|
||||
- [产品认证证书查询](http://webdata.cqccms.com.cn/webdata/query/CCCCerti.do)
|
||||
- [工业和信息化部政务服务平台](https://ythzxfw.miit.gov.cn/resultQuery)
|
||||
- [产品库-中国电信天翼终端信息平台](http://surfing.tydevice.com/)
|
||||
- [Google Play 支持的设备](http://storage.googleapis.com/play_public/supported_devices.html)
|
||||
- [Wi-Fi Alliance](https://www.wi-fi.org)
|
||||
- [Bluetooth Launch Studio](https://launchstudio.bluetooth.com/Listings/Search)
|
||||
- [Xiaomi Firmware Updater](https://xiaomifirmwareupdater.com/)
|
||||
- [Huawei Open Source Release Center](https://consumer.huawei.com/en/opensource/)
|
||||
- [ReaMEIZU](https://reameizu.com/)
|
||||
- [The Apple Wiki](https://theapplewiki.com/)
|
||||
- [ipsw.me](https://ipsw.me)
|
||||
- [XDA Developers](https://www.xda-developers.com)
|
||||
- [Huawei Firmware Database](https://pro-teammt.ru/en/online-firmware-database-ru/)
|
||||
- [XSMS IMEI 数据库](http://xsms.com.ua/phone/imei/all/1)
|
||||
- [Android Dumps](https://dumps.tadiphone.dev/dumps)
|
||||
- [Lenovo Android タブレット一覧](https://idomizu.dev/archives/20150)
|
||||
|
||||
以及各品牌官网、论坛、微博等,恕不一一列出
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有相关问题,请 [提交 Issue](https://github.com/KHwang9883/MobileModels/issues)。如有错误或缺漏,欢迎提交 PR。
|
||||
|
||||
其他平台同名(@KHwang9883),但不一定回复本 repo 相关问题。
|
||||
- [delivery/README.md](delivery/README.md)
|
||||
- [delivery/docs/README.md](delivery/docs/README.md)
|
||||
|
||||
87
README_en.md
87
README_en.md
@@ -1,85 +1,30 @@
|
||||
# Mobile Models
|
||||
# MobileModels Workspace
|
||||
|
||||
[](https://github.com/KHwang9883/MobileModels/issues)
|
||||
[](https://github.com/KHwang9883/MobileModels/pulls)
|
||||
[](https://github.com/KHwang9883/MobileModels)
|
||||
[](https://github.com/KHwang9883/MobileModels)
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
This repository is organized into two layers:
|
||||
|
||||
Collecting device names, models and internal codenames.
|
||||
- `workspace/`
|
||||
- source workspace
|
||||
- upstream raw data, notes, and historical files
|
||||
- `delivery/`
|
||||
- delivery artifact
|
||||
- a ready-to-run Docker Compose project with web UI, MySQL schema, and docs
|
||||
|
||||
[Issue submission](https://github.com/KHwang9883/MobileModels/issues) and [Pull Requests](https://github.com/KHwang9883/MobileModels/pulls) are welcomed if you find mistakes.
|
||||
## Usage
|
||||
|
||||
## Web UI
|
||||
|
||||
The project ships with device query and data management pages and now runs through `docker compose`:
|
||||
Run from the delivery directory:
|
||||
|
||||
```bash
|
||||
cd delivery
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Open:
|
||||
Entry pages:
|
||||
|
||||
- `http://127.0.0.1:8123/web/device_query.html`
|
||||
- `http://127.0.0.1:8123/web/brand_management.html`
|
||||
- `http://127.0.0.1:8123/web/device_query.html?view=docs`
|
||||
|
||||
MySQL is started together with the stack:
|
||||
More delivery details:
|
||||
|
||||
- host: `127.0.0.1`
|
||||
- port: `3306`
|
||||
- database: `mobilemodels`
|
||||
- reader user: `mobilemodels_reader`
|
||||
|
||||
If you want custom MySQL credentials, copy `.env.example` to `.env` before startup.
|
||||
Raw source data, rebuilt indexes, and MySQL seed files are persisted in Docker volumes instead of being written back to the local workspace at runtime.
|
||||
|
||||
More details: [web/README.md](web/README.md)
|
||||
|
||||
Unlisted brands usually not include international models.
|
||||
|
||||
| Name | Brand | Range |
|
||||
| :-: | :-: | :-: |
|
||||
| [apple_all_en](brands/apple_all_en.md) | Apple | iPhone, iPad, iPod touch, Apple Watch, Apple TV and Apple Vision |
|
||||
| [asus_en](brands/asus_en.md) | ASUS | ROG Phone, Zenfone |
|
||||
| [blackshark_en](brands/blackshark_en.md) | Black Shark | All models |
|
||||
| [google](brands/google.md) | Google | Google Pixel phones, tablets & watch |
|
||||
| [honor_global_en](brands/honor_global_en.md) | HONOR | All international models |
|
||||
| [huawei_global_en](brands/huawei_global_en.md) | HUAWEI | HUAWEI Mate, Pura, nova & Y series, MediaPad & MatePad series |
|
||||
| [meizu_en](brands/meizu_en.md) | Meizu | All models |
|
||||
| [mitv_global_en](brands/mitv_global_en.md) | Xiaomi | All international/Indian Xiaomi & Redmi TV models (excluding Chinese models) |
|
||||
| [nothing](brands/nothing.md) | Nothing | All models |
|
||||
| [oneplus_en](brands/oneplus_en.md) | OnePlus | All models |
|
||||
| [oppo_global_en](brands/oppo_global_en.md) | OPPO | International models since 2018 |
|
||||
| [samsung_global_en](brands/samsung_global_en.md) | Samsung | International models since 2019 |
|
||||
| [sony](brands/sony.md) | Sony | All models since 2015 |
|
||||
| [realme_global_en](brands/realme_global_en.md) | realme | All international models |
|
||||
| [vivo_global_en](brands/vivo_global_en.md) | vivo | International models since 2019 |
|
||||
| [xiaomi_en](xiaomi_en.md) | Xiaomi | Xiaomi/Redmi/POCO phones & tablets |
|
||||
|
||||
## Changelog
|
||||
|
||||
[CHANGELOG_en.md](CHANGELOG_en.md)
|
||||
|
||||
## References
|
||||
|
||||
- [TENAA](http://zd.taf.org.cn)
|
||||
- [CQCCMS](http://webdata.cqccms.com.cn/webdata/query/CCCCerti.do)
|
||||
- [MIIT](https://ythzxfw.miit.gov.cn/resultQuery)
|
||||
- [China Telecom Tianyi Devices](http://surfing.tydevice.com/)
|
||||
- [Google Play Supported Devices](http://storage.googleapis.com/play_public/supported_devices.html)
|
||||
- [Wi-Fi Alliance](https://www.wi-fi.org)
|
||||
- [Bluetooth Launch Studio](https://launchstudio.bluetooth.com/Listings/Search)
|
||||
- [Xiaomi Firmware Updater](https://xiaomifirmwareupdater.com/)
|
||||
- [Huawei Open Source Release Center](https://consumer.huawei.com/en/opensource/)
|
||||
- [ReaMEIZU](https://reameizu.com/)
|
||||
- [The Apple Wiki](https://theapplewiki.com/)
|
||||
- [ipsw.me](https://ipsw.me)
|
||||
- [XDA Developers](https://www.xda-developers.com)
|
||||
- [Huawei Firmware Database](https://pro-teammt.ru/en/online-firmware-database-ru/)
|
||||
- [XSMS IMEI Database](http://xsms.com.ua/phone/imei/all/1)
|
||||
- [Android Dumps](https://dumps.tadiphone.dev/dumps)
|
||||
- [Lenovo Android タブレット一覧](https://idomizu.dev/archives/20150)
|
||||
|
||||
## License
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License</a>.
|
||||
- [delivery/README.md](delivery/README.md)
|
||||
- [delivery/docs/README.md](delivery/docs/README.md)
|
||||
|
||||
@@ -9,7 +9,10 @@ RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends git ca-certificates default-mysql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . /app
|
||||
COPY workspace /app/workspace
|
||||
COPY delivery /app/delivery
|
||||
|
||||
WORKDIR /app/delivery
|
||||
|
||||
EXPOSE 8123
|
||||
|
||||
98
delivery/README.md
Normal file
98
delivery/README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 手机品牌型号汇总
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
这是交付目录,提供可直接运行的 Docker Compose 项目。
|
||||
|
||||
项目目标:
|
||||
|
||||
- 提供可直接交付的设备查询与数据管理系统
|
||||
- 通过容器内流程同步上游原始 git 数据
|
||||
- 在容器内重建索引并刷新 MySQL
|
||||
- 形成可独立部署的交付包
|
||||
|
||||
[English](README_en.md)
|
||||
|
||||
## 运行方式
|
||||
|
||||
在当前目录执行:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
打开:
|
||||
|
||||
- `http://127.0.0.1:8123/web/device_query.html`
|
||||
- `http://127.0.0.1:8123/web/brand_management.html`
|
||||
|
||||
MySQL 也会一并启动:
|
||||
|
||||
- host: `127.0.0.1`
|
||||
- port: `3306`
|
||||
- database: `mobilemodels`
|
||||
- reader user: `mobilemodels_reader`
|
||||
|
||||
如需自定义 MySQL 账号密码,可先复制 `.env.example` 为 `.env` 后再启动。
|
||||
|
||||
## 结构说明
|
||||
|
||||
```text
|
||||
dist/ 构建产物与 MySQL seed
|
||||
docs/ 交付文档
|
||||
sql/ MySQL schema
|
||||
tools/ 容器内构建与同步脚本
|
||||
web/ 页面与静态资源
|
||||
```
|
||||
|
||||
上游原始数据不放在本目录,而是从仓库根目录下的 `workspace/` 读取。
|
||||
|
||||
容器启动后会自动完成:
|
||||
|
||||
- 初始化 `workspace/` 与 `dist/` 的运行期数据目录
|
||||
- 从 `workspace/brands` 构建设备索引
|
||||
- 导出 MySQL seed
|
||||
- 加载 MySQL schema 与 seed
|
||||
- 启动 Web 页面与 API
|
||||
|
||||
更多说明参见:
|
||||
|
||||
- [docs/README.md](docs/README.md)
|
||||
- [docs/web-ui.md](docs/web-ui.md)
|
||||
- [docs/mysql-query-design.md](docs/mysql-query-design.md)
|
||||
- [docs/device-mapper.md](docs/device-mapper.md)
|
||||
## 工作空间
|
||||
|
||||
原始数据与补充资料位于仓库根目录下的 `workspace/`:
|
||||
|
||||
- `../workspace/brands`
|
||||
- `../workspace/misc`
|
||||
- `../workspace/CHANGELOG.md`
|
||||
- `../workspace/CHANGELOG_en.md`
|
||||
|
||||
### 2016 年 3 月
|
||||
- 小米手机型号汇总发布至 [小米社区](http://bbs.xiaomi.cn/t-12641411)(帖子已失效)。
|
||||
|
||||
### 2016 年 2 月
|
||||
- 我开始汇总一些国内手机品牌的型号,「手机品牌型号汇总」的雏形诞生。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [电信设备终端网](http://zd.taf.org.cn)
|
||||
- [产品认证证书查询](http://webdata.cqccms.com.cn/webdata/query/CCCCerti.do)
|
||||
- [工业和信息化部政务服务平台](https://ythzxfw.miit.gov.cn/resultQuery)
|
||||
- [产品库-中国电信天翼终端信息平台](http://surfing.tydevice.com/)
|
||||
- [Google Play 支持的设备](http://storage.googleapis.com/play_public/supported_devices.html)
|
||||
- [Wi-Fi Alliance](https://www.wi-fi.org)
|
||||
- [Bluetooth Launch Studio](https://launchstudio.bluetooth.com/Listings/Search)
|
||||
- [Xiaomi Firmware Updater](https://xiaomifirmwareupdater.com/)
|
||||
- [Huawei Open Source Release Center](https://consumer.huawei.com/en/opensource/)
|
||||
- [ReaMEIZU](https://reameizu.com/)
|
||||
- [The Apple Wiki](https://theapplewiki.com/)
|
||||
- [ipsw.me](https://ipsw.me)
|
||||
- [XDA Developers](https://www.xda-developers.com)
|
||||
- [Huawei Firmware Database](https://pro-teammt.ru/en/online-firmware-database-ru/)
|
||||
- [XSMS IMEI 数据库](http://xsms.com.ua/phone/imei/all/1)
|
||||
- [Android Dumps](https://dumps.tadiphone.dev/dumps)
|
||||
- [Lenovo Android タブレット一覧](https://idomizu.dev/archives/20150)
|
||||
|
||||
以及各品牌官网、论坛、微博等,恕不一一列出
|
||||
61
delivery/README_en.md
Normal file
61
delivery/README_en.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Mobile Models
|
||||
[](https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
This is the delivery directory and contains the ready-to-run Docker Compose project.
|
||||
|
||||
Project goals:
|
||||
|
||||
- provide a ready-to-deliver device query system
|
||||
- sync upstream raw git data inside containers
|
||||
- rebuild index and refresh MySQL inside containers
|
||||
- keep deployment content independent from workspace files
|
||||
|
||||
## Run
|
||||
|
||||
The project ships with device query and data management pages and now runs through `docker compose`:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
- `http://127.0.0.1:8123/web/device_query.html`
|
||||
- `http://127.0.0.1:8123/web/brand_management.html`
|
||||
|
||||
MySQL is started together with the stack:
|
||||
|
||||
- host: `127.0.0.1`
|
||||
- port: `3306`
|
||||
- database: `mobilemodels`
|
||||
- reader user: `mobilemodels_reader`
|
||||
|
||||
If you want custom MySQL credentials, copy `.env.example` to `.env` before startup.
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
dist/ build outputs and MySQL seed
|
||||
docs/ delivery docs
|
||||
sql/ MySQL schema
|
||||
tools/ container-side build and sync scripts
|
||||
web/ delivered web pages
|
||||
```
|
||||
|
||||
Raw source files are read from the repository root `workspace/` directory.
|
||||
|
||||
More details:
|
||||
|
||||
- [docs/README.md](docs/README.md)
|
||||
- [docs/web-ui.md](docs/web-ui.md)
|
||||
- [docs/mysql-query-design.md](docs/mysql-query-design.md)
|
||||
- [docs/device-mapper.md](docs/device-mapper.md)
|
||||
|
||||
## Workspace
|
||||
|
||||
Raw data and historical files live under the repository root `workspace/`:
|
||||
|
||||
- `../workspace/brands`
|
||||
- `../workspace/misc`
|
||||
- `../workspace/CHANGELOG.md`
|
||||
- `../workspace/CHANGELOG_en.md`
|
||||
@@ -23,10 +23,10 @@ services:
|
||||
|
||||
mobilemodels:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
context: ..
|
||||
dockerfile: delivery/Dockerfile
|
||||
container_name: mobilemodels-web
|
||||
working_dir: /app
|
||||
working_dir: /app/delivery
|
||||
environment:
|
||||
MOBILEMODELS_DATA_ROOT: /data
|
||||
MYSQL_HOST: mysql
|
||||
18
delivery/docs/README.md
Normal file
18
delivery/docs/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 项目文档
|
||||
|
||||
交付版文档统一收敛到本目录,便于部署、培训和对外交付。
|
||||
|
||||
## 文档索引
|
||||
|
||||
- [部署与使用说明](web-ui.md)
|
||||
- [MySQL 设计说明](mysql-query-design.md)
|
||||
- [索引构建与设备映射说明](device-mapper.md)
|
||||
|
||||
## 目录说明
|
||||
|
||||
- `web-ui.md`
|
||||
- Docker Compose 启动、页面入口、MySQL 连接和管理能力说明
|
||||
- `mysql-query-design.md`
|
||||
- 主表设计、兼容视图、推荐查询方式
|
||||
- `device-mapper.md`
|
||||
- `dist/device_index.json` 构建方式与索引字段说明
|
||||
57
delivery/docs/device-mapper.md
Normal file
57
delivery/docs/device-mapper.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Device Mapper Usage
|
||||
|
||||
This tool builds a cross-platform lookup index from `workspace/brands/*.md`.
|
||||
|
||||
## 1) Build index
|
||||
|
||||
```bash
|
||||
python3 tools/device_mapper.py build
|
||||
```
|
||||
|
||||
Output file: `dist/device_index.json`
|
||||
|
||||
## 2) Query from command line
|
||||
|
||||
```bash
|
||||
python3 tools/device_mapper.py find --name 'iPhone14,5' --brand Apple
|
||||
python3 tools/device_mapper.py find --name 'M2102J2SC' --brand Xiaomi
|
||||
python3 tools/device_mapper.py find --name 'L55M5-AD' --brand Xiaomi
|
||||
```
|
||||
|
||||
## 3) JSON structure
|
||||
|
||||
- `records`: normalized device records
|
||||
- `device_name`: standard marketing name
|
||||
- `brand`: normalized brand
|
||||
- `manufacturer_brand`: manufacturer-level brand
|
||||
- `market_brand`: market sub-brand
|
||||
- `device_type`: `phone | tablet | wear | tv | other`
|
||||
- `aliases`: all searchable aliases
|
||||
- `lookup`: normalized alias -> candidate `record.id[]`
|
||||
- `brand_aliases`: normalized brand aliases to filter by app-provided brand
|
||||
- `brand_management`: brand governance metadata
|
||||
|
||||
## 4) App-side integration
|
||||
|
||||
1. Load `dist/device_index.json` into memory.
|
||||
2. Normalize input `name` and optional `brand`.
|
||||
3. Use `lookup[normalized_name]` to fetch candidate records.
|
||||
4. Normalize brand via `brand_management`.
|
||||
5. Filter records by normalized manufacturer or market brand when needed.
|
||||
6. Return first candidate or all candidates.
|
||||
|
||||
Normalization rule:
|
||||
|
||||
- lower-case
|
||||
- keep only `[0-9a-z\u4e00-\u9fff]`
|
||||
- remove spaces, hyphens, underscores and punctuation
|
||||
|
||||
## 5) Device type mapping
|
||||
|
||||
Supported categories:
|
||||
|
||||
- `phone`
|
||||
- `tablet`
|
||||
- `wear`
|
||||
- `tv`
|
||||
- `other`
|
||||
142
delivery/docs/mysql-query-design.md
Normal file
142
delivery/docs/mysql-query-design.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# MySQL 设计说明
|
||||
|
||||
本文档说明交付版 MobileModels 的 MySQL 数据组织方式、兼容层设计与推荐查询方式。
|
||||
|
||||
## 设计目标
|
||||
|
||||
- 所有设备标识都能落到 MySQL 查询
|
||||
- 支持第三方直接查库,保证查询速度
|
||||
- 保留兼容旧结构的访问方式
|
||||
- 页面侧和 SQL 接入侧统一使用同一份设备数据
|
||||
|
||||
## 主表
|
||||
|
||||
主推物理表:
|
||||
|
||||
```sql
|
||||
mobilemodels.mm_device_catalog
|
||||
```
|
||||
|
||||
主表整合了设备型号、品牌、厂商、来源、别名归一化结果和兼容字段,适合作为统一查询入口。
|
||||
|
||||
### 关键字段
|
||||
|
||||
- `model`
|
||||
- `record_id`
|
||||
- `alias_norm`
|
||||
- `device_name`
|
||||
- `brand`
|
||||
- `manufacturer_brand`
|
||||
- `parent_brand`
|
||||
- `market_brand`
|
||||
- `device_type`
|
||||
- `source_file`
|
||||
- `section`
|
||||
- `source_rank`
|
||||
- `source_weight`
|
||||
- `code`
|
||||
- `code_alias`
|
||||
- `ver_name`
|
||||
|
||||
## 推荐查询方式
|
||||
|
||||
### 1. 第三方直接查主表
|
||||
|
||||
推荐按 `alias_norm` 等值查询:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
model,
|
||||
record_id,
|
||||
alias_norm,
|
||||
device_name,
|
||||
brand,
|
||||
manufacturer_brand,
|
||||
parent_brand,
|
||||
market_brand,
|
||||
device_type,
|
||||
source_file,
|
||||
section,
|
||||
source_rank,
|
||||
source_weight,
|
||||
code,
|
||||
code_alias,
|
||||
ver_name
|
||||
FROM mobilemodels.mm_device_catalog
|
||||
WHERE alias_norm = ?
|
||||
ORDER BY source_rank ASC, record_id ASC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 2. 页面 SQL 查询
|
||||
|
||||
页面的 `SQL 查询` tab 也是基于这张主表。
|
||||
|
||||
查询流程:
|
||||
|
||||
1. 接收客户端原始上报值
|
||||
2. 服务端归一化为 `alias_norm`
|
||||
3. 按主表等值查询
|
||||
4. 返回结果列表、执行 SQL 和 JSON 输出
|
||||
|
||||
## 兼容视图
|
||||
|
||||
为了兼容旧系统,当前仍保留以下视图:
|
||||
|
||||
```sql
|
||||
mobilemodels.mm_device_lookup
|
||||
mobilemodels.mm_device_record
|
||||
mobilemodels.models
|
||||
python_services_test.models
|
||||
```
|
||||
|
||||
其中旧结构 `python_services_test.models` 主要用于兼容既有查询逻辑,不再作为主推接入方式。
|
||||
|
||||
## 兼容旧结构查询示例
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
model,
|
||||
dtype,
|
||||
brand,
|
||||
brand_title,
|
||||
code,
|
||||
code_alias,
|
||||
model_name,
|
||||
ver_name
|
||||
FROM python_services_test.models
|
||||
WHERE model = ?
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
## 归一化规则
|
||||
|
||||
`alias_norm` 统一按以下规则生成:
|
||||
|
||||
- 全部转小写
|
||||
- 仅保留 `[0-9a-z中文]`
|
||||
- 去掉空格、横线、下划线和其他标点
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
SM-G9980 -> smg9980
|
||||
iPhone14,2 -> iphone142
|
||||
NOH-AL00 -> nohal00
|
||||
```
|
||||
|
||||
## 数据来源
|
||||
|
||||
主表和索引数据均由以下流程生成:
|
||||
|
||||
1. 同步上游原始 markdown 数据
|
||||
2. 解析 `workspace/brands/*.md`
|
||||
3. 构建 `dist/device_index.json`
|
||||
4. 导出 `dist/mobilemodels_mysql_seed.sql`
|
||||
5. 加载 MySQL schema 与 seed
|
||||
|
||||
## 交付建议
|
||||
|
||||
- 第三方新接入优先使用 `mm_device_catalog`
|
||||
- 页面联调和数据库联调使用同一套原始数据与归一化规则
|
||||
- 生产环境务必替换默认数据库密码
|
||||
101
delivery/docs/web-ui.md
Normal file
101
delivery/docs/web-ui.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Web UI
|
||||
|
||||
## 启动方式
|
||||
|
||||
在 `delivery/` 目录执行:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
如需自定义环境变量:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
停止服务:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
重置 MySQL 和运行期数据:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## 页面入口
|
||||
|
||||
- `http://127.0.0.1:8123/web/device_query.html`:设备查询
|
||||
- `http://127.0.0.1:8123/web/brand_management.html`:数据管理
|
||||
- `http://127.0.0.1:8123/web/device_query.html?view=docs`:相关文档
|
||||
|
||||
整个功能栈统一运行在 Docker Compose 中,不再依赖本地 Python 或本地 MySQL。
|
||||
|
||||
原始数据工作空间位于仓库根目录下的 `workspace/`,交付物位于 `delivery/`。
|
||||
|
||||
## 启动后自动完成的动作
|
||||
|
||||
- 从 `workspace/brands` 构建设备索引
|
||||
- 生成 `dist/device_index.json`
|
||||
- 导出 MySQL seed 文件
|
||||
- 加载 MySQL schema 与 seed 数据
|
||||
- 启动 Web 页面与 API 服务
|
||||
|
||||
## MySQL 默认连接
|
||||
|
||||
- Host: `127.0.0.1`
|
||||
- Port: `3306`
|
||||
- Database: `mobilemodels`
|
||||
- Reader User: `mobilemodels_reader`
|
||||
|
||||
如需自定义账号密码,请使用 `.env` 覆盖默认值。
|
||||
|
||||
## 设备查询
|
||||
|
||||
页面顶部统一提供三个导航入口:
|
||||
|
||||
- `设备查询`
|
||||
- `数据管理`
|
||||
- `相关文档`
|
||||
|
||||
设备查询页顶部包含两个页内 tab:
|
||||
|
||||
- `SQL 查询`
|
||||
- `索引查询`
|
||||
|
||||
### SQL 查询
|
||||
|
||||
- 直接调用 Compose 内 API 查询 MySQL 主表 `mobilemodels.mm_device_catalog`
|
||||
- 服务端先将输入归一化为 `alias_norm`
|
||||
- 页面展示实际执行的 SQL、返回结果和 JSON
|
||||
- 页面同时展示只读连接信息,便于第三方联调
|
||||
|
||||
### 索引查询
|
||||
|
||||
- 基于 `dist/device_index.json` 内存索引进行快速识别
|
||||
- 适合前端联调、接口对比和结果核验
|
||||
|
||||
### 平台输入建议
|
||||
|
||||
- Android / iOS / HarmonyOS:直接使用客户端原始上报的 `model_raw`
|
||||
- 输入框会根据所选平台自动提供示例值
|
||||
- 未输入时,系统会使用当前平台的默认示例值发起查询
|
||||
|
||||
## 数据管理
|
||||
|
||||
数据管理页支持:
|
||||
|
||||
- 品牌列表管理
|
||||
- 品牌与厂商关系管理
|
||||
- 品牌同义词管理
|
||||
- 数据来源优先级管理
|
||||
- 原始数据同步
|
||||
- 索引数据查看与重新加载
|
||||
|
||||
## 说明
|
||||
|
||||
- 原始数据、索引和 MySQL seed 运行时持久化在 Docker volume 中,不回写本地工作区
|
||||
- 交付环境建议覆盖默认的 `MYSQL_ROOT_PASSWORD` 和 `MYSQL_READER_PASSWORD`
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
cd /app
|
||||
cd /app/delivery
|
||||
|
||||
sh tools/init_runtime_data.sh
|
||||
|
||||
@@ -12,6 +12,8 @@ from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Optional, Set
|
||||
|
||||
from project_layout import DELIVERY_ROOT, WORKSPACE_ROOT
|
||||
|
||||
ENTRY_RE = re.compile(r"^\*\*(.+?)\*\*\s*$")
|
||||
VARIANT_RE = re.compile(r"^\s*((?:`[^`]+`\s*)+):\s*(.+?)\s*$")
|
||||
BACKTICK_RE = re.compile(r"`([^`]+)`")
|
||||
@@ -714,8 +716,8 @@ def main() -> None:
|
||||
parser.add_argument(
|
||||
"--repo-root",
|
||||
type=Path,
|
||||
default=Path(__file__).resolve().parents[1],
|
||||
help="Path to MobileModels repository root",
|
||||
default=WORKSPACE_ROOT,
|
||||
help="Path to workspace root",
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
@@ -741,7 +743,7 @@ def main() -> None:
|
||||
if args.command == "build":
|
||||
output_path: Path = args.output
|
||||
if not output_path.is_absolute():
|
||||
output_path = args.repo_root / output_path
|
||||
output_path = DELIVERY_ROOT / output_path
|
||||
export_index(records, output_path)
|
||||
print(f"Built index: {output_path}")
|
||||
print(f"Total records: {len(records)}")
|
||||
@@ -16,9 +16,9 @@ from device_mapper import (
|
||||
normalize_text,
|
||||
resolve_parent_brand,
|
||||
)
|
||||
from project_layout import DELIVERY_ROOT, WORKSPACE_ROOT
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
LEGACY_CODE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9,._/+\\-]{1,63}$")
|
||||
|
||||
|
||||
@@ -185,8 +185,8 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--repo-root",
|
||||
type=Path,
|
||||
default=REPO_ROOT,
|
||||
help="Path to MobileModels repository root",
|
||||
default=WORKSPACE_ROOT,
|
||||
help="Path to workspace root",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
@@ -200,7 +200,7 @@ def parse_args() -> argparse.Namespace:
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
repo_root = args.repo_root.resolve()
|
||||
output_path = args.output if args.output.is_absolute() else repo_root / args.output
|
||||
output_path = args.output if args.output.is_absolute() else DELIVERY_ROOT / args.output
|
||||
|
||||
records = build_records(repo_root)
|
||||
device_record_count = len(records)
|
||||
@@ -60,14 +60,8 @@ init_path() {
|
||||
}
|
||||
|
||||
for rel_path in \
|
||||
brands \
|
||||
misc \
|
||||
dist \
|
||||
README.md \
|
||||
README_en.md \
|
||||
CHANGELOG.md \
|
||||
CHANGELOG_en.md \
|
||||
LICENSE.txt
|
||||
workspace \
|
||||
delivery/dist
|
||||
do
|
||||
init_path "$rel_path"
|
||||
done
|
||||
@@ -10,8 +10,7 @@ import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
from project_layout import DELIVERY_ROOT
|
||||
|
||||
|
||||
def mysql_env(password: str) -> dict[str, str]:
|
||||
@@ -131,8 +130,8 @@ def parse_args() -> argparse.Namespace:
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
schema_path = args.schema if args.schema.is_absolute() else REPO_ROOT / args.schema
|
||||
seed_path = args.seed if args.seed.is_absolute() else REPO_ROOT / args.seed
|
||||
schema_path = args.schema if args.schema.is_absolute() else DELIVERY_ROOT / args.schema
|
||||
seed_path = args.seed if args.seed.is_absolute() else DELIVERY_ROOT / args.seed
|
||||
|
||||
wait_for_mysql(args.user, args.password, args.host, args.port, args.wait_timeout)
|
||||
|
||||
11
delivery/tools/project_layout.py
Normal file
11
delivery/tools/project_layout.py
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared path helpers for the workspace/delivery project layout."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DELIVERY_ROOT = Path(__file__).resolve().parent.parent
|
||||
PROJECT_ROOT = DELIVERY_ROOT.parent
|
||||
WORKSPACE_ROOT = PROJECT_ROOT / "workspace"
|
||||
@@ -11,15 +11,13 @@ import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from project_layout import DELIVERY_ROOT, PROJECT_ROOT, WORKSPACE_ROOT
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_REPO_URL = "https://github.com/KHwang9883/MobileModels.git"
|
||||
DEFAULT_BRANCH = "master"
|
||||
SYNC_PATHS = [
|
||||
"brands",
|
||||
"misc",
|
||||
"README.md",
|
||||
"README_en.md",
|
||||
"CHANGELOG.md",
|
||||
"CHANGELOG_en.md",
|
||||
"LICENSE.txt",
|
||||
@@ -27,7 +25,7 @@ SYNC_PATHS = [
|
||||
|
||||
|
||||
def run(cmd: list[str], cwd: Path | None = None) -> None:
|
||||
subprocess.run(cmd, cwd=cwd or REPO_ROOT, check=True)
|
||||
subprocess.run(cmd, cwd=cwd or PROJECT_ROOT, check=True)
|
||||
|
||||
|
||||
def remove_path(path: Path) -> None:
|
||||
@@ -59,7 +57,7 @@ def sync_path(src: Path, dst: Path) -> None:
|
||||
def sync_selected_paths(upstream_root: Path) -> None:
|
||||
for relative_path in SYNC_PATHS:
|
||||
src = upstream_root / relative_path
|
||||
dst = REPO_ROOT / relative_path
|
||||
dst = WORKSPACE_ROOT / relative_path
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(f"Missing upstream path: {relative_path}")
|
||||
sync_path(src, dst)
|
||||
@@ -69,7 +67,9 @@ def build_index(output_path: str) -> None:
|
||||
run(
|
||||
[
|
||||
sys.executable,
|
||||
str(REPO_ROOT / "tools/device_mapper.py"),
|
||||
str(DELIVERY_ROOT / "tools/device_mapper.py"),
|
||||
"--repo-root",
|
||||
str(WORKSPACE_ROOT),
|
||||
"build",
|
||||
"--output",
|
||||
output_path,
|
||||
@@ -81,9 +81,11 @@ def export_mysql_seed(output_path: str) -> None:
|
||||
run(
|
||||
[
|
||||
sys.executable,
|
||||
str(REPO_ROOT / "tools/export_mysql_seed.py"),
|
||||
str(DELIVERY_ROOT / "tools/export_mysql_seed.py"),
|
||||
"--output",
|
||||
output_path,
|
||||
"--repo-root",
|
||||
str(WORKSPACE_ROOT),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -92,7 +94,7 @@ def load_mysql_seed(seed_path: str) -> None:
|
||||
run(
|
||||
[
|
||||
sys.executable,
|
||||
str(REPO_ROOT / "tools/load_mysql_seed.py"),
|
||||
str(DELIVERY_ROOT / "tools/load_mysql_seed.py"),
|
||||
"--seed",
|
||||
seed_path,
|
||||
]
|
||||
@@ -14,13 +14,14 @@ from http import HTTPStatus
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
|
||||
from sync_upstream_mobilemodels import DEFAULT_BRANCH, DEFAULT_REPO_URL, REPO_ROOT
|
||||
from project_layout import DELIVERY_ROOT, PROJECT_ROOT, WORKSPACE_ROOT
|
||||
from sync_upstream_mobilemodels import DEFAULT_BRANCH, DEFAULT_REPO_URL
|
||||
|
||||
|
||||
SYNC_SCRIPT = REPO_ROOT / "tools/sync_upstream_mobilemodels.py"
|
||||
INDEX_PATH = REPO_ROOT / "dist/device_index.json"
|
||||
MYSQL_SEED_PATH = REPO_ROOT / "dist/mobilemodels_mysql_seed.sql"
|
||||
MYSQL_LOADER = REPO_ROOT / "tools/load_mysql_seed.py"
|
||||
SYNC_SCRIPT = DELIVERY_ROOT / "tools/sync_upstream_mobilemodels.py"
|
||||
INDEX_PATH = DELIVERY_ROOT / "dist/device_index.json"
|
||||
MYSQL_SEED_PATH = DELIVERY_ROOT / "dist/mobilemodels_mysql_seed.sql"
|
||||
MYSQL_LOADER = DELIVERY_ROOT / "tools/load_mysql_seed.py"
|
||||
DATA_ROOT = Path(os.environ.get("MOBILEMODELS_DATA_ROOT", "/data"))
|
||||
SYNC_METADATA_PATH = DATA_ROOT / "state/sync_status.json"
|
||||
SYNC_LOCK = threading.Lock()
|
||||
@@ -30,7 +31,7 @@ NORMALIZE_RE = re.compile(r"[^0-9a-z\u4e00-\u9fff]+")
|
||||
def run_command(args: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=REPO_ROOT,
|
||||
cwd=PROJECT_ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
@@ -192,15 +193,17 @@ def get_status_payload() -> dict[str, object]:
|
||||
return {
|
||||
"supports_upstream_sync": True,
|
||||
"storage_mode": "docker_volume",
|
||||
"repo_root": str(REPO_ROOT),
|
||||
"project_root": str(PROJECT_ROOT),
|
||||
"workspace_root": str(WORKSPACE_ROOT),
|
||||
"delivery_root": str(DELIVERY_ROOT),
|
||||
"data_root": str(DATA_ROOT),
|
||||
"upstream_repo_url": DEFAULT_REPO_URL,
|
||||
"upstream_branch": DEFAULT_BRANCH,
|
||||
"last_sync_time": sync_metadata.get("last_sync_time"),
|
||||
"last_upstream_commit": sync_metadata.get("last_upstream_commit"),
|
||||
"index_file": str(INDEX_PATH.relative_to(REPO_ROOT)),
|
||||
"index_file": str(INDEX_PATH.relative_to(DELIVERY_ROOT)),
|
||||
"index_mtime": index_mtime,
|
||||
"mysql_seed_file": str(MYSQL_SEED_PATH.relative_to(REPO_ROOT)),
|
||||
"mysql_seed_file": str(MYSQL_SEED_PATH.relative_to(DELIVERY_ROOT)),
|
||||
"mysql_seed_mtime": mysql_seed_mtime,
|
||||
"mysql_host": mysql_host,
|
||||
"mysql_port": mysql_port,
|
||||
@@ -240,18 +243,20 @@ def run_upstream_sync() -> dict[str, object]:
|
||||
|
||||
payload = {
|
||||
"storage_mode": "docker_volume",
|
||||
"repo_root": str(REPO_ROOT),
|
||||
"project_root": str(PROJECT_ROOT),
|
||||
"workspace_root": str(WORKSPACE_ROOT),
|
||||
"delivery_root": str(DELIVERY_ROOT),
|
||||
"data_root": str(DATA_ROOT),
|
||||
"upstream_repo_url": DEFAULT_REPO_URL,
|
||||
"upstream_branch": DEFAULT_BRANCH,
|
||||
"upstream_commit": upstream_commit,
|
||||
"last_sync_time": datetime.now().isoformat(timespec="seconds"),
|
||||
"last_upstream_commit": upstream_commit,
|
||||
"index_file": str(INDEX_PATH.relative_to(REPO_ROOT)),
|
||||
"index_file": str(INDEX_PATH.relative_to(DELIVERY_ROOT)),
|
||||
"index_mtime": datetime.fromtimestamp(INDEX_PATH.stat().st_mtime).isoformat(timespec="seconds")
|
||||
if INDEX_PATH.exists()
|
||||
else None,
|
||||
"mysql_seed_file": str(MYSQL_SEED_PATH.relative_to(REPO_ROOT)),
|
||||
"mysql_seed_file": str(MYSQL_SEED_PATH.relative_to(DELIVERY_ROOT)),
|
||||
"mysql_seed_mtime": datetime.fromtimestamp(MYSQL_SEED_PATH.stat().st_mtime).isoformat(timespec="seconds")
|
||||
if MYSQL_SEED_PATH.exists()
|
||||
else None,
|
||||
@@ -270,7 +275,7 @@ def run_upstream_sync() -> dict[str, object]:
|
||||
|
||||
class MobileModelsHandler(SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=str(REPO_ROOT), **kwargs)
|
||||
super().__init__(*args, directory=str(DELIVERY_ROOT), **kwargs)
|
||||
|
||||
def guess_type(self, path: str) -> str:
|
||||
content_type = super().guess_type(path)
|
||||
@@ -518,7 +518,9 @@
|
||||
const lines = [];
|
||||
if (fallbackTitle) lines.push(fallbackTitle);
|
||||
if (data.data_root) lines.push(`数据目录: ${data.data_root}`);
|
||||
if (data.repo_root) lines.push(`应用目录: ${data.repo_root}`);
|
||||
if (data.project_root) lines.push(`项目目录: ${data.project_root}`);
|
||||
if (data.workspace_root) lines.push(`工作空间目录: ${data.workspace_root}`);
|
||||
if (data.delivery_root) lines.push(`交付目录: ${data.delivery_root}`);
|
||||
if (data.storage_mode) lines.push(`存储模式: ${data.storage_mode}`);
|
||||
if (data.upstream_repo_url) lines.push(`上游仓库: ${data.upstream_repo_url}`);
|
||||
if (data.upstream_branch) lines.push(`上游分支: ${data.upstream_branch}`);
|
||||
@@ -694,15 +694,15 @@
|
||||
<h1 class="title">相关文档</h1>
|
||||
<p class="sub">这里统一整理页面调试、MySQL 接入和兼容查询相关说明,直接在当前页面查看,不再跳转到单独页面。</p>
|
||||
<div class="doc-link-list">
|
||||
<button type="button" class="doc-link active" data-doc-path="/misc/mysql-query-design.md" data-doc-title="MySQL 设计说明">MySQL 设计说明</button>
|
||||
<button type="button" class="doc-link" data-doc-path="/web/README.md" data-doc-title="Web 使用说明">Web 使用说明</button>
|
||||
<button type="button" class="doc-link active" data-doc-path="/docs/mysql-query-design.md" data-doc-title="MySQL 设计说明">MySQL 设计说明</button>
|
||||
<button type="button" class="doc-link" data-doc-path="/docs/web-ui.md" data-doc-title="Web 使用说明">Web 使用说明</button>
|
||||
<button type="button" class="doc-link" data-doc-path="/README.md" data-doc-title="项目 README">项目 README</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h2 class="title" id="docPanelTitle">MySQL 设计说明</h2>
|
||||
<p class="sub" id="docPanelPath">/misc/mysql-query-design.md</p>
|
||||
<p class="sub" id="docPanelPath">/docs/mysql-query-design.md</p>
|
||||
<pre id="docPanelContent">加载中...</pre>
|
||||
</article>
|
||||
|
||||
@@ -1880,7 +1880,7 @@ LIMIT 20;</pre>
|
||||
syncReportPlatformUI();
|
||||
syncSqlPlatformUI();
|
||||
loadReadonlyInfo();
|
||||
loadDocInPanel("/misc/mysql-query-design.md", "MySQL 设计说明");
|
||||
loadDocInPanel("/docs/mysql-query-design.md", "MySQL 设计说明");
|
||||
loadIndexFromPath();
|
||||
</script>
|
||||
</body>
|
||||
@@ -126,8 +126,8 @@
|
||||
<h1 class="title" id="docTitle">文档查看</h1>
|
||||
<p class="sub" id="docPath">正在加载文档...</p>
|
||||
<div class="btns">
|
||||
<a class="btn" href="/web/doc_viewer.html?path=/misc/mysql-query-design.md&title=MySQL%20%E8%AE%BE%E8%AE%A1%E8%AF%B4%E6%98%8E">MySQL 设计说明</a>
|
||||
<a class="btn" href="/web/doc_viewer.html?path=/web/README.md&title=Web%20%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E">Web 使用说明</a>
|
||||
<a class="btn" href="/web/doc_viewer.html?path=/docs/mysql-query-design.md&title=MySQL%20%E8%AE%BE%E8%AE%A1%E8%AF%B4%E6%98%8E">MySQL 设计说明</a>
|
||||
<a class="btn" href="/web/doc_viewer.html?path=/docs/web-ui.md&title=Web%20%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E">Web 使用说明</a>
|
||||
<a class="btn" href="/web/doc_viewer.html?path=/README.md&title=%E9%A1%B9%E7%9B%AE%20README">项目 README</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -139,14 +139,14 @@
|
||||
|
||||
<script>
|
||||
const ALLOWED_DOCS = new Map([
|
||||
["/misc/mysql-query-design.md", "MySQL 设计说明"],
|
||||
["/web/README.md", "Web 使用说明"],
|
||||
["/docs/mysql-query-design.md", "MySQL 设计说明"],
|
||||
["/docs/web-ui.md", "Web 使用说明"],
|
||||
["/README.md", "项目 README"],
|
||||
]);
|
||||
|
||||
async function main() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const path = params.get("path") || "/misc/mysql-query-design.md";
|
||||
const path = params.get("path") || "/docs/mysql-query-design.md";
|
||||
const title = params.get("title") || ALLOWED_DOCS.get(path) || "文档查看";
|
||||
const docTitleEl = document.getElementById("docTitle");
|
||||
const docPathEl = document.getElementById("docPath");
|
||||
@@ -1,372 +0,0 @@
|
||||
# MySQL Query Design
|
||||
|
||||
## Goal
|
||||
|
||||
让三方直接查 MySQL,同时保持查询简单、稳定、足够快。
|
||||
|
||||
当前数据规模:
|
||||
|
||||
- `device_record`: 6219
|
||||
- `lookup key`: 23463
|
||||
|
||||
这个量级不需要分库分表,也不需要 ES。MySQL 8 + 合适索引就足够。
|
||||
|
||||
## Overall Design
|
||||
|
||||
建议把 MySQL 拆成两层角色:
|
||||
|
||||
1. 离线构建层
|
||||
- 继续以 `brands/*.md` 为原始数据。
|
||||
- 通过 `tools/device_mapper.py` 生成标准化记录。
|
||||
- 通过 `tools/export_mysql_seed.py` 导出 MySQL seed SQL。
|
||||
2. 查询服务层
|
||||
- 三方只读查询 MySQL。
|
||||
- 只开放读账号,只允许 `SELECT`。
|
||||
- 最好挂在只读实例或只读副本上,不直接连主库。
|
||||
|
||||
## Tables
|
||||
|
||||
### `mm_device_record`
|
||||
|
||||
兼容视图,一条设备一行,来自 `mm_device_catalog` 聚合。
|
||||
|
||||
主要字段:
|
||||
|
||||
- `record_id`
|
||||
- `device_name`
|
||||
- `brand`
|
||||
- `manufacturer_brand`
|
||||
- `parent_brand`
|
||||
- `market_brand`
|
||||
- `device_type`
|
||||
- `source_file`
|
||||
- `section`
|
||||
- `source_rank`
|
||||
- `source_weight`
|
||||
- `aliases_json`
|
||||
|
||||
用途:
|
||||
|
||||
- 回源查看聚合后的设备信息
|
||||
- 兼容历史排查 SQL
|
||||
- 不再作为独立物理表维护
|
||||
|
||||
### `mm_device_catalog`
|
||||
|
||||
统一设备查询主表,一条设备 alias 一行。
|
||||
|
||||
主要字段:
|
||||
|
||||
- `model`
|
||||
- `alias_norm`
|
||||
- `record_id`
|
||||
- `device_name`
|
||||
- `brand`
|
||||
- `manufacturer_brand`
|
||||
- `parent_brand`
|
||||
- `market_brand`
|
||||
- `device_type`
|
||||
- `source_rank`
|
||||
- `source_weight`
|
||||
- `code`
|
||||
- `code_alias`
|
||||
- `ver_name`
|
||||
|
||||
这是当前唯一的设备物理表。
|
||||
|
||||
原因:
|
||||
|
||||
- 只需要等值查 `alias_norm`
|
||||
- 同时兼容 `mm_device_lookup` 和 `models`
|
||||
- 最适合高频只读场景
|
||||
|
||||
### `mm_device_lookup`
|
||||
|
||||
兼容视图,来自 `mm_device_catalog`。
|
||||
|
||||
### `mm_brand_lookup`
|
||||
|
||||
品牌归一化表,用于把三方传来的品牌值归到:
|
||||
|
||||
- `manufacturer_brand`
|
||||
- `parent_brand`
|
||||
- `market_brand`
|
||||
|
||||
比如:
|
||||
|
||||
- `荣耀` -> `HONOR`
|
||||
- `redmi` -> `market_brand=Redmi`
|
||||
- `xiaomi` -> `manufacturer_brand=Xiaomi`
|
||||
|
||||
### `models` / `python_services_test.models`
|
||||
|
||||
为了兼容旧查询链路,保留一层旧结构适配:
|
||||
|
||||
- `mobilemodels.models`(兼容视图)
|
||||
- `python_services_test.models`(兼容视图)
|
||||
|
||||
字段映射说明:
|
||||
|
||||
- `model`
|
||||
- 当前行可直接查询的原始设备标识
|
||||
- 一条设备的多个 alias 会展开成多行
|
||||
- `dtype`
|
||||
- 对应当前的 `device_type`
|
||||
- `brand`
|
||||
- 优先使用 `market_brand`
|
||||
- `brand_title`
|
||||
- 使用 `manufacturer_brand`
|
||||
- `code`
|
||||
- 当前设备识别出的主型号编码
|
||||
- `code_alias`
|
||||
- 其他型号编码,使用 ` | ` 拼接
|
||||
- `model_name`
|
||||
- 对应当前的 `device_name`
|
||||
- `ver_name`
|
||||
- 当前设备的人类可读别名,使用 ` | ` 拼接
|
||||
|
||||
这层主要用于兼容历史 SQL 和第三方直接查表,不建议作为后续新能力的主数据模型。
|
||||
|
||||
## Query Contract
|
||||
|
||||
三方不要直接查原始字符串,统一查归一化后的 `alias_norm`。
|
||||
|
||||
归一化规则与当前项目一致:
|
||||
|
||||
- 全部转小写
|
||||
- 只保留 `[0-9a-z\u4e00-\u9fff]`
|
||||
- 去掉空格、横线、下划线和其他标点
|
||||
|
||||
例如:
|
||||
|
||||
- `SM-G9980` -> `smg9980`
|
||||
- `iPhone14,2` -> `iphone142`
|
||||
- `NOH-AL00` -> `nohal00`
|
||||
|
||||
## Recommended SQL
|
||||
|
||||
推荐分三层:
|
||||
|
||||
1. 新接入
|
||||
- 直接查 `mobilemodels.mm_device_catalog`
|
||||
2. 兼容现有查询
|
||||
- 继续查 `mobilemodels.mm_device_lookup`
|
||||
3. 兼容历史旧结构
|
||||
- 继续查 `python_services_test.models`
|
||||
|
||||
### 1. 主推查法:按设备标识查主表
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
model,
|
||||
record_id,
|
||||
alias_norm,
|
||||
device_name,
|
||||
brand,
|
||||
manufacturer_brand,
|
||||
parent_brand,
|
||||
market_brand,
|
||||
device_type,
|
||||
source_file,
|
||||
section,
|
||||
source_rank,
|
||||
source_weight,
|
||||
code,
|
||||
code_alias,
|
||||
ver_name
|
||||
FROM mobilemodels.mm_device_catalog
|
||||
WHERE alias_norm = ?
|
||||
ORDER BY source_rank ASC, record_id ASC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `alias_norm` 是主查询键
|
||||
- `model` 是当前命中的原始设备标识
|
||||
- `code / code_alias / ver_name` 用于兼容历史字段和辅助展示
|
||||
|
||||
### 1.1 兼容查法:沿用 `mm_device_lookup`
|
||||
|
||||
如果接入方已经依赖 `mm_device_lookup`,可以不改 SQL:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
alias_norm,
|
||||
record_id,
|
||||
device_name,
|
||||
brand,
|
||||
manufacturer_brand,
|
||||
parent_brand,
|
||||
market_brand,
|
||||
device_type,
|
||||
source_file,
|
||||
section,
|
||||
source_rank,
|
||||
source_weight
|
||||
FROM mobilemodels.mm_device_lookup
|
||||
WHERE alias_norm = ?
|
||||
ORDER BY source_rank ASC, record_id ASC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 1.2 兼容旧结构查法:沿用 `python_services_test.models`
|
||||
|
||||
如果接入方仍然沿用旧表结构,可以继续查:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
model,
|
||||
dtype,
|
||||
brand,
|
||||
brand_title,
|
||||
code,
|
||||
code_alias,
|
||||
model_name,
|
||||
ver_name
|
||||
FROM python_services_test.models
|
||||
WHERE model = ?
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 2. 主推查法:带品牌约束查
|
||||
|
||||
先把品牌查成归一化结果,再过滤:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
l.model,
|
||||
l.record_id,
|
||||
l.alias_norm,
|
||||
l.device_name,
|
||||
l.brand,
|
||||
l.manufacturer_brand,
|
||||
l.parent_brand,
|
||||
l.market_brand,
|
||||
l.device_type,
|
||||
l.source_file,
|
||||
l.section,
|
||||
l.source_rank,
|
||||
l.source_weight,
|
||||
l.code,
|
||||
l.code_alias,
|
||||
l.ver_name
|
||||
FROM mobilemodels.mm_device_catalog AS l
|
||||
LEFT JOIN mobilemodels.mm_brand_lookup AS b
|
||||
ON b.alias_norm = ?
|
||||
WHERE l.alias_norm = ?
|
||||
AND (
|
||||
b.alias_norm IS NULL
|
||||
OR (b.market_brand IS NOT NULL AND l.market_brand = b.market_brand)
|
||||
OR (b.manufacturer_brand IS NOT NULL AND l.manufacturer_brand = b.manufacturer_brand)
|
||||
OR (b.parent_brand IS NOT NULL AND l.parent_brand = b.parent_brand)
|
||||
)
|
||||
ORDER BY l.source_rank ASC, l.record_id ASC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 3. 兼容查法:按设备记录聚合查看
|
||||
|
||||
如果历史排查逻辑需要“一台设备一行”,可以继续查:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
record_id,
|
||||
device_name,
|
||||
brand,
|
||||
manufacturer_brand,
|
||||
parent_brand,
|
||||
market_brand,
|
||||
device_type,
|
||||
source_file,
|
||||
section,
|
||||
source_rank,
|
||||
source_weight,
|
||||
aliases_json
|
||||
FROM mobilemodels.mm_device_record
|
||||
WHERE record_id = ?
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
## Migration Advice
|
||||
|
||||
建议迁移顺序:
|
||||
|
||||
1. 新增接入直接使用 `mobilemodels.mm_device_catalog`
|
||||
2. 旧查询先保持 `mm_device_lookup` / `python_services_test.models` 不变
|
||||
3. 待业务侧完成字段适配后,再逐步切到主表
|
||||
|
||||
这样可以做到:
|
||||
|
||||
- 数据底层只维护一张设备实体表
|
||||
- 现有查询链路不需要同时改造
|
||||
- 新能力统一围绕主表扩展
|
||||
|
||||
## Performance Strategy
|
||||
|
||||
要点只有四个:
|
||||
|
||||
1. 只允许等值查询 `alias_norm`
|
||||
- 禁止 `%xxx%` 这类模糊查
|
||||
2. 查询主表用 `mm_device_catalog`
|
||||
- 兼容链路走 `mm_device_lookup` / `models` 视图
|
||||
3. 给三方只读账号
|
||||
- 只开放 `SELECT`
|
||||
4. 最好放只读实例
|
||||
- 不让三方流量影响同步和管理操作
|
||||
|
||||
在当前数据规模下,`alias_norm` 命中是很轻的查询。
|
||||
|
||||
## Update Flow
|
||||
|
||||
建议每日全量刷新一次,不做增量。
|
||||
|
||||
原因:
|
||||
|
||||
- 数据量小
|
||||
- 全量替换更稳定
|
||||
- 不容易出现脏数据和漏删
|
||||
|
||||
建议流程:
|
||||
|
||||
1. 拉取上游原始数据
|
||||
2. 生成最新 `device_index.json`
|
||||
3. 导出 MySQL seed SQL
|
||||
4. 在 MySQL 中执行:
|
||||
- `DELETE FROM mm_device_catalog`
|
||||
- `DELETE FROM mm_brand_lookup`
|
||||
- 批量插入新数据
|
||||
5. 完成后切换读流量
|
||||
|
||||
## Security
|
||||
|
||||
如果一定让三方直连 MySQL,至少要做这些限制:
|
||||
|
||||
- 单独只读账号
|
||||
- IP 白名单
|
||||
- 只授权 `mobilemodels` 和兼容需要的 `python_services_test` schema
|
||||
- 不开放 DDL / DML
|
||||
- 连接数限制
|
||||
- 查询超时限制
|
||||
|
||||
更稳妥的方式仍然是:
|
||||
|
||||
- 三方查你们自己的只读网关
|
||||
- 网关内部查 MySQL
|
||||
|
||||
但如果现阶段必须直连库,上面的三张表已经足够支撑。
|
||||
|
||||
## Files
|
||||
|
||||
- Schema: `sql/mobilemodels_mysql_schema.sql`
|
||||
- Seed exporter: `tools/export_mysql_seed.py`
|
||||
|
||||
生成 seed:
|
||||
|
||||
```bash
|
||||
python3 tools/export_mysql_seed.py
|
||||
```
|
||||
|
||||
默认输出:
|
||||
|
||||
- `dist/mobilemodels_mysql_seed.sql`
|
||||
@@ -1,66 +0,0 @@
|
||||
# Device Mapper Usage
|
||||
|
||||
This tool builds a cross-platform lookup index from `brands/*.md`.
|
||||
|
||||
## 1) Build index
|
||||
|
||||
```bash
|
||||
python3 tools/device_mapper.py build
|
||||
```
|
||||
|
||||
Output file: `dist/device_index.json`
|
||||
|
||||
## 2) Query from command line
|
||||
|
||||
```bash
|
||||
python3 tools/device_mapper.py find --name 'iPhone14,5' --brand Apple
|
||||
python3 tools/device_mapper.py find --name 'M2102J2SC' --brand Xiaomi
|
||||
python3 tools/device_mapper.py find --name 'L55M5-AD' --brand Xiaomi
|
||||
```
|
||||
|
||||
## 3) JSON structure
|
||||
|
||||
- `records`: normalized device records
|
||||
- `device_name`: standard marketing name
|
||||
- `brand`: normalized brand
|
||||
- `manufacturer_brand`: manufacturer-level brand (e.g. `Xiaomi`)
|
||||
- `market_brand`: market sub-brand (e.g. `Xiaomi` / `Redmi` / `POCO`)
|
||||
- `device_type`: `phone | tablet | wear | tv | other`
|
||||
- `aliases`: all known searchable aliases
|
||||
- `lookup`: normalized alias -> candidate `record.id[]`
|
||||
- `brand_aliases`: normalized brand aliases to filter by app-provided brand
|
||||
- `brand_management`: brand governance metadata
|
||||
- `manufacturer_aliases`: canonical brand aliases (`华为/huawei/HUAWEI` -> `HUAWEI`)
|
||||
- `manufacturer_to_parent`: child -> parent brand (e.g. `OnePlus -> OPPO`)
|
||||
- `parent_to_children`: parent -> child brands
|
||||
- `market_brand_aliases`: sub-brand aliases (`redmi` -> `Redmi`)
|
||||
- `stats`: includes parent/manufacturer/market-level counts and Xiaomi combined + separated counts
|
||||
|
||||
## 4) App-side integration (iOS / Android / Harmony)
|
||||
|
||||
1. Load `dist/device_index.json` into memory.
|
||||
2. Normalize input `name` and optional `brand`.
|
||||
3. Use `lookup[normalized_name]` to fetch candidate records.
|
||||
4. If `brand` exists, normalize it by `brand_management`:
|
||||
- manufacturer aliases (e.g. `华为/huawei/HUAWEI` -> `HUAWEI`)
|
||||
- market sub-brand aliases (e.g. `redmi` -> `Redmi`, `xiaomi` -> manufacturer-level `Xiaomi` combined)
|
||||
5. Filter records by normalized manufacturer/market brand as needed.
|
||||
6. Return first candidate (or show all candidates when ambiguous).
|
||||
|
||||
Normalization rule should match this script:
|
||||
|
||||
- lower-case
|
||||
- keep only `[0-9a-z\u4e00-\u9fff]`
|
||||
- remove all spaces, hyphens, underscores and punctuation
|
||||
|
||||
## 5) Device type mapping
|
||||
|
||||
Supported categories:
|
||||
|
||||
- `phone`
|
||||
- `tablet`
|
||||
- `wear`
|
||||
- `tv`
|
||||
- `other`
|
||||
|
||||
Type inference uses keyword + source-file default fallback to reduce misses.
|
||||
140
web/README.md
140
web/README.md
@@ -1,140 +0,0 @@
|
||||
# Web UI
|
||||
|
||||
## Start
|
||||
|
||||
From repository root:
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Optional environment setup:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Stop:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Reset both MySQL and managed raw/index data:
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
Open:
|
||||
|
||||
- http://127.0.0.1:8123/web/device_query.html (设备查询)
|
||||
- http://127.0.0.1:8123/web/brand_management.html (数据管理)
|
||||
|
||||
整个功能栈统一运行在 Docker Compose 中,不再依赖本地 Python 或本地 MySQL 直接启动。
|
||||
|
||||
容器启动时会自动完成:
|
||||
|
||||
- 构建 `dist/device_index.json`
|
||||
- 导出 MySQL seed 文件
|
||||
- 加载 MySQL schema 与 seed 数据
|
||||
- 启动 Web 页面与 API 服务
|
||||
|
||||
容器内还会同时启动 MySQL:
|
||||
|
||||
- `127.0.0.1:3306`
|
||||
- database: `mobilemodels`
|
||||
- reader user: `mobilemodels_reader`
|
||||
|
||||
页面顶部有导航条(Gitea 风格):
|
||||
|
||||
- `设备查询`
|
||||
- `数据管理`
|
||||
|
||||
设备查询页顶部额外提供三个页内 tab:
|
||||
|
||||
- `索引查询`
|
||||
- `SQL 查询`
|
||||
- `相关文档`
|
||||
|
||||
## 设备查询
|
||||
|
||||
### 客户端上报模式
|
||||
|
||||
字段按你给的上报方式输入:
|
||||
|
||||
- Android
|
||||
- `platform=android`
|
||||
- `model_raw=Build.MODEL`(例如 `SM-G9980`)
|
||||
- iOS
|
||||
- `platform=ios`
|
||||
- `model_raw=utsname.machine`(例如 `iPhone14,2`)
|
||||
- HarmonyOS
|
||||
- `platform=harmony`
|
||||
- `model_raw`(例如 `NOH-AL00`)
|
||||
|
||||
说明:
|
||||
|
||||
- Android / iOS / HarmonyOS 都直接使用客户端原始上报的 `model_raw`。
|
||||
- 页面会输出完整候选列表,并附带 `report_payload` 方便核对上报值。
|
||||
|
||||
### SQL 查询模式
|
||||
|
||||
- 直接调用 Compose 内的 API 查询 MySQL 主表 `mobilemodels.mm_device_catalog`
|
||||
- 服务端会先把输入归一化成 `alias_norm`
|
||||
- 页面会显示实际执行的 SQL、结果列表和返回 JSON
|
||||
|
||||
### 相关文档
|
||||
|
||||
- 页面内统一展示主推 SQL、兼容 SQL、归一化规则和文档入口
|
||||
- 可直接跳转查看 `misc/mysql-query-design.md`、`web/README.md`、`README.md`
|
||||
|
||||
## Output
|
||||
|
||||
设备查询页会返回:
|
||||
|
||||
- best candidate
|
||||
- complete ranked candidates
|
||||
- normalized brand + parent manufacturer
|
||||
- source rank/weight (from source order config)
|
||||
- matched inputs
|
||||
- matched normalized keys
|
||||
- source markdown file and section
|
||||
- full JSON output for direct integration or debugging
|
||||
|
||||
## Data management
|
||||
|
||||
数据管理页支持:
|
||||
|
||||
- Independent maintenance:
|
||||
- Brand list is maintained independently in the UI and initialized from index data on first load.
|
||||
- Changes are stored in browser `localStorage`.
|
||||
- Alias normalization (brand only):
|
||||
- `华为 / huawei / HUAWEI` are normalized to brand `HUAWEI`.
|
||||
- Brand-manufacturer relations:
|
||||
- One brand has exactly one parent manufacturer.
|
||||
- One manufacturer can contain multiple brands.
|
||||
- Examples:
|
||||
- `Xiaomi` manufacturer includes brands `Xiaomi`, `Redmi`, `POCO`.
|
||||
- `vivo` manufacturer includes brands `vivo`, `iQOO`.
|
||||
- `OPPO` manufacturer includes brands `OPPO`, `OnePlus`, `realme`.
|
||||
- Click `品牌数 / 厂商数` to open full list popup.
|
||||
- `编辑品牌列表`: popup edit.
|
||||
- `编辑品牌-厂商关系`: popup edit.
|
||||
- `编辑品牌同义词`: popup edit.
|
||||
- 数据来源管理(新增):
|
||||
- Supports drag-and-drop source ordering.
|
||||
- Ranking weight is higher for sources at the top.
|
||||
- Initial order puts `*_cn.md` before non-`cn` sources.
|
||||
- 原始数据同步(新增):
|
||||
- Available in the third left tab.
|
||||
- Calls the Compose service API to sync upstream `KHwang9883/MobileModels` raw markdown, rebuild `dist/device_index.json`, export MySQL seed, and reload MySQL.
|
||||
- Requires `docker compose up --build -d`.
|
||||
- 索引数据(新增):
|
||||
- Available in the fourth left tab.
|
||||
- Includes index reload and index load status.
|
||||
|
||||
## Notes
|
||||
|
||||
- Managed raw data, rebuilt index, and MySQL seed files are persisted in Docker volumes, not written back to the local workspace during runtime.
|
||||
- For production, override `MYSQL_ROOT_PASSWORD` and `MYSQL_READER_PASSWORD` with your own values.
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
> Providing your device's codename is welcome. You can find it in version of system firmware, starting with `PD`.
|
||||
>
|
||||
> Please report any errors by [opening an issue](https://github.com/KHwang9883/MobileModels/issues).
|
||||
> Please report any errors through your project delivery channel.
|
||||
|
||||
## vivo X series
|
||||
|
||||
@@ -1107,4 +1107,4 @@
|
||||
|
||||
**JOVI Y39 5G (`PD2444F`):**
|
||||
|
||||
`V2444`: JOVI Y39 5G
|
||||
`V2444`: JOVI Y39 5G
|
||||
Reference in New Issue
Block a user