如何利用MyBatis Plus去实现数据权限控制呢?

2023-08-23
关注

前言背景

平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求。列表实现方案有两种,一是在开发初期就做好判断赛选,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式。在mybatis执行sql前修改语句,限定where范围。

当然拦截器生效后是全局性的,如何保证只对需要的接口进行拦截和转化,就可以应用注解进行识别

因此具体需要哪些步骤就明确了

创建注解类

创建拦截器实现InnerInterceptor接口,重写查询方法

创建处理类,获取数据权限 SQL 片段,设置where

将拦截器加到MyBatis-Plus插件中

自定义注解

importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceUserDataPermission{
}

拦截器

importcom.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
importcom.baomidou.mybatisplus.core.toolkit.PluginUtils;
importcom.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
importlombok.*;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.statement.select.PlainSelect;
importnet.sf.jsqlparser.statement.select.Select;
importnet.sf.jsqlparser.statement.select.SelectBody;
importnet.sf.jsqlparser.statement.select.SetOperationList;
importorg.apache.ibatis.executor.Executor;
importorg.apache.ibatis.mapping.BoundSql;
importorg.apache.ibatis.mapping.MappedStatement;
importorg.apache.ibatis.session.ResultHandler;
importorg.apache.ibatis.session.RowBounds;

importjava.sql.SQLException;
importjava.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper=true)
@EqualsAndHashCode(callSuper=true)
publicclassMyDataPermissionInterceptorextendsJsqlParserSupportimplementsInnerInterceptor{

/**
*数据权限处理器
*/
privateMyDataPermissionHandlerdataPermissionHandler;

@Override
publicvoidbeforeQuery(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{
if(InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())){
return;
}
PluginUtils.MPBoundSqlmpBs=PluginUtils.mpBoundSql(boundSql);
mpBs.sql(this.parserSingle(mpBs.sql(),ms.getId()));
}

@Override
protectedvoidprocessSelect(Selectselect,intindex,Stringsql,Objectobj){
SelectBodyselectBody=select.getSelectBody();
if(selectBodyinstanceofPlainSelect){
this.setWhere((PlainSelect)selectBody,(String)obj);
}elseif(selectBodyinstanceofSetOperationList){
SetOperationListsetOperationList=(SetOperationList)selectBody;
ListselectBodyList=setOperationList.getSelects();
selectBodyList.forEach(s->this.setWhere((PlainSelect)s,(String)obj));
}
}

/**
*设置where条件
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*/
privatevoidsetWhere(PlainSelectplainSelect,StringwhereSegment){

ExpressionsqlSegment=this.dataPermissionHandler.getSqlSegment(plainSelect,whereSegment);
if(null!=sqlSegment){
plainSelect.setWhere(sqlSegment);
}
}
}

拦截器处理器

基础只涉及 = 表达式,要查询集合范围 in 看进阶版用例

importcn.hutool.core.collection.CollectionUtil;
importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

