create-dwy 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/src/cli-help.js +31 -2
  4. package/src/sync-all.js +168 -577
  5. package/src/sync-codex.js +42 -28
  6. package/src/sync-cursor.js +0 -25
  7. package/src/sync-global-skills.js +2 -2
  8. package/src/sync-opencode.js +7 -33
  9. package/src/sync.js +59 -226
  10. package/src/tab-multiselect.js +15 -53
  11. package/src/utils.js +0 -1
  12. package/templates/ai-tools/AGENTS.md +223 -0
  13. package/templates/ai-tools/CHANGELOG.md +195 -0
  14. package/templates/ai-tools/VERSION +1 -1
  15. package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +5 -4
  16. package/templates/ai-tools/rules/Android/dwy-android-core.md +147 -13
  17. package/templates/ai-tools/rules/Android/dwy-android-layering.md +142 -0
  18. package/templates/ai-tools/rules/Android/dwy-android-mvp.md +55 -0
  19. package/templates/ai-tools/rules/Android/dwy-android-support-only.md +2 -0
  20. package/templates/ai-tools/rules/Docker/dwy-docker.md +23 -1
  21. package/templates/ai-tools/rules/Flutter/dwy-flutter-core.md +2 -0
  22. package/templates/ai-tools/rules/Flutter/dwy-flutter-testing.md +1 -1
  23. package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +12 -8
  24. package/templates/ai-tools/rules/Python/dwy-python-backend.md +4 -43
  25. package/templates/ai-tools/rules/Python/dwy-python-layering.md +169 -0
  26. package/templates/ai-tools/rules/Vue/dwy-vue-core.md +11 -49
  27. package/templates/ai-tools/rules/Vue/dwy-vue-layering.md +153 -0
  28. package/templates/ai-tools/rules/iOS/dwy-apple-layering.md +133 -0
  29. package/templates/ai-tools/rules/iOS/dwy-swift-core.md +2 -0
  30. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-code-craft.md +83 -0
  31. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dependency-freshness.md +27 -66
  32. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +8 -219
  33. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +27 -123
  34. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prod-branch.md +31 -0
  35. package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-postgres.md +17 -10
  36. package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-redis.md +6 -3
  37. package/templates/ai-tools/skills/Docker/dwy-deploy-first/SKILL.md +14 -14
  38. package/templates/ai-tools/skills/Docker/dwy-deploy-first/references/chapter-01-mirrors.md +2 -2
  39. package/templates/ai-tools/skills/Docker/dwy-docker/SKILL.md +21 -13
  40. package/templates/ai-tools/skills/Docker/dwy-docker/references/ask-templates.md +13 -12
  41. package/templates/ai-tools/skills/Docker/dwy-docker/references/templates.md +3 -1
  42. package/templates/ai-tools/skills/Docker/dwy-docker/references/version-rules.md +10 -6
  43. package/templates/ai-tools/skills/Docker/dwy-docker/scripts/query_dockerhub.py +21 -6
  44. package/templates/ai-tools/skills/Docker/dwy-docker/scripts/scan_images.sh +2 -2
  45. package/templates/ai-tools/skills/Docker/dwy-mirror-source/SKILL.md +44 -30
  46. package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/dockerfile-snippets.md +2 -0
  47. package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/mirror-providers.md +8 -8
  48. package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/apply_mirrors.py +14 -1
  49. package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/check_mirrors.py +4 -0
  50. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/SKILL.md +8 -6
  51. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/python-sdk.md +3 -3
  52. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/stream-engine.md +2 -2
  53. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/anti-patterns.md +3 -1
  54. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/change-checklist.md +4 -4
  55. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/lethal-violations.md +5 -5
  56. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/perf-baselines.md +5 -5
  57. package/templates/ai-tools/skills/Python/dwy-eapi/SKILL.md +2 -2
  58. package/templates/ai-tools/skills/Python/dwy-eapi/references/tasks-integration-guide.md +4 -4
  59. package/templates/ai-tools/skills/Python/dwy-eapi/v0/breaking.md +3 -0
  60. package/templates/ai-tools/skills/Python/dwy-eapi/v0/email-canonical.md +28 -3
  61. package/templates/ai-tools/skills/Python/dwy-eapi/v0/index.md +2 -2
  62. package/templates/ai-tools/skills/Python/dwy-eapi/v0/providers-email.md +12 -2
  63. package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-design-guide.md +1 -1
  64. package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-integration-guide.md +12 -0
  65. package/templates/ai-tools/skills/Vue/dwy-eui/shared/config-theme.md +2 -0
  66. package/templates/ai-tools/skills/Vue/dwy-eui/shared/form.md +10 -0
  67. package/templates/ai-tools/skills/Vue/dwy-eui/v2/index.md +5 -0
  68. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +11 -14
  69. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +1 -1
  70. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +1 -1
  71. package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-dev/SKILL.md +1 -1
  72. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +32 -15
  73. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/electron-release-workflow-template.yml +3 -1
  74. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/release-workflow-template.yml +8 -5
  75. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +1 -1
  76. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +1 -1
  77. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/aso.md +2 -0
  78. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +1 -1
  79. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +4 -3
  80. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +1 -1
  81. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +1 -1
  82. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +45 -27
  83. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +1 -1
  84. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +1 -1
  85. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +1 -1
  86. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +1 -1
  87. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +1 -1
  88. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +8 -0
  89. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +7 -7
  90. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +63 -59
  91. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_base.sh +10 -1
  92. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_capacity.sh +21 -23
  93. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_db.sh +9 -1
  94. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +20 -24
  95. package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_logs.sh +3 -3
  96. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +4 -2
  97. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +1 -1
  98. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +1 -0
  99. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +7 -5
  100. package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +5 -3
  101. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +140 -0
  102. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-git-commit/SKILL.md +113 -0
  103. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-shared/SKILL.md +17 -14
  104. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-whiteboard-prototype/SKILL.md +1 -1
  105. package/templates/project/fullstack-monorepo/{CLAUDE.md.hbs → AGENTS.md.hbs} +1 -1
  106. package/src/sync-packs.js +0 -223
  107. package/src/sync-scene.js +0 -178
  108. package/templates/ai-tools/CLAUDE.md +0 -210
  109. package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md +0 -115
  110. /package/templates/project/backend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
  111. /package/templates/project/fullstack-monorepo/backend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
  112. /package/templates/project/fullstack-monorepo/frontend/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
  113. /package/templates/project/web/{CLAUDE.md.hbs → AGENTS.md.hbs} +0 -0
