实战:手写一个通用Web层鉴权注解,解决水平权限漏洞

实战:手写一个通用Web层鉴权注解,解决水平权限漏洞

实战:手写一个通用Web层鉴权注解,解决水平权限漏洞

🌺The Begin🌺点点关注,收藏不迷路🌺

一、背景:一次渗透测试引发的改造

前段时间公司做渗透测试,我们系统暴露了一个典型的安全漏洞——水平权限漏洞

简单来说,就是用户A可以看到不属于他所在公司的数据。比如:用户A登录系统后,修改URL中的公司ID参数,就能查看到B公司的业务数据。

这个问题在行业内其实很常见,核心原因是Web层缺少水平鉴权。我们的系统运行好几年了,接口越来越多,但鉴权这块一直没好好做。

漏洞等级被定为高危,修复工作立刻提上议程。

二、需求分析:如何高效修复

面对几十个Controller、几百个接口,我的修复方案必须满足:

  1. 接入简单:开发人员加个注解就能搞定,不用写重复代码
  2. 灵活通用:能处理各种奇葩的入参结构(直接参数、对象属性、集合嵌套等)
  3. 兼容老代码:不能影响现有逻辑,老的接口不加注解就保持原样
  4. 可扩展:后续可能增加角色鉴权、垂直鉴权等

三、业务模型:用户-公司授权关系

先看下我们的权限模型:

has

has

User

string

userName

PK

string

nickName

UserCompany

string

userName

FK

long

companyId

FK

Company

long

companyId

PK

string

companyName

规则很简单:

  • 一个用户可以被授权访问多个公司的数据
  • 一个公司可以有多个授权用户
  • 用户只能查看他有权限的公司的数据

四、整体架构设计

鉴权注解的核心流程:

权限平台

请求处理流程

HTTP请求

Spring MVC

是否有@UserPermission?

直接执行业务方法

进入AOP切面

从Request获取用户信息

是否是Admin?

从入参提取鉴权对象

调用权限平台接口

是否有权限?

抛出权限异常

用户-公司关系服务

五、代码实现:一步一步来

5.1 注解定义

首先定义注解,通过属性来描述"要从哪里取鉴权信息":

packagecom.example.auth.annotation;importjava.lang.annotation.*;/** * 用户权限注解 * 用在Controller方法或类上,进行水平权限校验 */@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceUserPermission{/** * 鉴权对象类型:单个公司还是多个公司 */AuthObjectTypeobjectType()defaultAuthObjectType.COMPANY;/** * 鉴权值类型:描述如何从入参中提取值 */AuthValueTypevalueType()defaultAuthValueType.RAW;/** * 参数索引,当valueType不是RAW时,指定从第几个参数取值 */intindex()default0;/** * 参数名称,支持多级,如 "companyInfo.companyId" */StringparamName()default"companyId";/** * 是否忽略鉴权,用于覆盖类上的注解 */booleanignore()defaultfalse;}

两个枚举的定义:

