Android学习之实战登录注册能力
我们可以从本地 Token 存储、时效管理、服务端通知联动、定时器优化四个维度深入展开
一、本地 Token 存储设计(基于 SharedPreferences)
1. 存储结构优化(包含时效性字段)
// 定义存储类(封装SharedPreferences操作)
public class TokenManager {private static final String PREF_NAME = "AuthPrefs";private static final String KEY_TOKEN = "access_token";private static final String KEY_TOKEN_EXPIRE_TIME = "token_expire_time"; // 时间戳(毫秒)private static final String KEY_REFRESH_TOKEN = "refresh_token"; // 用于自动续期private final SharedPreferences sp;public TokenManager(Context context) {// 推荐使用EncryptedSharedPreferences进行加密存储(需AndroidX Security库)sp = EncryptedSharedPreferences.create(context,PREF_NAME,MasterKey.getInstance(context, MasterKey.DEFAULT_KEY_SCHEME),EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);}// 保存Token及有效期public void saveToken(String accessToken, String refreshToken, long expireTimeMillis) {sp.edit().putString(KEY_TOKEN, accessToken).putString(KEY_REFRESH_TOKEN, refreshToken).putLong(KEY_TOKEN_EXPIRE_TIME, expireTimeMillis).apply(); // 异步提交,避免ANR}// 获取Token(自动检查是否过期)public String getValidToken() {String token = sp.getString(KEY_TOKEN, null);long expireTime = sp.getLong(KEY_TOKEN_EXPIRE_TIME, 0);if (token != null && System.currentTimeMillis() < expireTime) {return token;}return null; // 过期或未存储}// 清除Token(密码修改/主动退出时调用)public void clearToken() {sp.edit().clear().apply();}
}
2. 关键设计点:
- 加密存储:使用 AndroidX Security 库的
EncryptedSharedPreferences
,防止 Token 被明文读取(比普通 SP 更安全)。 - 双 Token 机制:
accessToken
(短有效期,如 1 小时)+refreshToken
(长有效期,如 7 天),自动续期时用refreshToken
获取新accessToken
,避免频繁登录。 - 时间戳校验:通过
expireTimeMillis
判断 Token 是否过期(当前时间 > 过期时间戳则失效)。
二、Token 时效处理:手动 vs 自动
1. 网络请求前的预处理(统一拦截器)
// OkHttp拦截器,自动注入Token并处理过期
public class AuthInterceptor implements Interceptor {private final TokenManager tokenManager;public AuthInterceptor(TokenManager tokenManager) {this.tokenManager = tokenManager;}@Overridepublic Response intercept(Chain chain) throws IOException {Request originalRequest = chain.request();String token = tokenManager.getValidToken();// 注入Token到请求头Request authenticatedRequest = originalRequest.newBuilder().header("Authorization", "Bearer " + token).build();Response response = chain.proceed(authenticatedRequest);// 处理401/403状态码(Token失效)if (response.code() == 401) {// 自动处理:尝试用RefreshToken获取新AccessTokenif (handleTokenExpireAutomatically()) {// 重试原请求String newToken = tokenManager.getValidToken();authenticatedRequest = originalRequest.newBuilder().header("Authorization", "Bearer " + newToken).build();return chain.proceed(authenticatedRequest);} else {// 手动处理:跳转到登录页(需通过Activity上下文或EventBus通知UI)EventBus.getDefault().post(new TokenExpiredEvent());}}return response;}private boolean handleTokenExpireAutomatically() {String refreshToken = tokenManager.getRefreshToken(); // 自定义方法获取RefreshTokenif (refreshToken != null) {try {// 调用服务端Refresh APIRefreshTokenResponse response = RetrofitClient.getInstance().getAuthService().refreshToken(refreshToken).execute().body();if (response != null && response.isSuccess()) {tokenManager.saveToken(response.getAccessToken(),response.getRefreshToken(), // 可能返回新的RefreshToken(按需更新)response.getExpireTimeMillis());return true; // 续期成功}} catch (Exception e) {e.printStackTrace();}}return false; // 续期失败,需手动登录}
}
2. 手动处理逻辑(UI 层响应)
// 在BaseActivity或全局EventBus监听Token失效事件
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EventBus.getDefault().register(this);}@Subscribepublic void onTokenExpired(TokenExpiredEvent event) {// 清除本地Token并跳转登录tokenManager.clearToken();Intent intent = new Intent(this, LoginActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}@Overrideprotected void onDestroy() {super.onDestroy();EventBus.getDefault().unregister(this);}
}
三、服务端密码修改后的跨端通知(核心同步机制)
1. 服务端主动通知客户端(基于 WebSocket/MQTT)
// 客户端WebSocket监听(示例,使用OkHttp WebSocket)
public class AuthWebSocketClient {private WebSocket webSocket;public void connect() {Request request = new Request.Builder().url("ws://your-server/ws/auth?token=" + tokenManager.getValidToken()).build();OkHttpClient client = new OkHttpClient();client.newWebSocket(request, new WebSocketListener() {@Overridepublic void onMessage(WebSocket webSocket, String text) {// 解析通知内容(如:{"type":"password_changed"})handleServerNotification(text);}private void handleServerNotification(String message) {try {JSONObject json = new JSONObject(message);if ("password_changed".equals(json.getString("type"))) {// 密码已修改,主动验证Token有效性(服务端可能已使旧Token失效)checkTokenValidityAndLogout();}} catch (JSONException e) {e.printStackTrace();}}private void checkTokenValidityAndLogout() {// 调用一个轻量API验证Token(如/me/check-token)RetrofitClient.getInstance().getAuthService().checkTokenValidity().enqueue(new Callback<Boolean>() {@Overridepublic void onResponse(Call<Boolean> call, Response<Boolean> response) {if (response.body() != null && !response.body()) {// Token已失效,清除本地数据并跳转登录tokenManager.clearToken();runOnUiThread(() -> {Intent intent = new Intent(context, LoginActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);startActivity(intent);});}}@Overridepublic void onFailure(Call<Boolean> call, Throwable t) {// 网络失败,默认认为Token可能失效,安全起见跳转登录tokenManager.clearToken();// ... 跳转逻辑 ...}});}});}
}
2. 通知机制核心逻辑:
- 触发时机:用户在任意端修改密码时,服务端遍历该用户所有设备的 WebSocket 连接,发送
password_changed
通知(需设备 ID 与 WebSocket 连接绑定)。 - 二次验证:客户端收到通知后,不直接清除 Token,而是通过轻量 API(如
/check-token
)验证当前 Token 是否有效(服务端此时已使旧 Token 失效,返回 401),确保通知不是伪造的。 - 兼容性:对于不支持 WebSocket 的旧设备,可通过下次网络请求时被动触发失效(如拦截器检测到 401 后跳转登录)。
四、定时器优化:替代方案与最佳实践
1. 避免使用java.util.Timer
(推荐 WorkManager)
// 使用WorkManager实现定时Token检查(Android Jetpack组件)
public class TokenRefreshWorker extends Worker {private static final String WORK_NAME = "Token_Refresh_Worker";public TokenRefreshWorker(@NonNull Context context, @NonNull WorkerParameters params) {super(context, params);}@NonNull@Overridepublic Result doWork() {// 检查Token是否即将过期(如提前5分钟刷新)long expireTime = tokenManager.getTokenExpireTime(); // 自定义方法获取过期时间戳long currentTime = System.currentTimeMillis();if (expireTime - currentTime < 5 * 60 * 1000) { // 剩余5分钟handleTokenExpireAutomatically(); // 复用拦截器中的自动续期逻辑}return Result.success();}// 调度Worker(在登录成功后调用)public static void scheduleTokenRefresh(Context context) {PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(TokenRefreshWorker.class,15, // 检查间隔(建议不低于15分钟,避免耗电)TimeUnit.MINUTES).setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()).build();WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME,ExistingPeriodicWorkPolicy.KEEP,workRequest);}
}
2. 为什么不用Timer
?
- 后台限制:Android 8.0 + 限制后台定时器精度,
Timer.schedule()
可能延迟执行,而 WorkManager 会根据系统状态(如省电模式、充电状态)智能调度,更省电。 - 进程重启:WorkManager 支持设备重启后自动恢复任务,而
Timer
在进程被杀后失效。 - 兼容性:WorkManager 是 Google 推荐的后台任务方案,覆盖从 API 14 到最新版本。
五、边缘场景处理与安全增强
1. 多端并发修改密码的冲突处理
- 服务端在密码修改接口(
/password/update
)中,每次修改后生成一个全局token_version
(存入用户表),并随 Token 返回给客户端。 - 客户端每次网络请求携带
token_version
,服务端发现请求中的版本号小于数据库中的最新版本时,直接返回 401(即使 Token 未过期,也视为失效)。
2. 防止定时器被恶意利用
- 在
WorkManager
的Constraints
中添加网络限制(仅 Wi-Fi 时执行),避免移动数据下频繁请求。 - 定时器检查时,仅执行自动续期逻辑,不处理跳转登录等 UI 操作(UI 逻辑通过 EventBus 在主线程处理)。
3. 日志与调试
- 记录 Token 的存储、更新、过期、清除操作到本地日志(如
Log.d("TokenManager", "Token expired, trying to refresh...")
),方便排查问题。 - 在调试阶段,提供 “强制 Token 过期” 的测试按钮,模拟失效场景。
六、总结:从本地到云端的完整链路
- 本地存储:使用加密 SP 存储 Token 及过期时间,确保安全性和时效性校验。
- 网络层拦截:通过 OkHttp 拦截器统一处理 Token 注入和过期重试,分离业务逻辑。
- 服务端通知:基于 WebSocket 实现实时密码修改通知,结合二次验证接口确保通知真实性。
- 后台任务:用 WorkManager 替代传统定时器,优化耗电并保证可靠性。
扩展追问
一、为什么选择 MMKV 替代 SharedPreferences?
1. MMKV 的核心优势(面试官必问):
- 高性能:基于 mmap 内存映射技术,读写速度远优于 SharedPreferences(尤其频繁读写场景),避免 ANR。
- 多进程安全:原生支持多进程访问,无需额外处理并发冲突(SharedPreferences 多进程需手动加锁)。
- 易用性:API 与 SharedPreferences 类似,但支持更多数据类型(如 boolean 数组、自定义对象需序列化),且提供批量操作。
- 体积轻量:腾讯开源,稳定性高,适合移动端大规模使用。
2. 适用场景匹配:
- Token 和时间戳需高频读写(如每次网络请求前校验),MMKV 的高性能能显著提升效率。
- 若 App 存在多进程(如后台 Service、推送进程),MMKV 可保证数据一致性。
二、MMKV 实现 Token 存储与时效性管理
1. 初始化 MMKV(在 Application 中):
// 基础初始化(默认存储路径:/data/data/包名/documents/MMKV/)
MMKV.initialize(this);// 可选:加密(如 AES,适合敏感数据)
MMKV.defaultMMKV().setEncryptKey("自定义加密密钥");
2. 存储 Token 与时间戳:
// 保存 Token 及过期时间(例:Token 有效期 1 小时)
MMKV mmkv = MMKV.defaultMMKV();
mmkv.putString("token", response.getToken());
mmkv.putLong("token_expire_time", System.currentTimeMillis() + 3600 * 1000); // 时间戳(毫秒)
3. 获取 Token 并校验时效性:
public String getValidToken() {MMKV mmkv = MMKV.defaultMMKV();String token = mmkv.getString("token", "");long expireTime = mmkv.getLong("token_expire_time", 0);if (TextUtils.isEmpty(token) || System.currentTimeMillis() >= expireTime) {return null; // Token 无效或过期}return token;
}
面试扩展
1. 本地存储层:安全与性能的平衡
面试高频考点:
- 如何设计安全的 Token 存储方案?(涉及加密、防窃取)
- 对比 SharedPreferences、MMKV、数据库等存储方案的优劣。
解决方案:
- 加密存储:使用
EncryptedSharedPreferences
或 MMKV 的 AES 加密功能,防止 Token 被明文读取。// MMKV加密初始化(需配置密钥) MMKV mmkv = MMKV.mmkvWithID("auth", MMKV.MULTI_PROCESS, new AESKeyProvider("your_secure_key"));
- 双 Token 机制:
accessToken
(短有效期)+refreshToken
(长有效期),减少用户登录频率。// 存储结构 mmkv.putString("access_token", token); mmkv.putString("refresh_token", refreshToken); mmkv.putLong("expire_time", System.currentTimeMillis() + 3600 * 1000); // 1小时有效期
- 多进程支持:MMKV 原生支持多进程访问,避免 SharedPreferences 的并发冲突。
三、Token 过期处理(手动 / 自动)
1. 自动续期(重点:避免频繁网络请求):
- 提前续期:在 Token 过期前(如剩余 10 分钟)触发续期,而非过期后再处理,减少用户无感知中断。
private void checkTokenExpire() {long expireTime = MMKV.defaultMMKV().getLong("token_expire_time", 0);if (expireTime - System.currentTimeMillis() < 10 * 60 * 1000) { // 剩余 10 分钟refreshToken(); // 调用接口续期}
}
- 续期逻辑:
private void refreshToken() {// 携带旧 Token 申请新 Tokenapi.refreshToken(oldToken).enqueue(new Callback<TokenResponse>() {@Overridepublic void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {if (response.isSuccessful()) {saveNewToken(response.body()); // 保存新 Token 及时间戳} else {// 续期失败,触发手动处理(跳转登录)navigateToLogin();}}}); }
2. 手动处理(用户感知场景):
- 当网络请求返回
401 Unauthorized
(Token 无效)时,清除本地数据并跳转登录:
// 在 BaseUrlInterceptor 或全局错误处理中
if (code == 401) {MMKV.defaultMMKV().removeValueForKey("token"); // 清除旧 TokenMMKV.defaultMMKV().removeValueForKey("token_expire_time");// 跳转登录,需处理 Activity 栈(如使用 Application 单例标记登录状态)Intent intent = new Intent(context, LoginActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(intent);
}
面试扩展
- 如果 Token 被窃取,如何防止中间人攻击?
- A:
- HTTPS 传输:所有 Token 交互使用 HTTPS,防止传输中被截获。
- 时间戳校验:每次请求携带时间戳,服务端验证时间差(如 5 分钟内有效)。
- 签名机制:将 Token、时间戳、请求参数组合后用 HMAC-SHA256 签名,防止参数篡改。
// 客户端签名示例 String sign = HmacSHA256Utils.hmacSHA256(token + timestamp + params, secretKey);
- A:
2. 网络层:Token 自动续期与失效处理
面试高频考点:
- 如何处理 Token 过期?(自动续期 vs 手动跳转)
- 如何设计网络拦截器统一处理 Token?
解决方案:
- OkHttp 拦截器:自动注入 Token 并处理 401 错误。
public class AuthInterceptor implements Interceptor {@Overridepublic Response intercept(Chain chain) throws IOException {Request request = chain.request().newBuilder().header("Authorization", "Bearer " + mmkv.getString("access_token")).build();Response response = chain.proceed(request);if (response.code() == 401) {// 自动续期String newToken = refreshToken();if (newToken != null) {request = request.newBuilder().header("Authorization", "Bearer " + newToken).build();return chain.proceed(request);}// 手动处理:跳转登录navigateToLogin();}return response;} }
- 自动续期策略:在 Token 过期前 10 分钟触发续期,减少用户感知。
private void checkTokenExpire() {long expireTime = mmkv.getLong("expire_time");if (expireTime - System.currentTimeMillis() < 10 * 60 * 1000) {refreshToken();} }
四、多端密码修改后的 Token 失效同步(核心逻辑)
1. 服务端主动通知客户端(需结合推送或长连接):
- 当用户在另一端修改密码时,服务端需向所有在线客户端发送 Token 失效通知(如通过 WebSocket、FCM 推送)。
- 客户端收到通知后,立即校验本地 Token 并强制失效:
// 推送接收逻辑示例 private void handleTokenInvalidate() {MMKV mmkv = MMKV.defaultMMKV();// 清除本地 Token(即使未过期也强制失效)mmkv.removeValueForKey("token");mmkv.removeValueForKey("token_expire_time");// 跳转登录(需处理当前页面栈)navigateToLoginAndFinishAll(); }
2. 跨设备 / 端的最终一致性:
- 若未收到推送(如客户端离线),下次启动或网络请求时,通过
getValidToken()
发现 Token 无效,仍会跳转登录,保证最终一致性。
面试追问
多端同步:服务端通知与本地校验
面试高频考点:
- 如何实现一端修改密码,其他端自动失效?
- 如何处理服务端主动通知客户端?
解决方案:
- WebSocket 长连接:接收服务端密码修改通知。
WebSocketClient webSocket = new WebSocketClient(Uri.parse("ws://your-server/ws")) {@Overridepublic void onMessage(String message) {if ("password_changed".equals(message)) {// 清除本地Token并跳转登录mmkv.clearAll();navigateToLogin();}} };
- 本地校验增强:每次请求时携带
token_version
,服务端检测版本号是否一致。// 服务端返回token时包含版本号 mmkv.putInt("token_version", response.getVersion());// 客户端请求时携带版本号 request = request.newBuilder().header("X-Token-Version", mmkv.getInt("token_version")).build();
面试官追问:
- Q:如果客户端未收到 WebSocket 通知(如离线),如何保证最终一致性?
- A:
- 下次启动时校验:App 启动后主动调用
/check-token
接口验证有效性。 - 定时任务:使用 WorkManager 每小时执行一次 Token 有效性检查。
- 下次启动时校验:App 启动后主动调用
- A:
五、定时器优化(替代 SharedPreferences 时的注意点)
1. 使用 Handler 或 WorkManager 替代 Timer(更适配 Android 生命周期):
- Timer 缺陷:后台可能被系统杀死,且非精准定时。
- 推荐方案:
- Handler + postDelayed(短时定时,如每分钟检查一次):
private final Handler handler = new Handler(Looper.getMainLooper()); private final Runnable checkRunnable = this::checkTokenExpire;// 启动定时任务 handler.postDelayed(checkRunnable, 60 * 1000);// 停止任务(如 Activity 销毁时) handler.removeCallbacks(checkRunnable);
- WorkManager(长时后台定时,适合低功耗场景):
WorkRequest workRequest = PeriodicWorkRequestBuilder<TokenCheckWorker>(15, TimeUnit.MINUTES).setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()).build(); WorkManager.getInstance(context).enqueueUniquePeriodicWork("token_check",ExistingWorkPolicy.KEEP,workRequest );
- Handler + postDelayed(短时定时,如每分钟检查一次):
2. 减少定时任务频率:
- 结合 MMKV 的高性能,无需频繁检查(建议 5-15 分钟一次),主要依赖网络请求时的实时校验(每次请求前调用
getValidToken()
)。
面试扩展
后台任务优化:省电与可靠性
面试高频考点:
- 如何优化后台任务以避免耗电?
- 对比 Timer、Handler、WorkManager 的优劣。
解决方案:
- WorkManager:替代 Timer 实现定时 Token 检查。
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(TokenCheckWorker.class,15, TimeUnit.MINUTES ) .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) .build(); WorkManager.getInstance(context).enqueue(workRequest);
- 条件约束:仅在 Wi-Fi 连接时执行任务,避免移动数据下频繁请求。
面试官追问:
- Q:WorkManager 在 API 23 以下的设备如何兼容?
- A:
- 使用 WorkManager 1.0.0+:内部已处理兼容性问题。
- 回退方案:对于 API < 14 的设备,使用 AlarmManager 实现基本定时功能。
- A:
六、进阶优化(面试官可能深挖的点)
1. 数据加密增强:
- MMKV 支持自定义加密(如 AES),对 Token 这种敏感数据,初始化时设置加密密钥:
// 使用 AES 加密(需处理密钥存储,建议结合 Keystore) MMKV mmkv = MMKV.mmkvWithID("encrypted_mmkv", MMKV.MULTI_PROCESS, new AESKeyProvider("你的密钥"));
2. 内存泄漏预防:
- MMKV 本身基于文件映射,不会导致内存泄漏,但定时器(如 Handler)需在页面销毁时移除回调,避免持有 Activity 引用。
3. 多版本兼容性:
- MMKV 支持 Android 4.0+(API 14),需注意在旧版本设备上的 mmap 限制(如文件大小上限),但 Token 存储量小,几乎无影响。
面试总结
1. 如何设计安全的 Token 存储方案?
标准答案:
- 加密存储:使用
EncryptedSharedPreferences
或 MMKV 的 AES 加密,防止明文泄露。 - 双 Token 机制:
accessToken
(短有效期)+refreshToken
(长有效期),减少登录频率。 - 时间戳校验:每次请求携带时间戳,服务端验证时间差(如 5 分钟内有效)。
- 签名机制:将 Token、时间戳、请求参数组合后用 HMAC-SHA256 签名,防止参数篡改。
2. 如何处理 Token 过期?
标准答案:
- 自动续期:通过
refreshToken
获取新的accessToken
,减少用户感知。 - 网络拦截器:统一处理 401 错误,自动重试或跳转登录。
- 手动处理:清除本地 Token 并跳转登录页,同时通过 EventBus 通知 UI 层。
3. 如何实现多端密码修改同步?
标准答案:
- WebSocket 长连接:接收服务端密码修改通知,立即清除本地 Token。
- 版本号校验:每次请求携带
token_version
,服务端检测版本号是否一致。 - 定时任务:使用 WorkManager 定时检查 Token 有效性,确保最终一致性。
4. 如何优化后台任务以避免耗电?
标准答案:
- 使用 WorkManager:替代 Timer,利用系统调度优化资源。
- 条件约束:仅在 Wi-Fi 连接时执行任务,避免移动数据下频繁请求。
- 精确唤醒:通过
setExact()
或setAndAllowWhileIdle()
控制唤醒时间。
总结:技术方案的核心优势
技术点 | 解决的问题 | 面试加分项 |
---|---|---|
加密存储 | Token 明文泄露风险 | 提到EncryptedSharedPreferences 和 MMKV 的加密实现 |
双 Token 机制 | 频繁登录问题 | 说明accessToken 和refreshToken 的协作流程 |
WebSocket 通知 | 多端数据一致性问题 | 结合服务端主动通知和本地校验逻辑 |
WorkManager | 后台任务耗电问题 | 对比 Timer、Handler,强调系统调度优势 |
时间戳 + 签名 | 中间人攻击和参数篡改风险 | 展示具体实现代码(如 HMAC-SHA256) |
通过以上方案,既能满足多端密码验证同步的核心需求,又能覆盖安全存储、网络优化、后台任务知识点
相关文章:
Android学习之实战登录注册能力
我们可以从本地 Token 存储、时效管理、服务端通知联动、定时器优化四个维度深入展开 一、本地 Token 存储设计(基于 SharedPreferences) 1. 存储结构优化(包含时效性字段) // 定义存储类(封装SharedPreferences操作…...
【数据可视化-19】智能手机用户行为可视化分析
🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...
基于一致性哈希算法原理和分布式系统容错机制
一、传统取模算法的局限性分析 当使用User ID取模路由时,Pod挂断会导致以下问题: 数据雪崩效应:节点失效后所有请求需要重新计算取模值,导致缓存穿透和服务震荡服务不可用窗口:节点失效期间,原本路由到该节…...
[SpringBoot-1] 概述和快速入门(使用vscode)
1 SpringBoot 概念 SpringBoot提供了一种快速使用Spring的方式,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程…...
学习笔记二十——Rust trait
🧩 Rust Trait 彻底搞懂版 👀 目标读者:对 Rust 完全陌生,但想真正明白 “Trait、Trait Bound、孤岛法则” 在做什么、怎么用、为什么这样设计。 🛠 方法: 先给“心里模型”——用生活类比把抽象概念掰开揉…...
llama factory
微调大模型可以像这样轻松… https://github.com/user-attachments/assets/e6ce34b0-52d5-4f3e-a830-592106c4c272 选择你的打开方式: 入门教程:https://zhuanlan.zhihu.com/p/695287607框架文档:https://llamafactory.readthedocs.io/zh-…...
机器学习 Day12 集成学习简单介绍
1.集成学习概述 1.1. 什么是集成学习 集成学习是一种通过组合多个模型来提高预测性能的机器学习方法。它类似于: 超级个体 vs 弱者联盟 单个复杂模型(如9次多项式函数)可能能力过强但容易过拟合 组合多个简单模型(如一堆1次函数)可以增强能力而不易过拟合 集成…...
基于 Spring Boot 瑞吉外卖系统开发(五)
基于 Spring Boot 瑞吉外卖系统开发(五) 删除分类 分类列表中每条分类信息右侧提供了一个“删除”按钮,当需要将已经存在的分类信息删除时,可以通过单击“删除”按钮实现。 请求路径为/category,携带参数id…...
PyTorch基础笔记
PyTorch张量 多维数组:张量可以是标量(0D)、向量(1D)、矩阵(2D)或更高维的数据(3D)。 数据类型:支持多种数据类型(如 float32, int64, bool 等&a…...
什么是 IDE?集成开发环境的功能与优势
原文:什么是 IDE?集成开发环境的功能与优势 | w3cschool笔记 (注意:此为科普文章,请勿标记为付费文章!且此文章并非我原创,不要标记为付费!) IDE 是什么? …...
基于大数据的房产估价解决方案
基于大数据的房产估价解决方案 一、项目背景与目标 1.1 背景 在房地产市场中,准确的房产估价至关重要。传统的房产估价方法往往依赖于估价师的经验和有限的数据样本,存在主观性强、效率低等问题。随着大数据技术的发展,大量的房产相关数据被积…...
基于深度学习的线性预测:创新应用与挑战
一、引言 1.1 研究背景 深度学习作为人工智能领域的重要分支,近年来在各个领域都取得了显著的进展。在线性预测领域,深度学习也逐渐兴起并展现出强大的潜力。传统的线性预测方法在处理复杂数据和动态变化的情况时往往存在一定的局限性。而深度学习凭借…...
WEMOS LOLIN32
ESP32是結合Wi-Fi和藍牙的32位元系統單晶片(SoC)與外接快閃記憶體的模組。許多廠商生產採用ESP32模組的控制板,最基本的ESP控制板包含ESP32模組、直流電壓轉換器和USB序列通訊介面IC。一款名為WEMOS LOLIN32的ESP32控制板具備3.7V鋰電池插座。…...
VSCode 扩展离线下载方法
学习自该文章,感谢作者! 2025 年 VSCode 插件离线下载攻略:官方渠道一键获取 - 知乎 获取扩展关键信息 方法一:官网获取 打开 VSCode 扩展官方网站 搜索要下载的扩展,以 CodeGeeX 为例,网址为…...
计算机视觉与深度学习 | RNN原理,公式,代码,应用
RNN(循环神经网络)详解 一、原理 RNN(Recurrent Neural Network)是一种处理序列数据的神经网络,其核心思想是通过循环连接(隐藏状态)捕捉序列中的时序信息。每个时间步的隐藏状态 ( h_t ) 不仅依赖当前输入 ( x_t ),还依赖前一时间步的隐藏状态 ( h_{t-1} ),从而实现…...
对于网络资源二级缓存的简单学习
缓存学习 前言认识缓存磁盘储存内存储存磁盘内存组合优化 具体实现WebCacheMD5签名 WebDownloadOperationWebDownloaderWebCombineOperation 总结 前言 在最近的写的仿抖音app中,遇到了当往下滑动视频后,当上方的视频进入复用池后,会自动清空…...
【计量地理学】实验六 地理属性空间插值
一、实验目的 本次实验的主要目的在于熟练掌握空间克里格法插值的理论基础,包括其核心概念和步骤,能够通过数据可视化和统计分析方法识别数据中的异常值,并且掌握数据正态性的检验方法,理解正态分布对克里格法的重要性࿰…...
26考研 | 王道 | 数据结构 | 第六章 图
第六章 图 文章目录 第六章 图6.1. 图的基本概念6.2. 图的存储6.2.1. 邻接矩阵6.2.2. 邻接表6.2.3. 十字链表、临接多重表6.2.4. 图的基本操作 6.3. 图的遍历6.3.1. 广度优先遍历6.3.2. 深度优先遍历6.3.3 图的遍历与连通性 6.4. 图的应用6.4.1. 最小生成树6.4.2. 无权图的单源…...
window.addEventListener 和 document.addEventListener
window.addEventListener 和 document.addEventListener 是 JavaScript 中绑定事件的两个常用方法,核心区别在于 绑定的对象不同,导致事件的作用范围、触发时机和适用场景不同。下面用最直白的语言和案例对比说明: 一、核心区别:…...
51单片机的原理图和PCB绘制
51单片机最小系统原理图 加了两个led灯和按键检测电路。 PCB中原件摆放位置 成品 资源链接:https://download.csdn.net/download/qq_61556106/90656365...
kotlin知识体系(五) :Android 协程全解析,从作用域到异常处理的全面指南
1. 什么是协程 协程(Coroutine)是轻量级的线程,支持挂起和恢复,从而避免阻塞线程。 2. 协程的优势 协程通过结构化并发和简洁的语法,显著提升了异步编程的效率与代码质量。 2.1 资源占用低(一个线程可运行多个协程)…...
数据通信学习笔记之OSPF其他内容3
对发送的 LSA 进行过滤 当两台路由器之间存在多条链路时,可以在某些链路上通过对发送的 LSA 进行过滤,减少不必要的重传,节省带宽资源。 通过对 OSPF 接口出方向的 LSA 进行过滤可以不向邻居发送无用的 LSA,从而减少邻居 LSDB 的…...
Kubernetes相关的名词解释API Server组件(9)
什么是API Server? API Server(kube-apiserver) 是 Kubernetes 的核心组件之一,负责管理整个集群的通信和操作入口。 API Server 的作用在整个 Kubernetes 集群的正常运作中至关重要,可以说它是整个系统的神经中枢。…...
[密码学实战]密码服务平台部署架构详解与学习路线
密码服务平台部署架构详解与学习路线 引言 在数字化转型的浪潮中,数据安全已成为企业生存的“生命线”。国密算法(SM2/SM3/SM4)作为我国自主研发的密码标准,正在政务、金融、医疗等领域加速落地。然而,构建一套高可用、高性能、合规的密码服务平台,仍需攻克架构设计、性…...
如何成为Prompt工程师:学习路径、核心技能与职业发展
一、什么是Prompt工程师? Prompt工程师是专注于通过设计、优化和调试大语言模型(LLM)的输入提示词(Prompt),以精准引导模型输出符合业务需求结果的技术人才。其核心能力在于将模糊的业务需求转化为结构化、…...
OpenCV 边缘检测(Edge Detection)cv2.Canny
OpenCV 边缘检测(Edge Detection)cv2.Canny flyfish import cv2video_path input_video.mp4 cap cv2.VideoCapture(video_path)while True:ret, frame cap.read()if not ret:break # 视频结束# 转灰度frame_gray cv2.cvtColor(frame, cv2.COLOR_B…...
【C++】win 10 / win 11:Dev-C++ 下载与安装
目录 一、Dev-C 下载 (1)sourceforge 官网下载 (2)腾讯官网下载 二、Dev-C 安装 三、Dev-C 配置 (1)配置 C11 (2)配置产生调试信息 (3)个性化配置…...
2025年MathorCup竞赛助攻资料免费分享
对于本界竞赛B题其中问题需要设计软件框架,对于该问题回答,个人认为可以在设计框架下简单的进行软件展示,下面是初步展示的结果,仅供参考 【问题四:老城区平移置换决策软件设计】规划局希望这个案例能起到示范作用&am…...
征程 6 VIO 通路断流分析
自动驾驶场景中,常见的是多路感知通路,在不考虑应用获取释放帧异常操作的前提下,一般出现帧获取异常的情况,主要原因是通路中某段断流的情况,如何去准确的定位,对大部分客户来说,依赖我司的支持…...
JavaScript 性能优化
JavaScript 性能优化是提高 Web 应用性能的关键步骤,特别是在处理大量数据、复杂计算或频繁的 DOM 操作时。以下是一些常见的 JavaScript 性能优化技巧和策略: 文章目录 @[TOC]一、代码层面优化1. **减少全局变量**2. **避免使用 `with` 语句**3. **使用局部变量**4. **减少 …...
机器学习中的“三态模型“:过拟合、欠拟合和刚刚好
文章目录 说明1. 模型表现的"三国演义"2. 可视化理解:从曲线看状态3. 诊断模型:你的模型"病"了吗?4. 学习曲线:模型的"体检报告"5. 治疗"模型病"的药方 6. 偏差-方差分解:理解…...
在FVM(有限体积法)的CFD仿真中,AI和机器学习的应用
在FVM(有限体积法)的CFD仿真中,AI和机器学习(ML)可以通过以下方式显著提高收敛速度与计算效率,具体分为六个方向: 1. 加速非线性迭代收敛 替代传统松弛方法: 使用ML模型(…...
【21天学习打卡挑战赛】如何学习WEB安全:逼自己在短时间掌握WEB安全核心内容
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
网络安全知识点3
1.AES密钥长度可以为128,192,256位,但分组长度为128位 2.DES加密算法的密钥长度为56位,三重DES的密钥长度为112位 3.主动攻击:拒绝服务攻击,分布式拒绝服务DDOS,信息篡改,资源使用,欺骗,伪装,重放,会话拦截 被动攻击:窃听,流量分析,破解弱加密的数据流 4.IPSec可对数据进行…...
力扣每日打卡16 781. 森林中的兔子(中等)
力扣 781. 森林中的兔子 中等 前言一、题目内容二、解题方法1. 哈希函数(来自评论区大佬的解题方法)2.官方题解2.1 方法一:贪心 前言 这是刷算法题的第十六天,用到的语言是JS 题目:力扣 781. 森林中的兔子 (中等) 一、…...
STM32基础教程——HEX数据包接收
前言 串口,是一种应用范围十分广泛的通信接口,串口的成本较低,容易使用,通信线路简单,可以实现两个设备之间的通信。单片机的串口可以实现单片机与单片机,单片机与电脑,单片机与其他设备的通信…...
【JavaWeb后端开发02】SpringBootWeb + Https协议
课程内容: SpringBootWeb 入门 Http协议 SpringBootWeb案例 分层解耦 文章目录 1. SpringBootWeb入门1.1 概述1.2 入门程序1.2.1 需求1.2.2 开发步骤1.2.3 常见问题 1.3 入门解析 2. HTTP协议2.1 HTTP概述2.1.1 介绍2.1.2 特点 2.2 HTTP请求协议2.2.1 介绍2.2.2…...
基于论文的大模型应用:基于SmartETL的arXiv论文数据接入与预处理(三)
上一篇 介绍了数据接入处理的整体方案设计。本篇介绍基于SmartETL框架的流程实现。 5. 流程开发 5.1.简单采集流程 从指定时间(yy年 mm月)开始,持续采集arXiv论文。基于月份和顺序号,构造论文ID,进而下载论文PDF文件…...
深入理解Linux中的线程控制:多线程编程的实战技巧
个人主页:chian-ocean 文章专栏-Linux 前言: POSIX线程(Pthreads) 是一种在 POSIX 标准下定义的线程库,它为多线程编程提供了统一的接口,主要用于 UNIX 和类 UNIX 系统(如 Linux、MacOS 和 BS…...
从内核到用户态:Linux信号内核结构、保存与处理全链路剖析
Linux系列 文章目录 Linux系列前言一、信号的保存1.1 信号保存概念引入1.2 信号的阻塞与保存1.2.1 信号其他相关常见概念1.2.2 信号在内核中的表示 二、信号相关接口2.1 signal_t 结构体类型2.2 信号集操作函数 三、信号的处理3.1 进程地址空间信号的检测与处理 总结 前言 Lin…...
【AI图像创作变现】02工具推荐与差异化对比
引言 市面上的AI绘图工具层出不穷,但每款工具都有自己的“性格”:有的美学惊艳但无法微调,有的自由度极高却需要动手配置,还有的完全零门槛适合小白直接上手。本节将用统一格式拆解五类主流工具,帮助你根据风格、控制…...
Spring Boot 集成Poi-tl实现动态Word文档生成
Spring Boot 集成Poi-tl实现动态Word文档生成 「gen-pic-word.zip」 链接: https://pan.quark.cn/s/74396770a5c2 前言 在项目开发过程中,遇到了一个需求:将用户输入的数据填充到给定格式的 Word 文档中。简单来说,就是要根据预…...
【失败总结】Win10系统安装docker
1.启用或关闭windows功能中,将Hyper-V功能勾选全部启用,容器勾选。设置好后要重启电脑。 2.管网下载下载安装Docker Docker官网:https://www.docker.com/ 3.可以自定义Docker安装路径 新建安装目录:d:\MySoftware\Docker并将D…...
区块链预言机(Oracle)详解:如何打通链上与现实世界的关键桥梁?
文章目录 一、什么是区块链预言机?1.1 区块链的封闭性问题1.2 预言机的定义与作用举个例子: 1.3 为什么预言机是 Web3 的关键基础设施? 二、预言机的基本分类与工作模式2.1 输入型与输出型预言机(1)输入型预言机&#…...
Halcon应用:相机标定
提示:若没有查找的算子,可以评论区留言,会尽快更新 Halcon应用:相机标定 前言一、Halcon应用?二、应用实战1、图像理解1.1、开始标定 前言 本篇博文主要用于记录学习Halcon中算子的应用场景,及其使用代码和…...
【中间件】redis使用
一、redis介绍 redis是一种NoSQL类型的数据库,其数据存储在内存中,因此其数据查询效率很高,很快。常被用作数据缓存,分布式锁 等。SpringBoot集成了Redis,可查看开发文档Redis开发文档。Redis有自己的可视化工具Redis …...
【OSG学习笔记】Day 4: 相机与视口——控制观察视角
相机与视口 相机和视口的关系如下图: ```paintext +----------------------+ | 相机 (Camera) | +----------------------+ | - FOV | | - 投影模式 | | - 裁剪平面 | | - 视点矩阵 | +----------------------+|V +--…...
Vue.js 简介
Vue.js 简介 Vue.js 是一款非常流行的 渐进式 JavaScript 框架,用于构建用户界面,特别是在开发 单页应用(SPA) 时表现出色。Vue 由 尤雨溪(Evan You)在 2014 年创建,它的核心库专注于 视图层&a…...
快速下载Node.js
Node.js 是基于 Chrome V8 引擎的开源 JavaScript 运行时,允许开发者使用 JavaScript 构建服务器端应用、命令行工具和分布式系统。它以事件驱动、非阻塞 I/O 模型著称,适合开发高性能、可扩展的网络应用。 下载与安装配置 下载 LTS 版本:访问…...
使用 PCL 和 Qt 实现点云可视化与交互
下面我将介绍如何结合点云库(PCL)和Qt框架(特别是QML)来实现点云的可视化与交互功能,包括高亮选择等效果。 1. 基本架构设计 首先需要建立一个结合PCL和Qt的基本架构: // PCLQtViewer.h #pragma once#include <QObject> #include <pcl/point…...