@@ -13,7 +13,7 @@ paths:
13
13
 
14
14
  适用于 **Android 原生项目**(Java + Kotlin 混编,XML 布局,Gradle 构建脚本)。本规范**重点是代码注释**,兼带最小必要的命名 / 简约 / 反模式约束。
15
15
 
16
- 不限制构建工具、UI 框架、架构模式、SDK 版本、依赖库选型 —— 与老项目兼容。
16
+ 不限制构建工具、UI 框架、SDK 版本、依赖库选型 —— 与老项目兼容。页面架构走 `dwy-android-mvp`(实际 MVP 读当前项目上下文;本文件只定行为);模块四层走 `dwy-android-layering`。
17
17
 
18
18
  ## 全面屏 / 刘海屏适配
19
19
 
@@ -23,6 +23,136 @@ paths:
23
23
  - 全屏背景、沉浸式遮罩可以延伸到系统栏区域,但前景可交互内容默认留在安全显示区内。
24
24
  - 禁止写死状态栏高度、导航栏高度、刘海高度,统一通过系统 inset 获取。
25
25
 
26
+ ## 尺寸单位
27
+
28
+ 布局、间距、圆角、描边用 `dp`。字号用 `sp`,跟随系统字体缩放。禁止 `px`。数值走 `dimens.xml`,禁止在 XML / 代码里裸写 `16dp` / `16sp`。
29
+
30
+ **为什么字号用 sp**:系统大字是可访问性能力。把字号锁成 `dp`、或除掉 `fontScale`,大字模式会失效。
31
+
32
+ **例外**:电视,或设计明确要求不跟随系统字号的固定版式,字号资源才用 `dp`,并在资源旁写明原因。
33
+
34
+ ### 禁止 / 必须
35
+
36
+
37
+ | 场景 | 禁止 | 必须 |
38
+ | --- | --- | --- |
39
+ | XML 宽高 / 间距 / 圆角 / 描边 | `16px`、裸数字 | `@dimen/xxx`,资源值 `16dp` |
40
+ | XML / style 字号 | `16px`、裸 `16sp`、用 `dp` 锁死字号 | `@dimen/xxx`,资源值 `16sp` |
41
+ | 代码设字号 | 裸数字、再除一次 `fontScale` | `setTextSize(COMPLEX_UNIT_PX, getDimension(R.dimen.xxx))`。dimen 是 `sp` 时 `getDimension` 已按当前字号缩放换成 px |
42
+ | Jetpack Compose 布局 | `16.px`、裸 `.dp` 字面量 | `dimensionResource(R.dimen.xxx)` |
43
+ | Jetpack Compose 字号 | 裸 `16.sp`、`toPx() / fontScale` | 从 `sp` 的 dimen 读出后再 `.toSp()`,让系统字体缩放只生效一次 |
44
+
45
+
46
+ 系统 API 返回值本身是 px(`View.getWidth()`、`MotionEvent`、Canvas、`getDimension()`)——那是运行时像素,不是声明单位。声明布局尺寸仍用 dp,声明字号仍用 sp。
47
+
48
+ ```xml
49
+ <!-- res/values/dimens.xml -->
50
+ <dimen name="text_size_title">18sp</dimen>
51
+ <dimen name="padding_page">16dp</dimen>
52
+ ```
53
+
54
+ ```xml
55
+ <!-- ❌ -->
56
+ <AppCompatTextView android:textSize="16sp" android:layout_width="100px" />
57
+ <AppCompatTextView android:textSize="16dp" />
58
+
59
+ <!-- ✅ 字号 sp、间距 dp,都走资源 -->
60
+ <AppCompatTextView
61
+ android:textSize="@dimen/text_size_title"
62
+ android:padding="@dimen/padding_page" />
63
+ ```
64
+
65
+ ```kotlin
66
+ // ❌
67
+ textView.textSize = 16f
68
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
69
+ Text("标题", fontSize = 16.sp)
70
+
71
+ // ✅ View:dimen 是 sp。getDimension 已按 fontScale 换成 px,不要再套一层 SP,也不要除 fontScale。
72
+ textView.setTextSize(
73
+ TypedValue.COMPLEX_UNIT_PX,
74
+ resources.getDimension(R.dimen.text_size_title)
75
+ )
76
+
77
+ // ✅ Compose:dimensionResource 把 sp 资源按当前 fontScale 收成 Dp。toSp() 除掉这一次,绘制时系统再乘一次。
78
+ val titleSize = with(LocalDensity.current) {
79
+ dimensionResource(R.dimen.text_size_title).toSp()
80
+ }
81
+ Text("标题", fontSize = titleSize)
82
+ ```
83
+
84
+ 布局用 `dimensionResource(R.dimen.xxx)` 读 dimens,不要散落裸 `.dp`。字号不要写裸 `.sp`。
85
+
86
+ ## 屏幕适配(AndroidAutoSize,只按宽)
87
+
88
+ 屏幕适配用 [JessYan AndroidAutoSize](https://github.com/JessYanCoding/AndroidAutoSize) `1.2.1`(今日头条 density 方案)。**只按宽度适配**,不按高度。布局写 `dp`、字号写 `sp`(见上一节),由框架改 `density`,让屏幕逻辑宽等于设计稿宽。字号仍跟随系统字体缩放。
89
+
90
+ **为什么只按宽**:高度含状态栏 / 刘海 / 手势条,按高适配会把系统栏算进设计高,全面屏变形。宽是稳定轴。
91
+
92
+ `1.2.0+` 同时兼容 Support 与 AndroidX,按当前项目推断,**禁止**为接本库引入 `androidx.*`。Support 老项目见 `dwy-android-support-only`。
93
+
94
+ 分端只体现在 **设计稿尺寸 + `design_width_in_dp`**。AutoSize 按宽铺满,不必再用 `values-television`、`productFlavor`、`UI_MODE_TYPE_TELEVISION` 去拆资源或运行时切换。
95
+
96
+ ### 设计基准
97
+
98
+ 单位一律 `dp`。`design_width_in_dp` 填当前产品那一行的宽。
99
+
100
+ | 端 | 画布 | 方向 | `design_width_in_dp` | `design_height_in_dp`(只记录,不参与适配) |
101
+ | --- | --- | --- | --- | --- |
102
+ | 移动端 | **390 × 844** | 竖屏 | **390** | 844 |
103
+ | TV 端 | **1280 × 720** | 横屏 | **1280** | 720 |
104
+
105
+ ### 依赖
106
+
107
+ JCenter 已停,走 JitPack。用仍兼容 Support、不引入 AndroidX 的正式版。已核对过的起点是 `v1.2.1`(`1.2.0+` 才兼容 Support)。新 tag 先看传递依赖,拉进 AndroidX 就丢掉。具体 tag 按依赖新鲜度现查,不要把 `v1.2.1` 当成永远唯一坐标。
108
+
109
+ ```gradle
110
+ // 起点先验。写入前确认该 tag 仍不带 androidx
111
+ implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1'
112
+ ```
113
+
114
+ ### 配置(强制)
115
+
116
+ Manifest 按上表填当前产品。下面是移动端;TV 把宽改成 `1280`、高改成 `720`。
117
+
118
+ ```xml
119
+ <application>
120
+ <meta-data
121
+ android:name="design_width_in_dp"
122
+ android:value="390" />
123
+ <meta-data
124
+ android:name="design_height_in_dp"
125
+ android:value="844" />
126
+ </application>
127
+ ```
128
+
129
+ ```java
130
+ // 移动端保留系统字体缩放。TV、或设计写明固定字号的界面,才 setExcludeFontScale(true)。
131
+ AutoSizeConfig.getInstance()
132
+ .setBaseOnWidth(true)
133
+ .setExcludeFontScale(false);
134
+ ```
135
+
136
+ 页面基类必须重写 `getResources()`,防止厂商 ROM / Dialog / 三方把 `density` 改回去导致偶发错位:
137
+
138
+ ```java
139
+ @Override
140
+ public Resources getResources() {
141
+ AutoSizeCompat.autoConvertDensityOfGlobal(super.getResources());
142
+ return super.getResources();
143
+ }
144
+ ```
145
+
146
+ 三方 WebView / 扫码 / 系统 Dialog 错乱:该页实现 `CancelAdapt`,或 `show()` 前再调一次 `AutoSizeCompat.autoConvertDensityOfGlobal`。
147
+
148
+ ### 禁止
149
+
150
+ - `setBaseOnWidth(false)` 或按高度适配
151
+ - 移动端 `setExcludeFontScale(true)`,或用除 `fontScale` 把字号锁死
152
+ - 副单位 `pt` / `in` / `mm`
153
+ - 再接 `sdp` / 百分比库 / 另一套改 `density` 的方案
154
+ - 布局改回 `px`
155
+
26
156
  ## 控件选型(AppCompat)
27
157
 
28
158
  XML 布局、代码 `new`、自定义 View:**只要存在 AppCompat 对应类,就必须用 AppCompat 版**,禁止 `android.widget` 平台控件。没有 AppCompat 对应类的布局容器 / 列表(LinearLayout、ConstraintLayout、RecyclerView 等)保持原样。
@@ -101,6 +231,7 @@ class PriceText(context: Context, attrs: AttributeSet?) : AppCompatTextView(cont
101
231
  | Fragment | `XxxFragment` | `HomeFragment` |
102
232
  | Adapter / ViewHolder | `XxxAdapter` / `XxxViewHolder` | `OrderAdapter` |
103
233
  | Dialog | `XxxDialog` | `ConfirmDialog` |
234
+ | MVP 相关 | 跟当前项目已有命名 | 常见 `XxxPresenter`(有 Contract 则跟本仓) |
104
235
  | 布尔变量 / 方法 | `is`* / `has*` / `can*` / `should*` | `isLoading`、`hasNext` |
105
236
 
106
237
 
@@ -114,7 +245,7 @@ class PriceText(context: Context, attrs: AttributeSet?) : AppCompatTextView(cont
114
245
  | drawable | `<用途>_<状态>` | `bg_button_primary`、`ic_arrow_right`、`shape_card_corner` |
115
246
  | color | `<语义>` | `color_primary`、`color_text_secondary` |
116
247
  | string | `<场景>_<语义>` | `login_button_submit`、`home_title` |
117
- | dimen | `<语义>` | `padding_normal`、`text_size_title` |
248
+ | dimen | `<语义>`。布局 `dp`,字号 `sp`(禁止 `px`) | `padding_normal`、`text_size_title` |
118
249
  | anim / animator | `<逻辑名>_<方向|序号>` 或通用动画名 | `fade_in.xml`、`push_bottom_in.xml`、`loading_001.xml` |
119
250
  | menu | `menu_<场景>.xml` | `menu_order_detail.xml` |
120
251
  | style / theme | `PascalCase`,点号继承,从通用到特殊 | `Widget.App.Button`、`Theme.App.Dark` |
@@ -219,10 +350,10 @@ class UserActivity : AppCompatActivity() {
219
350
 
220
351
  ```xml
221
352
  <!-- ❌ 裸文案 / 裸数字 -->
222
- <TextView android:text="登录" android:textSize="16sp" />
353
+ <AppCompatTextView android:text="登录" android:textSize="16dp" />
223
354
 
224
- <!-- ✅ 走资源 -->
225
- <TextView android:text="@string/login_button_submit" android:textSize="@dimen/text_size_normal" />
355
+ <!-- ✅ 走资源;字号单位也是 dp,见「尺寸单位(强制 dp)」 -->
356
+ <AppCompatTextView android:text="@string/login_button_submit" android:textSize="@dimen/text_size_normal" />
226
357
  ```
227
358
 
228
359
  ## 二、代码注释
@@ -792,7 +923,7 @@ for (Order o : orders) {
792
923
 
793
924
  | 覆盖 | 不覆盖 |
794
925
  | ---- | ------ |
795
- | Model / Entity / DTO / VO / Bean 及同类数据载体 | Activity / Fragment / Adapter / ViewHolder / Repository / ViewModel 等非数据载体 |
926
+ | Model / Entity / DTO / VO / Bean 及同类数据载体 | Activity / Fragment / Adapter / ViewHolder / Repository / Presenter 等非数据载体 |
796
927
  | 含功能包内私有、`internal` 实体 | `enum`;仅含常量的 `object` / `Constants` 类 |
797
928
  | 网络响应体、跨层传递的数据对象 | 纯 UI 状态若仅为页面内部临时结构且不跨层,可豁免(跨层则按实体处理) |
798
929
 
@@ -926,9 +1057,11 @@ class User(val id: Long, val name: String)
926
1057
 
927
1058
  ### 包组织(按功能聚合)
928
1059
 
1060
+ **Gradle 模块四层(`:app` / `:features:*` / `:biz-foundation` / `:foundation`)与依赖边 → 见 `dwy-android-layering`。** 本节只约束**模块内部** Java/Kotlin 包。
1061
+
929
1062
  **按功能聚合(package-by-feature),不按技术分层(package-by-layer)。**
930
1063
 
931
- 同一个功能/页面相关的所有类 —— 页面(Activity / Fragment)、其数据实体、业务逻辑(ViewModel / Presenter / UseCase)、数据访问(Repository)、列表 Adapter、UI 状态等 —— 放在**同一个功能包**下聚合,而不是按类型横切成 `activities/`、`models/`、`adapters/`、`repositories/` 这种分层包。
1064
+ 同一个功能/页面相关的所有类 —— 页面(Activity / Fragment)、其数据实体、业务逻辑(Presenter 或本仓对等角色)、数据访问(Repository)、列表 Adapter、UI 状态等 —— 放在**同一个功能包**下聚合,而不是按类型横切成 `activities/`、`models/`、`adapters/`、`presenters/` 这种分层包。页面架构见 `dwy-android-mvp`(切法跟当前项目)。
932
1065
 
933
1066
  **理由**:改一个功能时,相关文件都在一个包内,无需在多个分层目录间跳转;包之间按业务边界解耦,删除/迁移一个功能只动一个包;高内聚低耦合,符合就近维护。
934
1067
 
@@ -937,24 +1070,23 @@ class User(val id: Long, val name: String)
937
1070
  com.example.app/
938
1071
  login/ <!-- 登录功能,自包含 -->
939
1072
  LoginActivity.kt
940
- LoginViewModel.kt
1073
+ LoginPresenter.kt <!-- 或本仓已有的 Contract / XxxView,跟项目 -->
941
1074
  LoginRepository.kt
942
- LoginUiState.kt
943
1075
  Credential.kt <!-- 该功能私有实体 -->
944
1076
  order/ <!-- 订单功能 -->
945
1077
  OrderListActivity.kt
946
1078
  OrderDetailActivity.kt
947
- OrderViewModel.kt
1079
+ OrderListPresenter.kt <!-- 同上,不强制 Contract 文件 -->
948
1080
  OrderRepository.kt
949
1081
  OrderAdapter.kt
950
1082
  Order.kt
951
- common/ <!-- 跨功能共享:基类、工具、网络、通用实体 -->
1083
+ common/ <!-- 未拆四层时:本产品 Base / 工具。有 :biz-foundation 则 Base 放那个模块 -->
952
1084
  base/ network/ util/ model/
953
1085
 
954
1086
  // ❌ 按技术分层横切 —— 改一个功能要在多个目录间反复跳
955
1087
  com.example.app/
956
1088
  activities/ LoginActivity.kt OrderListActivity.kt OrderDetailActivity.kt
957
- viewmodels/ LoginViewModel.kt OrderViewModel.kt
1089
+ presenters/ LoginPresenter.kt OrderListPresenter.kt
958
1090
  repositories/ LoginRepository.kt OrderRepository.kt
959
1091
  adapters/ OrderAdapter.kt
960
1092
  models/ Credential.kt Order.kt
@@ -1090,7 +1222,7 @@ if (trimmed.isNotEmpty()) { ... }
1090
1222
  | Kotlin 顶层 `var` | `val`;需可变用类封装 + 同步原语 |
1091
1223
  | 内部类持有外部 Activity | `static` 内部类 + `WeakReference` |
1092
1224
  | 拼接 SQL 字符串 | 参数化查询 |
1093
- | 硬编码 dp / sp 值 | `@dimen/` |
1225
+ | 硬编码尺寸 / 字号用 `px` 或用 `dp` 锁死字号 | 布局 `@dimen/` 值 `dp`,字号 `@dimen/` 值 `sp` |
1094
1226
  | 硬编码颜色值 `#FF0000` | `@color/color_primary` |
1095
1227
  | 用户可见文案硬编码 | `@string/...` |
1096
1228
  | 单行 `if` / `for` / `while` / `do-while` 无 `{}`(Kotlin 表达式 `if` 除外) | 即使只一行也写 `{}`;详见 §四「控制流大括号」 |
@@ -1131,6 +1263,8 @@ if (trimmed.isNotEmpty()) { ... }
1131
1263
  | 22 | 数据实体(Model/DTO/VO/Bean 等):Java 字段 `private` + get/set + `toString`;Kotlin 优先 `data class`+`val`(自带 toString),禁止 `@JvmField`,非 data class 必须手写 `toString` | ✓ |
1132
1264
  | 23 | 字符串空判断用 `isEmpty` / `isNotEmpty` / `isNotBlank`,禁止 `length() > 0` / `== 0` / `!= 0` 及 `trim().length() > 0` 判空;详见 §八「字符串空判断」 | ✓ |
1133
1265
  | 24 | 有 AppCompat 对应类的控件必须用 AppCompat 版(XML / `new` / 自定义 View 父类),包名按当前项目推断;Switch 用 `SwitchCompat`;无对应类的布局容器保持原样 | ✓ |
1266
+ | 25 | 布局 `dp`、字号 `sp`,禁止 `px` 和裸字面量(含 Compose `16.sp` / `16.px`);数值走 `dimens.xml`。不要除掉 `fontScale`。TV / 固定版式才允许字号用 `dp` 并写明原因 | ✓ |
1267
+ | 26 | 屏幕适配走 AndroidAutoSize(Support 兼容线,起点先验 `v1.2.1`,不锁死这个 tag),只按宽;移动端 `design_width=390`(画布 `390×844`),TV `design_width=1280`(画布 `1280×720`);禁止按高适配、禁止副单位;页面基类重写 `getResources()` 调 `AutoSizeCompat` | ✓ |
1134
1268
 
1135
1269
 
1136
1270
  **不执行自检就提交代码 = 违规。**
@@ -0,0 +1,142 @@
1
+ ---
2
+ description: Android 四层模块与依赖方向(app / features / biz-foundation / foundation);Gradle Version Catalog 根锁定第三方版本
3
+ paths:
4
+ - "**/*.kt"
5
+ - "**/*.java"
6
+ - "**/*.gradle"
7
+ - "**/*.kts"
8
+ - "**/libs.versions.toml"
9
+ - "**/settings.gradle"
10
+ ---
11
+
12
+ # Android 四层架构与依赖
13
+
14
+ 适用于多模块 Android 应用。注释 / 命名 / AppCompat 见 `dwy-android-core`。页面 MVP 与业务 Base 见 `dwy-android-mvp`。本文件只管**Gradle 模块层、引用边、版本写在哪**。模块**内部** Java 包仍按功能聚合(见 `dwy-android-core`「包组织」)。
15
+
16
+ 新项目强制本结构。未拆模块的存量**不回溯拆包**;新增模块必须按本文件落层。Support 老项目另走 `dwy-android-support-only`。
17
+
18
+ 单壳项目**不建** PlatformFoundation。
19
+
20
+ ---
21
+
22
+ ## 一、层名(冻结)
23
+
24
+ 正式层名只有下表四个。模块可以叫 `:app`、`:biz-foundation`,文档可写「本仓 `:layer-infra` = Foundation」。**禁止**把 `Domain` / `infra` / `core` 当层名。
25
+
26
+ | 层 | 判定(只用这一条) | 禁止 |
27
+ |---|---|---|
28
+ | **Foundation** | 换一个完全不同的产品仍原样拿走 | 出现本业务名词 |
29
+ | **BizFoundation** | 本产品才有,但跨多个 Feature 都要用 | 页面、导航、某一端壳状态 |
30
+ | **Features** | 可整包删/裁剪的业务域 | 被其他 Feature 直接引用 |
31
+ | **Assembly** | 生命周期、DI、路由挂载、把实现插进 Protocol | 业务规则、业务计算 |
32
+
33
+ 升层:第二个 Feature 或第二个产品**真在用**,才升到 BizFoundation / Foundation。禁止「可能复用」提前抽。
34
+
35
+ 业务内聚:一个业务域 = `:features:<name>`。域内 Activity / Presenter(或本仓对等角色)/ Repository 聚在该模块。禁止全局 `activities/` / `presenters/` 模块(本仓已经那样切的,跟本仓)。
36
+
37
+ `BaseActivity` / `BaseFragment` / `BaseDialog` 落在 `:biz-foundation`(本产品壳)。Foundation 不放页面基类。细则见 `dwy-android-mvp`。
38
+
39
+ ---
40
+
41
+ ## 二、依赖边(强制)
42
+
43
+ ```
44
+ :app → :features:* → :biz-foundation → :foundation
45
+ ✕ 互引
46
+ ```
47
+
48
+ | 谁 | 允许 project 依赖 | 禁止 |
49
+ |---|---|---|
50
+ | `:app` | `:features:*`、`:biz-foundation` | 业务规则写在 Application / HomePresenter |
51
+ | `:features:orders` | **仅** `implementation project(':biz-foundation')` | 其它 Feature、`:foundation`、`:app` |
52
+ | `:biz-foundation` | `:foundation`(可用 `api` 给编译用,见下) | Feature / app |
53
+ | `:foundation` | 无 project 下层 | 业务模型、Feature |
54
+
55
+ **Feature 互引为零。** 跳转只走 `:app`。跨域只许:升到 `:biz-foundation`;Feature 依赖 `biz-foundation` 里的接口,由 app 注入;壳编排。
56
+
57
+ **透传不等于可直连。** `:biz-foundation` 即使 `api project(':foundation')`,Feature **源码仍禁止** `import` foundation 包名。要用 Http/Log,走 biz-foundation 门面。
58
+
59
+ ```gradle
60
+ // features/orders/build.gradle
61
+ dependencies {
62
+ implementation project(':biz-foundation')
63
+ implementation libs.okhttp
64
+ }
65
+ ```
66
+
67
+ ```gradle
68
+ // 禁止
69
+ implementation project(':features:inventory')
70
+ implementation project(':foundation')
71
+ implementation project(':app')
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 三、分包树
77
+
78
+ 打开工程必须能数出四层。业务词只出现在 `features/` 下。
79
+
80
+ ```
81
+ :app # Assembly
82
+ :features:orders # Features
83
+ :features:inventory
84
+ :biz-foundation # BizFoundation(鉴权、跨域协议放这个模块里面)
85
+ :foundation # Foundation(Http / Log / 进程工具)
86
+ ```
87
+
88
+ ```
89
+ app/
90
+ features/
91
+ orders/
92
+ inventory/
93
+ biz-foundation/
94
+ src/main/java/.../bizfoundation/auth/
95
+ foundation/
96
+ src/main/java/.../foundation/http/
97
+ gradle/libs.versions.toml
98
+ settings.gradle
99
+ ```
100
+
101
+ `settings.gradle` include 上述模块。鉴权不是独立层,是 `:biz-foundation` 内的包。
102
+
103
+ ---
104
+
105
+ ## 四、Monorepo 版本(强制)
106
+
107
+ 第三方版本号**只**出现在 `gradle/libs.versions.toml`。模块用 `libs.xxx`。内部模块 `project(':x')`。选哪个版本走 skill `dwy-dependency-choice`。
108
+
109
+ 存量若仍用根 `config.gradle` 的 `ext`,视为同一 catalog,禁止再在子模块写版本。
110
+
111
+ ```toml
112
+ # gradle/libs.versions.toml
113
+ [versions]
114
+ okhttp = "4.12.0"
115
+
116
+ [libraries]
117
+ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
118
+ ```
119
+
120
+ ```gradle
121
+ // features/orders/build.gradle
122
+ dependencies {
123
+ implementation project(':biz-foundation')
124
+ implementation libs.okhttp
125
+ }
126
+ ```
127
+
128
+ 禁止 feature 模块 `implementation 'com.squareup.okhttp3:okhttp:4.12.0'`。
129
+
130
+ ---
131
+
132
+ ## 五、检查清单
133
+
134
+ | 检查项 | 严重 |
135
+ |---|---|
136
+ | 无 `:features` / `:biz-foundation` / `:foundation` 三层模块(新项目) | 高 |
137
+ | Feature `implementation` 另一 Feature 或 `:foundation` 或 `:app` | 高 |
138
+ | Feature 源码 `import` foundation 包名(即使经 `api` 透传) | 高 |
139
+ | 开机链 / 业务编排写在 `:app` 的 Presenter | 高 |
140
+ | 业务 BaseActivity 放在 `:foundation` 或 Feature 里 | 高 |
141
+ | 子模块写死第三方坐标版本 | 高 |
142
+ | `Domain` / `infra` / `core` 当层名 | 中 |
@@ -0,0 +1,55 @@
1
+ ---
2
+ description: Android 用 MVP 做页面分工;实际架构读当前项目上下文;业务 Base 落在 BizFoundation;简单静态页和确认框允许无 Presenter
3
+ paths:
4
+ - "**/*.java"
5
+ - "**/*.kt"
6
+ - "**/AndroidManifest.xml"
7
+ - "**/res/**/*.xml"
8
+ ---
9
+
10
+ # Android MVP 与业务 Base
11
+
12
+ **实际 MVP 架构以当前项目上下文为准。** 本文件只定大行为,不定骨架、类名、目录、库。
13
+
14
+ 写任何页面前必须先读本仓,再动手。读到的继承链、Contract 切法、基类名字、生命周期挂法、包怎么放,全部跟项目。禁止用记忆里的「标准 MVP」覆盖本仓。
15
+
16
+ 模块四层见 `dwy-android-layering`;注释 / 命名 / AppCompat 见 `dwy-android-core`。Support 与 AndroidX 也跟当前项目推断。
17
+
18
+ ---
19
+
20
+ ## 一、写页面前读什么
21
+
22
+ 打开工程后搜并读(命中即按它写):
23
+
24
+ - 已有 `*Presenter` / `*View` / `*Contract` / Presenter 基类
25
+ - 业务 `BaseActivity` / `BaseFragment` / `BaseDialog` 的位置与继承链
26
+ - 一页的类怎么摆:同功能包,还是本仓已经横切
27
+ - View 与 Presenter 怎么绑生命周期(attach / 构造传入 / 其它)
28
+
29
+ 新页对齐读到的那套。禁止另起一套。存量不回溯。
30
+
31
+ 新仓还没有任何页面:用下面行为准则写第一页;之后新页对齐第一页,仍不从外部套模板。
32
+
33
+ ---
34
+
35
+ ## 二、行为准则(骨架仍跟项目)
36
+
37
+ 1. **View 管 UI,Presenter(或本仓对等角色)管业务与请求,数据层管读写。** 有业务的页:请求、校验、状态编排不进 Activity。
38
+ 2. **Presenter 不碰控件。** 不 `findViewById`,不把 Activity / 控件当字段;需要 Context 走 View 回调。页面销毁后不再调 View。怎么 detach 跟项目。
39
+ 3. **简单静态页、确认框、单按钮提示:允许不写 Presenter。** 不造空文件。复杂 Dialog 仍走本仓 MVP。
40
+ 4. **业务 Base 给业务扩展。** `BaseActivity` / `BaseFragment` / `BaseDialog` 落在 BizFoundation(未拆四层则本仓已有的 base 包):本产品壳。Feature 有 Base 之后禁止直接继承框架页。Foundation 不放本产品页面基类。Base 禁止上帝类。
41
+
42
+ ---
43
+
44
+ ## 三、检查清单
45
+
46
+ | 检查项 | 严重 |
47
+ |---|---|
48
+ | 没读本仓已有 MVP / Base 就按外部模板开写 | 高 |
49
+ | 新页没对齐本仓已有切法 / 命名 / 库 | 高 |
50
+ | 有请求的页在 Activity 里写接口 / 拼业务 | 高 |
51
+ | Presenter(或对等角色)持有控件 / Activity,销毁后仍回调 View | 高 |
52
+ | 已有业务 Base,新页仍直接继承框架页 | 高 |
53
+ | 业务 Base 里出现某 Feature 方法(上帝类) | 高 |
54
+ | Foundation 里放本产品 BaseActivity | 高 |
55
+ | 静态页 / 确认框硬造空 Presenter | 中 |
@@ -84,6 +84,8 @@ android.enableJetifier=false
84
84
  3. 传递依赖混入 androidx 时,用 `exclude` 排除,并替换为 support 等价物;**无法排除或无 support 等价物 → 换库**,不要将就
85
85
  4. **禁止**为使用某个只支持 androidx 的库而引入 androidx —— 一个 androidx 进来就破坏整套隔离,得不偿失
86
86
 
87
+ 屏幕适配用 AndroidAutoSize(JitPack,Support 兼容线 `1.2.0+`,起点先验 `v1.2.1`,不锁死这个 tag),只按宽度。移动端设计宽 `390`,TV 设计宽 `1280`。禁止改用 `androidx.window` / WindowSizeClass / Compose Adaptive。细则见 `dwy-android-core`「屏幕适配」。
88
+
87
89
  ```groovy
88
90
  // 库 foo 的 3.x 已迁 androidx,锁在最后支持 support 的 2.8.0(升级需先整体迁 androidx,见 issue #1234)
89
91
  implementation 'com.foo:bar:2.8.0'
@@ -1,5 +1,14 @@
1
1
  ---
2
- description: Docker 通用规则(镜像固定版本、容器内存上限、日志大小限制、配置变更重启姿势)
2
+ description: Docker 通用规则(镜像固定版本、容器内存上限、日志大小限制、配置变更重启姿势)。只在容器和 CI 清单上注入。
3
+ paths:
4
+ - "**/Dockerfile"
5
+ - "**/Dockerfile.*"
6
+ - "**/docker-compose*.yml"
7
+ - "**/docker-compose*.yaml"
8
+ - "**/compose.yml"
9
+ - "**/compose.yaml"
10
+ - "**/.github/workflows/**"
11
+ - "**/k8s/**"
3
12
  ---
4
13
 
5
14
  # Docker 通用规则
@@ -32,6 +41,8 @@ image: postgres:16.4
32
41
  image: postgres:16.4@sha256:xxxxx
33
42
  ```
34
43
 
44
+ 正例里的版本号只示范具体 tag 的写法。该装哪一个,以当次查到的 tag 列表为准,不从本规则抄数字。
45
+
35
46
  ## 二、Docker 容器配置要求
36
47
 
37
48
  ### 1. 内存设置
@@ -46,6 +57,8 @@ image: postgres:16.4@sha256:xxxxx
46
57
  - **必须**限制单个日志文件大小与轮转文件数量
47
58
  - **禁止**不设上限长期写入,避免占满磁盘
48
59
 
60
+ 具体数字以当次测到的内存占用和日志增速为准。没有实测的新环境才用经验起点,服务跑起来后重测。下面示例只示范字段怎么写。
61
+
49
62
  ### compose 示例
50
63
 
51
64
  ```yaml
@@ -120,3 +133,12 @@ docker compose exec backend printenv | grep DOLPHINDB_FACTOR__
120
133
  ### 一句话
121
134
 
122
135
  > 配置改了别 restart,`up -d --force-recreate`。验证别只看连通,`printenv` 看容器内真值。
136
+
137
+ ## 四、镜像源
138
+
139
+ 文档和脚本里的镜像站 URL 是当前候选,不是指定赢家。
140
+
141
+ - 先探测即将真正使用的路径(包索引、发行版 `Release`、镜像 manifest)。超时、证书错误、状态不对、没有目标包或 tag 的源丢掉
142
+ - 在剩下的源里用本次延迟最低的。连通不等于可用
143
+ - 私服、用户指定的源不参与公网竞速
144
+ - 候选全部不成立时另找可用源,再测一次。测速结果不写回本规则
@@ -125,6 +125,7 @@ class App extends ConsumerWidget {
125
125
 
126
126
  ```yaml
127
127
  # pubspec.yaml
128
+ # 下面的 caret 只示范当时能编过的下限,不是该装的版本。装之前按 dwy-dependency-choice 现查。
128
129
  dependencies:
129
130
  flutter_riverpod: ^2.6.0
130
131
  riverpod_annotation: ^2.6.0
@@ -337,6 +338,7 @@ shared/widgets/ # snake_case
337
338
 
338
339
  ```yaml
339
340
  # pubspec.yaml
341
+ # 版本号是写法示例。装之前按 dwy-dependency-choice 现查,不要抄这里的数字。
340
342
  dependencies:
341
343
  freezed_annotation: ^2.4.0
342
344
  json_annotation: ^4.9.0
@@ -72,7 +72,7 @@ testWidgets('登录页面渲染正确', (tester) async {
72
72
 
73
73
  ## 四、Mock 工具
74
74
 
75
- 统一使用 `mocktail`(不是 mockito):
75
+ 统一使用 `mocktail`(不是 mockito)。`^1.0.0` 只示范写法,实际版本按 `dwy-dependency-choice` 现查。
76
76
 
77
77
  ```yaml
78
78
  dev_dependencies:
@@ -172,10 +172,13 @@ private async safe(name, step) {
172
172
 
173
173
  ## 9. DevEco 命令行构建(绕开 IDE,拿带 File:行:列 的错误)
174
174
 
175
+ 路径用本机 DevEco 根,不要抄某台 Mac 的默认安装位置。先找到本机安装目录,再拼 `tools/node/bin/node`、`tools/hvigor/bin/hvigorw.js` 和该布局里的 sdk。找不到就问用户。
176
+
175
177
  ```bash
176
- NODE=/Applications/DevEco-Studio.app/Contents/tools/node/bin/node
177
- HVIGOR=/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js
178
- export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk # 停 daemon 后必设
178
+ # <DEVECO> = 本机 DevEco 根。macOS 默认安装常见 /Applications/DevEco-Studio.app,不是唯一位置。
179
+ NODE=<DEVECO>/Contents/tools/node/bin/node
180
+ HVIGOR=<DEVECO>/Contents/tools/hvigor/bin/hvigorw.js
181
+ export DEVECO_SDK_HOME=<DEVECO>/Contents/sdk # 停 daemon 后必设
179
182
 
180
183
  # 配置校验
181
184
  $NODE $HVIGOR --sync -p product=default --daemon
@@ -205,14 +208,15 @@ $NODE $HVIGOR --mode module -p module=entry@default -p product=default -p pageTy
205
208
  - **正确做法**:DevEco → Project Structure → Signing Configs 自动签名写回工程;**模拟器**可装 unsigned;**真机**必须签名。
206
209
  - **适用**:CLI / CI 打包。
207
210
 
208
- ## 12. 本地模拟器(HVD)命令行:`-hvd` + hdc `127.0.0.1:5555`
211
+ ## 12. 本地模拟器(HVD)命令行:`-hvd` + 本次 `hdc list targets`
209
212
 
210
213
  - **现象**:不知如何脱离 IDE 起模拟器、装 HAP。
211
214
  - **正确做法**:
212
- - 启动:`/Applications/DevEco-Studio.app/Contents/tools/emulator/Emulator -hvd "Pura 90"`(名见 `~/.Huawei/Emulator/deployed/*.ini`)。
213
- - 设备:`hdc list targets` → 常见 `127.0.0.1:5555`。
214
- - 安装:`hdc -t 127.0.0.1:5555 install entry/.../entry-default-unsigned.hap`。
215
- - 启动:`hdc -t 127.0.0.1:5555 shell aa start -a EntryAbility -b <bundleName>`。
215
+ - 模拟器名读 `~/.Huawei/Emulator/deployed/*.ini`,不要写死某一台机型。
216
+ - 启动:`<DEVECO>/Contents/tools/emulator/Emulator -hvd "<ini 里的名字>"`。`<DEVECO>` 与第 9 节同一台机器的安装根。
217
+ - 设备:`hdc list targets` 的输出就是 `-t`。`127.0.0.1:5555` 只是本机常见先验,列表里没有就用列表里的那个。
218
+ - 安装:`hdc -t <本次 target> install entry/.../entry-default-unsigned.hap`。
219
+ - 启动:`hdc -t <本次 target> shell aa start -a EntryAbility -b <bundleName>`。
216
220
  - **适用**:无 GUI / 脚本化调试。
217
221
 
218
222
  ## 13. 模拟器验不了局域网 TCP / mDNS / 设备发现
@@ -12,52 +12,11 @@ paths:
12
12
 
13
13
  ## 一、项目结构与业务聚合(强制)
14
14
 
15
- ### 核心原则
15
+ **仓库四层(`app` / `features` / `biz_foundation` / `foundation`)、Feature 互不依赖、uv 根锁定版本 → 见 `dwy-python-layering`。** 本节只约定 Feature **域内**文件名。
16
16
 
17
- **按功能模块(feature / domain)聚合,禁止按技术层散落。** 同一业务功能的 router / schema / service / model / dependency 必须集中在同一个功能目录下;**禁止**全局存在 `routers/` / `services/` / `models/` / `schemas/` 等"技术层"顶级目录,把不相关功能的同类型文件强行揉到一起。
18
-
19
- ### 为什么
20
-
21
- - **跨项目迁移**:复制单个功能目录即可携带该功能全部代码;技术层散落需在 4+ 目录里翻找
22
- - **可读性**:阅读功能时所有相关代码在同一目录,无需跨目录跳转
23
- - **变更影响域清晰**:一次改动 diff 集中,code review 更高效
24
- - **删除友好**:删功能时整个目录删掉即可,不会留下孤儿文件
25
-
26
- ### 标准结构(参考)
27
-
28
- ```
29
- app/
30
- ├── users/ # 用户功能(一个业务模块自成一个目录)
31
- │ ├── __init__.py
32
- │ ├── router.py # FastAPI 路由
33
- │ ├── schemas.py # Pydantic 请求 / 响应模型
34
- │ ├── service.py # 业务逻辑
35
- │ ├── models.py # ORM 模型
36
- │ ├── dependencies.py # 该功能专用 Depends(可选)
37
- │ └── exceptions.py # 该功能专用异常(可选)
38
- ├── orders/
39
- │ ├── router.py
40
- │ ├── schemas.py
41
- │ ├── service.py
42
- │ └── models.py
43
- ├── core/ # 跨功能共享:配置 / 全局中间件 / 启动逻辑
44
- │ ├── config.py
45
- │ ├── middleware.py
46
- │ └── lifespan.py
47
- └── main.py # 注册各功能 router
48
- ```
49
-
50
- ### 强制规则
51
-
52
- - **禁止**顶级 `routers/` / `services/` / `models/` / `schemas/` 把不同功能的同类文件混放
53
17
  - 一个功能目录内文件名固定:`router.py` / `schemas.py` / `service.py` / `models.py`(单数,不带功能前缀)
54
- - 跨功能真正共享的代码放 `core/` 或 `shared/`,**禁止**为"可能复用"把单一功能逻辑提前抽离
55
18
  - 单文件超过约 400 行时再考虑拆分(如 `service.py` → `services/create.py` + `services/query.py`),**禁止**未到规模就预拆分
56
- - 功能间引用通过明确 import,**禁止**循环依赖
57
- - 测试镜像功能聚合结构:`tests/users/test_router.py` / `tests/users/test_service.py`(详见第九节)
58
-
59
- ### 例外
60
- - 通用基础设施代码(如基础框架库内部)可保持技术层结构
19
+ - 测试镜像功能聚合:`tests/features/orders/test_router.py`(详见第九节)
61
20
 
62
21
  ---
63
22
 
@@ -179,6 +138,7 @@ class UserResponse(UserBase):
179
138
  | 分页 page_size | 必须有上限(≤ 100) |
180
139
  | 字符串 | 必须显式 `max_length` |
181
140
  | 文件上传 | 后端二次校验大小 + magic bytes,**禁止**只信任前端或 Nginx |
141
+ | 图片上传 | 同时校验文件大小和像素宽高上限,防止 decompression bomb |
182
142
 
183
143
  ---
184
144
 
@@ -466,6 +426,7 @@ AI 编写或审查后端代码时,**必须**检查以下违规模式,按严
466
426
  |--------|---------|---------|
467
427
  | 重复造轮子 | 自造异常基类 / 自封装统一响应 / 散落手写 bcrypt 或 JWT 调用 / 自建 Redis 连接池 等本应走项目级统一模块的能力 | 高 |
468
428
  | 技术层散落 | 顶级出现 `routers/` / `services/` / `models/` / `schemas/` 等技术层目录把不同功能文件混放,未按功能聚合 | 高 |
429
+ | 分层依赖 | Feature `from app.` / import 另一 Feature / 源码 `from foundation.`;成员包写死第三方版本。细则见 `dwy-python-layering` | 高 |
469
430
  | 测试与源码混放 | 测试文件放在源码目录中(如 `app/users/test_router.py`),未独立到测试目录 | 高 |
470
431
  | 密码明文 | `password` 字段出现在 Response Schema | **致命 → STOP** |
471
432
  | 密码弱存储 | 使用 MD5 / SHA 而非 bcrypt | **致命 → STOP** |