【开源项目】权限框架Nepxion Permission原理解析

news2024/11/29 4:34:48

项目介绍

Nepxion Permission是一款基于Spring Cloud的微服务API权限框架,并通过Redis分布式缓存进行权限缓存。它采用Nepxion Matrix AOP框架进行切面实现,支持注解调用方式,也支持Rest调用方式

项目地址

https://toscode.gitee.com/nepxion/Permission

原理解析

permission-aop-starter自动配置

permission-aop-starter项目下spring.factories

com.nepxion.permission.annotation.EnablePermission=\
com.nepxion.permission.configuration.PermissionAopConfiguration

PermissionAopConfiguration注入了PermissionAutoScanProxyPermissionInterceptorPermissionAuthorizationPermissionPersisterPermissionFeignBeanFactoryPostProcessor

@Configuration
public class PermissionAopConfiguration {
	//...

    @Value("${" + PermissionConstant.PERMISSION_SCAN_PACKAGES + ":}")
    private String scanPackages;

    @Bean
    public PermissionAutoScanProxy permissionAutoScanProxy() {
        return new PermissionAutoScanProxy(scanPackages);
    }

    @Bean
    public PermissionInterceptor permissionInterceptor() {
        return new PermissionInterceptor();
    }

    @Bean
    public PermissionAuthorization permissionAuthorization() {
        return new PermissionAuthorization();
    }

    @Bean
    public PermissionPersister permissionPersister() {
        return new PermissionPersister();
    }

    @Bean
    public PermissionFeignBeanFactoryPostProcessor permissionFeignBeanFactoryPostProcessor() {
        return new PermissionFeignBeanFactoryPostProcessor();
    }
}

权限拦截器

PermissionAutoScanProxy核心功能就是给带有注解Permission的方法生成代理类,收集所有的PermissionEntity

public class PermissionAutoScanProxy extends DefaultAutoScanProxy {
    private static final long serialVersionUID = 3188054573736878865L;

    @Value("${" + PermissionConstant.PERMISSION_AUTOMATIC_PERSIST_ENABLED + ":true}")
    private Boolean automaticPersistEnabled;

    @Value("${" + PermissionConstant.SERVICE_NAME + "}")
    private String serviceName;

    @Value("${" + PermissionConstant.SERVICE_OWNER + ":Unknown}")
    private String owner;

    private String[] commonInterceptorNames;

    @SuppressWarnings("rawtypes")
    private Class[] methodAnnotations;

    private List<PermissionEntity> permissions = new ArrayList<PermissionEntity>();

    public PermissionAutoScanProxy(String scanPackages) {
        super(scanPackages, ProxyMode.BY_METHOD_ANNOTATION_ONLY, ScanMode.FOR_METHOD_ANNOTATION_ONLY);
    }