/**
*获取数据权限SQL片段
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*@returnJSqlParser条件表达式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
//待执行SQLWhere条件表达式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("开始进行权限过滤,where:{},mappedStatementId:{}",where,whereSegment);
//获取mapper名称
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//获取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有别名用别名,无别名用表名,防止字段冲突报错
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//获取当前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍历判断mapper的所以方法,判断方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、当前用户Code
Useruser=SecurityUtils.getUser();
//查看自己的数据
//=表达式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
}
}
//说明无权查看,
where=newHexValue("1=2");
returnwhere;
}

}

将拦截器加到MyBatis-Plus插件中

如果你之前项目配插件 ,直接用下面方式就行

@Bean
publicMybatisPlusInterceptormybatisPlusInterceptor(){
MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();
//添加数据权限插件
MyDataPermissionInterceptordataPermissionInterceptor=newMyDataPermissionInterceptor();
//添加自定义的数据权限处理器
dataPermissionInterceptor.setDataPermissionHandler(newMyDataPermissionHandler());
interceptor.addInnerInterceptor(dataPermissionInterceptor);
interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));
returninterceptor;
}

但如果你项目之前是依赖包依赖,或有公司内部统一拦截设置好,也可以往MybatisPlusInterceptor进行插入,避免影响原有项目配置

@Bean
publicMyDataPermissionInterceptormyInterceptor(MybatisPlusInterceptormybatisPlusInterceptor){
MyDataPermissionInterceptorsql=newMyDataPermissionInterceptor();
sql.setDataPermissionHandler(newMyDataPermissionHandler());
Listlist=newArrayList<>();
//添加数据权限插件
list.add(sql);
//分页插件
mybatisPlusInterceptor.setInterceptors(list);
list.add(newPaginationInnerInterceptor(DbType.MYSQL));
returnsql;
}

以上就是简单版的是拦截器修改语句使用

使用方式

在mapper层添加注解即可

@UserDataPermission
ListselectAllCustomerPage(IPagepage,@Param("customerName")StringcustomerName);

基础班只是能用,业务功能没有特别约束,先保证能跑起来

进阶版 解决两个问题:

加了角色,用角色决定范围

解决不是mapper层自定义sql查询问题。

两个是完全独立的问题 ,可根据情况分开解决

解决不是mapper层自定义sql查询问题。

例如我们名称简单的sql语句 直接在Service层用mybatisPluse自带的方法

xxxxService.list(WrapperqueryWrapper)
xxxxService.page(newPage<>(),WrapperqueryWrapper)

以上这种我应该把注解加哪里呢

因为service层,本质上还是调mapper层, 所以还是在mapper层做文章,原来的mapper实现了extends BaseMapper 接口,所以能够查询,我们要做的就是在 mapper层中间套一个中间接口,来方便我们加注解

xxxxxMapper——》DataPermissionMapper(中间)——》BaseMapper

根据自身需要,在重写的接口方法上加注解即可,这样就影响原先的代码

importcom.baomidou.mybatisplus.core.conditions.Wrapper;
importcom.baomidou.mybatisplus.core.mapper.BaseMapper;
importcom.baomidou.mybatisplus.core.metadata.IPage;
importcom.baomidou.mybatisplus.core.toolkit.Constants;
importorg.apache.ibatis.annotations.Param;

importjava.io.Serializable;
importjava.util.Collection;
importjava.util.List;
importjava.util.Map;

publicinterfaceDataPermissionMapperextendsBaseMapper{

/**
*根据ID查询
*
*@paramid主键ID
*/
@Override
@UserDataPermission
TselectById(Serializableid);

/**
*查询(根据ID批量查询)
*
*@paramidList主键ID列表(不能为null以及empty)
*/
@Override
@UserDataPermission
ListselectBatchIds(@Param(Constants.COLLECTION)CollectionidList);

/**
*查询(根据columnMap条件)
*
*@paramcolumnMap表字段map对象
*/
@Override
@UserDataPermission
ListselectByMap(@Param(Constants.COLUMN_MAP)MapcolumnMap);

/**
*根据entity条件,查询一条记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
TselectOne(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询总记录数
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
IntegerselectCount(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据entity条件,查询全部记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
ListselectList(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询全部记录
*
*@paramqueryWrapper实体对象封装操作类(可以为null)
*/
@Override
@UserDataPermission
List>selectMaps(@Param(Constants.WRAPPER)WrapperqueryWrapper);

/**
*根据Wrapper条件,查询全部记录
*

注意:只返回第一个字段的值

* *@paramqueryWrapper实体对象封装操作类(可以为null) */ @Override @UserDataPermission ListselectObjs(@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根据entity条件,查询全部记录(并翻页) * *@parampage分页查询条件(可以为RowBounds.DEFAULT) *@paramqueryWrapper实体对象封装操作类(可以为null) */ @Override @UserDataPermission >EselectPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); /** *根据Wrapper条件,查询全部记录(并翻页) * *@parampage分页查询条件 *@paramqueryWrapper实体对象封装操作类 */ @Override @UserDataPermission >>EselectMapsPage(Epage,@Param(Constants.WRAPPER)WrapperqueryWrapper); }

解决角色控制查询范围

引入角色,我们先假设有三种角色,按照常规的业务需求,一种是管理员查看全部、一种是部门管理查看本部门、一种是仅查看自己。

有了以上假设,就可以设置枚举类编写业务逻辑, 对是业务逻辑,所以我们只需要更改”拦截器处理器类“

建立范围枚举

建立角色枚举以及范围关联关系

重写拦截器处理方法

范围枚举

@AllArgsConstructor
@Getter
publicenumDataScope{
//Scope数据权限范围:ALL(全部)、DEPT(部门)、MYSELF(自己)
ALL("ALL"),
DEPT("DEPT"),
MYSELF("MYSELF");
privateStringname;
}

角色枚举

