幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

学会自己编写Mybatis插件(拦截器)实现自定义需求

  前言
  你有了解过它是如何实现的吗?你有没有自己编写Mybatis插件去实现一些自定义需求呢?
  插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或改变框架原有的功能。
  Mybatis中也提供了插件的功能,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现的,通过拦截某些方法的调用,在执行目标逻辑之前插入我们自己的逻辑实现。另外在MyBatis的插件模块中还涉及责任链模式和JDK动态代理
  文章大纲:
  一、应用场景一些字段的自动填充SQL语句监控、打印、数据权限等数据加解密操作、数据脱敏操作分页插件参数、结果集的类型转换
  这些都是一些可以使用Mybatis插件实现的场景,当然也可以使用其他的方式来实现,只不过拦截的地方不一样罢了,有早有晚。二、Mybatis实现自定义拦截器
  我们用自定义拦截器实现一个相对简单的需求,在大多数表设计中,都会有createtime和updatetime等字段,在创建或更新时需要更新相关字段。
  如果是使用过MybatisPlus的小伙伴,可能知道在MybatisPlus中有一个自动填充功能,通过实现MetaObjectHandler接口中的方法来进行实现(主要的实现代码在com。baomidou。mybatisplus。core。MybatisParameterHandler中)。
  但使用Mybatis,并没有相关的方法或API可以直接来实现。所以我们这次就用以此处作为切入点,自定义拦截器来实现类似的自动填充功能。
  编写步骤编写一个拦截器类实现Interceptor接口添加拦截注解Intercepts在xml文件中配置拦截器或者添加到Configuration中
  基础的环境我就不再贴出来啦哈,直接上三个步骤的代码2。1、编写拦截器packagecom。nzc。interceptor;importlombok。extern。slf4j。Slf4j;importorg。apache。ibatis。executor。Executor;importorg。apache。ibatis。executor。parameter。ParameterHandler;importorg。apache。ibatis。executor。resultset。ResultSetHandler;importorg。apache。ibatis。executor。statement。StatementHandler;importorg。apache。ibatis。mapping。MappedStatement;importorg。apache。ibatis。mapping。SqlCommandType;importorg。apache。ibatis。plugin。Interceptor;importorg。apache。ibatis。plugin。Intercepts;importorg。apache。ibatis。plugin。Invocation;importorg。apache。ibatis。plugin。Signature;importorg。springframework。beans。factory。annotation。Value;importjava。lang。reflect。Field;importjava。util。;author宁在春version1。0description:通过实现拦截器来实现部分字段的自动填充功能date20234621:49Intercepts({Signature(typeExecutor。class,methodupdate,args{MappedStatement。class,Object。class})})Slf4jpublicclassMybatisMetaInterceptorimplementsInterceptor{OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{MappedStatementmappedStatement(MappedStatement)invocation。getArgs()〔0〕;StringsqlIdmappedStatement。getId();log。info(sqlIdsqlId);SqlCommandTypesqlCommandTypemappedStatement。getSqlCommandType();Objectparameterinvocation。getArgs()〔1〕;log。info(sqlCommandTypesqlCommandType);log。info(拦截查询请求Executorupdate方法invocation。getMethod());if(parameternull){returninvocation。proceed();}if(SqlCommandType。INSERTsqlCommandType){Field〔〕fieldsgetAllFields(parameter);for(Fieldfield:fields){log。info(field。namefield。getName());try{注入创建时间if(createTime。equals(field。getName())){field。setAccessible(true);ObjectlocalcreateDatefield。get(parameter);field。setAccessible(false);if(localcreateDatenulllocalcreateDate。equals()){field。setAccessible(true);field。set(parameter,newDate());field。setAccessible(false);}}}catch(Exceptione){}}}if(SqlCommandType。UPDATEsqlCommandType){Field〔〕fieldsgetAllFields(parameter);for(Fieldfield:fields){log。info(field。namefield。getName());try{if(updateTime。equals(field。getName())){field。setAccessible(true);field。set(parameter,newDate());field。setAccessible(false);}}catch(Exceptione){e。printStackTrace();}}}returninvocation。proceed();}OverridepublicObjectplugin(Objecttarget){returnInterceptor。super。plugin(target);}稍后会展开说的OverridepublicvoidsetProperties(Propertiesproperties){System。out。println(begin);System。out。println(properties。getProperty(param1));System。out。println(properties。getProperty(param2));Interceptor。super。setProperties(properties);System。out。println(end);}获取类的所有属性,包括父类paramobjectreturnpublicstaticField〔〕getAllFields(Objectobject){Classlt;?clazzobject。getClass();ListFieldfieldListnewArrayList();while(clazz!null){fieldList。addAll(newArrayList(Arrays。asList(clazz。getDeclaredFields())));clazzclazz。getSuperclass();}Field〔〕fieldsnewField〔fieldList。size()〕;fieldList。toArray(fields);returnfields;}}2。2、添加到Mybatis配置
  我这里使用的JavaConfig的方式packagecom。nzc。config;importcom。nzc。interceptor。;importorg。mybatis。spring。boot。autoconfigure。ConfigurationCustomizer;importorg。springframework。context。annotation。Bean;importorg。springframework。context。annotation。Configuration;ConfigurationpublicclassMyBatisConfig{BeanpublicConfigurationCustomizerconfigurationCustomizer(){returnnewConfigurationCustomizer(){Overridepublicvoidcustomize(org。apache。ibatis。session。Configurationconfiguration){开启驼峰命名映射configuration。setMapUnderscoreToCamelCase(true);MybatisMetaInterceptormybatisMetaInterceptornewMybatisMetaInterceptor();PropertiespropertiesnewProperties();properties。setProperty(param1,javaconfigvalue1);properties。setProperty(param2,javaconfigvalue2);mybatisMetaInterceptor。setProperties(properties);configuration。addInterceptor(mybatisMetaInterceptor);}};}}
  如果是xml配置的话,则是如下:property是设置拦截器中需要用到的参数configurationpluginsplugininterceptorcom。nzc。interceptor。MybatisMetaInterceptorpropertynameparam1valuevalue1propertynameparam2valuevalue2pluginpluginsconfiguration2。3、测试
  测试代码:实现了一个SysMapper的增删改查packagecom。nzc。mapper;importcom。nzc。entity。SysUser;importorg。apache。ibatis。annotations。Insert;importorg。apache。ibatis。annotations。Mapper;importorg。apache。ibatis。annotations。Select;importorg。apache。ibatis。annotations。Update;importjava。util。List;author宁在春description针对表【sysuser】的数据库操作MapperMapperpublicinterfaceSysUserMapper{Select(SELECTFROMtbsysuser)ListSysUserlist();Insert(insertintotbsysuser(id,username,realname,createtime,updatetime)values({id},{username},{realname},{createTime},{updateTime}))Booleaninsert(SysUsersysUser);Update(updatetbsysusersetusername{username},realname{realname},updatetime{updateTime}whereid{id})booleanupdate(SysUsersysUser);}author宁在春version1。0description:TODOdate20234621:38Slf4jRunWith(SpringRunner。class)SpringBootTestpublicclassSysUserMapperTest{AutowiredprivateSysUserMappersysUserMapper;Testpublicvoidtest1(){System。out。println(sysUserMapper。list());}Testpublicvoidtest2(){SysUsersysUsernewSysUser();sysUser。setId(1235);sysUser。setUsername(nzc5);sysUser。setRealname(nzc5);System。out。println(sysUserMapper。insert(sysUser));}Testpublicvoidtest3(){SysUsersysUsernewSysUser();sysUser。setId(1235);sysUser。setUsername(nzc7);sysUser。setRealname(nzc5);System。out。println(sysUserMapper。update(sysUser));}}
  当然重点不在这里,而是在我们打印的日志上,一起来看看效果吧
  此处相关日志对应Interceptor中的日志打印,想要了解的更为详细的可以debug查看一番。2。4、小结
  通过这个小小的案例,我想大伙对于Mybatis中的拦截器应当是没有那般陌生了吧,接下来再来仔细聊聊吧如果你使用过MybatisPlus的话,在读完这篇博文后,可以思考思考下面这个问题,或去看一看源码,将知识串联起来,如果可以的话,记得把答案贴到评论区啦
  思考:还记得这一小节开始我们聊到的MybatisPlus实现的自动填充功能吗?它是怎么实现的呢?三、拦截器接口介绍
  MyBatis插件可以用来实现拦截器接口Interceptor,在实现类中对拦截对象和方法进行处理publicinterfaceInterceptor{执行拦截逻辑的方法Objectintercept(Invocationinvocation)throwsThrowable;这个方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用。该方法的实现很简单,只需要调用MyBatis提供的Plug类的wrap静态方法就可以通过Java动态代理拦截目标对象。defaultObjectplugin(Objecttarget){returnPlugin。wrap(target,this);}这个方法用来传递插件的参数,可以通过参数来改变插件的行为defaultvoidsetProperties(Propertiesproperties){NOP}}
  有点懵没啥事,一个一个展开说:intercept方法Objectintercept(Invocationinvocation)throwsThrowable;
  简单说就是执行拦截逻辑的方法,但不得不说这句话是个高度概括
  首先我们要明白参数Invocation是个什么东东:publicclassInvocation{privatefinalObjecttarget;拦截的对象信息privatefinalMethodmethod;拦截的方法信息privatefinalObject〔〕args;拦截的对象方法中的参数publicInvocation(Objecttarget,Methodmethod,Object〔〕args){this。targettarget;this。methodmethod;this。argsargs;}get。。。利用反射来执行拦截对象的方法publicObjectproceed()throwsInvocationTargetException,IllegalAccessException{returnmethod。invoke(target,args);}}
  联系我们之前实现的自定义拦截器上的注解:Intercepts({Signature(typeExecutor。class,methodupdate,args{MappedStatement。class,Object。class})})target对应我们拦截的Executor对象method对应Executorupdate方法args对应Executorupdateargs参数plugin方法
  这个方法其实也很好说:
  那就是Mybatis在创建拦截器代理时候会判断一次,当前这个类Interceptor到底需不需要生成一个代理进行拦截,如果需要拦截,就生成一个代理对象,这个代理就是一个{linkPlugin},它实现了jdk的动态代理接口{linkInvocationHandler},如果不需要代理,则直接返回目标对象本身加载时机:该方法在mybatis加载核心配置文件时被调用defaultObjectplugin(Objecttarget){returnPlugin。wrap(target,this);}publicclassPluginimplementsInvocationHandler{利用反射,获取这个拦截器MyInterceptor的注解Intercepts和Signature,然后解析里面的值,1先是判断要拦截的对象是哪一个2然后根据方法名称和参数判断要对哪一个方法进行拦截3根据结果做出决定,是返回一个对象呢还是代理对象publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){MapClasslt;?,SetMethodsignatureMapgetSignatureMap(interceptor);Classlt;?typetarget。getClass();这边就是判断当前的interceptor是否包含在Classlt;?〔〕interfacesgetAllInterfaces(type,signatureMap);if(interfaces。length0){returnProxy。newProxyInstance(type。getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}如果不需要代理,则直接返回目标对象本身returntarget;}。。。。}setProperties方法
  在拦截器中可能需要使用到一些变量参数,并且这个参数是可配置的,这个时候我们就可以使用这个方法啦,加载时机:该方法在mybatis加载核心配置文件时被调用defaultvoidsetProperties(Propertiesproperties){NOP}
  关于如何使用:
  javaConfig方式设置:BeanpublicConfigurationCustomizerconfigurationCustomizer(){returnnewConfigurationCustomizer(){Overridepublicvoidcustomize(org。apache。ibatis。session。Configurationconfiguration){开启驼峰命名映射configuration。setMapUnderscoreToCamelCase(true);MybatisMetaInterceptormybatisMetaInterceptornewMybatisMetaInterceptor();PropertiespropertiesnewProperties();properties。setProperty(param1,javaconfigvalue1);properties。setProperty(param2,javaconfigvalue2);mybatisMetaInterceptor。setProperties(properties);configuration。addInterceptor(mybatisMetaInterceptor);}};}
  通过mybatisconfig。xml文件进行配置configurationpluginsplugininterceptorcom。nzc。interceptor。MybatisMetaInterceptorpropertynameparam1valuevalue1propertynameparam2valuevalue2pluginpluginsconfiguration
  测试效果就是测试案例上那般,通过了解拦截器接口的信息,对于之前的案例不再是那般模糊啦
  接下来再接着聊一聊拦截器上面那一坨注解信息是用来干嘛的吧,注意
  当配置多个拦截器时,MyBatis会遍历所有拦截器,按顺序执行拦截器的plugin口方法,被拦截的对象就会被层层代理。
  在执行拦截对象的方法时,会一层层地调用拦截器,拦截器通invocationproceed()调用下层的方法,直到真正的方法被执行。
  方法执行的结果从最里面开始向外层层返回,所以如果存在按顺序配置的三个签名相同的拦截器,MyBaits会按照CBAtarget。proceed()ABC的顺序执行。如果签名不同,就会按照MyBatis拦截对象的逻辑执行。
  这也是我们最开始谈到的Mybatis插件模块所使用的设计模式责任链模式。四、拦截器注解介绍
  上一个章节,我们只说明如何实现Interceptor接口来实现拦截,却没有说明要拦截的对象是谁,在什么时候进行拦截。就关系到我们之前编写的注解信息啦。Intercepts({Signature(typeExecutor。class,methodupdate,args{MappedStatement。class,Object。class})})
  这两个注解用来配置拦截器要拦截的接口的方法。
  Intercepts({})注解中是一个Signature()数组,可以在一个拦截器中同时拦截不同的接口和方法。
  MyBatis允许在己映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的接口包括以下几个。ExecutorParameterHandlerResultSetHandlerStatementHandler
  Signature注解包含以下三个属性。type设置拦截接口,可选值是前面提到的4个接口method设置拦截接口中的方法名可选值是前面4个接口中所对应的方法,需要和接口匹配args设置拦截方法的参数类型数组通过方法名和参数类型可以确定唯一一个方法Executor接口
  下面就是Executor接口的类信息publicinterfaceExecutor{intupdate(MappedStatementms,Objectparameter)throwsSQLException;EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;ListBatchResultflushStatements()throwsSQLException;voidcommit(booleanrequired)throwsSQLException;voidrollback(booleanrequired)throwsSQLException;CacheKeycreateCacheKey(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,BoundSqlboundSql);booleanisCached(MappedStatementms,CacheKeykey);voidclearLocalCache();voiddeferLoad(MappedStatementms,MetaObjectresultObject,Stringproperty,CacheKeykey,Classlt;?targetType);TransactiongetTransaction();voidclose(booleanforceRollback);booleanisClosed();voidsetExecutorWrapper(Executorexecutor);}
  我只会简单说一些最常用的
  1、updateintupdate(MappedStatementms,Objectparameter)throwsSQLException;
  该方法会在所有的INSERT、UPDATE、DELETE执行时被调用,因此如果想要拦截这类操作,可以拦截该方法。接口方法对应的签名如下。Intercepts({Signature(typeExecutor。class,methodupdate,args{MappedStatement。class,Object。class})})
  2、queryEListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;
  该方法会在所有SELECT查询方法执行时被调用通过这个接口参数可以获取很多有用的信息,这也是最常被拦截的方法。Intercepts({Signature(typeExecutor。class,methodquery,args{MappedStatement。class,Object。class,RowBounds。class,ResultHandler。class}),Signature(typeExecutor。class,methodquery,args{MappedStatement。class,Object。class,RowBounds。class,ResultHandler。class,CacheKey。class,BoundSql。class})})
  3、queryCursor:ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;
  该方法只有在查询的返回值类型为Cursor时被调用。接口方法对应的签名类似于之前的。该方法只在通过SqlSession方法调用commit方法时才被调用voidcommit(booleanrequired)throwsSQLException;该方法只在通过SqlSessio口方法调用rollback方法时才被调用voidrollback(booleanrequired)throwsSQLException;该方法只在通过SqlSession方法获取数据库连接时才被调用,TransactiongetTransaction();该方法只在延迟加载获取新的Executor后才会被执行voidclose(booleanforceRollback);该方法只在延迟加载执行查询方法前被执行booleanisClosed();
  注解的编写方法都是类似的。ParameterHandler接口publicinterfaceParameterHandler{该方法只在执行存储过程处理出参的时候被调用ObjectgetParameterObject();该方法在所有数据库方法设置SQL参数时被调用。voidsetParameters(PreparedStatementps)throwsSQLException;}
  我都写一块啦,如果要拦截某一个的话只写一个即可Intercepts({Signature(typeParameterHandler。class,methodgetParameterObject,args{}),Signature(typeParameterHandler。class,methodsetParameters,args{PreparedStatement。class})})ResultSetHandler接口publicinterfaceResultSetHandler{该方法会在除存储过程及返回值类型为Cursor以外的查询方法中被调用。EListEhandleResultSets(Statementstmt)throwsSQLException;只会在返回值类型为ursor查询方法中被调用ECursorEhandleCursorResultSets(Statementstmt)throwsSQLException;只在使用存储过程处理出参时被调用,voidhandleOutputParameters(CallableStatementcs)throwsSQLException;}Intercepts({Signature(typeResultSetHandler。class,methodhandleResultSets,args{Statement。class}),Signature(typeResultSetHandler。class,methodhandleCursorResultSets,args{Statement。class}),Signature(typeResultSetHandler。class,methodhandleOutputParameters,args{CallableStatement。class})})StatementHandler接口publicinterfaceStatementHandler{该方法会在数据库执行前被调用优先于当前接口中的其他方法而被执行Statementprepare(Connectionconnection,IntegertransactionTimeout)throwsSQLException;该方法在prepare方法之后执行,用于处理参数信息voidparameterize(Statementstatement)throwsSQLException;在全局设置配置defaultExecutorTypeBATCH时,执行数据操作才会调用该方法voidbatch(Statementstatement)throwsSQLException;执行UPDATE、DELETE、INSERT方法时执行intupdate(Statementstatement)throwsSQLException;执行SELECT方法时调用,接口方法对应的签名如下。EListEquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException;ECursorEqueryCursor(Statementstatement)throwsSQLException;获取实际的SQL字符串BoundSqlgetBoundSql();ParameterHandlergetParameterHandler();}Intercepts({Signature(typeStatementHandler。class,methodprepare,args{Connection。class,Integer。class}),Signature(typeStatementHandler。class,methodparameterize,args{Statement。class}),Signature(typeStatementHandler。class,methodbatch,args{Statement。class}),Signature(typeStatementHandler。class,methodupdate,args{Statement。class}),Signature(typeStatementHandler。class,methodquery,args{Statement。class,ResultHandler。class}),Signature(typeStatementHandler。class,methodqueryCursor,args{Statement。class}),Signature(typeStatementHandler。class,methodgetBoundSql,args{}),Signature(typeStatementHandler。class,methodgetParameterHandler,args{})}
  如果有时间的话,我会更加建议看了的小伙伴,自己去实现接口做个测试,验证一番,也能了解的更彻底些。看会了,很多时候知识的记忆还是浅的。五、进一步思考
  看完这篇文章后,不知道你有没有什么收获。
  再次看看这张文章大纲的图吧
  试着思考思考下面几个问题:Mybatis插件适用于哪些场景?回忆一下你做过的项目,是否有可以使用Mybatis插件来实现的呢?你可以编写一个Mybatis插件了吗?感兴趣的话,你可以试着去了解一下Mybatis分页插件的实现方式。
  最后留下一个遇到的问题,也是下一篇文章可能会写的吧,同时也使用到了今天所谈到了的拦截器。
  在项目中,你们都是如何针对表中某些字段进行加解密的呢?
  链接:https:juejin。cnpost7220321558103097404

潘玮柏身份曝光!中国说唱巅峰对决踢馆阵容出炉?嘻笑堂抵达好家伙。潘玮柏的身份水落石出了。这几天,潘玮柏成为说唱粉丝最关注的人物之一。人们纷纷好奇,潘玮柏既然公开录制《中国说唱巅峰对决》,但为什么在选手阵容中,却没有……iPhone不是唯一的选择,国产双芯旗舰手机登京东平台高端手今年618,你换新手机了吗?都618了你还不知道买什么,不如来看看京东618爆款播报局看看,豹豹先生每天都会为你安排好,赶快来抄作业吧!随着中国科技产业链的推动,市……刘敏涛46岁还这么有活力,短袖叠穿时尚减龄,纤细竹竿腿太出镜今天穿什么四十岁以上的熟龄女性,虽已到了阿姨辈的年龄,但在娱乐圈中依旧被叫作姐姐。她们不仅拥有年轻有活力的姿态,穿搭思想也很前卫,46岁的刘敏涛也是其中之一。现身机场,短……国家航天局探月四期开启星际探测新征程下一步探索小行星来源:央视新闻客户端2021年,中国航天的成就举世瞩目:中国空间站拉开建造大幕,祝融号火星车完成火星巡视探测、嫦娥五号月壤样品取得一系列成果、航天发射次数超过50次,再创……官宣!洛城核心意外报销,詹姆斯送祝福,总冠军没戏了北京时间12月26日,NBA常规赛进入关键阶段,在刚刚结束的一场圣诞大战中,凭借着哈登和米尔斯的联袂爆发,最终篮网客场以122比115战胜湖人,继续以22胜9负的战绩稳居东部第……CBA三消息辽宁全队备战汇总,同曦强人被弃用,深圳三叉戟59爱国篮,爱CBA,我是洛姐,小伙伴们看完记得点赞!CBA常规赛第二阶段比赛已经在如火如荼地进行当中,福建队和山西队以及深圳队和同曦队的比赛全部都结束了,这两场比赛受到的关……美不美先看腿!这14位女明星大长腿瞩目,个个都是腿精有句老话怎么说来着:美不美先看腿!娱乐圈向来不缺少美女,天生一张好看的脸已经是赢在起跑线上了,如果还有一双笔直纤细的大长腿,那就属于是锦上添花了!不过腿美不美并不只是靠长……印度首富安巴尼吹嘘的印度手机,出来了首先说一下这个事情的来龙去脉。当时印度首富安巴尼扬言说要搞特别廉价的手机,才50美元,然后销售,再利用推广出去的手机搞金融业务,XX贷之类的。说实话,这个模式有一定……曼联14揪出最水巨星!踢前腰没防守,踢前锋没身体,数据刷子在近日进行的英超联赛第12轮比赛中,曼联队与沃特福德队进行了一场交锋。结果在双方实力悬殊的情况下,曼联队爆出了冷门,他们以14落败,范德贝克帮助曼联打进了一球。现在,当曼……用健康食物当零食,就健康了吗?来源:北京青年报零食不仅孩子们爱吃,也是许多大人的所爱。对于吃零食人们一直有种看法,认为吃零食是不好的习惯。其实,对待吃零食不能简单地一概而论,不能武断地说成是一种不健康的饮食……OPPOWatch3发布会官宣8月10日正式发布,易建联代言在智能穿戴设备发展的这些年里,越来越多实用又好看的智能穿戴设备陆续到来。比如说OPPO旗下的多款智能手表,就凭借不俗的表现收获了很多用户的认可,市场口碑也一直不错。正因如此,近……最近华为P60Pro遭到曝光,华为P50无奈价崩黯然低价退出大家都知道,华为鸿蒙OS作为面向新一代物联网发布的操作系统,目前已经可以在手机、平板、汽车等各类物联网设备上实现互联互通,这些也都是目前安卓、iOs系统无法进行实现的地方,华为……
夜间活跃用户8。6亿,夜娱夜购夜宵等消费场景线上线下融合QuestMobile2022夜经济洞察报告一、夜经济成为促进消费的新动力、经济增长的新风口1、随着城镇化的发展和居民消费水平的不断提高,夜间经济已成为人们生活的重……冬日觅秋有感今天是2022年11月10日,立冬已经三天了。却意外地收到了一个觅秋的任务,所以走出家门,置身大自然中,寻觅秋的踪迹。红枫乌桕水杉按时节划分,虽说是冬日……ampampquot我想你了,我们和好吧ampampquot在感情的世界里,根本就没有所谓的胜利者,只有一荣俱荣,一损俱损。逛微博的时候,看到一个这样的故事。男生和女生因为兴趣相似而互相吸引,刚在一起时也是甜甜蜜蜜。但……异性相处,哪些行为算是暧昧呢?看看你有没有触碰暧昧的真实含义是什么?我想每个人的答案都不一样,正如他们对待暧昧时的态度一样。就好像雾里看花一样,除了当事人双方以外,谁也看不透,猜不透。就好比明明两个人在一起很亲密,但……能治愈人心的经典语录献给迷茫的你图片来源:小红书小楚超级喜欢她的图片,一眼看过去,心情都会变好。学习绘画断断续续有了一年时间,昨晚再次打开iPad,发现自己还是什么也没学会,尝试画一个橙子,结果一塌糊涂……沿着杭徽高速看临安苕溪畔的吴越文化,滋养着世世代代的临安人来源:【杭州市临安区融媒体中心】今年夏天,临安迎来了首届吴越文化节,青山湖的山水前,一曲《吴越风》,重现了吴越国满堂花醉三千客的繁华盛景。青山湖,苕溪上的一颗明珠,……古巴当选77国集团和中国轮值主席国据古巴国家通讯社拉美社23日报道,77国集团和中国当天在纽约举行会议,古巴当选该组织2023年轮值主席国。古巴外交部长布鲁诺罗德里格斯当天在社交媒体上说,古巴是第一次当选……世界杯揭幕战东道主首败!巴伦西亚梅开二度厄瓜多尔20卡塔尔北京时间11月21日凌晨0点,第22届卡塔尔世界杯揭幕战在卡塔尔首都多哈的巴伊特球场进行,对阵双方是东道主卡塔尔队和南美劲旅厄瓜多尔队。上半场,恩纳巴伦西亚利用点球打进了本届世……12年来最差开局!萨利踢巴萨必须要提升三个档位德甲联赛第六轮,拜仁慕尼黑在主场22艰难战平斯图加特,本场比赛,拜仁慕尼黑全队表现低迷,和实力并不强大的斯图加特打的有来有回,甚至在自己的主场一度被对手逼迫的非常狼狈。本轮过后……爵士手握最佳夺冠拼图筹码,湖人交易威少一举两得,费城欲截胡?今年的休赛期相比以往并没有太多球星进入自由市场引起各支球队的争抢,反倒是实力不俗的犹他爵士在多年无法突破西部之后选择了重建之路;队中两大全明星球员和一众实力悍将成为了联盟最为关……越南导游为何都是美女?提供全方位服务,让人无限遐想科技在日益发展的今天,人们不再像之前那样简单地为了满足吃穿方面的问题,他们也开始走向世界,去了解各种不同的风土人情。(此处已添加小程序,请到今日头条客户端查看)毕竟在大众……除了马拉多纳,他俩也是为放纵买单的天才20年年末,老马西去,引发了人们关于自律与生命健康的思考。其实,放纵的天才何止是老马,英伦绿茵历史最出色的贝斯特,桑巴舞步最绚丽的加林查,一个又一个名字让人叹息。前……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网