    @Override
    protected String[] getCommonInterceptorNames() {
        if (commonInterceptorNames == null) {
            commonInterceptorNames = new String[] { "permissionInterceptor" };
        }

        return commonInterceptorNames;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Class<? extends Annotation>[] getMethodAnnotations() {
        if (methodAnnotations == null) {
            methodAnnotations = new Class[] { Permission.class };
        }

        return methodAnnotations;
    }

    @Override
    protected void methodAnnotationScanned(Class<?> targetClass, Method method, Class<? extends Annotation> methodAnnotation) {
        if (automaticPersistEnabled) {
            if (methodAnnotation == Permission.class) {
                Permission permissionAnnotation = method.getAnnotation(Permission.class);

                String name = permissionAnnotation.name();
                if (StringUtils.isEmpty(name)) {
                    throw new PermissionAopException("Annotation [Permission]'s name is null or empty");
                }

                String label = permissionAnnotation.label();

                String description = permissionAnnotation.description();

                // 取类名、方法名和参数类型组合赋值
                String className = targetClass.getName();
                String methodName = method.getName();
                Class<?>[] parameterTypes = method.getParameterTypes();
                String parameterTypesValue = ProxyUtil.toString(parameterTypes);
                String resource = className + "." + methodName + "(" + parameterTypesValue + ")";

                PermissionEntity permission = new PermissionEntity();
                permission.setName(name);
                permission.setLabel(label);
                permission.setType(PermissionType.API.getValue());
                permission.setDescription(description);
                permission.setServiceName(serviceName);
                permission.setResource(resource);
                permission.setCreateOwner(owner);
                permission.setUpdateOwner(owner);

                permissions.add(permission);
            }
        }
    }

    public List<PermissionEntity> getPermissions() {
        return permissions;
    }
}

PermissionInterceptor,根据方法上的UserId或者UserType,获取用户;或者根据方法上的Token获取token,再根据token信息获取用户数据。

public class PermissionInterceptor extends AbstractInterceptor {
	//...

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (interceptionEnabled) {
            Permission permissionAnnotation = getPermissionAnnotation(invocation);
            if (permissionAnnotation != null) {
                String name = permissionAnnotation.name();
                String label = permissionAnnotation.label();
                String description = permissionAnnotation.description();

                return invokePermission(invocation, name, label, description);
            }
        }

        return invocation.proceed();
    }
    
    
    private Object invokePermission(MethodInvocation invocation, String name, String label, String description) throws Throwable {
        if (StringUtils.isEmpty(serviceName)) {
            throw new PermissionAopException("Service name is null or empty");
        }

        if (StringUtils.isEmpty(name)) {
            throw new PermissionAopException("Annotation [Permission]'s name is null or empty");
        }

        String proxyType = getProxyType(invocation);
        String proxiedClassName = getProxiedClassName(invocation);
        String methodName = getMethodName(invocation);

        if (frequentLogPrint) {
            LOG.info("Intercepted for annotation - Permission [name={}, label={}, description={}, proxyType={}, proxiedClass={}, method={}]", name, label, description, proxyType, proxiedClassName, methodName);
        }

        UserEntity user = getUserEntityByIdAndType(invocation);
        if (user == null) {
            user = getUserEntityByToken(invocation);
        }

        if (user == null) {
            throw new PermissionAopException("No user context found");
        }

        String userId = user.getUserId();
        String userType = user.getUserType();

        // 检查用户类型白名单,决定某个类型的用户是否要执行权限验证拦截
        boolean checkUserTypeFilters = checkUserTypeFilters(userType);
        if (checkUserTypeFilters) {
            boolean authorized = permissionAuthorization.authorize(userId, userType, name, PermissionType.API.getValue(), serviceName);
            if (authorized) {
                return invocation.proceed();
            } else {
                String parameterTypesValue = getMethodParameterTypesValue(invocation);

                throw new PermissionAopException("No permision to proceed method [name=" + methodName + ", parameterTypes=" + parameterTypesValue + "], permissionName=" + name + ", permissionLabel=" + label);
            }
        }

        return invocation.proceed();
    }
    
    private UserEntity getUserEntityByIdAndType(MethodInvocation invocation) {
        // 获取方法参数上的注解值
        String userId = getValueByParameterAnnotation(invocation, UserId.class, String.class);
        String userType = getValueByParameterAnnotation(invocation, UserType.class, String.class);

        if (StringUtils.isEmpty(userId) && StringUtils.isNotEmpty(userType)) {
            throw new PermissionAopException("Annotation [UserId]'s value is null or empty");
        }

        if (StringUtils.isNotEmpty(userId) && StringUtils.isEmpty(userType)) {
            throw new PermissionAopException("Annotation [UserType]'s value is null or empty");
        }

        if (StringUtils.isEmpty(userId) && StringUtils.isEmpty(userType)) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                userId = attributes.getRequest().getHeader(PermissionConstant.USER_ID);
                userType = attributes.getRequest().getHeader(PermissionConstant.USER_TYPE);
            }
        }

        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(userType)) {
            return null;
        }

        UserEntity user = new UserEntity();
        user.setUserId(userId);
        user.setUserType(userType);

        return user;
    }

    private UserEntity getUserEntityByToken(MethodInvocation invocation) {
        // 获取方法参数上的注解值
        String token = getValueByParameterAnnotation(invocation, Token.class, String.class);

        if (StringUtils.isEmpty(token)) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                token = attributes.getRequest().getHeader(PermissionConstant.TOKEN);
            }
        }

        if (StringUtils.isEmpty(token)) {
            return null;
        }

        // 根据token获取userId和userType
        UserEntity user = userResource.getUser(token);
        if (user == null) {
            throw new PermissionAopException("No user found for token=" + token);
        }

        return user;
    }

}