packagecom.example.auth.annotation;/** * 鉴权对象类型 */publicenumAuthObjectType{COMPANY,// 单个公司COMPANIES// 多个公司}
packagecom.example.auth.annotation;/** * 鉴权值类型:定义如何从入参中提取值 */publicenumAuthValueType{RAW,// 原始参数,直接就是companyId或companyIdsOBJECT_FIELD,// 对象的属性,如 bo.companyIdCOLLECTION_FIELD,// 集合元素的属性,如 List<Bo> 取 Bo.companyIdNESTED_FIELD,// 嵌套属性,如 bo.companyInfo.companyIdCOLLECTION_NESTED// 集合中的嵌套属性,如 bo.companyList.companyId}

5.2 权限管理服务

封装调用外部权限平台的逻辑:

packagecom.example.auth.manager;importcom.example.auth.client.UserPermissionFeignClient;importcom.example.common.exception.BizException;importcom.example.common.util.UserUtil;importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.collections4.CollectionUtils;importorg.apache.commons.lang3.BooleanUtils;importorg.springframework.stereotype.Component;importjava.util.List;importjava.util.stream.Collectors;/** * 用户权限管理器 * 封装调用权限平台的逻辑 */@Slf4j@Component@RequiredArgsConstructorpublicclassUserPermissionManager{privatefinalUserPermissionFeignClient permissionClient;/** * 校验用户是否有指定公司的权限 */publicbooleancheckCompany(String userName,Long companyId){if(companyId ==null){thrownewBizException("公司ID不能为空");} log.debug("校验用户{}对公司{}的权限", userName, companyId);var result = permissionClient.checkCompany(userName, companyId);returncheckResult(result);}/** * 校验用户是否有所有指定公司的权限 */publicbooleancheckCompanies(String userName,List<Long> companyIds){if(CollectionUtils.isEmpty(companyIds)){thrownewBizException("公司ID列表不能为空");}// 先去重,减少调用次数List<Long> distinctIds = companyIds.stream().distinct().collect(Collectors.toList()); log.debug("校验用户{}对{}个公司的权限", userName, distinctIds.size());if(distinctIds.size()==1){// 单个公司走单条接口returncheckCompany(userName, distinctIds.get(0));}var result = permissionClient.checkCompanies(userName, distinctIds);returncheckResult(result);}privatebooleancheckResult(Result<Boolean> result){if(result ==null||!result.isSuccess()|| result.getData()==null){ log.error("调用权限平台失败: {}", result);thrownewBizException("权限校验服务异常");}returnBooleanUtils.isTrue(result.getData());}}

5.3 AOP切面:核心逻辑

这是最关键的代码,负责拦截请求、提取鉴权值、调用权限服务:

packagecom.example.auth.aspect;importcom.example.auth.annotation.UserPermission;importcom.example.auth.annotation.AuthObjectType;importcom.example.auth.annotation.AuthValueType;importcom.example.auth.manager.UserPermissionManager;importcom.example.auth.model.UserInfo;importcom.example.common.exception.BizException;importcom.example.common.util.UserUtil;importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjava.beans.PropertyDescriptor;importjava.lang.reflect.Method;importjava.util.Collection;importjava.util.List;importjava.util.stream.Collectors;/** * 用户权限切面 */@Slf4j@Aspect@Component@RequiredArgsConstructorpublicclassUserPermissionAspect{privatefinalUserPermissionManager permissionManager;/** * 切点:所有Controller下的public方法 */@Pointcut("execution(public * com.example.web.controller..*.*(..))")publicvoidcontrollerMethod(){}@Around("controllerMethod()")publicObjectcheckPermission(ProceedingJoinPoint joinPoint)throwsThrowable{// 1. 获取注解UserPermission annotation =getAnnotation(joinPoint);if(annotation ==null|| annotation.ignore()){// 没注解或忽略鉴权,直接放行return joinPoint.proceed();}// 2. 获取当前用户UserInfo currentUser =getCurrentUser();if(currentUser ==null){thrownewBizException("获取用户信息失败");}// 3. Admin直接放行if(UserUtil.isAdmin(currentUser.getUserName())){ log.debug("Admin用户放行");return joinPoint.proceed();}// 4. 从入参中提取鉴权值Object authValue =extractAuthValue(joinPoint, annotation);// 5. 校验权限boolean hasPermission =checkUserPermission( currentUser.getUserName(), authValue, annotation.objectType());if(!hasPermission){ log.warn("用户{}没有权限访问: {}", currentUser.getUserName(), authValue);thrownewBizException("您没有权限访问该数据");}// 6. 放行return joinPoint.proceed();}/** * 获取方法上的注解,优先取方法级,没有则取类级 */privateUserPermissiongetAnnotation(ProceedingJoinPoint joinPoint){MethodSignature signature =(MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();Class<?> targetClass = signature.getDeclaringType();// 方法上的注解优先级更高UserPermission methodAnn = method.getAnnotation(UserPermission.class);if(methodAnn !=null){return methodAnn;}return targetClass.getAnnotation(UserPermission.class);}/** * 从请求中获取当前用户 */privateUserInfogetCurrentUser(){ServletRequestAttributes attrs =(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attrs ==null){returnnull;}return(UserInfo) attrs.getRequest().getAttribute("userInfo");}/** * 从方法参数中提取鉴权值(核心方法) */privateObjectextractAuthValue(ProceedingJoinPoint joinPoint,UserPermission annotation){MethodSignature signature =(MethodSignature) joinPoint.getSignature();String[] paramNames = signature.getParameterNames();Object[] args = joinPoint.getArgs();if(paramNames ==null|| paramNames.length ==0){thrownewBizException("方法没有参数,无法提取鉴权值");}AuthValueType valueType = annotation.valueType();// 场景1:原始参数if(valueType ==AuthValueType.RAW){returnextractRawParam(paramNames, args, annotation.paramName());}// 其他场景:需要从对象中取值int index = annotation.index();if(index <0|| index >= args.length){thrownewBizException("参数索引越界: "+ index);}Object target = args[index];if(target ==null){thrownewBizException("第"+ index +"个参数为null");}switch(valueType){caseOBJECT_FIELD:// 场景2:对象的属性,如 bo.companyIdreturngetFieldValue(target, annotation.paramName());caseCOLLECTION_FIELD:// 场景3:集合元素的属性,如 List<Bo> 取 Bo.companyIdreturngetCollectionFieldValues(target, annotation.paramName());caseNESTED_FIELD:// 场景4:嵌套属性,如 bo.companyInfo.companyIdreturngetNestedFieldValue(target, annotation.paramName());caseCOLLECTION_NESTED:// 场景5:集合中的嵌套属性,如 bo.companyList.companyIdreturngetCollectionNestedValues(target, annotation.paramName());default:thrownewBizException("不支持的取值类型: "+ valueType);}}/** * 提取原始参数 */privateObjectextractRawParam(String[] paramNames,Object[] args,String paramName){for(int i =0; i < paramNames.length; i++){if(paramName.equals(paramNames[i])){return args[i];}}thrownewBizException("未找到参数: "+ paramName);}/** * 通过反射获取对象属性值(使用getter方法) */privateObjectgetFieldValue(Object obj,String fieldName){try{PropertyDescriptor pd =newPropertyDescriptor(fieldName, obj.getClass());Method getter = pd.getReadMethod();if(getter ==null){thrownewBizException("属性 "+ fieldName +" 没有getter方法");}return getter.invoke(obj);}catch(Exception e){ log.error("获取属性值失败: {}", fieldName, e);thrownewBizException("解析参数失败: "+ fieldName);}}/** * 获取集合元素的属性值列表 */privateList<Object>getCollectionFieldValues(Object obj,String fieldName){if(!(obj instanceofCollection)){thrownewBizException("参数不是Collection类型");}Collection<?> collection =(Collection<?>) obj;return collection.stream().map(item ->getFieldValue(item, fieldName)).collect(Collectors.toList());}/** * 获取嵌套属性值,如 obj.field1.field2 */privateObjectgetNestedFieldValue(Object obj,String fieldPath){String[] fields = fieldPath.split("\\.");Object current = obj;for(String field : fields){if(current ==null){thrownewBizException("嵌套属性路径中有null值: "+ fieldPath);} current =getFieldValue(current, field);}return current;}/** * 获取集合中的嵌套属性值 */privateList<Object>getCollectionNestedValues(Object obj,String fieldPath){String[] parts = fieldPath.split("\\.");if(parts.length !=2){thrownewBizException("COLLECTION_NESTED类型需要两级路径,如: companyList.companyId");}// 第一级:获取集合属性Object collectionObj =getFieldValue(obj, parts[0]);if(!(collectionObj instanceofCollection)){thrownewBizException(parts[0]+"不是Collection类型");}// 第二级:遍历集合,获取每个元素的属性Collection<?> collection =(Collection<?>) collectionObj;return collection.stream().map(item ->getFieldValue(item, parts[1])).collect(Collectors.toList());}/** * 执行权限校验 */privatebooleancheckUserPermission(String userName,Object authValue,AuthObjectType objectType){if(objectType ==AuthObjectType.COMPANY){// 单个公司Long companyId =convertToLong(authValue);return permissionManager.checkCompany(userName, companyId);}else{// 多个公司List<Long> companyIds =convertToLongList(authValue);return permissionManager.checkCompanies(userName, companyIds);}}privateLongconvertToLong(Object value){if(value instanceofLong){return(Long) value;}if(value instanceofInteger){return((Integer) value).longValue();}if(value instanceofString){returnLong.parseLong((String) value);}thrownewBizException("无法转换为Long类型: "+ value);}@SuppressWarnings("unchecked")privateList<Long>convertToLongList(Object value){if(value instanceofCollection){return((Collection<?>) value).stream().map(this::convertToLong).collect(Collectors.toList());}thrownewBizException("无法转换为Long列表: "+ value);}}

六、使用示例

看看实际项目中怎么用这个注解:

6.1 场景1:最简单的用法

@RestController@RequestMapping("/api/app")publicclassAppController{/** * 直接参数:companyId就在参数列表里 */@GetMapping("/list")@UserPermissionpublicResult<List<AppInfo>>listApps(long companyId){// 直接使用companyId,注解自动取值returnResult.success(appService.listByCompany(companyId));}}

6.2 场景2:对象属性

@DatapublicclassAppQueryRequest{privateLong companyId;privateString appName;privateInteger pageNum;privateInteger pageSize;}@PostMapping("/query")@UserPermission( valueType =AuthValueType.OBJECT_FIELD, paramName ="companyId")publicResult<PageInfo<AppInfo>>queryApps(@RequestBodyAppQueryRequest request){// 从request.companyId取值returnResult.success(appService.queryPage(request));}

6.3 场景3:批量操作

@PostMapping("/batch/delete")@UserPermission( objectType =AuthObjectType.COMPANIES, valueType =AuthValueType.COLLECTION_FIELD, paramName ="companyId")publicResult<Void>batchDelete(@RequestBodyList<AppInfo> apps){// 从每个AppInfo对象中提取companyId,组成列表后校验// 确保用户对这些companyId都有权限 appService.batchDelete(apps);returnResult.success();}

6.4 场景4:嵌套属性

@DatapublicclassComplexRequest{privateCompanyInfo companyInfo;@DatapublicstaticclassCompanyInfo{privateLong companyId;}}@PostMapping("/complex")@UserPermission( valueType =AuthValueType.NESTED_FIELD, paramName ="companyInfo.companyId")publicResult<Object>complexOperation(@RequestBodyComplexRequest request){// 从request.companyInfo.companyId取值returnResult.success();}

6.5 场景5:类级别默认配置

@RestController@RequestMapping("/api/user")@UserPermission(valueType =AuthValueType.OBJECT_FIELD, paramName ="companyId")publicclassUserController{/** * 继承类上的注解 */@GetMapping("/list")publicResult<List<UserVO>>listUsers(@RequestParamLong companyId){returnResult.success(userService.listByCompany(companyId));}/** * 覆盖类上的注解 */@PostMapping("/batch/query")@UserPermission( objectType =AuthObjectType.COMPANIES, valueType =AuthValueType.COLLECTION_FIELD, paramName ="companyId")publicResult<List<UserVO>>batchQuery(@RequestBodyList<CompanyQuery> queries){returnResult.success(userService.batchQuery(queries));}/** * 忽略鉴权 */@GetMapping("/public/info")@UserPermission(ignore =true)publicResult<PublicInfo>getPublicInfo(){returnResult.success(userService.getPublicInfo());}}

七、遇到的坑和解决方案

坑1:参数名获取不到

问题:编译后参数名变成arg0、arg1,导致按名称取值失败。

解决:Maven编译插件添加-parameters参数:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><compilerArgs><arg>-parameters</arg></compilerArgs></configuration></plugin>

坑2:循环依赖

问题:AOP切面注入UserPermissionManagerUserPermissionManager又依赖FeignClient,FeignClient可能依赖AOP,形成循环。

解决:使用@Lazy注解延迟加载:

@Aspect@Component@RequiredArgsConstructorpublicclassUserPermissionAspect{@LazyprivatefinalUserPermissionManager permissionManager;}

坑3:集合参数去重

问题:批量接口传入的companyIds可能重复,重复调用权限平台浪费资源。

解决:在UserPermissionManager中先做去重:

List<Long> distinctIds = companyIds.stream().distinct().collect(Collectors.toList());

坑4:事务失效

问题:切面中抛出异常,但业务方法的事务没有回滚。

解决:确保异常在事务切面之后抛出,调整切面顺序:

@Order(1)// 数字越小越先执行publicclassUserPermissionAspect{// ...}

八、编译期校验:把问题扼杀在摇篮里

注解用起来很方便,但也很容易配错。比如COLLECTION_FIELD必须搭配COMPANIES使用,如果配成COMPANY,运行时就会出错。

我们可以用注解处理器在编译期就发现这些问题:

8.1 创建注解处理器

packagecom.example.auth.processor;importcom.example.auth.annotation.UserPermission;importcom.example.auth.annotation.AuthObjectType;importcom.example.auth.annotation.AuthValueType;importjavax.annotation.processing.*;importjavax.lang.model.SourceVersion;importjavax.lang.model.element.*;importjavax.lang.model.type.TypeMirror;importjavax.tools.Diagnostic;importjava.util.Set;/** * UserPermission注解处理器 * 编译期校验注解配置是否正确 */@SupportedAnnotationTypes("com.example.auth.annotation.UserPermission")@SupportedSourceVersion(SourceVersion.RELEASE_8)publicclassUserPermissionProcessorextendsAbstractProcessor{@Overridepublicbooleanprocess(Set<?extendsTypeElement> annotations,RoundEnvironment roundEnv){for(Element element : roundEnv.getElementsAnnotatedWith(UserPermission.class)){checkAnnotation(element);}returntrue;}privatevoidcheckAnnotation(Element element){UserPermission annotation = element.getAnnotation(UserPermission.class);// 规则1:COLLECTION_FIELD必须搭配COMPANIESif(annotation.valueType()==AuthValueType.COLLECTION_FIELD&& annotation.objectType()!=AuthObjectType.COMPANIES){ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,"当valueType=COLLECTION_FIELD时,objectType必须是COMPANIES", element );}// 规则2:COLLECTION_NESTED必须搭配COMPANIESif(annotation.valueType()==AuthValueType.COLLECTION_NESTED&& annotation.objectType()!=AuthObjectType.COMPANIES){ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,"当valueType=COLLECTION_NESTED时,objectType必须是COMPANIES", element );}// 规则3:index不能小于0if(annotation.index()<0){ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,"index不能小于0", element );}// 规则4:RAW类型时,paramName必须存在if(annotation.valueType()==AuthValueType.RAW&&(annotation.paramName()==null|| annotation.paramName().isEmpty())){ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,"RAW类型时,paramName不能为空", element );}}}

8.2 注册处理器

resources/META-INF/services/javax.annotation.processing.Processor文件中:

com.example.auth.processor.UserPermissionProcessor 

配置好后,如果写错注解,IDE会直接报红:

@UserPermission( valueType =AuthValueType.COLLECTION_FIELD,// 编译错误! objectType =AuthObjectType.COMPANY// 应该用COMPANIES)publicResult<?>badMethod(){// ...}

九、性能优化建议

随着接入的应用越来越多,有几个性能点需要注意:

9.1 缓存权限结果

权限关系相对稳定,可以加一层缓存:

@ComponentpublicclassCachedUserPermissionManagerextendsUserPermissionManager{privatefinalCache<String,Boolean> permissionCache =Caffeine.newBuilder().expireAfterWrite(5,TimeUnit.MINUTES).maximumSize(10000).build();@OverridepublicbooleancheckCompany(String userName,Long companyId){String key = userName +":"+ companyId;return permissionCache.get(key, k ->super.checkCompany(userName, companyId));}}

9.2 批量接口合并请求

对于checkCompanies接口,可以合并同一用户的多次请求:

// 使用异步批量处理publicCompletableFuture<Boolean>checkCompaniesAsync(String userName,List<Long> companyIds){// 合并请求,批量调用}

9.3 反射优化

反射获取属性值有一定开销,可以考虑缓存PropertyDescriptor:

@ComponentpublicclassFieldReader{privatefinalConcurrentMap<String,PropertyDescriptor> cache =newConcurrentHashMap<>();publicObjectreadField(Object obj,String fieldName){Class<?> clazz = obj.getClass();String key = clazz.getName()+"#"+ fieldName;PropertyDescriptor pd = cache.computeIfAbsent(key, k ->{try{returnnewPropertyDescriptor(fieldName, clazz);}catch(IntrospectionException e){thrownewRuntimeException(e);}});try{return pd.getReadMethod().invoke(obj);}catch(Exception e){thrownewRuntimeException(e);}}}

十、总结与展望

通过这次改造,我们实现了:

  1. ✅ 统一鉴权:所有接口都用同一套注解,规范统一
  2. ✅ 接入简单:开发人员只需要加注解,不用写重复代码
  3. ✅ 灵活通用:支持5种常见的取值场景,覆盖95%以上的接口
  4. ✅ 安全可靠:编译期校验+运行时检查,双重保障

目前这个注解已经在我们的核心业务上线,覆盖了200+接口。后续还可以扩展:

  • 支持角色鉴权:增加@RolePermission注解
  • 支持数据脱敏:结合注解实现字段级脱敏
  • 支持操作审计:自动记录谁在什么时间操作了什么数据
  • 做成Starter:封装成Spring Boot Starter,供其他项目复用

如果觉得文章有帮助,欢迎点赞收藏。有问题可以在评论区交流,我会尽量回复。

在这里插入图片描述

🌺The End🌺点点关注,收藏不迷路🌺

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk