应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

从阿里跳槽来的工程师,写个Controller都这么优雅

  目录一个优秀的Controller层逻辑从现状看问题改造Controller层逻辑总结
  一个优秀的Controller层逻辑
  说到Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是不可或缺的配角,说它不可或缺是因为无论是传统的三层架构还是现在的COLA架构,Controller层依旧有一席之地,说明他的必要性;说它是配角是因为Controller层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求
  从现状看问题
  Controller主要的工作有以下几项
  接收请求并解析参数
  调用Service执行具体的业务代码(可能包含参数校验)
  捕获业务逻辑异常做出反馈
  业务逻辑执行成功做出响应DTO
  Data
  publicclassTestDTO{
  privateIntegernum;
  privateStringtype;
  }
  Service
  Service
  publicclassTestService{
  publicDoubleservice(TestDTOtestDTO)throwsException{
  if(testDTO。getNum0){
  thrownewException(输入的数字需要大于0);
  }
  if(testDTO。getType。equals(square)){
  returnMath。pow(testDTO。getNum,2);
  }
  if(testDTO。getType。equals(factorial)){
  doubleresult1;
  intnumtestDTO。getNum;
  while(num1){
  resultresultnum;
  num1;
  }
  returnresult;
  }
  thrownewException(未识别的算法);
  }
  }
  Controller
  RestController
  publicclassTestController{
  privateTestServicetestService;
  PostMapping(test)
  publicDoubletest(RequestBodyTestDTOtestDTO){
  try{
  Doubleresultthis。testService。service(testDTO);
  returnresult;
  }catch(Exceptione){
  thrownewRuntimeException(e);
  }
  }
  Autowired
  publicDTOidsetTestService(TestServicetestService){
  this。testServicetestService;
  }
  }
  如果真的按照上面所列的工作项来开发Controller代码会有几个问题
  参数校验过多地耦合了业务代码,违背单一职责原则
  可能在多个业务中都抛出同一个异常,导致代码重复
  各种异常反馈和成功响应格式不统一,接口对接不友好
  改造Controller层逻辑
  统一返回结构
  统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为就判断成功与否,因为有些接口的设计就是如此),使用一个状态码、状态信息就能清楚地了解接口调用情况定义返回数据结构
  publicinterfaceIResult{
  IntegergetCode;
  StringgetMessage;
  }
  常用结果的枚举
  publicenumResultEnumimplementsIResult{
  SUCCESS(2001,接口调用成功),
  VALIDATEFAILED(2002,参数校验失败),
  COMMONFAILED(2003,接口调用失败),
  FORBIDDEN(2004,没有权限访问资源);
  privateIntegercode;
  privateStringmessage;
  省略get、set方法和构造方法
  }
  统一返回数据结构
  Data
  NoArgsConstructor
  AllArgsConstructor
  publicclassResultT{
  privateIntegercode;
  privateStringmessage;
  privateTdata;
  publicstaticTResultTsuccess(Tdata){
  returnnewResult(ResultEnum。SUCCESS。getCode,ResultEnum。SUCCESS。getMessage,data);
  }
  publicstaticTResultTsuccess(Stringmessage,Tdata){
  returnnewResult(ResultEnum。SUCCESS。getCode,message,data);
  }
  publicstaticResultlt;?failed{
  returnnewResult(ResultEnum。COMMONFAILED。getCode,ResultEnum。COMMONFAILED。getMessage,);
  }
  publicstaticResultlt;?failed(Stringmessage){
  returnnewResult(ResultEnum。COMMONFAILED。getCode,message,);
  }
  publicstaticResultlt;?failed(IResulterrorResult){
  returnnewResult(errorResult。getCode,errorResult。getMessage,);
  }
  publicstaticTResultTinstance(Integercode,Stringmessage,Tdata){
  ResultTresultnewResult;
  result。setCode(code);
  result。setMessage(message);
  result。setData(data);
  returnresult;
  }
  }
  统一返回结构后,在Controller中就可以使用了,但是每一个Controller都写这么一段最终封装的逻辑,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构
  统一包装处理
  Spring中提供了一个类codeResponseBodyAdvicecode,能帮助我们实现上述需求
  ResponseBodyAdvice是对Controller返回的内容在strongtoutiaooriginspanHttpMessageConverterstrong进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。那这样就可以把统一包装的工作放到这个类里面。publicinterfaceResponseBodyAdviceT{
  booleansupports(MethodParameterreturnType,Classlt;?extendsHttpMessageConverterlt;?converterType);
  able
  TbeforeBodyWrite(ableTbody,MethodParameterreturnType,MediaTypeselectedContentType,Classlt;?extendsHttpMessageConverterlt;?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse);
  }
  supports:判断是否要交给beforeBodyWrite方法执行,ture:需要;false:不需要
  beforeBodyWrite:对response进行具体的处理如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成
  RestControllerAdvice(basePackagescom。example。demo)
  publicclassResponseAdviceimplementsResponseBodyAdviceObject{
  Override
  publicbooleansupports(MethodParameterreturnType,Classlt;?extendsHttpMessageConverterlt;?converterType){
  如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解
  returntrue;
  }
  Override
  publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,Classlt;?extendsHttpMessageConverterlt;?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
  提供一定的灵活度,如果body已经被包装了,就不进行包装
  if(bodyinstanceofResult){
  returnbody;
  }
  returnResult。success(body);
  }
  }
  经过这样改造,既能实现对Controller返回的数据进行统一包装,又不需要对原有代码进行大量的改动
  处理cannotbecasttojava。lang。String问题
  如果直接使用ResponseBodyAdvice,对于一般的类型都没有问题,当处理字符串类型时,会抛出blockquotetoutiaoorigincodexxx。包装类cannotbecasttojava。lang。Stringblockquote的类型转换的异常
  在ResponseBodyAdvice实现类中debug发现,只有String类型的strongtoutiaooriginspanselectedConverterTypestrong参数值是blockquotetoutiaooriginspanorg。springframework。http。converter。StringHttpMessageConverterblockquote,而其他数据类型的值是blockquotetoutiaooriginspanorg。springframework。http。converter。json。MappingJackson2HttpMessageConverterblockquote
  String类型
  其他类型(如Integer类型)
  现在问题已经较为清晰了,因为我们需要返回一个codeResultcode对象
  所以使用blockquotetoutiaoorigincodeMappingJackson2HttpMessageConverterblockquote是可以正常转换的
  而使用codestrongtoutiaoorigincodeStringHttpMessageConverterstrongcode字符串转换器会导致类型转换失败
  现在处理这个问题有两种方式
  在beforeBodyWrite方法处进行判断,如果返回值是String类型就对codeResultcode对象手动进行转换成JSON字符串,另外方便前端使用,最好在codeRequestMappingcode中指定ContentTypeRestControllerAdvice(basePackagescom。example。demo)
  publicclassResponseAdviceimplementsResponseBodyAdviceObject{
  。。。
  Override
  publicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,Classlt;?extendsHttpMessageConverterlt;?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){
  提供一定的灵活度,如果body已经被包装了,就不进行包装
  if(bodyinstanceofResult){
  returnbody;
  }
  如果返回值是String类型,那就手动把Result对象转换成JSON字符串
  if(bodyinstanceofString){
  try{
  returnthis。objectMapper。writeValueAsString(Result。success(body));
  }catch(JsonProcessingExceptione){
  thrownewRuntimeException(e);
  }
  }
  returnResult。success(body);
  }
  。。。
  }
  GetMapping(valuereturnString,producesapplicationjson;charsetUTF8)
  publicStringreturnString{
  returnsuccess;
  }
  修改strongtoutiaoorigincodeHttpMessageConverterstrong实例集合中MappingJackson2HttpMessageConverter的顺序。因为发生上述问题的根源所在是集合中strongtoutiaooriginspanStringHttpMessageConverterstrong的顺序先于blockquotetoutiaooriginspanMappingJackson2HttpMessageConverterblockquote的,调整顺序后即可从根源上解决这个问题
  网上有不少做法是直接在集合中第一位添加blockquotetoutiaooriginspanMappingJackson2HttpMessageConverterblockquoteConfiguration
  publicclassWebConfigurationimplementsWebMvcConfigurer{
  Override
  publicvoidconfigureMessageConverters(ListHttpMessageConverterlt;?converters){
  converters。add(0,newMappingJackson2HttpMessageConverter);
  }
  }
  诚然,这种方式可以解决问题,但其实问题的根源不是集合中缺少这一个转换器,而是转换器的顺序导致的,所以最合理的做法应该是调整blockquotetoutiaooriginspanMappingJackson2HttpMessageConverterblockquote在集合中的顺序Configuration
  publicclassWebMvcConfigurationimplementsWebMvcConfigurer{
  交换MappingJackson2HttpMessageConverter与第一位元素
  让返回值类型为String的接口能正常返回包装结果
  paramconvertersinitiallyanemptylistofconverters
  Override
  publicvoidconfigureMessageConverters(ListHttpMessageConverterlt;?converters){
  for(inti0;iconverters。size;i){
  if(converters。get(i)instanceofMappingJackson2HttpMessageConverter){
  MappingJackson2HttpMessageConvertermappingJackson2HttpMessageConverter(MappingJackson2HttpMessageConverter)converters。get(i);
  converters。set(i,converters。get(0));
  converters。set(0,mappingJackson2HttpMessageConverter);
  break;
  }
  }
  }
  }
  参数校验
  JavaAPI的规范JSR303定义了校验的标准validationapi,其中一个比较出名的实现是hibernatevalidation,springvalidation是对其的二次封装,常用于SpringMVC的参数自动校验,参数校验的代码就不需要再与业务逻辑代码进行耦合了
  PathVariable和RequestParam参数校验
  Get请求的参数接收一般依赖这两个注解,但是处于url有长度限制和代码的可维护性,超过5个参数尽量用实体来传参
  对PathVariable和RequestParam参数进行校验需要在入参声明约束的注解
  如果校验失败,会抛出strongtoutiaooriginspanMethodArgumentNotValidExceptionstrong异常RestController(valueprettyTestController)
  RequestMapping(pretty)
  Validated
  publicclassTestController{
  privateTestServicetestService;
  GetMapping({num})
  publicIntegerdetail(PathVariable(num)Min(1)Max(20)Integernum){
  returnnumnum;
  }
  GetMapping(getByEmail)
  publicTestDTOgetByAccount(RequestParamNotBlankEmailStringemail){
  TestDTOtestDTOnewTestDTO;
  testDTO。setEmail(email);
  returntestDTO;
  }
  Autowired
  publicvoidsetTestService(TestServiceprettyTestService){
  this。testServiceprettyTestService;
  }
  }
  校验原理
  在SpringMVC中,有一个类是RequestResponseBodyMethodProcessor,这个类有两个作用(实际上可以从名字上得到一点启发)
  用于解析RequestBody标注的参数
  处理ResponseBody标注方法的返回值
  解析RequestBoyd标注参数的方法是resolveArgumentpublicclassRequestResponseBodyMethodProcessorextendsAbstractMessageConverterMethodProcessor{
  ThrowsMethodArgumentNotValidExceptionifvalidationfails。
  throwsHttpMessageNotReadableExceptionif{linkRequestBodyrequired}
  is{codetrue}andthereisnobodycontentorifthereisnosuitable
  convertertoreadthecontentwith。
  Override
  publicObjectresolveArgument(MethodParameterparameter,ableModelAndViewContainermavContainer,
  NativeWebRequestwebRequest,ableWebDataBinderFactorybinderFactory)throwsException{
  parameterparameter。nestedIfOptional;
  把请求数据封装成标注的DTO对象
  ObjectargreadWithMessageConverters(webRequest,parameter,parameter。getNestedGenericParameterType);
  StringnameConventions。getVariableNameForParameter(parameter);
  if(binderFactory!){
  WebDataBinderbinderbinderFactory。createBinder(webRequest,arg,name);
  if(arg!){
  执行数据校验
  validateIfApplicable(binder,parameter);
  如果校验不通过,就抛出MethodArgumentNotValidException异常
  如果我们不自己捕获,那么最终会由DefaultHandlerExceptionResolver捕获处理
  if(binder。getBindingResult。hasErrorsisBindExceptionRequired(binder,parameter)){
  thrownewMethodArgumentNotValidException(parameter,binder。getBindingResult);
  }
  }
  if(mavContainer!){
  mavContainer。addAttribute(BindingResult。MODELKEYPREFIXname,binder。getBindingResult);
  }
  }
  returnadaptArgumentIfNecessary(arg,parameter);
  }
  }
  publicabstractclassAbstractMessageConverterMethodArgumentResolverimplementsHandlerMethodArgumentResolver{
  Validatethebindingtargetifapplicable。
  pThedefaultimplementationchecksfor{codejavax。validation。Valid},
  Springs{linkorg。springframework。validation。annotation。Validated},
  andcustomannotationswhosenamestartswithValid。
  parambindertheDataBindertobeused
  paramparameterthemethodparameterdescriptor
  since4。1。5
  seeisBindExceptionRequired
  protectedvoidvalidateIfApplicable(WebDataBinderbinder,MethodParameterparameter){
  获取参数上的所有注解
  Annotationannotationsparameter。getParameterAnnotations;
  for(Annotationann:annotations){
  如果注解中包含了Valid、Validated或者是名字以Valid开头的注解就进行参数校验
  ObjectvalidationHintsValidationAnnotationUtils。determineValidationHints(ann);
  if(validationHints!){
  实际校验逻辑,最终会调用HibernateValidator执行真正的校验
  所以SpringValidation是对HibernateValidation的二次封装
  binder。validate(validationHints);
  break;
  }
  }
  }
  }
  RequestBody参数校验
  Post、Put请求的参数推荐使用RequestBody请求体参数
  对RequestBody参数进行校验需要在DTO对象中加入校验条件后,再搭配Validated即可完成自动校验
  如果校验失败,会抛出strongtoutiaoorigincodeConstraintViolationExceptionstrong异常DTO
  Data
  publicclassTestDTO{
  NotBlank
  privateStringuserName;
  NotBlank
  Length(min6,max20)
  privateStringpassword;
  Not
  Email
  privateStringemail;
  }
  Controller
  RestController(valueprettyTestController)
  RequestMapping(pretty)
  publicclassTestController{
  privateTestServicetestService;
  PostMapping(testvalidation)
  publicvoidtestValidation(RequestBodyValidatedTestDTOtestDTO){
  this。testService。save(testDTO);
  }
  Autowired
  publicvoidsetTestService(TestServicetestService){
  this。testServicetestService;
  }
  }校验原理
  声明约束的方式,注解加到了参数上面,可以比较容易猜测到是使用了AOP对方法进行增强
  而实际上Spring也是通过strongtoutiaooriginspanMethodValidationPostProcessorstrong动态注册AOP切面,然后使用strongtoutiaoorigincodeMethodValidationInterceptorstrong对切点方法进行织入增强publicclassMethodValidationPostProcessorextendsAbstractBeanFactoryAwareAdvisingPostProcessorimplementsInitializingBean{
  指定了创建切面的Bean的注解
  privateClasslt;?extendsAnnotationvalidatedAnnotationTypeValidated。class;
  Override
  publicvoidafterPropertiesSet{
  为所有Validated标注的Bean创建切面
  PointcutpointcutnewAnnotationMatchingPointcut(this。validatedAnnotationType,true);
  创建Advisor进行增强
  this。advisornewDefaultPointcutAdvisor(pointcut,createMethodValidationAdvice(this。validator));
  }
  创建Advice,本质就是一个方法拦截器
  protectedAdvicecreateMethodValidationAdvice(ableValidatorvalidator){
  return(validator!?newMethodValidationInterceptor(validator):newMethodValidationInterceptor);
  }
  }
  publicclassMethodValidationInterceptorimplementsMethodInterceptor{
  Override
  publicObjectinvoke(MethodInvocationinvocation)throwsThrowable{
  无需增强的方法,直接跳过
  if(isFactoryBeanMetadataMethod(invocation。getMethod)){
  returninvocation。proceed;
  }
  Classlt;?groupsdetermineValidationGroups(invocation);
  ExecutableValidatorexecValthis。validator。forExecutables;
  MethodmethodToValidateinvocation。getMethod;
  SetConstraintViolationObjectresult;
  try{
  方法入参校验,最终还是委托给HibernateValidator来校验
  所以SpringValidation是对HibernateValidation的二次封装
  resultexecVal。validateParameters(
  invocation。getThis,methodToValidate,invocation。getArguments,groups);
  }
  catch(IllegalArgumentExceptionex){
  。。。
  }
  校验不通过抛出ConstraintViolationException异常
  if(!result。isEmpty){
  thrownewConstraintViolationException(result);
  }
  Controller方法调用
  ObjectreturnValueinvocation。proceed;
  下面是对返回值做校验,流程和上面大概一样
  resultexecVal。validateReturnValue(invocation。getThis,methodToValidate,returnValue,groups);
  if(!result。isEmpty){
  thrownewConstraintViolationException(result);
  }
  returnreturnValue;
  }
  }
  自定义校验规则
  有些时候JSR303标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则
  自定义校验规则需要做两件事情
  自定义注解类,定义错误信息和一些其他需要的内容
  注解校验器,定义判定规则自定义注解类
  Target({ElementType。METHOD,ElementType。FIELD,ElementType。ANNOTATIONTYPE,ElementType。CONSTRUCTOR,ElementType。PARAMETER})
  Retention(RetentionPolicy。RUNTIME)
  Documented
  Constraint(validatedByMobileValidator。class)
  publicinterfaceMobile{
  是否允许为空
  booleanrequireddefaulttrue;
  校验不通过返回的提示信息
  Stringmessagedefault不是一个手机号码格式;
  Constraint要求的属性,用于分组校验和扩展,留空就好
  Classlt;?groupsdefault{};
  Classlt;?extendsPayloadpayloaddefault{};
  }
  注解校验器
  publicclassMobileValidatorimplementsConstraintValidatorMobile,CharSequence{
  privatebooleanrequiredfalse;
  privatefinalPatternpatternPattern。compile(1〔34578〕〔09〕{9});验证手机号
  在验证开始前调用注解里的方法,从而获取到一些注解里的参数
  paramconstraintAnnotationannotationinstanceforagivenconstraintdeclaration
  Override
  publicvoidinitialize(MobileconstraintAnnotation){
  this。requiredconstraintAnnotation。required;
  }
  判断参数是否合法
  paramvalueobjecttovalidate
  paramcontextcontextinwhichtheconstraintisevaluated
  Override
  publicbooleanisValid(CharSequencevalue,ConstraintValidatorContextcontext){
  if(this。required){
  验证
  returnisMobile(value);
  }
  if(StringUtils。hasText(value)){
  验证
  returnisMobile(value);
  }
  returntrue;
  }
  privatebooleanisMobile(finalCharSequencestr){
  Matchermpattern。matcher(str);
  returnm。matches;
  }
  }
  自动校验参数真的是一项非常必要、非常有意义的工作。JSR303提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。
  自定义异常与统一拦截异常
  原来的代码中可以看到有几个问题
  抛出的异常不够具体,只是简单地把错误信息放到了Exception中
  抛出异常后,Controller不能具体地根据异常做出反馈
  虽然做了参数自动校验,但是异常返回结构和正常返回结构不一致
  自定义异常是为了后面统一拦截异常时,对业务中的异常有更加细颗粒度的区分,拦截时针对不同的异常作出不同的响应
  而统一拦截异常的目的一个是为了可以与前面定义下来的统一包装返回结构能对应上,另一个是我们希望无论系统发生什么异常,Http的状态码都要是200,尽可能由业务来区分系统的异常自定义异常
  publicclassForbiddenExceptionextendsRuntimeException{
  publicForbiddenException(Stringmessage){
  super(message);
  }
  }
  自定义异常
  publicclassBusinessExceptionextendsRuntimeException{
  publicBusinessException(Stringmessage){
  super(message);
  }
  }
  统一拦截异常
  RestControllerAdvice(basePackagescom。example。demo)
  publicclassExceptionAdvice{
  捕获{codeBusinessException}异常
  ExceptionHandler({BusinessException。class})
  publicResultlt;?handleBusinessException(BusinessExceptionex){
  returnResult。failed(ex。getMessage);
  }
  捕获{codeForbiddenException}异常
  ExceptionHandler({ForbiddenException。class})
  publicResultlt;?handleForbiddenException(ForbiddenExceptionex){
  returnResult。failed(ResultEnum。FORBIDDEN);
  }
  {codeRequestBody}参数校验不通过时抛出的异常处理
  ExceptionHandler({MethodArgumentNotValidException。class})
  publicResultlt;?handleMethodArgumentNotValidException(MethodArgumentNotValidExceptionex){
  BindingResultbindingResultex。getBindingResult;
  StringBuildersbnewStringBuilder(校验失败:);
  for(FieldErrorfieldError:bindingResult。getFieldErrors){
  sb。append(fieldError。getField)。append(:)。append(fieldError。getDefaultMessage)。append(,);
  }
  Stringmsgsb。toString;
  if(StringUtils。hasText(msg)){
  returnResult。failed(ResultEnum。VALIDATEFAILED。getCode,msg);
  }
  returnResult。failed(ResultEnum。VALIDATEFAILED);
  }
  {codePathVariable}和{codeRequestParam}参数校验不通过时抛出的异常处理
  ExceptionHandler({ConstraintViolationException。class})
  publicResultlt;?handleConstraintViolationException(ConstraintViolationExceptionex){
  if(StringUtils。hasText(ex。getMessage)){
  returnResult。failed(ResultEnum。VALIDATEFAILED。getCode,ex。getMessage);
  }
  returnResult。failed(ResultEnum。VALIDATEFAILED);
  }
  顶级异常捕获并统一处理,当其他异常无法处理时候选择使用
  ExceptionHandler({Exception。class})
  publicResultlt;?handle(Exceptionex){
  returnResult。failed(ex。getMessage);
  }
  }
  总结
  做好了这一切改动后,可以发现Controller的代码变得非常简洁,可以很清楚地知道每一个参数、每一个DTO的校验规则,可以很明确地看到每一个Controller方法返回的是什么数据,也可以方便每一个异常应该如何进行反馈
  这一套操作下来后,我们能更加专注于业务逻辑的开发,代码简洁、功能完善,何乐而不为呢?
  来源:https:juejin。cnpost7123091045071454238