UserResource接口定义了Open Feign的接口格式,提供了根据token获取用户的接口。

@FeignClient(value = "${permission.service.name}")
public interface UserResource {
    @RequestMapping(path = "/user/getUser/{token}", method = RequestMethod.GET)
    UserEntity getUser(@PathVariable(value = "token") String token);
}

permission-service服务中,有具体的RestController实现了UserResource,提供了获取用户信息的真正接口。

@RestController
public class UserResourceImpl implements UserResource {
    private static final Logger LOG = LoggerFactory.getLogger(UserResourceImpl.class);

    // 根据Token获取User实体
    @Override
    public UserEntity getUser(@PathVariable(value = "token") String token) {
        // 当前端登录后,它希望送token到后端,查询出用户信息(并以此调用authorize接口做权限验证,permission-aop已经实现,使用者并不需要关心)
        // 需要和单点登录系统,例如OAuth或者JWT等系统做对接
        // 示例描述token为abcd1234对应的用户为lisi
        LOG.info("Token:{}", token);
        if (StringUtils.equals(token, "abcd1234")) {
            UserEntity user = new UserEntity();
            user.setUserId("lisi");
            user.setUserType("LDAP");

            return user;
        }

        return null;
    }
}

PermissionInterceptor#checkUserTypeFilters,检查用户类型白名单。

    private boolean checkUserTypeFilters(String userType) {
        if (StringUtils.isEmpty(whitelist)) {
            return true;
        }

        if (whitelist.toLowerCase().indexOf(userType.toLowerCase()) > -1) {
            return true;
        }

        return false;
    }

用户认证

PermissionAuthorization#authorize,调用远程服务,判断是否授权。会判断缓存中是否存在。

    // 通过自动装配的方式,自身调用自身的注解方法
    @Autowired
    private PermissionAuthorization permissionAuthorization;

    public boolean authorize(String userId, String userType, String permissionName, String permissionType, String serviceName) {
        return permissionAuthorization.authorizeCache(userId, userType, permissionName, permissionType, serviceName);
    }

    @Cacheable(name = "cache", key = "#userId + \"_\" + #userType + \"_\" + #permissionName + \"_\" + #permissionType + \"_\" + #serviceName", expire = -1L)
    public boolean authorizeCache(String userId, String userType, String permissionName, String permissionType, String serviceName) {
        boolean authorized = permissionResource.authorize(userId, userType, permissionName, permissionType, serviceName);

        LOG.info("Authorized={} for userId={}, userType={}, permissionName={}, permissionType={}, serviceName={}", authorized, userId, userType, permissionName, permissionType, serviceName);

        return authorized;
    }

PermissionResource提供了授权方法。

@FeignClient(value = "${permission.service.name}")
public interface PermissionResource {
    @RequestMapping(path = "/permission/persist", method = RequestMethod.POST)
    void persist(@RequestBody List<PermissionEntity> permissions);

    @RequestMapping(path = "/authorization/authorize/{userId}/{userType}/{permissionName}/{permissionType}/{serviceName}", method = RequestMethod.GET)
    boolean authorize(@PathVariable(value = "userId") String userId, @PathVariable(value = "userType") String userType, @PathVariable(value = "permissionName") String permissionName, @PathVariable(value = "permissionType") String permissionType, @PathVariable(value = "serviceName") String serviceName);
}

