框架
快来分享你的内容吧~
- 2025-09-01·Java后端
- 2025-07-17·管理员 | 首席助手 @面试鸭
- 2025-06-13
花了几天时间 整了个脚手架工具
考虑到我们团队后续将陆续开展一些前后端项目的开发,且这些项目均需从零开始搭建,为此我专门开发了一套脚手架工具。该工具支持多种开发语言,集成了 Git 仓库管理和发布流程等功能,方便项目快速初始化,后续会考虑使用GitHub Action功能来打包构建项目结合其他工具进行自动化部署。 项目地址:https://github.com/Zhengke0110/zhengke-cli 该工具基于 [Nx](https://nx.dev/docs/getting-started/intro) 构建,目前已在本地进行过测试,整体使用体验良好。支持从 npm 拉取模板,也支持从 GitHub 拉取模板(需在个人或组织账号下创建模板仓库后进行拉取)。 该工具会持续维护,如在使用过程中遇到任何问题,欢迎提交 Issue 或在本帖下方留言,我会及时跟进处理。 **cli命令help帮助**  **初始化命令 help帮助**  **执行初始化命令(使用npm上的模板)**  **执行初始化命令(使用GitHub上的sprinboot模板)**   **git init命令展示**   **git commit命令展示**  **git publish命令展示**  
nz-select小坑
## 🎯 问题场景 在响应式或自适应布局中,当页面尺寸变化(如窗口缩放、设备旋转)时,`nz-select` 的下拉选项(`dropdown`)未及时更新位置,导致其与 `select` 触发器**错位**。 ## **解决思路** 希望通过监听页面尺寸变化,**动态获取 `option` 下拉层的 `top` 和 `left` 值**,手动调整位置以保证对齐。 关于 `nz-select` 组件在使用 `(nzOpenChange)` 事件时,通过 `className` 获取 DOM 元素后发现 `HTMLCollection` 的长度与预期不一致的问题,以下是常见原因的总结: ------ ### 📌 问题核心: 在 `(nzOpenChange)` 回调中通过 `document.getElementsByClassName()` 或类似方法获取 `.ant-select-item`(或其他 nz-option 对应的类名)时,返回的 DOM 元素数量与实际可见的 `<nz-option>` 数量不一致。 ------ ### ✅ 原因分析总结: #### 1. **DOM 渲染异步滞后(最常见)(工作中解决的)** - `(nzOpenChange)` 事件触发时,下拉菜单 **尚未完成渲染**。 - Angular 的变更检测和 `nz-select` 内部的下拉层(`cdk-overlay`)是异步渲染的。 - 此时通过 `getElementsByClassName` 查询,可能查不到任何 `.ant-select-item` 元素,或只查到部分。 > 🚫 错误做法: > > ```ts > onOpenChange(open: boolean) { > if (open) { > const items = document.getElementsByClassName('ant-select-item'); > console.log(items.length); // 可能为 0,即使有多个 nz-option > } > } > ``` > ✅ 正确做法:使用 `setTimeout` 或 `Promise.resolve().then()` 延迟查询,等待 DOM 更新。 > > ```ts > onOpenChange(open: boolean) { > if (open) { > setTimeout(() => { > const items = document.getElementsByClassName('ant-select-item'); > console.log(items.length); // 此时更接近真实数量 > }, 0); > } > } > ``` ------ #### 2. **虚拟滚动(Virtual Scrolling)机制** - `nz-select` 在选项较多时默认启用 **虚拟滚动**,只渲染可视区域内的 `<nz-option>`。 - 实际 DOM 中存在的 `.ant-select-item` 元素数量远小于总 `<nz-option>` 数量。 - 因此 `HTMLCollection` 长度远小于选项总数。 > 🔍 例如:有 100 个选项,但只渲染 8 个可见项 → `getElementsByClassName` 返回长度为 8。 > ⚙️ 解决方案:可通过设置 `[nzVirtual]="false"` 关闭虚拟滚动(不推荐大量数据时使用)。 ------ #### 3. **类名不准确或层级错误** - 使用的 `className` 不正确,如误用 `.ant-select-dropdown` 而非 `.ant-select-item`。 - 或未限定查询范围,导致匹配到其他组件的同名元素。 > ✅ 建议:通过 DevTools 确认正确的类名,如: > > > > ```ts > const dropdown = document.querySelector('.ant-select-dropdown'); > const items = dropdown?.getElementsByClassName('ant-select-item'); > ``` ------ #### 4. **动态数据加载延迟** - 如果 `<nz-option>` 是通过异步请求动态生成的,在 `(nzOpenChange)` 触发时尚未加载完成。 - 导致此时 DOM 中没有或只有部分选项。 > ✅ 应在数据加载完成后,再进行 DOM 查询,或监听数据变化事件。 ------ #### 5. **CDK Overlay 渲染到 body 外层** - `nz-select` 的下拉菜单使用 `cdk-overlay` 渲染,默认挂载在 `<body>` 下,**不在组件视图内**。 - 使用 `@ViewChild` 查询可能失败,`document` 查询需注意上下文。 > ✅ 建议:使用 `document` 查询,并确保选择器足够精确。 ------ ### ✅ 推荐解决方案 ```ts onOpenChange(open: boolean) { if (open) { // 等待下一轮事件循环,确保 DOM 渲染完成 Promise.resolve().then(() => { const items = document.getElementsByClassName('ant-select-item'); console.log('实际渲染的选项数量:', items.length); }); } } ``` 或使用更稳定的 `MutationObserver` / `@ViewChildren` + `QueryList` 监听。 ------ ### ✅ 更佳实践(避免直接操作 DOM) - 使用`@ViewChildren`和`QueryList`监听`NzOptionComponent` ```ts @ViewChildren(NzOptionComponent) options: QueryList<NzOptionComponent>; ngAfterViewInit() { this.options.changes.subscribe(() => { console.log('选项数量:', this.options.length); }); } ``` - 避免依赖 DOM 查询,优先使用 Angular 组件模型。 ------ ### 总结 | 原因 | 是否常见 | 解决方案 | | ---------------- | -------- | -------------------------------------- | | DOM 渲染异步 | ⭐⭐⭐⭐⭐ | `setTimeout` / `Promise.then` 延迟查询 | | 虚拟滚动 | ⭐⭐⭐⭐ | 设置 `[nzVirtual]="false"` 或理解机制 | | 类名错误 | ⭐⭐ | 检查 DevTools 确认正确类名 | | 数据未加载 | ⭐⭐⭐ | 等待数据加载完成后再查询 | | Overlay 渲染位置 | ⭐⭐ | 使用 `document` 查询,注意作用域 | 📌 **建议:优先使用 Angular 的 `@ViewChildren` + `QueryList`,避免直接操作 DOM。**
springboot+vue+小程序做的积分兑换系统
# 用户端功能 1. 用户端具有注册、登录、退出功能。 2. 用户端能实现对个人信息的编辑、自动关联所属村庄。 3. 用户端可以查看当前积分、历史记录(按“获取/消费”分类)、本村积分规则。 4. 用户端可以报名活动和会议(村级/乡镇级)、进行行为申报(拍照上传证明,如垃圾分类)。 5. 用户可以查看本村及跨村通用商品(标注“仅限本村”“全乡镇通用”),提交兑换申请。 6. 用户可以收到消息通知:积分到账提醒、活动报名成功、兑换审核结果、乡镇/村级公告。 # 管理端功能 7. 管理端实现对所有用户信息进行增删改等操作。 8. 管理员端审核村民申报的行为(通过/驳回,填写理由)、批量添加活动参与积分。 9. 管理员端进行活动管理,发布本村活动(设置时间、地点、积分奖励)、查看报名列表。 10. 管理员端进行商品管理,上架/下架本村兑换商品(设置积分值、库存、兑换规则)。 11. 管理员端通过数据查看本村积分排行、村民参与度分析、积分消耗TOP商品。 12. 后端管理员可以新增/编辑村庄信息、配置各村积分规则模板、查看各村数据汇总。 13. 后端管理员进行发布乡镇级活动(全乡镇村民可参与)、设置统一积分奖励。 14. 后端管理员可以与政务系统同步村民户籍信息、与连锁商户对接商品库存。 15. 后端管理员可以查看乡镇积分总览、各村活跃度排名、积分规则效果分析。                                 
Android 自定义系统服务初始化流程详解
# Android 自定义系统服务初始化流程详解 ## 1. 概述 自定义系统服务的初始化是一个系统化的过程,涉及多个组件和阶段的协同工作。下面是完整的初始化流程图和详细说明: ```mermaid flowchart TD A[定义AIDL接口] --> B[实现服务Stub] B --> C[创建SystemService] C --> D[在SystemServer中注册] D --> E[Context中添加常量] E --> F[权限配置] F --> G[SELinux策略] G --> H[构建配置] H --> I[系统启动时初始化] I --> J[启动阶段回调] J --> K[服务就绪] ``` ## 2. 详细初始化流程 ### 2.1 定义服务接口 (AIDL) 首先创建AIDL接口文件,这是定义服务契约的基础: ```java // frameworks/base/core/java/android/os/ICustomService.aidl package android.os; import android.annotation.Nullable; import android.annotation.NonNull; /** * @hide Custom service interface for advanced system operations */ interface ICustomService { // 示例方法:设置配置值 boolean setConfiguration(in String key, in String value); // 示例方法:获取配置值 String getConfiguration(in String key); // 示例方法:执行特定操作 int performOperation(in int operationId); } ``` ### 2.2 实现服务核心逻辑 创建服务的具体实现,继承自Stub类: ```java // frameworks/base/services/core/java/com/android/server/CustomService.java package com.android.server; import android.content.Context; import android.os.ICustomService; import android.util.Slog; import android.util.ArrayMap; import java.util.Map; public class CustomService extends ICustomService.Stub { private static final String TAG = "CustomService"; private final Context mContext; private final Map<String, String> mConfigurations = new ArrayMap<>(); // 单例实例 private static CustomService sInstance; public CustomService(Context context) { mContext = context; sInstance = this; Slog.i(TAG, "CustomService initialized"); // 初始化默认配置 initializeDefaultConfigurations(); } public static CustomService getInstance() { return sInstance; } private void initializeDefaultConfigurations() { mConfigurations.put("timeout", "5000"); mConfigurations.put("retry_count", "3"); mConfigurations.put("debug_mode", "false"); } @Override public boolean setConfiguration(String key, String value) { // 权限检查 mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_CUSTOM_SERVICE, "Requires MANAGE_CUSTOM_SERVICE permission"); if (key == null || value == null) { Slog.w(TAG, "setConfiguration: null key or value"); return false; } mConfigurations.put(key, value); Slog.d(TAG, "Configuration set: " + key + " = " + value); return true; } @Override public String getConfiguration(String key) { // 权限检查 mContext.enforceCallingOrSelfPermission( android.Manifest.permission.USE_CUSTOM_SERVICE, "Requires USE_CUSTOM_SERVICE permission"); return mConfigurations.get(key); } @Override public int performOperation(int operationId) { // 权限检查 mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_CUSTOM_SERVICE, "Requires MANAGE_CUSTOM_SERVICE permission"); Slog.i(TAG, "Performing operation: " + operationId); // 执行具体操作逻辑 switch (operationId) { case 1: return operation1(); case 2: return operation2(); default: Slog.w(TAG, "Unknown operation ID: " + operationId); return -1; } } private int operation1() { // 操作1的具体实现 return 100; } private int operation2() { // 操作2的具体实现 return 200; } // 系统启动阶段回调 public void onSystemReady() { Slog.i(TAG, "System is ready, performing final initialization"); // 系统就绪后的初始化操作 } // 启动完成回调 public void onBootCompleted() { Slog.i(TAG, "Boot completed, starting background operations"); // 启动后台任务等 } } ``` ### 2.3 创建SystemService包装器 为了更好的集成到系统服务生命周期中,创建SystemService包装器: ```java // frameworks/base/services/core/java/com/android/server/CustomSystemService.java package com.android.server; import android.content.Context; import android.util.Slog; import com.android.server.SystemService; public class CustomSystemService extends SystemService { private static final String TAG = "CustomSystemService"; private final CustomService mCustomService; public CustomSystemService(Context context) { super(context); mCustomService = new CustomService(context); Slog.i(TAG, "CustomSystemService created"); } @Override public void onStart() { Slog.i(TAG, "Publishing CustomService"); publishBinderService(Context.CUSTOM_SERVICE, mCustomService); publishLocalService(CustomService.class, mCustomService); } @Override public void onBootPhase(int phase) { Slog.d(TAG, "Boot phase: " + phase); if (phase == PHASE_SYSTEM_SERVICES_READY) { // 系统服务就绪阶段 mCustomService.onSystemReady(); } else if (phase == PHASE_BOOT_COMPLETED) { // 启动完成阶段 mCustomService.onBootCompleted(); } } @Override public void onStartUser(int userHandle) { Slog.i(TAG, "Starting for user: " + userHandle); // 用户启动时的处理 } @Override public void onUnlockUser(int userHandle) { Slog.i(TAG, "Unlocking user: " + userHandle); // 用户解锁时的处理 } @Override public void onStopUser(int userHandle) { Slog.i(TAG, "Stopping user: " + userHandle); // 用户停止时的处理 } } ``` ### 2.4 在SystemServer中注册服务 在SystemServer的适当阶段启动自定义服务: ```java // frameworks/base/services/java/com/android/server/SystemServer.java public final class SystemServer { // ... private void startBootstrapServices() { // ... 其他引导服务 // 启动自定义服务(如果需要早期启动) try { traceBeginAndSlog("StartCustomService"); mSystemServiceManager.startService(CustomSystemService.class); traceEnd(); } catch (Throwable e) { reportWtf("starting CustomService", e); } } private void startOtherServices() { // ... 其他服务 // 或者在这里启动(如果不需要早期启动) try { traceBeginAndSlog("StartCustomService"); mSystemServiceManager.startService(CustomSystemService.class); traceEnd(); } catch (Throwable e) { reportWtf("starting CustomService", e); } // ... } } ``` ### 2.5 在Context中添加服务常量 ```java // frameworks/base/core/java/android/content/Context.java public abstract class Context { // ... /** * Use with {@link #getSystemService} to retrieve a {@link android.os.ICustomService} * for managing custom system operations. * * @see #getSystemService * @hide */ public static final String CUSTOM_SERVICE = "custom_service"; // ... } ``` ### 2.6 添加权限定义 ```xml <!-- frameworks/base/core/res/AndroidManifest.xml --> <permission android:name="android.permission.MANAGE_CUSTOM_SERVICE" android:protectionLevel="signature|privileged" android:label="@string/permlab_manageCustomService" android:description="@string/permdesc_manageCustomService" /> <permission android:name="android.permission.USE_CUSTOM_SERVICE" android:protectionLevel="normal" android:label="@string/permlab_useCustomService" android:description="@string/permdesc_useCustomService" /> ``` ### 2.7 添加SELinux策略 ```selinux # device/yourvendor/yourdevice/sepolicy/common/service_contexts custom_service u:object_r:custom_service:s0 # device/yourvendor/yourdevice/sepolicy/common/service.te type custom_service, system_api_service, system_server_service, service_manager_type; # device/yourvendor/yourdevice/sepolicy/common/system_server.te allow system_server custom_service:service_manager { add find }; allow system_server custom_service:binder { call transfer }; # device/yourvendor/yourdevice/sepolicy/common/your_app.te allow your_app custom_service:service_manager find; allow your_app custom_service:binder call; ``` ### 2.8 构建配置 在对应的Android.bp或Makefile中添加构建配置: ```bp // frameworks/base/Android.bp android_library { name: "custom-service", srcs: [ "services/core/java/com/android/server/CustomService.java", "services/core/java/com/android/server/CustomSystemService.java", ], libs: [ "android.app", "android.hardware", ], static_libs: [ "services.core", ], } ``` ## 3. 初始化阶段详解 ### 3.1 阶段1:类加载和实例化 ```java // SystemServiceManager启动服务时发生: CustomSystemService service = new CustomSystemService(context); // -> 创建CustomSystemService实例 // -> 创建CustomService实例 // -> 执行CustomService构造函数中的初始化 ``` ### 3.2 阶段2:服务发布 ```java // onStart()方法被调用时: publishBinderService(Context.CUSTOM_SERVICE, mCustomService); // -> 服务注册到ServiceManager // -> 现在其他进程可以通过ServiceManager.getService("custom_service")获取服务 ``` ### 3.3 阶段3:启动阶段回调 ```java // 系统启动过程中,不同阶段会调用onBootPhase() onBootPhase(PHASE_SYSTEM_SERVICES_READY); // 系统服务就绪 onBootPhase(PHASE_ACTIVITY_MANAGER_READY); // ActivityManager就绪 onBootPhase(PHASE_THIRD_PARTY_APPS_CAN_START); // 第三方应用可启动 onBootPhase(PHASE_BOOT_COMPLETED); // 启动完成 ``` ### 3.4 阶段4:用户相关回调 ```java // 多用户相关回调 onStartUser(int userHandle); // 用户启动 onUnlockUser(int userHandle); // 用户解锁 onStopUser(int userHandle); // 用户停止 ``` ## 4. 客户端访问方式 ### 4.1 系统组件访问方式 ```java // 通过ServiceManager直接访问 ICustomService customService = ICustomService.Stub.asInterface( ServiceManager.getService(Context.CUSTOM_SERVICE)); // 或者通过Context获取 CustomManager customManager = (CustomManager) context.getSystemService( Context.CUSTOM_SERVICE); ``` ### 4.2 创建CustomManager客户端 ```java // frameworks/base/core/java/android/os/CustomManager.java package android.os; import android.content.Context; import android.annotation.SystemService; import android.annotation.RequiresPermission; @SystemService(Context.CUSTOM_SERVICE) public class CustomManager { private final ICustomService mService; public CustomManager(Context context, ICustomService service) { mService = service; } @RequiresPermission(android.Manifest.permission.USE_CUSTOM_SERVICE) public String getConfiguration(String key) { try { return mService.getConfiguration(key); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @RequiresPermission(android.Manifest.permission.MANAGE_CUSTOM_SERVICE) public boolean setConfiguration(String key, String value) { try { return mService.setConfiguration(key, value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } ``` ## 5. 调试和验证 ### 5.1 服务状态检查 ```bash # 检查服务是否注册 adb shell service list | grep custom # 检查服务运行状态 adb shell dumpsys custom_service # 检查权限设置 adb shell dumpsys package | grep -A5 -B5 custom ``` ### 5.2 日志监控 ```java // 在服务中添加详细的日志记录 Slog.d(TAG, "Service method called with params: " + parameters); Slog.w(TAG, "Unexpected condition: " + condition); Slog.e(TAG, "Error occurred: ", exception); ``` ## 6. 常见问题排查 1. **服务未注册**:检查SystemServer中添加服务的代码是否正确执行 2. **权限拒绝**:检查SELinux策略和权限定义 3. **绑定失败**:检查AIDL接口定义是否一致 4. **启动顺序问题**:确保服务在依赖的服务之后启动
Android SystemServer 中 Service 的创建和启动方式
今天导师给我将讲了一些如何新建一个系统服务,以及如何去初始化。 # Android SystemServer 中 Service 的创建和启动方式 在 Android 系统中,SystemServer 是系统服务的核心进程,负责启动和管理各种系统服务。以下是 SystemServer 中服务创建和启动的详细方式: ## 1. SystemServer 概述 SystemServer 是 Android 系统启动过程中的关键组件,它运行在 `system_server` 进程中,负责启动和管理所有核心系统服务。 ## 2. 服务启动流程 ### 2.1 主要启动阶段 SystemServer 的服务启动分为几个主要阶段: ```java // frameworks/base/services/java/com/android/server/SystemServer.java private void run() { // 1. 启动引导服务 (Bootstrap Services) startBootstrapServices(); // 2. 启动核心服务 (Core Services) startCoreServices(); // 3. 启动其他服务 (Other Services) startOtherServices(); // 4. 系统就绪后的回调 startSystemUi(context); systemReady(); } ``` ### 2.2 引导服务启动 ```java // frameworks/base/services/java/com/android/server/SystemServer.java private void startBootstrapServices() { // 启动 ActivityManagerService mActivityManagerService = ActivityManagerService.Lifecycle.startService( mSystemServiceManager, atm); mActivityManagerService.setSystemProcess(); // 启动 PowerManagerService mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class); // 启动 PackageManagerService mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // 更多引导服务... } ``` ### 2.3 核心服务启动 ```java private void startCoreServices() { // 启动 BatteryService mSystemServiceManager.startService(BatteryService.class); // 启动 UsageStatsService mSystemServiceManager.startService(UsageStatsService.class); mActivityManagerService.setUsageStatsManager( LocalServices.getService(UsageStatsManagerInternal.class)); // 启动 WebViewUpdateService mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class); // 更多核心服务... } ``` ### 2.4 其他服务启动 ```java private void startOtherServices() { // 启动 WindowManagerService wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore, new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager); ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); // 启动 NotificationManagerService mSystemServiceManager.startService(NotificationManagerService.class); // 启动自定义服务(如你的 MDM 服务) try { traceBeginAndSlog("StartMdmPolicyManager"); mdmPolicyManager = new MdmPolicyManagerService(context); ServiceManager.addService(Context.MDM_POLICY_SERVICE, mdmPolicyManager); traceEnd(); } catch (Throwable e) { reportWtf("starting Mdm Policy Manager", e); } // 更多其他服务... } ``` ## 3. 服务创建方式 ### 3.1 使用 SystemServiceManager 启动 这是推荐的方式,适用于继承自 `SystemService` 的服务: ```java // 在 SystemServer 中 mSystemServiceManager.startService(YourService.class); // 服务类定义 public class YourService extends SystemService { public YourService(Context context) { super(context); } @Override public void onStart() { // 服务启动逻辑 publishBinderService(Context.YOUR_SERVICE, new YourBinder()); } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_BOOT_COMPLETED) { // 启动完成后的操作 } } } ``` ### 3.2 直接实例化并注册 对于不继承 `SystemService` 的服务: ```java // 创建服务实例 YourService yourService = new YourService(context); // 添加到 ServiceManager ServiceManager.addService(Context.YOUR_SERVICE, yourService); // 或者使用带权限的添加方式 ServiceManager.addService(Context.YOUR_SERVICE, yourService, false, ServiceManager.DUMP_FLAG_PRIORITY_DEFAULT); ``` ### 3.3 使用静态 main 方法 某些服务有静态的 `main()` 方法: ```java // 服务类中的静态方法 public static YourService main(Context context) { YourService service = new YourService(context); ServiceManager.addService(Context.YOUR_SERVICE, service); return service; } // 在 SystemServer 中调用 YourService.main(mSystemContext); ``` ## 4. 服务生命周期管理 ### 4.1 启动阶段(Boot Phases) 系统服务可以在不同的启动阶段执行初始化: ```java public class YourService extends SystemService { // ... @Override public void onBootPhase(int phase) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { // 第三方应用可以启动时的初始化 } else if (phase == PHASE_BOOT_COMPLETED) { // 系统启动完成后的操作 } } } ``` ### 4.2 系统就绪回调 ```java private void systemReady() { // 通知所有服务系统已就绪 mActivityManagerService.systemReady(() -> { // 系统就绪后的操作 }, BOOT_TIMINGS_TRACE_LOG); } ``` ## 5. 自定义服务示例 以下是在 SystemServer 中添加自定义服务的完整示例: ### 5.1 服务接口定义 (AIDL) ```java // frameworks/base/core/java/android/app/IMyCustomService.aidl package android.app; interface IMyCustomService { void doSomething(int param); int getSomething(); } ``` ### 5.2 服务实现 ```java // frameworks/base/services/core/java/com/android/server/MyCustomService.java package com.android.server; import android.app.IMyCustomService; import android.content.Context; import android.os.IBinder; import android.util.Slog; public class MyCustomService extends IMyCustomService.Stub { private static final String TAG = "MyCustomService"; private final Context mContext; public MyCustomService(Context context) { mContext = context; Slog.i(TAG, "MyCustomService created"); } @Override public void doSomething(int param) { Slog.d(TAG, "doSomething called with param: " + param); // 实现具体功能 } @Override public int getSomething() { Slog.d(TAG, "getSomething called"); return 42; // 示例返回值 } } ``` ### 5.3 在 SystemServer 中启动服务 ```java // frameworks/base/services/java/com/android/server/SystemServer.java public final class SystemServer { // ... private void startOtherServices() { // ... // 启动自定义服务 try { traceBeginAndSlog("StartMyCustomService"); MyCustomService myCustomService = new MyCustomService(context); ServiceManager.addService(Context.MY_CUSTOM_SERVICE, myCustomService); traceEnd(); } catch (Throwable e) { reportWtf("starting My Custom Service", e); } // ... } } ``` ### 5.4 在 Context 中定义服务常量 ```java // frameworks/base/core/java/android/content/Context.java public abstract class Context { // ... public static final String MY_CUSTOM_SERVICE = "my_custom_service"; // ... } ``` ## 6. 注意事项 1. **启动顺序**:服务的启动顺序很重要,依赖其他服务的服务应该在依赖服务之后启动 2. **异常处理**:使用 try-catch 块捕获服务启动过程中的异常 3. **性能考虑**:避免在服务启动过程中执行耗时操作 4. **权限控制**:确保服务有适当的权限检查 5. **进程间通信**:如果服务需要跨进程访问,确保正确实现 Binder 接口 ## 7. 调试技巧 1. 使用 `dumpsys` 命令检查服务状态: ```bash adb shell dumpsys my_custom_service ``` 2. 查看服务列表: ```bash adb shell service list ``` 3. 检查系统日志: ```bash adb logcat -s SystemServer ``` 通过以上方式,你可以在 Android SystemServer 中成功创建和启动自定义系统服务。
学习过程中遇到的疑惑问了一下ai,分享出来一起学习一下
在 Android 框架层开发 `DreamInterfaceManager`、`DreamInterfaceService` 和 `DreamInterfaceManager.aidl` 时,它们之间的逻辑关系涉及 **Binder 跨进程通信(IPC)** 和 **服务管理**。这些组件通常用于系统级功能(如设备管理、策略控制等)。 --- ## **1. 组件职责与逻辑关系** | 组件 | 作用 | 关键逻辑 | |------|------|---------| | **`DreamInterfaceManager.aidl`** | **定义 Binder 接口**(客户端 ↔ 服务端通信协议) | 声明远程调用的方法(如 `setWifiPolicy(int)`),供客户端和服务端实现。 | | **`DreamInterfaceService.java`** | **系统服务实现**(运行在 `system_server` 进程) | 继承 `Service` 或 `SystemService`,实现 AIDL 接口的具体逻辑(如调用底层 HAL)。 | | **`DreamInterfaceManager.java`** | **客户端代理**(供 App 或系统模块调用) | 封装 Binder 调用,提供对外的 Java API(内部通过 `IDreamInterface` 调用 `DreamInterfaceService`)。 | --- ## **2. 调用流程(以 `setWifiPolicy(int)` 为例)** ```mermaid sequenceDiagram App/Framework->>DreamInterfaceManager: setWifiPolicy(1) DreamInterfaceManager->>DreamInterfaceService: Binder IPC (transact) DreamInterfaceService->>HAL/Driver: 执行底层操作(如 Wifi 策略设置) HAL/Driver-->>DreamInterfaceService: 返回结果 DreamInterfaceService-->>DreamInterfaceManager: Binder 返回结果 DreamInterfaceManager-->>App/Framework: 返回成功/失败 ``` --- ## **3. 关键实现步骤** ### **(1) 定义 AIDL 接口 (`DreamInterfaceManager.aidl`)** ```java // frameworks/base/core/java/android/server/DreamInterfaceManager.aidl package android.server; interface IDreamInterface { boolean setWifiPolicy(int policy); } ``` - 编译后生成 `IDreamInterface.java`(包含 `Stub` 和 `Proxy` 类)。 --- ### **(2) 实现服务端 (`DreamInterfaceService.java`)** ```java // frameworks/base/services/core/java/com/android/server/DreamInterfaceService.java public class DreamInterfaceService extends IDreamInterface.Stub { @Override public boolean setWifiPolicy(int policy) { // 实际逻辑(如调用 WifiManager) return true; } } ``` - **注册服务**(在 `SystemServer` 中): ```java // frameworks/base/services/java/com/android/server/SystemServer.java public void run() { ... mSystemServiceManager.startService(DreamInterfaceService.class); } ``` --- ### **(3) 实现客户端封装 (`DreamInterfaceManager.java`)** ```java // frameworks/base/core/java/android/server/DreamInterfaceManager.java public class DreamInterfaceManager { private final IDreamInterface mService; public DreamInterfaceManager(Context context) { // 获取 Binder 代理 mService = IDreamInterface.Stub.asInterface( ServiceManager.getService("dream_interface")); } public boolean setWifiPolicy(int policy) { try { return mService.setWifiPolicy(policy); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } ``` --- ## **4. 常见问题与解决** ### **(1) `NullPointerException`(服务未注册)** - **原因**:`DreamInterfaceService` 未在 `SystemServer` 中启动,或客户端未正确获取 Binder 引用。 - **解决**: ```java // 检查服务是否注册 ServiceManager.checkService("dream_interface"); // 或确保 SystemServer 中调用了 startService() ``` ### **(2) `SecurityException`(权限不足)** - **原因**:未在 `AndroidManifest.xml` 或 SELinux 策略中声明权限。 - **解决**: - 在 `DreamInterfaceService` 中添加 `@RequiresPermission` 注解。 - 更新 `sepolicy` 规则(如 `hal_dream_interface_default.te`)。 ### **(3) Binder 调用超时** - **原因**:服务端阻塞(如死锁或耗时操作)。 - **解决**: ```java @Override public boolean setWifiPolicy(int policy) { // 异步执行或优化耗时逻辑 return true; } ``` --- ## **5. 扩展建议** - **使用 `HIDL`/`AIDL` HAL**:如果涉及硬件操作,建议通过 HAL 层实现。 - **添加 SELinux 策略**:确保 `system_server` 和客户端有权限访问服务。 - **日志与调试**:在 `DreamInterfaceService` 中添加 `Slog` 以便追踪问题。 如果需要更具体的实现细节(如 SELinux 规则或 HAL 集成),可以进一步讨论!
为什么很多程序员不用 switch,而是大量的 if...else if ...?哪个好?给你看下底层实现。(文末有小彩蛋)
昨晚直播时候,有位连麦的小伙伴吐槽他们的业务技术含量不高。但是逻辑复杂。 if else 100 多个。我当即出发被动追问道请问如何优化这种情况? 弹幕说:换成 switch。 这一下就把我拉回了 2021 年。那时候我从 dubbo 的源码“追击”到了 cpu 分支预测。 今天上知乎看了看,发现这个回答意外收获了非常多的认同。  4年后的今天,我再给大家分享一遍。 事情是这样的。 那时候在看 Dubbo 源码,然后发现了一处很奇怪的代码,它属于 `ChannelEventRunnable`,这个 runnable 是 Dubbo IO 线程创建,将此任务扔到业务线程池中处理。  看到没,把 `state == ChannelState.RECEIVED` 拎出来独立一个 if,而其他的 state 还是放在 switch 里面判断。  我当时脑子里就来回扫描,想想这个到底有什么花头,奈何知识浅薄一脸懵逼。 于是就开始了一波探险之旅! ## 原来是 CPU 分支预测 遇到问题当然是问搜索引擎了,一般而言我会同时搜索各大引擎,咱这也不说谁比谁好,反正有些时候度娘还是不错的,比如这次搜索度娘给的结果比较靠前,google 较靠后。 一般搜索东西我都喜欢先在官网上搜,找不到了再放开搜,所以先这么搜 `site:xxx.com key`。  你看这就有了,完美啊! 我们先来看看官网的这篇博客怎么说的,然后再详细地分析一波。 ## Dubbo 官网的博客 > 现代 CPU 都支持分支预测 (branch prediction) 和指令流水线 (instruction pipeline),这两个结合可以极大提高 CPU 效率。对于像简单的 if 跳转,CPU 是可以比较好地做分支预测的。但是对于 switch 跳转,CPU 则没有太多的办法。 switch 本质上是根据索引,从地址数组里取地址再跳转。 也就是说 if 是跳转指令,如果是简单的跳转指令的话 CPU 可以**利用分支预测来预执行指令**,而 switch 是要先根据值去一个类似数组结构找到对应的地址,然后再进行跳转,这样的话 CPU 预测就帮不上忙了。 然后又因为一个 channel 建立了之后,**超过99.9%情况它的 state 都是 ChannelState.RECEIVED**,因此就把这个状态给挑出来,这样就能利用 CPU 分支预测机制来提高代码的执行效率。 并且还给出了 Benchmark 的代码,就是通过随机生成 100W 个 state,并且 99.99% 是 ChannelState.RECEIVED,然后按照以下两种方式来比一比(这 benchSwitch 官网的例子名字打错了,我一开始没发现后来校对文章才发现)。  虽然博客也给出了它的对比结果,但是我还是本地来跑一下看看结果如何,其实 JMH 不推荐在 ide 里面跑,但是我懒,直接 idea 里面跑了。  从结果来看确实通过 if 独立出来代码的执行效率更高(注意这里测的是吞吐),博客还提出了这种技巧可以放在性能要求严格的地方,也就是一般情况下没必要这样特殊做。 至此我们已经知道了这个结论是对的,不过我们还需要深入分析一波,首先得看看 if 和 switch 的执行方式到底差别在哪里,然后再看看 CPU 分支预测和指令流水线的到底是干啥的,为什么会有这两个东西? ## if vs switch 我们先简单来个小 demo 看看 if 和 switch 的执行效率,其实就是添加一个全部是 if else 控制的代码, switch 和 if + switch 的不动,看看它们之间对比效率如何(此时还是 RECEIVED 超过99.9%)。  来看一下执行的结果如何:  好家伙,我跑了好几次,这全 if 的比 if + switch 强不少啊,所以是不是源码应该全改成 if else 的方式,你看这吞吐量又高,还不会像现在一下 if 一下又 switch 有点不伦不类的样子。 我又**把 state 生成的值改成随机的**,再来跑一下看看结果如何:  我跑了多次还是 if 的吞吐量都是最高的,怎么整这个全 if 的都是最棒滴。 ## 反编译 if 和 switch 在我的印象里这个 switch 应该是优于 if 的,不考虑 CPU 分支预测的话,当从字节码角度来说是这样的,我们来看看各自生成的字节码。 **先看一下 switch 的反编译**,就截取了关键部分。  也就是说 switch 生成了一个 tableswitch,上面的 getstatic 拿到值之后可以根据索引直接查这个 table,然后跳转到对应的行执行即可,也就是时间复杂度是 O(1)。 比如值是 1 那么直接跳到执行 64 行,如果是 4 就直接跳到 100 行。 关于 switch 还有一些小细节,**当 swtich 内的值不连续且差距很大的时候,生成的是 lookupswitch**,按网上的说法是二分法进行查询(我没去验证过),时间复杂度是 O(logn),不是根据索引直接能找到了,我看生成的 lookup 的样子应该就是二分了,因为按值大小排序了。  还有当 **switch 里面的值不连续但是差距比较小的时候,还是会生成 tableswtich 不过填充了一些值**,比如这个例子我 switch 里面的值就 1、3、5、7、9,它自动填充了2、4、6、8 都指到 default 所跳的行。  **让我们再来看看 if 的反编译结果**:  可以看到 if 是每次都会取出变量和条件进行比较,而 switch 则是取一次变量之后查表直接跳到正确的行,从这方面来看 switch 的效率应该是优于 if 的。当然如果 if 在第一次判断就过了的话也就直接 goto 了,不会再执行下面的哪些判断了。 所以从生成的字节码角度来看 switch 效率应该是大于 if 的,但是从测试结果的角度来看 if 的效率又是高于 switch 的,不论是随机生成 state,还是 99.99% 都是同一个 state 的情况下。 首先 CPU 分支预测的优化是肯定的,那关于随机情况下 if 还是优于 switch 的话这我就有点不太确定为什么了,可能是 JIT 做了什么优化操作,或者是随机情况下分支预测成功带来的效益大于预测失败的情形? 难道是我枚举值太少了体现不出 switch 的效果?不过在随机情况下 switch 也不应该弱于 if 啊,我又加了 7 个枚举值,一共 12 个值又测试了一遍,结果如下:  好像距离被拉近了,我看有戏,于是我背了波 26 个字母,实不相瞒还是唱着打的字母。  扩充了分支的数量后又进行了一波测试,这次 swtich 争气了,终于比 if 强了。  > 题外话: 我看网上也有对比 if 和 switch 的,它们对比出来的结果是 switch 优于 if,首先 jmh 就没写对,定义一个常量来测试 if 和 switch,并且测试方法的 result 写了没有消费,这代码也不知道会被 JIT 优化成啥样了,写了几十行,可能直接优化成 return 某个值了。 ## 小结一下测试结果 对比了这么多我们来小结一下。 首先**对于热点分支**将其从 switch 提取出来用 if 独立判断,充分利用 CPU 分支预测带来的便利确实优于纯 swtich,从我们的代码测试结果来看,大致吞吐量高了两倍。 而**在热点分支的情形下**改成纯 if 判断而不是 if + swtich的情形下,吞吐量提高的更多。是纯 switch 的 3.3 倍,是 if + switch 的 1.6 倍。 **在随机分支的情形下**,三者差别不是很大,但是还是纯 if 的情况最优秀。 但是从字节码角度来看其实 switch 的机制效率应该更高的,不论是 O(1) 还是 O(logn),但是从测试结果的角度来说不是的。 在选择条件少的情况下 if 是优于 switch 的,这个我不太清楚为什么,可能是在值较少的情况下查表的消耗相比带来的收益更大一些?有知道的小伙伴可以在文末留言。 在选择条件很多的情况下 switch 是优于 if 的,再多的选择值我就没测了,大伙有兴趣可以自己测测,不过趋势就是这样的。 ## CPU 分支预测 接下来咱们再来看看这个分支预测到底是怎么弄的,为什么会有分支预测这玩意,不过在谈到分支预测之前需要先介绍下指令流水线(Instruction pipelining),也就是现代微处理器的 pipeline。 CPU 本质就是取指执行,而取指执行我们来看下五大步骤,分别是获取指令(IF)、指令解码(ID)、执行指令(EX)、内存访问(MEM)、写回结果(WB),再来看下维基百科上的一个图。  当然步骤实际可能更多,反正就是这个意思需要经历这么多步,所以说一次执行可以分成很多步骤,那么这么多步骤就可以并行,来提升处理的效率。 所以说指令流水线就是试图用一些指令**使处理器的每一部分保持忙碌,方法是将传入的指令分成一系列连续的步骤,由不同的处理器单元执行,不同的指令部分并行处理。** 就像我们工厂的流水线一样,我这个奥特曼的脚拼上去了马上拼下一个奥特曼的脚,我可不会等上一个奥特曼的都组装完了再组装下一个奥特曼。  当然也没有这么死板,不一定就是顺序执行,有些指令在等待而后面的指令其实不依赖前面的结果,所以可以提前执行,这种叫**乱序执行**。 我们再说回我们的分支预测。 这代码就像我们的人生一样总会面临着选择,只有做了选择之后才知道后面的路怎么走呀,但是事实上发现这代码经常走的是同一个选择,于是就想出了一个分支预测器,让它来预测走势,提前执行一路的指令。  那预测错了怎么办?这和咱们人生不一样,它可以**把之前执行的结果全抛了然后再来一遍**,但是也有影响,也就是流水线越深,错的越多浪费的也就越多,**错误的预测延迟是10至20个时钟周期之间**,所以还是有副作用的。 简单的说就是通过分支预测器来预测将来要跳转执行的那些指令,然后预执行,这样到真正需要它的时候可以直接拿到结果了,提升了效率。 分支预测又分了很多种预测方式,有静态预测、动态预测、随机预测等等,从维基百科上看有16种。  我简单说下我提到的三种,**静态预测**就是愣头青,就和蒙英语选择题一样,我管你什么题我都选A,也就是说它会预测一个走势,一往无前,简单粗暴。 **动态预测**则会根据历史记录来决定预测的方向,比如前面几次选择都是 true ,那我就走 true 要执行的这些指令,如果变了最近几次都是 false ,那我就变成 false 要执行的这些指令,其实也是利用了局部性原理。 **随机预测**看名字就知道了,这是蒙英语选择题的另一种方式,瞎猜,随机选一个方向直接执行。 还有很多就不一一列举了,各位有兴趣自行去研究,顺便提一下在 2018 年谷歌的零项目和其他研究人员公布了一个名为 Spectre 的灾难性安全漏洞,其可利用 CPU 的分支预测执行泄漏敏感信息,这里就不展开了,文末会附上链接。 之后又有个名为 BranchScope 的攻击,也是利用预测执行,所以说每当一个新的玩意出来总是会带来利弊。 至此我们已经知晓了什么叫指令流水线和分支预测了,也理解了 Dubbo 为什么要这么优化了,但是文章还没有结束,我还想提一提这个 stackoverflow 非常有名的问题,看看这数量。  ## 为什么处理有序数组要比非有序数组快? 这个问题在那篇博客开头就被提出来了,很明显这也是和分支预测有关系,既然看到了索性就再分析一波,大伙可以在脑海里先回答一下这个问题,毕竟咱们都知道答案了,看看思路清晰不。 就是下面这段代码,数组排序了之后循环的更快。  然后各路大神就蹦出来了,我们来看一下首赞的大佬怎么说的。 一开口就是,直击要害。 > You are a victim of branch prediction fail. 紧接着就上图了,一看就是老司机。  他说让我们回到 19世纪,一个无法远距离交流且无线电还未普及的时候,如果是你这个铁路交叉口的扳道工,当火车快来的时候,你如何得知该扳哪一边? 火车停车再重启的消耗是很大的,每次到分叉口都停车,然后你问他,哥们去哪啊,然后扳了道,再重启就很耗时,怎么办?猜!  猜对了火车就不用停,继续开。猜错了就停车然后倒车然后换道再开。 所以就看猜的准不准了!搏一搏单车变摩托。 然后大佬又指出了关键代码对应的汇编代码,也就是跳转指令了,这对应的就是火车的岔口,该选条路了。  后面我就不分析了,大伙儿应该都知道了,排完序的数组执行到值大于 128 的之后肯定全部大于128了,所以每次分支预测的结果都是对了!所以执行的效率很高。 而没排序的数组是乱序的,所以很多时候都会预测错误,而预测错误就得指令流水线排空啊,然后再来一遍,这速度当然就慢了。 所以大佬说这个题主你是分支预测错误的受害者。 最终大佬给出的修改方案是咱不用 if 了,惹不起咱还躲不起嘛?直接利用位运算来实现这个功能,具体我就不分析了,给大家看下大佬的建议修改方案。  ## 最后 这篇文章就差不多了,今天就是从 Dubbo 的一段代码开始了探险之旅,分析了波 if 和 switch,从测试结果来看 Dubbo 的这次优化还不够彻底,应该全部改成 if else 结构。 而 swtich 从字节码上看是优于 if 的,但是从测试结果来看在分支很多的情况下能显示出优势,一般情况下还是打不过 if 。 然后也知晓了什么叫指令流水线,这其实就是结合实际了,流水线才够快呀,然后分支预测预执行也是一个提高效率的方法,当然得猜的对,不然分支预测错误的副作用还是无法忽略的,所以对分支预测器的要求也是很高的。 JMH 的测试代码我也打个包,想自己跑的同学后台输入「分支预测」即可获取,如果觉得这篇文章不错点个在看哟,如果有纰漏请赶紧联系鞭挞我。 ## 巨人的肩膀 *Spectre :https://www.freebuf.com/vuls/160161.html* *Dubbo 博客 :http://dubbo.apache.org/zh-cn/blog/optimization-branch-prediction.html* *https://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-processing-an-unsorted-array* *https://en.wikipedia.org/wiki/Instruction_pipelining* *https://en.wikipedia.org/wiki/Branch_predictor* --- 如果都看到这了,那再给大家来个彩蛋吧,算是一种激励。  如果你喜欢钻研技术,并且再把他分享出来,那么也许这是你能进大厂的另一个途径。 再给告知下大家,[面试鸭的场景题](https://www.mianshiya.com/bank/1795650132375805954)已经高达 100 道了,你可以用它来押面试题、包装简历、学习设计思路等等,含金量还是比较高的!
Python Django 添加自定义管理器-实现逻辑删除
在我们用户中心项目里面,如果用 django 框架去实现逻辑删除是跟 java 不太一样,java 通过修改,MyBatis ,这边 django 自带 ORM,它的工作方式,是每个 Model 类其实对应一个数据库表,而每个Model 类 都会有一个默认管理器,但是现在我们希望只显示表中is_delete=0 的数据,也就是没有被逻辑删除的数据,所以我们需要对 model 自定义一个管理器才可以实现 1. 在 models.py 当中 添加自定义管理器来实现逻辑删除 ```python # 自定义管理器,实现逻辑删除 class ActiveUserManager(models.Manager): #重写这个方法,过滤逻辑上删除的用户,再返回 def get_queryset(self) -> models.QuerySet: return super().get_queryset().filter(is_delete=0) ``` 2. 在要使用自定义管理器的 model 里,把自定义管理器改成默认使用项 ```python class Users(models.Model): # ... 现有字段 ... # 添加自定义管理器 objects = ActiveUserManager() # 默认只返回未删除用户 all_objects = models.Manager() # 原始管理器 class Meta: managed = False db_table = "users" db_table_comment = "用户" ``` 3. 在调用的时候 默认过滤逻辑删除用户 ```python user.objects.all() # 返回逻辑删除用户合集 ```
Python Django Ninja 框架 snake-case蛇形 转换 camel-case 驼峰
方案一:定义 Schema 进行转换 ================== 在 Django Ninja 中,可以实现自动转换,不需要手动重命名每个字段。以下是具体实现方式: ### 自动转换方案(基于您现有代码) ```python from ninja import Schema from pydantic import Field from pydantic.alias_generators import to_camel from typing import Optional class CamelModel(Schema): class Config: alias_generator = to_camel # 自动snake_case -> camelCase populate_by_name = True # 允许通过别名访问 class UserLoginResponse(CamelModel): # 继承自CamelModel # 保持原有字段名(snake_case),会自动转为camelCase user_account: str user_name: str user_id: int avatar_url: Optional[str] = None # ...其他字段保持原样... ``` ### 关键点说明 1. `alias_generator = to_camel`:自动将 snake_case 转为 camelCase 2. `populate_by_name = True`:允许通过原始字段名访问 3. 无需修改现有代码: * 服务层继续返回 `user_account` 等 snake_case 字段 * 前端将收到 `userAccount` 等 camelCase 字段 ### 效果验证 ```python # 测试输出 response = UserLoginResponse( user_account="test", user_name="Test User", user_id=1 ) print(response.json()) # 输出: {"userAccount":"test","userName":"Test User","userId":1} ``` 这样既保持了后端代码的 Python 风格,又满足了前端 JavaScript 的命名习惯 保险起见的写法 ======= ### 1. 自动转换已足够的情况 ```python class UserLoginResponse(CamelModel): user_account: str # 会自动转为 userAccount # ...其他字段... ``` * 足够:如果所有字段都遵循 `snake_case` 命名规范 * 自动转换:`alias_generator = to_camel` 会处理大多数情况 ### 2. 需要显式 `Field` 的情况 ```python class UserLoginResponse(CamelModel): user_account: str = Field(alias="userAccount") # 显式指定 # ...其他字段... ``` * 特殊字段:当字段名不符合常规转换规则时 * 文档清晰:让 API 文档明确显示前端应该使用的字段名 * 前后端约定:确保即使未来修改转换逻辑,该字段名保持不变 ### !!! 但是这种定义 不能对 request 起到效果 因为,在 ninja 项目源码中的schema.py 下的 DjangoGetter class中的 `def __getattr__(self, key: str) -> Any` 函数,你会发现,框架,是先对request 中的 keys 和 schemas 的 keys 进行比对,保证数据符合要求再进行,对应的to_snake的转换,而不是先转换,request key 的格式,所以就会发生报错, 解决方式有用显示 Field ,alias 去处理如上方写法,定义每一个 字段,的 camel case ,这样 函数 会优先使用 alias 的字段,对比就不会出错 ```python class UserLoginRequest(RequestBase): user_account: str = Field(..., alias="userAccount") user_password: str = Field(..., alias="userPassword") ``` 方案二,写个中间件,对所有请求进行字体转换 ===================== **1. 这种也是解决,上面,省去对请求,每个字段进行表明写法的方法,创建中间件文件** ### **推荐位置** ```plain your_project/ ├── middleware/ # 新建目录存放中间件 │ ├── __init__.py │ └── camel_case.py # 驼峰转蛇形中间件 ├── api.py # Django Ninja 的 api 实例 └── settings.py ``` ### `middleware/camel_case.py` **内容** ```python import json import re from django.utils.text import camel_case_to_spaces, slugify from django.http import HttpRequest def camel_to_snake(data): """递归将字典的键从 camelCase 转为 snake_case""" if isinstance(data, dict): return { # 转换逻辑:camelCase → snake_case slugify(camel_case_to_spaces(key)).replace("-", "_"): camel_to_snake(value) for key, value in data.items() } elif isinstance(data, list): return [camel_to_snake(item) for item in data] return data class CamelCaseMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request: HttpRequest): # 只处理 JSON 请求(避免影响表单/文件上传等) if request.content_type == "application/json" and request.body: try: data = json.loads(request.body) # 修改请求体数据 request._body = json.dumps(camel_to_snake(data)).encode("utf-8") except json.JSONDecodeError: pass return self.get_response(request) ``` * * * **2. 注册中间件到 Django Ninja** -------------------------- ### **在** `api.py` **中注册** ```python from ninja import NinjaAPI from .middleware.camel_case import CamelCaseMiddleware api = NinjaAPI() api.add_middleware(CamelCaseMiddleware) # 关键!添加中间件 ``` * * * **3. 确保中间件能被导入** ---------------- ### **在** `middleware/__init__.py` **中暴露中间件** ```python from .camel_case import CamelCaseMiddleware __all__ = ["CamelCaseMiddleware"] ``` * * * **4. (可选)添加到 Django 全局中间件** --------------------------- 如果希望中间件 **对所有请求(包括非 API 请求)生效**,可以在 `settings.py` 中添加: ```python # settings.py MIDDLEWARE = [ ..., 'your_project.middleware.CamelCaseMiddleware', # 全局中间件 ] ``` 但 **不建议这样做**,因为: 1. 可能影响 Django Admin、静态文件等非 API 请求。 2. Django Ninja 的 `api.add_middleware()` 是更精准的注册方式。 * * * **5. 测试中间件** ------------ ### **发送请求** ```bash curl -X POST http://localhost:8000/api/user \ -H "Content-Type: application/json" \ -d '{"userName":"John", "userAge":25}' ``` ### **后端接收的数据** ```python # 自动转换为: {"user_name": "John", "user_age": 25} ``` * * * **关键注意事项** ---------- 1. **中间件顺序** Django Ninja 的中间件按注册顺序执行,确保转换中间件在认证中间件之前。 2. **性能影响** 对每个 JSON 请求会进行递归转换,高频 API 可能需要优化。 3. **异常处理** 中间件中捕获了 `JSONDecodeError`,避免非法 JSON 导致崩溃。 4. **Content-Type 检查** 仅处理 `application/json` 请求,避免影响文件上传等场景。 * * * **替代方案对比** ---------- | 方案 | 优点 | 缺点 | | --- | --- | --- | | 中间件 | 全局生效,无需改 Schema | 需要处理递归转换逻辑 Pydantic 别名 | 声明式配置,代码更干净 | 每个 Schema 需单独设置 手动转换 | 灵活控制 | 代码冗余,维护成本高 | 推荐优先使用 **中间件** 或 **Pydantic 别名**,根据项目规模选择。
javaweb做的学校教务管理系统
# 教务管理系统 ## 项目介绍 这是一个基于JavaWeb技术栈开发的教务管理系统,采用传统的MVC架构模式,使用Servlet+JSP+JDBC技术实现。 ## 技术栈 - 后端:Java EE(Servlet、JSP) - 数据库:MySQL 5.7 - 前端:原生JavaScript、CSS - 连接池:JDBC直连 - 开发工具:Eclipse ## 功能特点 1. 用户管理 - 用户注册 - 用户登录 - 权限控制 2. 学生管理 - 学生信息CRUD - 学生查询 3. 课程管理 - 课程信息CRUD - 课程查询 4. 成绩管理 - 成绩录入 - 成绩查询 - 成绩统计 5. 班级管理 - 班级信息CRUD - 班级查询 <img src="https://pic.code-nav.cn/post_picture/1900441785166540802/u7bk71Llxxjhgn28.webp" alt="1.png" width="476px" /> <img src="https://pic.code-nav.cn/post_picture/1900441785166540802/yJN0DCZHc9PxjYTN.webp" alt="2.png" width="479px" />        