小马智卡最新进展曝光中国高速,风雨雾交加,连续6小时不接管浩楠雷刚发自副驾寺智能车参考公众号AI4Auto盆友们,要论自动驾驶技术的卷王,是不是非小马智行莫属?就在去年,他们秀出过一段1小时连续无接管的广州城市内完全……征途22021年终决战新区今日预创角福利大揭秘《征途2》年终决战版本专属新区年终决战于12月17日开启,新区以激情战斗为核心,为玩家送出大福利,诚邀各位英雄前来体验。今日新区开启角色预创建,避开高峰提前抢注你喜欢的角色吧!……4月23日最新崩盘跑路和即将出事的问题平台1、社群共创、博览共享:这个项目小编持续聊了一年多,现在大家也都看到结果了,项目方就是一帮骗子,持续忽悠用户而已,不要在相信他们了!2、Telegram已出现新诈骗方式,……威廉王子曾试图在情人节约小甜甜布兰妮出去,险些取代凯特?英国剑桥公爵威廉王子和公爵夫人凯特米德尔顿被外界誉为王室的模范夫妻,自2011年大婚以来,两人一直相互扶持、携手并进,并且从未像上一代王室成员那样不断因为婚姻问题传出损害王室形……一骑绝尘!华为P50在DxOMark手机影像排名中位列第一近日,有专业测评机构DxOMark公布了手机影像的排名,华为P50Pro以144分的排名位列第一,而在此次排名中前10款有8款都是国产手机,可见国产手机在当下的智能手机市场上表……女版黑镜没上热搜,也太奇幻了妮可基德曼参演的新剧《咆哮》悄无声息地扑了,甚至连一点儿水花也没溅起。剧完播了一个多月,剧评还是春天时留下来的寥寥几条。妮可基德曼在读原著小说。说是拥有黑镜气质的女……大S发假哭视频博同情,经纪人公开澄清这不是本人大S发假哭视频博同情,经纪人公开澄清:这不是本人!对于大S大家一定都不陌生吧,大S出道多年,曾是大家非常喜欢的一位艺人,后来嫁给京圈公子汪小菲之后,婚姻也算是很美满,不过……NBA科比去世后,瓦妮莎为何不让孩子去看年迈公婆?原因现实可目前科比去世已经两年多的时间了,一开始瓦妮莎悲痛欲绝,体重也是暴瘦了不少,但是随着时间的流逝,瓦妮莎慢慢从悲伤中走了出来,如今的她体重增长了很多,可以说面色红润,白白胖胖,而且……春季补钙,别总吃大鱼大肉,4种素菜含钙量高,老人小孩都受益春天补钙,别总吃大鱼大肉,4种食物含钙量高,老人小孩都受益春天是补钙的好时机,也是黄金期。传统观念里,补钙要大鱼大肉地吃,或者吃一些补钙的营养品。其实补钙不应该一味地满足……介绍一下防老丹首乌延寿丹首乌延寿丹组成:制何首乌,豨莶草,菟丝子,杜仲,牛膝,女贞子,霜桑叶,忍冬花,生地,桑椹子膏,金樱子膏,旱莲草膏,黑芝麻膏。清末著名医家陆九芝,在《老人治法》一文中说:老……一波未平一波又起,广东队再添三伤员,四连冠或彻底无望要说近日CBA哪支球队的状态最令人担忧,那么卫冕冠军广东队必定会是其中的答案之一,继张皓嘉、张昊、赵睿等主力球员出现伤病之后,球队唯一外援威姆斯又因为球场冲突而被禁赛5场,在人……中国队集体退赛,8大奥运冠军全部缺席!世锦赛全靠沈锡希撑门面在我们成功举办北京冬奥运会后,国际体坛发生了一连串的事件,先是俄罗斯运动员被集体禁赛,导致法国的花滑世锦赛成为日本锦标赛,而由于疫情的缘故,中国国羽也宣布退出瑞士公开赛。本月初……
徘徊在品牌迷宫里的长城汽车,还有崛起的资本吗?(2)徘徊在品牌迷宫不能自拔的长城汽车长城汽车最近又做了品牌调整,12月8日,长城汽车宣布:为了更好地集中优势资源,强化战略协同,建立更清晰、差异化的品牌定位,长城汽车将对品牌……大数据必学Java基础(十二)基本数据类型讲解头条创作挑战赛基本数据类型Java是一种强类型语言,每个变量都必须声明其数据类型。Java的数据类型可分为两大类:基本数据类型(primitivedataty……大龄产妇坚持顺产酿悲剧,自己难产去世,孩子出生就患病到现在我都不能原谅自己,当初儿媳在生产时认为顺产对孩子成长好一点,选择了顺产。要是我能坚持阻拦,也许儿媳就不会因为难产大出血去世,孙女也不会因为缺氧患上重病,耽误了一辈子。可是……梦幻西游175小资狮驼,5件变身套5只宝宝,这号玩起来才轻松Hello大家好,我是浩仔!关注浩仔,每天给你带来最新梦幻西游游戏资讯!以下点评仅代表个人意见,不代表官方或平台立场。高端三攻狮驼岭看多了,各位应该审美疲劳了……这届世界杯有多拼?内马尔医美,C罗整容?梅西直接成了黄金矿工有几多个朋友同大头一样,捱眼瞓睇咗巴西对战瑞士?虽然巴西不负众望赢咗,但好多人都仲有D小失望。因为,那个男人不在。第一轮对阵塞尔维亚,内马尔畀多次侵犯后脚眼受……卫龙辣条让人人爱上中国味提到卫龙,想必大家都不会陌生。那么卫龙辣条是如何从一包小小的零食逐步做大做强走向世界的呢?1999年,一名满怀激情的小伙儿踏上了前往漯河的火车,开启了他的辣条之旅。这个人……广东徐闻与海南相向而行重大项目集中开工总投资近150亿元徐闻与海南相向而行重大项目集中开工?谭章摄中新网湛江12月26日电(梁盛李梅英郑雅)26日,广东湛江市徐闻县举行与海南相向而行重大项目集中开工暨招商项目集中签约活动。此次……激活儿童前庭功能的6次黄金时机,你把握住了几次?感统训练中,触觉是基础,前庭是核心,本体是后天。前庭系统很重要,这一点老岳每天都在说,但到底什么是前庭系统?它为什么重要?家长又该如何预防孩子前庭系统失调?今天,老岳就来……行程码今日下线,你截图保存了吗?陪伴了大家三年的行程码在今日的凌晨准时下线。通信行程卡短信、网页、微信小程序、支付宝小程序、APP等查询渠道将同步下线。也预示着三年的疫情终将告一段落。作为最初支持疫情防……设计有趣的轻巧真无线,体积小续航长,南卡小音舱上手大家平时都会听听音乐、玩玩游戏,这时候就需要用到蓝牙耳机,特别是在户外接打电话时,戴上一副耳机都会方便很多。最近发现了一款南卡小音舱Lite2,这些天用过之后感觉它质量不错,做……什么是虚拟化?每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。本文是关于虚拟化以及虚拟化如何成为软件部署和IT领域中非常常用的术……长安160周年以电动化为航向,驰骋新能源赛道风雨兼程,长安160周年之际,长安汽车正以全新的姿态迎接这场新能源领域的汽车电动化改革。根据长安汽车发布的最新公告,长安汽车10月自主品牌新能源销量为36537辆,同比增……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网