PermissionResourceImpl#authorize,提供具体的实现。

    // 权限验证
    @Override
    public boolean authorize(@PathVariable(value = "userId") String userId, @PathVariable(value = "userType") String userType, @PathVariable(value = "permissionName") String permissionName, @PathVariable(value = "permissionType") String permissionType, @PathVariable(value = "serviceName") String serviceName) {
        LOG.info("权限获取: userId={}, userType={}, permissionName={}, permissionType={}, serviceName={}", userId, userType, permissionName, permissionType, serviceName);
        // 验证用户是否有权限
        // 需要和用户系统做对接,userId一般为登录名,userType为用户系统类型。目前支持多用户类型,所以通过userType来区分同名登录用户,例如财务系统有用户叫zhangsan,支付系统也有用户叫zhangsan
        // permissionName即在@Permission注解上定义的name,permissionType为权限类型,目前支持接口权限(API),网关权限(GATEWAY),界面权限(UI)三种类型的权限(参考PermissionType.java类的定义)
        // serviceName即服务名,在application.properties里定义的spring.application.name
        // 对于验证结果,在后端实现分布式缓存,可以避免频繁调用数据库而出现性能问题
        // 示例描述用户zhangsan有权限,用户lisi没权限
        if (StringUtils.equals(userId, "zhangsan")) {
            return true;
        } else if (StringUtils.equals(userId, "lisi")) {
            return false;
        }

        return true;
    }

权限数据持久化

PermissionPersister#onApplicationEvent,失败进行重试。

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (automaticPersistEnabled) {
            if (event.getApplicationContext().getParent() instanceof AnnotationConfigApplicationContext) {
                LOG.info("Start to persist with following permission list...");
                LOG.info("------------------------------------------------------------");
                List<PermissionEntity> permissions = permissionAutoScanProxy.getPermissions();
                if (CollectionUtils.isNotEmpty(permissions)) {
                    for (PermissionEntity permission : permissions) {
                        LOG.info("Permission={}", permission);
                    }

                    persist(permissions, automaticPersistRetryTimes + 1);
                } else {
                    LOG.warn("Permission list is empty");
                }
                LOG.info("------------------------------------------------------------");
            }
        }
    }

PermissionFeignBeanFactoryPostProcessor后置处理器。

public class PermissionFeignBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition definition = beanFactory.getBeanDefinition("feignContext");
        definition.setDependsOn("eurekaServiceRegistry", "inetUtils");
    }
}

permission-service-starter自动配置

permission-service-starter的项目下自动配置spring.factories

com.nepxion.permission.service.annotation.EnablePermissionSerivce=\
com.nepxion.permission.service.configuration.PermissionServiceConfiguration

PermissionServiceConfiguration注入了PermissionResourceUserResource

@Configuration
public class PermissionServiceConfiguration {

    @Bean
    public PermissionResource permissionResource() {
        return new PermissionResourceImpl();
    }

    @Bean
    public UserResource userResource() {
        return new UserResourceImpl();
    }
}

permission-feign-starter自动配置

com.nepxion.permission.feign.annotation.EnablePermissionFeign=\
com.nepxion.permission.configuration.PermissionFeignConfiguration

PermissionFeignConfiguration注入了PermissionFeignInterceptor

@Configuration
public class PermissionFeignConfiguration {
    @Bean
    public PermissionFeignInterceptor permissionFeignInterceptor() {
        return new PermissionFeignInterceptor();
    }
}

PermissionFeignInterceptor,如果请求头上有user-iduser-typetoken,调用feign的时候复制一份。

public class PermissionFeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }

        HttpServletRequest request = attributes.getRequest();

        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames == null) {
            return;
        }

        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String header = request.getHeader(headerName);

            if (PermissionFeignConstant.PERMISSION_FEIGN_HEADERS.contains(headerName.toLowerCase())) {
                requestTemplate.header(headerName, header);
            }
        }
    }
}