@AllArgsConstructor
@Getter
publicenumDataPermission{

//枚举类型根据范围从前往后排列,避免影响getScope
//Scope数据权限范围:ALL(全部)、DEPT(部门)、MYSELF(自己)
DATA_MANAGER("数据管理员","DATA_MANAGER",DataScope.ALL),
DATA_AUDITOR("数据审核员","DATA_AUDITOR",DataScope.DEPT),
DATA_OPERATOR("数据业务员","DATA_OPERATOR",DataScope.MYSELF);

privateStringname;
privateStringcode;
privateDataScopescope;


publicstaticStringgetName(Stringcode){
for(DataPermissiontype:DataPermission.values()){
if(type.getCode().equals(code)){
returntype.getName();
}
}
returnnull;
}

publicstaticStringgetCode(Stringname){
for(DataPermissiontype:DataPermission.values()){
if(type.getName().equals(name)){
returntype.getCode();
}
}
returnnull;
}

publicstaticDataScopegetScope(Collectioncode){
for(DataPermissiontype:DataPermission.values()){
for(Stringv:code){
if(type.getCode().equals(v)){
returntype.getScope();
}
}
}
returnDataScope.MYSELF;
}
}

重写拦截器处理类 MyDataPermissionHandler

importlombok.SneakyThrows;
importlombok.extern.slf4j.Slf4j;
importnet.sf.jsqlparser.expression.Alias;
importnet.sf.jsqlparser.expression.Expression;
importnet.sf.jsqlparser.expression.HexValue;
importnet.sf.jsqlparser.expression.StringValue;
importnet.sf.jsqlparser.expression.operators.conditional.AndExpression;
importnet.sf.jsqlparser.expression.operators.relational.EqualsTo;
importnet.sf.jsqlparser.expression.operators.relational.ExpressionList;
importnet.sf.jsqlparser.expression.operators.relational.InExpression;
importnet.sf.jsqlparser.expression.operators.relational.ItemsList;
importnet.sf.jsqlparser.schema.Column;
importnet.sf.jsqlparser.schema.Table;
importnet.sf.jsqlparser.statement.select.PlainSelect;

importjava.lang.reflect.Method;
importjava.util.List;
importjava.util.Objects;
importjava.util.Set;
importjava.util.stream.Collectors;

@Slf4j
publicclassMyDataPermissionHandler{

privateRemoteRoleServiceremoteRoleService;
privateRemoteUserServiceremoteUserService;


/**
*获取数据权限SQL片段
*
*@paramplainSelect查询对象
*@paramwhereSegment查询条件片段
*@returnJSqlParser条件表达式
*/
@SneakyThrows(Exception.class)
publicExpressiongetSqlSegment(PlainSelectplainSelect,StringwhereSegment){
remoteRoleService=SpringUtil.getBean(RemoteRoleService.class);
remoteUserService=SpringUtil.getBean(RemoteUserService.class);

//待执行SQLWhere条件表达式
Expressionwhere=plainSelect.getWhere();
if(where==null){
where=newHexValue("1=1");
}
log.info("开始进行权限过滤,where:{},mappedStatementId:{}",where,whereSegment);
//获取mapper名称
StringclassName=whereSegment.substring(0,whereSegment.lastIndexOf("."));
//获取方法名
StringmethodName=whereSegment.substring(whereSegment.lastIndexOf(".")+1);
TablefromItem=(Table)plainSelect.getFromItem();
//有别名用别名,无别名用表名,防止字段冲突报错
AliasfromItemAlias=fromItem.getAlias();
StringmainTableName=fromItemAlias==null?fromItem.getName():fromItemAlias.getName();
//获取当前mapper的方法
Method[]methods=Class.forName(className).getMethods();
//遍历判断mapper的所以方法,判断方法上是否有UserDataPermission
for(Methodm:methods){
if(Objects.equals(m.getName(),methodName)){
UserDataPermissionannotation=m.getAnnotation(UserDataPermission.class);
if(annotation==null){
returnwhere;
}
//1、当前用户Code
Useruser=SecurityUtils.getUser();
//2、当前角色即角色或角色类型(可能多种角色)
SetroleTypeSet=remoteRoleService.currentUserRoleType();

DataScopescopeType=DataPermission.getScope(roleTypeSet);
switch(scopeType){
//查看全部
caseALL:
returnwhere;
caseDEPT:
//查看本部门用户数据
//创建IN表达式
//创建IN范围的元素集合
ListdeptUserList=remoteUserService.listUserCodesByDeptCodes(user.getDeptCode());
//把集合转变为JSQLParser需要的元素列表
ItemsListdeptList=newExpressionList(deptUserList.stream().map(StringValue::new).collect(Collectors.toList()));
InExpressioninExpressiondept=newInExpression(newColumn(mainTableName+".creator_code"),deptList);
returnnewAndExpression(where,inExpressiondept);
caseMYSELF:
//查看自己的数据
//=表达式
EqualsTousesEqualsTo=newEqualsTo();
usesEqualsTo.setLeftExpression(newColumn(mainTableName+".creator_code"));
usesEqualsTo.setRightExpression(newStringValue(user.getUserCode()));
returnnewAndExpression(where,usesEqualsTo);
default:
break;
}
}

}
//说明无权查看,
where=newHexValue("1=2");
returnwhere;
}
}

