当前位置: 首页 > news >正文

Android入门第66天-使用AOP

开篇

        这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx

        首先这些错的文章我不知道是怎么来的,其次那些案例真的运行成功过吗?它们用的都是aspectjx功能,是为了兼容Kotlin。

        还是那句话,Java开发和Kotlin还有Flutter开发到底有什么区别?整一堆无用功、把框架搞得很复杂、一堆错误的无用的代码混在一起,这才是本质。开发了好用什么语言都可以开发出好东西来,开发了不好就只剩下了:我用的开发语言是最先进的。因此我们坚持使用Java。

        扯回来,我们继续来看AOP功能在Android中的使用,由于Android是Java语言,因此它也支持aspectj功能,用法和在spring系里使用一模一样的简单。只不过都是被了这个叫:com.hujiang.aspectjx:gradle-android-plugin-aspectjx搞了太复杂了,甚至有人几天都调不通一个AOP功能。

        因此本篇就是交给大家正确的、纯净的、纯真的AOP使用方法。

AOP的使用场景

        AOP使用场景太多了,如:拦截方法打印日志,特别是:APP里有很多activity,它要求用户必须登录才可以打开,如:用户积分、我的历史订单等。对于这些界面,我们要求如果用户不登录那么APP自动跳到登录页。

        这种操作就是用的AOP功能去实现的。

如何使用一个AOP来判断用户打开界面前是否已经登录

        一个activity从进入到展示界面在onCreate方法里一般会经历这么几个方法,这里的initMain()就是用来展示界面的。

我们为了做这个界面在展示前判断用户是否已经登录,我们会这么“切”一刀。

然后把这个判断用户是否已经登录做成一个和后端交互的API,整个是否登录的判断逻辑如下截图:

动手使用AOP实现

        我们假设后台的这个接口如下所示(这边用了上一篇讲到的retrofit2+okhttp3+rxjava)。

package com.mkyuan.aset.mall.android.login.api;import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;public interface LoginAPI {@POST("/api/account/checkLoginUT")Observable <LoginResponseBean> checkLoginUT(@Header("ut") String ut);
}

        所以我们现在开始动手制作我们的AOP了。

先引入AOP包

        我们这边不会使用网上的已经不维护的、一堆问题的“com.hujiang.aspectjx:gradle-android-plugin-aspectjx”。

        请直接使用“org.aspectj:aspectjrt:1.9.6”。为此

第一步:编程全局build.gradle

加入以下语句:

dependencies {classpath 'org.aspectj:aspectjtools:1.9.6'
}

第二步:编辑我们的模块级别的build.gradle文件

先引入aspectjrt包

    implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj

然后再要在同一个build.gradle中加入如下语句

//使得aop生效
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main// 获取log打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->if (!variant.buildType.isDebuggable()) {// 判断是否debug,如果打release把return去掉就可以log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")// return;}// 使aspectj配置生效JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.8","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);//在编译时打印信息如警告、error等等for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}

加完后的build.gradle长这个样