PermissionFeignConstant中定义了PERMISSION_FEIGN_HEADERS

public class PermissionFeignConstant {
    public static final String PERMISSION_FEIGN_ENABLED = "permission.feign.enabled";

    public static final String TOKEN = "token";
    public static final String USER_ID = "user-id";
    public static final String USER_TYPE = "user-type";

    public static final String PERMISSION_FEIGN_HEADERS = TOKEN + ";" + USER_ID + ";" + USER_TYPE;
}

服务调用流程解析

ermission-springcloud-my-service-example服务启动会执行MyController的方法。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = { "com.nepxion.permission.api" })
@EnablePermission
@EnableCache
public class MyApplication {
    private static final Logger LOG = LoggerFactory.getLogger(MyApplication.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(MyApplication.class, args);

        MyController myController = applicationContext.getBean(MyController.class);
        try {
            LOG.info("Result : {}", myController.doA("zhangsan", "LDAP", "valueA"));
        } catch (Exception e) {
            LOG.error("Error", e);
        }

        try {
            LOG.info("Result : {}", myController.doB("abcd1234", "valueB"));
        } catch (Exception e) {
            LOG.error("Error", e);
        }
    }
}

MyController提供了三种demo,作为参考。

@RestController
public class MyController {
    private static final Logger LOG = LoggerFactory.getLogger(MyController.class);

    // 显式基于UserId和UserType注解的权限验证,参数通过注解传递
    @RequestMapping(path = "/doA/{userId}/{userType}/{value}", method = RequestMethod.GET)
    @Permission(name = "A-Permission", label = "A权限", description = "A权限的描述")
    public int doA(@PathVariable(value = "userId") @UserId String userId, @PathVariable(value = "userType") @UserType String userType, @PathVariable(value = "value") String value) {
        LOG.info("===== doA被调用");

        return 123;
    }

    // 显式基于Token注解的权限验证,参数通过注解传递
    @RequestMapping(path = "/doB/{token}/{value}", method = RequestMethod.GET)
    @Permission(name = "B-Permission", label = "B权限", description = "B权限的描述")
    public String doB(@PathVariable(value = "token") @Token String token, @PathVariable(value = "value") String value) {
        LOG.info("----- doB被调用");

        return "abc";
    }

    // 隐式基于Rest请求的权限验证,参数通过Header传递
    @RequestMapping(path = "/doC/{value}", method = RequestMethod.GET)
    @Permission(name = "C-Permission", label = "C权限", description = "C权限的描述")
    public boolean doC(@PathVariable(value = "value") String value) {
        LOG.info("----- doC被调用");

        return true;
    }
}

第一个接口是用户zhangsan,认证结果是可以的。

第二个接口是token,需要根据token获取用户,token等于abcd1234的用户是lisi,lisi是认证不通过的。

Redis日志打印

RedisCacheDelegateImpl#invokeCacheable,判断配置文件的属性决定日志打印。

    @Value("${frequent.log.print:false}")
    private Boolean frequentLogPrint;  

	public Object invokeCacheable(MethodInvocation invocation, List<String> keys, long expire) throws Throwable {
        Object object = null;

        try {
            object = this.valueOperations.get(keys.get(0));
            if (this.frequentLogPrint) {
                LOG.info("Before invocation, Cacheable key={}, cache={} in Redis", keys, object);
            }
        } catch (Exception var9) {
            if (!this.cacheAopExceptionIgnore) {
                throw var9;
            }

            LOG.error("Redis exception occurs while Cacheable", var9);
        }
    }

总结一下

  • 如果自己使用这套框架,首先permission-service-starter是需要自己去实现的,需要自己定义根据token获取用户信息,需要自己定义根据用户判断权限认证。

  • 核心架构只有Feign的使用,可以适配任意的注册中心。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现图形算法API[软光栅渲染器,C++]