以上就是全篇知识点, 需要注意的点可能有:

记得把拦截器加到MyBatis-Plus的插件中,确保生效

要有一个业务赛选标识字段, 这里用的创建人 creator_code, 也可以用dept_code 等等。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

  • 处理器

    处理器

    +关注

    关注

    67

    文章

    16227

    浏览量

    219708

  • SQL

    SQL

    +关注

    关注

    1

    文章

    612

    浏览量

    43131

收藏 人收藏

扫一扫,分享给好友

复制链接分享

    评论

    发布评论请先 登录

    相关推荐

    如何利用STM32接口接收和探测实现数据的接收和发送

    利用STM32接口接收和探测实现数据的接收和发送?其代码是如何实现的?

    发表于 11-17 07:12

    如何利用DW1000实现数据的接受与发送

    利用DW1000实现数据的接受与发送

    发表于 02-11 06:40

    请问RK3326 android 8.1默认打开截图权限该怎样实现

    权限该怎样实现

    发表于 02-17 07:01

    Lora sx1278是怎样利用串口协议实现数据传输的

    利用串口协议实现数据传输的?其代码该怎样实现

    发表于 02-21 06:37

    Mybatis-Plus Mybatis增强工具包

    mybatis-plus.zip

    发表于 06-13 11:34 •1次下载

    MyBatis实现原理

    MyBatis实现原理。mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor

    发表于 02-24 11:25 •6087次阅读

    MyBatis-Plus的使用与测试

    mybatis-plus这款插件,针对springboot用户。包括引入,配置,使用,以及扩展等常用的方面做一个汇总整理,尽量包含大家常用的场景内容。

    发表于 08-22 11:56 •652次阅读

    Fluent Mybatis、原生MybatisMybatis Plus对比

    Mybatis, Mybatis Plus或者其他框架,FluentMybatis提供了哪些便利

    发表于 09-15 15:41 •766次阅读

    手动实现SpringBoot日志链路追踪

    MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

    发表于 12-15 15:04 •537次阅读

    SpringBoot 实现异步记录复杂日志

    MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

    发表于 12-22 10:35 •170次阅读

    SpringBoot+ElasticSearch实现模糊查询功能

    MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

    发表于 12-30 14:00 •326次阅读

    MyBatis-Plus为什么不支持联表

    MyBatis Plus Join`同样拥有;框架的使用方式和`MyBatis Plus`一样简单,几行代码就能实现联表查询的功能

    发表于 02-28 15:19 •1001次阅读

    基于Mybatis拦截器实现数据范围权限

    权限都可以通过配置来实现,但很多时候,后台查询数据数据权限需要通过手动添加SQL来实现

    发表于 06-20 09:57 •292次阅读

    如何实现基于Mybatis拦截器实现数据范围权限

    权限都可以通过配置来实现,但很多时候,后台查询数据数据权限需要通过手动添加SQL来实现

    发表于 06-20 09:59 •164次阅读

    你还在手写join联表查询?MyBatis-Plus这样写太香了!

    mybatis plus 封装的 mapper 不支持 join,如果需要支持就必须自己实现。但是对于大部分的业务场景来说,都需要多表 join,要不然就没必要采用关系型数据库了。

    发表于 07-07 10:19 •308次阅读

    芋道源码

    专栏

    0 文章 0 阅读 0 粉丝 0 点赞

    关注 个人主页

    • Hot ChatGPT能接入微信了!
    • Hot Guava的这些骚操作,让我的代码量减少了50%
    • New 用了Stream后,代码反而越写越丑?
    • New 如何利用MyBatis Plus去实现数据权限控制呢?

    精选推荐

    更多

    • 文章
    • 资料
    • 视频
    • 话题
    • 风口浪尖行稳致远 - 英伟达等企业如何顺应高性能计算大模型浪潮

      焦点讯

      3小时前

      219 阅读

    • 什么是三坐标测量仪:功能、原理与应用领域

      中图仪器

      3小时前

      103 阅读

    • 技术案例分享:高温流量测试丢包问题

      扬兴科技

      5小时前

      219 阅读

    • 去耦电容使用的基础知识

      jf_60870435

      4小时前

      132 阅读

    • 汽车电路设计要点有哪些

      深圳(耀创)电子科技有限公司

      4天前

      245 阅读

    • 单片机应用程序设计技术

      liuxin

      6.22 MB

      免费

      1136下载

    • HTC G8野火系统自带软件删除方法

      yezi888

      185 KB

      免费

      51下载

    • USB基本概念和硬件特性以及通信协议

      KK

      0.82 MB

      免费

      26下载

    • openharmony第三方组件适配移植的音乐表演控件

      姚小熊27

      0.83 MB

      免费

      1下载

    • Accelerated Container Image基于块设备的容器镜像加速服务

      江左盟

      0.43 MB

      免费

      0下载

    • BMS系列-锂电池案例分析#pcb设计 #硬声新人计划

    • 我们去超市结账时使用的POS机,拆开后才发现和安卓手机大同小异 #硬核拆解

    • 指纹挂锁安全吗?拆解开一个看看它的封装结构和机械原理 #硬核拆解

    • 这年头高科技无所不用,能持续响三年的太阳能念佛机,拆开了解下 #硬核拆解

    • 奇怪的粉丝送来个15斤重的开关电源,拆开看看这个大家伙里边有啥 #硬核拆解

    • 零基础学习电工

      10个视频

      1771播放量

    • 自动控制原理与应用

      47个视频

      8177播放量

    • 功放

      27个视频

      5058播放量

    • 电容

      48个视频

      2.6w播放量

    • 物联网开发

      58个视频

      1.8w播放量

    推荐专栏

    更多

      华秋(原“华强聚丰”):
      电子发烧友
      华秋开发
      华秋电路(原"华强PCB")
      华秋商城(原"华强芯城")
      华秋智造
      My ElecFans
      APP
      网站地图

      设计技术
      可编程逻辑
      电源/新能源
      MEMS/传感技术
      测量仪表
      嵌入式技术
      制造/封装
      模拟技术
      RF/无线
      接口/总线/驱动
      处理器/DSP
      EDA/IC设计
      存储技术
      光电显示
      EMC/EMI设计
      连接器
      行业应用
      LEDs
      汽车电子
      音视频及家电
      通信网络
      医疗电子
      人工智能
      虚拟现实
      可穿戴设备
      机器人
      安全设备/系统
      军用/航空电子
      移动通信
      工业控制
      便携设备
      触控感测
      物联网
      智能电网
      区块链
      新科技
      特色内容
      专栏推荐
      学院
      设计资源
      设计技术
      电子百科
      电子视频
      元器件知识
      工具箱
      VIP会员
      最新技术文章
      社区
      小组
      论坛
      问答
      评测试用
      企业服务
      产品
      资料
      文章
      方案
      企业
      供应链服务
      硬件开发
      华秋电路
      华秋商城
      华秋智造
      nextPCB
      BOM配单
      媒体服务
      网站广告
      在线研讨会
      活动策划
      新闻发布
      新品发布
      小测验
      设计大赛
      华秋
      关于我们
      投资关系
      新闻动态
      加入我们
      联系我们
      侵权投诉
      社交网络
      微博
      移动端
      发烧友APP
      硬声APP
      WAP
      联系我们
      广告合作
      王婉珠:wangwanzhu@elecfans.com
      内容合作
      黄晶晶:huangjingjing@elecfans.com
      内容合作(海外)
      张迎辉:mikezhang@elecfans.com
      供应链服务 PCB/IC/PCBA
      江良华:lanhu@huaqiu.com
      投资合作
      曾海银:zenghaiyin@huaqiu.com
      社区合作
      刘勇:liuyong@huaqiu.com
      • 关注我们的微信

      • 下载发烧友APP

      • 电子发烧友观察

      • 华秋简介
      • 企业动态
      • 联系我们
      • 企业文化
      • 企业宣传片
      • 加入我们

      版权所有 © 深圳华秋电子有限公司

      电子发烧友 (电路图) 粤公网安备 44030402000349 号 电信与信息服务业务经营许可证:粤 B2-20160233 工商网监 湘ICP备 2023018690 号

      • mybatis
      • 数据封装
      • mapper
      您觉得本篇内容如何
      评分

      评论

      您需要登录才可以回复|注册

      提交评论

      慧生活

      这家伙很懒,什么描述也没留下

      关注

      点击进入下一篇

      什么是数据脱敏?常用的脱敏规则有哪些呢?

      提取码
      复制提取码
      点击跳转至百度网盘