/*** following plugins is the original version*/
/*
plugins {id 'com.android.application'
}
*/
/*** by using aspectj must ad following lines tart*/
apply plugin: 'com.android.application'
//apply plugin: 'android-aspectjx' //暂时注了
/*** by using aspectj must ad following lines tart*/
android {namespace 'com.mkyuan.aset.mall.android'compileSdk 32defaultConfig {applicationId "com.mkyuan.aset.mall.android"minSdk 27targetSdk 32versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}dataBinding {enabled = true}//aspectjx {//    exclude "**/module-info.class"//    exclude "META-INF.versions.9.module-info"//    exclude "META-INF/versions/9/*.class"//    exclude 'com.google', 'com.squareup', 'org.apache','com.taobao','com.ut'//    exclude 'androidx', 'com.squareup', 'com.alipay', 'org.apache', 'org.jetbrains.kotlin',//    "module-info", 'versions.9'//}}dependencies {implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectjimplementation 'com.github.bumptech.glide:glide:4.11.0'annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'implementation 'org.aspectj:aspectjrt:1.9.6'implementation 'io.github.youth5201314:banner:2.2.2'//com.google.android.material.theme//implementation 'com.google.android.material:material:<version>'//retrofit2implementation 'com.squareup.retrofit2:retrofit:2.9.0'implementation 'com.squareup.retrofit2:converter-gson:2.9.0'//日志拦截器implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'//rxjavaimplementation 'io.reactivex.rxjava2:rxandroid:2.1.1'implementation 'io.reactivex.rxjava2:rxjava:2.2.12'//gsonimplementation 'com.google.code.gson:gson:2.8.7'implementation 'androidx.appcompat:appcompat:1.4.1'implementation 'com.google.android.material:material:1.5.0'implementation 'androidx.constraintlayout:constraintlayout:2.1.3'testImplementation 'junit:junit:4.13.2'androidTestImplementation 'androidx.test.ext:junit:1.1.3'androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}//使得aop生效
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main// 获取log打印工具和构建配置
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->if (!variant.buildType.isDebuggable()) {// 判断是否debug,如果打release把return去掉就可以log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")// return;}// 使aspectj配置生效JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo","-1.8","-inpath", javaCompile.destinationDir.toString(),"-aspectpath", javaCompile.classpath.asPath,"-d", javaCompile.destinationDir.toString(),"-classpath", javaCompile.classpath.asPath,"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);//在编译时打印信息如警告、error等等for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}
}

制作AOP相关的代码

Login.java-定义一个基于方法切面的annotation

package com.mkyuan.aset.mall.android.util.aop.login;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;//不需要回调的处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}

LoginAspect.java -使用Around模式对含有@Login的方法进行切面

package com.mkyuan.aset.mall.android.util.aop.login;import android.util.Log;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;@Aspect
public class LoginAspect {private static final String TAG = "LoginAspect";@Pointcut("execution(@com.mkyuan.aset.mall.android.util.aop.login.Login  * *(..))")public void executionCheckLogin() {}//不带回调的注解处理@Around("executionCheckLogin()")public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {Log.i(TAG, ">>>>>>走进AOP方法");Signature signature = joinPoint.getSignature();if (!(signature instanceof MethodSignature)) {throw new RuntimeException("该注解只能用于方法上");}Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);if (login == null)return;//判断当前是否已经登录LoginManager.isLogin(new LoginCheckCallBack() {@Overridepublic void changeValue(boolean loginResult) {if(loginResult) {Log.i(TAG, ">>>>>>用户己登录走入下一步");try {joinPoint.proceed();} catch (Throwable e) {Log.e(TAG,e.getMessage(),e);}}else{//如果未登录,去登录页面Log.i(TAG, ">>>>>>用户未登录去登录页面");LoginManager.gotoLoginPage();}}});}
}

LoginManager.java

        内含有和后台是否登录接口交互以及判断用户如果已经登录那么继续“走下去-打开界面”,否则跳到Login登录界面的逻辑

package com.mkyuan.aset.mall.android.util.aop.login;import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;import com.google.gson.Gson;
import com.mkyuan.aset.mall.android.home.MainActivity;
import com.mkyuan.aset.mall.android.login.LoginActivity;
import com.mkyuan.aset.mall.android.login.SmsLoginActivity;
import com.mkyuan.aset.mall.android.login.api.LoginAPI;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import com.mkyuan.aset.mall.android.network.BaseObserver;
import com.mkyuan.aset.mall.android.network.NetworkApi;
import com.mkyuan.aset.mall.android.util.ContextUtils;
import com.mkyuan.aset.mall.android.util.SharedPreferenceHelper;
import com.mkyuan.aset.mall.android.util.activity.ActivityCollector;import java.util.Map;import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.RequestBody;public class LoginManager {private static final String TAG = "LoginAspect";public static void isLogin(LoginCheckCallBack loginCheckCallBack) {Context ctx = null;try {ctx = ContextUtils.getCurApplicationContext();SharedPreferenceHelper spHelper = new SharedPreferenceHelper(ctx);Map<String, String> data = spHelper.read();if (data.get("ut") != null && data.get("ut").trim().length() > 0) {String utValue = data.get("ut");//开始调用okhttp3, retrofit, rxjava框架LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);//loginAPI.checkLoginUT().loginAPI.checkLoginUT(utValue).compose(NetworkApi.applySchedulers(new BaseObserver<LoginResponseBean>() {@Overridepublic void onSuccess(LoginResponseBean loginResponseBean) {Log.i(TAG, ">>>>>>" + new Gson().toJson(loginResponseBean));int returnCode = loginResponseBean.getCode();String returnMsg = loginResponseBean.getMessage();if (returnCode == 0) {//result = true;loginCheckCallBack.changeValue(true);Log.i(TAG,">>>>>>get verifiedCode->" + loginResponseBean.getData());//startActivity(new Intent(SmsLoginActivity.this, MainActivity// .class));} else {loginCheckCallBack.changeValue(false);}}@Overridepublic void onFailure(Throwable e) {Log.e(TAG, ">>>>>>Network Error: " + e.toString(), e);loginCheckCallBack.changeValue(false);}}));} else {loginCheckCallBack.changeValue(false);}} catch (Exception e) {Log.e(TAG, ">>>>>>isLogin error: " + e.getMessage());loginCheckCallBack.changeValue(false);}}public static void gotoLoginPage() {Context ctx = null;try {ctx = ContextUtils.getCurApplicationContext();Intent intent = new Intent();intent.setClass(ctx, LoginActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);ctx.startActivity(intent);ActivityCollector.getInstance().quitCurrentProcess("com.mkyuan.aset.mall.android.home.MainActivity");} catch (Exception e) {Log.e(TAG, ">>>>>>gotoLoginPage error: " + e.getMessage(), e);}}
}

使用这个AOP

        在MainActivity代码里有一个initMain方法,渲染界面的逻辑全部在这个initMain方法里。

接着我们来看这个initMain()方法。在方法前我们加入了自定义的“切入点”。

效果演示

第一次打开APP,用户未登录,因此直接被跳到了Login界面

然后输入手机,点获取验证码

按提交,登录成功。

然后关闭APP,再次打开APP

由于之前用户已经登录过了,因此AOP直接会把用户带入到主页

结合着我上一篇:retrofit2+okhttp3+rxjava自己不妨动动手试试看吧

相关文章:

Android入门第66天-使用AOP

开篇这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx。首先这些错的文章我不知道是怎么来的&#xff0c;其次那些案例真的运行成功…...

pl/sql篇之触发器

简述本文將具体简述触发器的语法&#xff0c;触发条件及其适用场景&#xff0c;希望对读者理解&#xff0c;使用触发器能起到作用。触发器的定位触发器是数据库独立编译&#xff0c;存储的对象&#xff0c;是数据库重要的技术。和函数不同&#xff0c;触发器的执行是主动的&…...

黑马《数据结构与算法2023版》正式发布

有人的地方就有江湖。 在“程序开发”的江湖之中&#xff0c;各种技术流派风起云涌&#xff0c;变幻莫测&#xff0c;每一位IT侠客&#xff0c;对“技术秘籍”的追求和探索也从未停止过。 要论开发技术哪家强&#xff0c;可谓众说纷纭。但长久以来&#xff0c;确有一技&#…...

Spring的创建和使用

目录 创建Spring项目 步骤 1)使用Maven的方式创建Spring项目 2)添加Spring依赖 3)创建启动类 存Bean对象 1.创建Bean对象 2.将Bean注册到Spring中 取Bean对象并使用 步骤 1.先得到Spring上下文对象 2.从Spring中获取Bean对象 3.使用Bean ApplicationContext VS Bea…...

如何实现外网跨网远程控制内网计算机?快解析来解决

远程控制&#xff0c;是指管理人员在异地通过计算机网络异地拨号或双方都接入Internet等手段&#xff0c;连通需被控制的计算机&#xff0c;将被控计算机的桌面环境显示到自己的计算机上&#xff0c;通过本地计算机对远方计算机进行配置、软件安装程序、修改等工作。通俗来讲&a…...

【跟着ChatGPT学深度学习】ChatGPT教我文本分类

【跟着ChatGPT学深度学习】ChatGPT教我文本分类 ChatGPT既然无所不能&#xff0c;我为啥不干脆拜他为师&#xff0c;直接向他学习&#xff0c;岂不是妙哉。说干就干&#xff0c;我马上就让ChatGPT给我生成了一段文本分类的代码&#xff0c;不看不知道&#xff0c;一看吓一跳&am…...

IM即时通讯架构技术:可靠性、有序性、弱网优化等

消息的可靠性是IM系统的典型技术指标&#xff0c;对于用户来说&#xff0c;消息能不能被可靠送达&#xff08;不丢消息&#xff09;&#xff0c;是使用这套IM的信任前提。 换句话说&#xff0c;如果这套IM系统不能保证不丢消息&#xff0c;那相当于发送的每一条消息都有被丢失的…...

【算法】三道算法题两道难度中等一道困难

算法目录只出现一次的数字&#xff08;中等难度&#xff09;java解答参考二叉树的层序遍历&#xff08;难度中等&#xff09;java 解答参考给表达式添加运算符&#xff08;比较困难&#xff09;java解答参考大家好&#xff0c;我是小冷。 上一篇是算法题目 接下来继续看下算法题…...

正交实验与极差分析

正交试验极差分析流程如下图&#xff1a; 正交试验说明 正交试验是研究多因素试验的设计方法。对于多因素、多水平的实验要求&#xff0c;如果每个因素的每个水平都要进行试验&#xff0c;这样就会耗费大量的人力和时间&#xff0c;正交试验可以选择出具有代表性的少数试验进行…...

DEXTUpload .NET增强的上传速度和可靠性

DEXTUpload .NET增强的上传速度和可靠性 DEXTUpload.NET Pro托管在Windows操作系统上的Internet Information Server(IIS)上&#xff0c;服务器端组件基于HTTP协议&#xff0c;支持从web浏览器到web服务器的文件上载。它也可以在ASP.NET服务器应用程序平台开发的任何网站上使用…...

SkyWalking 将方法加入追踪链路(@Trace)

SkyWalking8 自定义链路追踪@Trace 自定义链路,需要依赖skywalking官方提供的apm-toolkit-trace包.在pom.xml的dependencies中添加如下依赖: <dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-trace</artifactId>&…...

MySQL Administrator定时备份MySQL数据库

1、下载并安装软件mysql-gui-tools-5.0-r17-win32.exe 2、将汉化包zh_CN文件夹拷贝到软件安装目录 3、菜单中打开MySql Adminstrator&#xff0c;见下图&#xff0c;初次打开无服务实例。 点击已存储连接右侧按钮①&#xff0c;打开下图对话框。点击“新连接”按钮&#xff…...

Kubernetes入门教程 --- 使用二进制安装

Kubernetes入门教程 --- 使用二进制安装1. Introduction1.1 架构图1.2 关键字介绍1.3 简述2. 使用Kubeadm Install2.1 申请三个虚拟环境2.2 准备安装环境2.3 配置yum源2.4 安装Docker2.4.1 配置docker加速器并修改成k8s驱动2.5 时间同步2.6 安装组件3. 基础知识3.1 Pod3.2 控制…...

深度学习模型压缩方法概述

一,模型压缩技术概述 1.1,模型压缩问题定义 因为嵌入式设备的算力和内存有限,因此深度学习模型需要经过模型压缩后,方才能部署到嵌入式设备上。 模型压缩问题的定义可以从 3 角度出发: 模型压缩的收益: 计算: 减少浮点运算量(FLOPs),降低延迟(Latency)存储: 减少内…...

《NFL橄榄球》:坦帕湾海盗·橄榄1号位

坦帕湾海盗&#xff08;英语&#xff1a;Tampa Bay Buccaneers&#xff09;是一支位于佛罗里达州的坦帕湾职业美式橄榄球球队。他们是全国橄榄球联盟的南区其中一支球队。在1976年&#xff0c;与西雅图海鹰成为NFL的球队。球队在最初的两个球季连败26场&#xff0c;在二十世纪七…...

Xmake v2.7.7 发布,支持 Haiku 平台,改进 API 检测和 C++ Modules 支持

layout: post.cn title: “Xmake v2.7.7 发布&#xff0c;支持 Haiku 平台&#xff0c;改进 API 检测和 C Modules 支持” tags: xmake lua C/C package modules haiku cmodules categories: xmake Xmake 是一个基于 Lua 的轻量级跨平台构建工具。 它非常的轻量&#xff0c;没…...

苹果ios签名证书的生成方法

在使用hbuilderx打包uniapp或html5应用的时候&#xff0c;假如是打包ios应用&#xff0c;是需要ios签名证书&#xff0c;和证书profile文件的&#xff0c;这个证书要求是p12格式的证书&#xff0c;profile文件又叫描述文件。 这两个文件&#xff0c;需要在苹果开发者中心生成&…...

c++开发配置常用网站记录

1.ubuntu 镜像源&#xff1a; (1) 清华源&#xff1a;https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/ (2) 阿里源&#xff1a;https://developer.aliyun.com/mirror/ubuntu?spma2c6h.13651102.0.0.3e221b11VuM27s 包含了ubuntu各个版本的source源 2.ubuntu iso镜像下载…...

DC-1 靶场学习

以前写过了&#xff0c;有一些忘了&#xff0c;快速的重温一遍。 DC一共九个靶场&#xff0c;目标一天一个。 文章目录环境配置&#xff1a;信息搜集&#xff1a;漏洞复现&#xff1a;FLAG获取环境配置&#xff1a; 最简单的办法莫过于将kali和DC-1同属为一个nat的网络下。 信…...

oracle 不使用索引深入解析

首先&#xff0c;我们要确定数据库运行在何种优化模式下&#xff0c;相应的参数是&#xff1a;optimizer_mode。缺省的设置应是"choose"&#xff0c;即如果对已分析的表查询的话选择CBO&#xff0c;否则选择RBO。如果该参数设为“rule”&#xff0c;则不论表是否分析…...

别再只会用PWM调速度了!STM32驱动直流有刷电机,H桥的三种模式(单极/双极/受限)到底怎么选?

STM32驱动直流有刷电机的三种H桥模式深度解析与实战选型指南 在嵌入式电机控制领域&#xff0c;PWM调速早已成为基础技能&#xff0c;但真正决定系统性能的往往是H桥工作模式的选择。当你的电机出现异常发热、刹车响应迟缓或低速抖动时&#xff0c;问题很可能就出在模式选择不当…...

魔兽争霸III终极优化指南:让你的经典游戏在现代系统上焕发新生

魔兽争霸III终极优化指南&#xff1a;让你的经典游戏在现代系统上焕发新生 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸III在Window…...

WarcraftHelper:5分钟解决魔兽争霸III现代兼容性问题的终极指南

WarcraftHelper&#xff1a;5分钟解决魔兽争霸III现代兼容性问题的终极指南 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为经典魔兽争霸III在W…...

KMS_VL_ALL_AIO终极指南:三步永久激活Windows和Office系统

KMS_VL_ALL_AIO终极指南&#xff1a;三步永久激活Windows和Office系统 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统频繁弹出激活提示而烦恼吗&#xff1f;Office文档突然变…...

中性点不接地系统或中性点经消弧线圈接地系统的小电流接地故障仿真研究(Simulink仿真实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 &#x1f381…...

从入门到发烧:2026 Linux 必装 13 款播放器(VLC/MPV/Kodi 全覆盖)

Linux视频播放器选择多样&#xff0c;如榛名、MPlayer、VLC等&#xff0c;功能强大、支持多格式&#xff0c;满足各类用户需求 一、榛名视频播放器 榛名视频播放器是一款基于Qt的开源视频播放器&#xff0c;提供了许多基本功能。其特点包括支持Youtube-dl、控制播放速度、丰富…...

手把手教你从零搭建 MCP Server:AI 连接万物的保姆级实战教程

为什么要学 MCP&#xff1f; 说实话&#xff0c;最近半年 AI 开发圈最火的协议就是 MCP&#xff08;Model Context Protocol&#xff09;了。你可能已经用上了各种 AI 助手&#xff0c;但有没有想过&#xff1a;这些 AI 怎么连接你的数据库&#xff1f;怎么读你的本地文件&…...

Lovable主题定制深度教程:不改一行PHP代码,实现品牌专属UI/UX升级(仅限当前版本v4.8.3私有补丁包)

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;Lovable主题定制深度教程&#xff1a;不改一行PHP代码&#xff0c;实现品牌专属UI/UX升级&#xff08;仅限当前版本v4.8.3私有补丁包&#xff09; Lovable v4.8.3 通过其增强型 CSS 变量体系与声明式主题注入…...

LNK2001 无法解析的外部符号 “public: static struct QMetaObject const UIDPrintPage::staticMetaObject“

排查一早上的问题&#xff0c;不知道设置哪里出了这个问题&#xff0c;突然提示无法生成Qt的元对象moc_对应的文件&#xff0c;所以这里查找问题根源&#xff0c;语法错误还是路径设置等问题。最终定位还是文件属性设置有问题&#xff0c;估计是改了那些设置吧&#xff0c;最终…...

用 jose 正确实现 JWT 签发、验签与密钥轮换

1. 为什么你写的 JWT 总是“看起来能用&#xff0c;上线就出事”JWT&#xff08;JSON Web Token&#xff09;这东西&#xff0c;我第一次在项目里用的时候&#xff0c;也是照着文档抄了三行代码&#xff1a;jwt.sign(payload, secret)、jwt.verify(token, secret)、res.json({ …...