最近有点烦&#xff0c;发烧感冒了三天[事实上是俩天&#xff0c;第三天是因为摆得太舒服了索性多玩一天]&#xff0c;啥都没学&#xff0c;打守望先锋也把把被虐...&#xff0c;想着今天来提起键盘把之前的东西都总结一下。 那么话归真题&#xff0c;首先我是仿造opengl来写的…

tensorflow/keras如何自定义layer

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

【Linux高级 I/O(3)】如何使用阻塞 I/O 与非阻塞 I/O?——poll()函数

poll()函数介绍 系统调用 poll()与 select()函数很相似&#xff0c;但函数接口有所不同。在 select()函数中&#xff0c;我们提供三个 fd_set 集合&#xff0c;在每个集合中添加我们关心的文件描述符&#xff1b;而在 poll()函数中&#xff0c;则需要构造一个 struct pollfd 类…

opencv图像增强实现方法

opencv是一款开源的图像增强工具&#xff0c;主要用于在 python环境下实现图像增强功能。 使用 opencv实现图像增强&#xff0c;需要使用 opencv的 GUI模块&#xff0c;如图1所示。 在 opencv中&#xff0c;有一个 datasets模块&#xff0c;这个模块主要用于处理数据和可视化操…

剑指 Offer 30. 包含min函数的栈【辅助栈】

剑指 Offer 30. 包含min函数的栈【辅助栈】 文章目录 剑指 Offer 30. 包含min函数的栈【辅助栈】题目描述题解 题目描述 题解 class MinStack {/*** initialize your data structure here.*/Stack<Integer> A, B;public MinStack(){A new Stack<Integer>();B ne…

使用OpenCV进行肺炎诊断检测

肺炎是一种由感染引起的严重呼吸道疾病&#xff0c;特别是在高危人群中&#xff0c;可能会出现危及生命的并发症。必须尽快诊断和治疗肺炎&#xff0c;以最大限度地提高患者康复的机会。 诊断过程并不容易&#xff0c;需要一些医学实验室工具和先进的医疗技能&#xff0c;但我们…

计算机专业毕业,有人Offer 50w,有人挂科重修!

昨天有两个VIP的小伙伴问我问题&#xff1a; 同学小明&#xff1a;孟哥&#xff0c;我小硕一枚&#xff0c;有两个offer&#xff0c;一个拿到了阿里的offer&#xff0c;乱七八糟加起来有四五十&#xff1b;还有一个是老家的电网。但是父母想让我回去&#xff0c;毕竟稳定&#…

Kali-linux绕过Utilman登录

Utilman是Windows辅助工具管理器。该程序是存放在Windows系统文件中最重要的文件&#xff0c;通常情况下是在安装系统过程中自动创建的&#xff0c;对于系统正常运行来说至关重要。在Windows下&#xff0c;使用WindowsU组合键可以调用Utilman进程。本节将介绍绕过Utilman程序登…

瑞芯微RGMII的配置

主要配置项 除去复位等信号&#xff0c;我们主要关注两大块的配置&#xff1a; 时钟配置 MAC 采用125M时钟&#xff0c;PHY采用25M时钟。 主要配置时钟源&#xff0c;这个和具体硬件连线强相关。例如125M时钟可以来源于soc内部的PLL&#xff0c;也可以由对端PHY 提供。 由对端P…

【JS】1688- 重学 JavaScript API - Fetch API

❝ 前期回顾&#xff1a; 1. Page Visibility API 2. Broadcast Channel API 3. Beacon API 4. Resize Observer API 5. Clipboard API ❞ &#x1f3dd; 1. 什么是 Fetch API 1.1 概念介绍 Fetch API[1] 是一种现代的 JavaScript API&#xff0c;用于进行「网络请求」。它提供…

过滤器(filter)、watch 侦听器 、计算属性 、axios、vue-cli 的使用 、vue组件化

过滤器&#xff08;filter&#xff09;、watch 侦听器 、计算属性 、axios、vue-cli 的使用 、vue组件化 1.过滤器&#xff08;filter&#xff09;过滤器的注意点定义全局变量 2.watch 侦听器侦听器的格式 3.计算属性4.axiosaxios 的基本使用 5.vue-cli 的使用6.vue组件化 1.过…

安卓与串口通信-基础篇

前言 安卓并不仅仅只是一个手机操作系统&#xff0c;在很多领域都能见到安卓的身影。 无论是车载系统、工控系统、屏控系统还是物联网设备基本都有安卓的一席之地。 在所谓的寒冬之下&#xff0c;纯粹的安卓开发似乎已经不再吃香&#xff0c;于是越来越多的安卓开发者转向了…

浅谈新兴室内外无线局域精准定位技术UWB(超宽带)

浅谈新兴室内外无线局域精准定位技术UWB&#xff08;超宽带&#xff09; 1、UWB高精度定位系统概述2、与传统定位比较3、应用场景4、实现uwb高精度定位4、UWB室内定位的缺陷5、应用案例 1、UWB高精度定位系统概述 UWB室内定位技术是一种全新的、与传统通信技术有极大差异的通信…

【MySQL学习】事务管理

文章目录 一、事务的基本认识1.1 事务的基本概念1.2 事务的基本属性1.3 支持事务的存储引擎 二、为什么要有事务三、事务的基本操作3.1 事务的提交方式3.2 事务的操作案例 四、事务的隔离级别4.1 对事务隔离性的初步理解4.2 四种隔离级别4.3 读未提交&#xff08;Read Uncommit…

Mybatis Plus代码生成器

文章目录 1 代码生成器原理分析2 代码生成器实现步骤1:创建一个Maven项目代码2:导入对应的jar包步骤3:编写引导类步骤4:创建代码生成类步骤5:运行程序 3 MP中Service的CRUD 1 代码生成器原理分析 造句: 我们可以往空白内容进行填词造句&#xff0c;比如: 在比如: 观察我们之…

FAT NTFS Ext3文件系统有什么区别

10 年前 FAT 文件系统还是常见的格式&#xff0c;而现在 Windows 上主要是 NTFS&#xff0c;Linux 上主要是Ext3、Ext4 文件系统。关于这块知识&#xff0c;一般资料只会从支持的磁盘大小、数据保护、文件名等各种维度帮你比较&#xff0c;但是最本质的内容却被一笔带过。它们最…

SQL教程(四)简单实例学习:时间函数(一)基础入门级

目录 一、&#x1f30e;SQL 简介 1.1 &#x1f4dc;SQL 是什么&#xff1f; 1.2 &#x1f4dc;SQL 能做什么&#xff1f; 1.3 &#x1f4dc;SQL 是一种标准 - 但是... 1.4 &#x1f4dc;在您的网站中使用 SQL 1.4 &#x1f4dc;RDBMS 1.5 &#x1f4dc;请记住... 1.6 &…

物业管理可视化大屏

物业管理可视化大屏是一种可视化的智能物业管理&#xff0c;它可以将物业管理中的各种数据进行可视化展示&#xff0c;帮助物业管理人员更好地管理社区或园区。 什么是物业可视化数据大屏&#xff1f; 物业可视化数据大屏就是利用大数据技术&#xff0c;将物业管理中的各种信…

1091 Acute Stroke

One important factor to identify acute stroke (急性脑卒中) is the volume of the stroke core. Given the results of image analysis in which the core regions are identified in each MRI slice, your job is to calculate the volume of the stroke core. Input Spec…

C++中的取余函数%、remainder、fmod以及matlab中的取余函数mod

C 1 整数取余 % 2 remainder函数 https://cplusplus.com/reference/cmath/remainder/?kwremainder double remainder (double numer , double denom); float remainder (float numer , float denom); long double remainder (long double numer, long double denom); doub…