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

Controller层代码就该这么写,简洁又优雅

  一个优秀的Controller层逻辑
  说到Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是不可或缺的配角,说它不可或缺是因为无论是传统的三层架构还是现在的COLA架构,Controller层依旧有一席之地,说明他的必要性;说它是配角是因为Controller层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求从现状看问题
  Controller主要的工作有以下几项接收请求并解析参数调用Service执行具体的业务代码(可能包含参数校验)捕获业务逻辑异常做出反馈业务逻辑执行成功做出响应DTODatapublicclassTestDTO{privateIntegernum;privateStringtype;}ServiceServicepublicclassTestService{publicDoubleservice(TestDTOtestDTO)throwsException{if(testDTO。getNum()0){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(未识别的算法);}}ControllerRestControllerpublicclassTestController{privateTestServicetestService;PostMapping(test)publicDoubletest(RequestBodyTestDTOtestDTO){try{Doubleresultthis。testService。service(testDTO);returnresult;}catch(Exceptione){thrownewRuntimeException(e);}}AutowiredpublicDTOidsetTestService(TestServicetestService){this。testServicetestService;}}
  如果真的按照上面所列的工作项来开发Controller代码会有几个问题参数校验过多地耦合了业务代码,违背单一职责原则可能在多个业务中都抛出同一个异常,导致代码重复各种异常反馈和成功响应格式不统一,接口对接不友好改造Controller层逻辑统一返回结构
  统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为null就判断成功与否,因为有些接口的设计就是如此),使用一个状态码、状态信息就能清楚地了解接口调用情况定义返回数据结构publicinterfaceIResult{IntegergetCode();StringgetMessage();}常用结果的枚举publicenumResultEnumimplementsIResult{SUCCESS(2001,接口调用成功),VALIDATEFAILED(2002,参数校验失败),COMMONFAILED(2003,接口调用失败),FORBIDDEN(2004,没有权限访问资源);privateIntegercode;privateStringmessage;省略get、set方法和构造方法}统一返回数据结构DataNoArgsConstructorAllArgsConstructorpublicclassResultT{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(),null);}publicstaticResultlt;?failed(Stringmessage){returnnewResult(ResultEnum。COMMONFAILED。getCode(),message,null);}publicstaticResultlt;?failed(IResulterrorResult){returnnewResult(errorResult。getCode(),errorResult。getMessage(),null);}publicstaticTResultTinstance(Integercode,Stringmessage,Tdata){ResultTresultnewResult();result。setCode(code);result。setMessage(message);result。setData(data);returnresult;}}
  统一返回结构后,在Controller中就可以使用了,但是每一个Controller都写这么一段最终封装的逻辑,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构统一包装处理
  Spring中提供了一个类ResponseBodyAdvice,能帮助我们实现上述需求
  ResponseBodyAdvice是对Controller返回的内容在HttpMessageConverter进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。那这样就可以把统一包装的工作放到这个类里面。publicinterfaceResponseBodyAdviceT{booleansupports(MethodParameterreturnType,Classlt;?extendsHttpMessageConverterlt;?converterType);NullableTbeforeBodyWrite(NullableTbody,MethodParameterreturnType,MediaTypeselectedContentType,Classlt;?extendsHttpMessageConverterlt;?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse);}supports:判断是否要交给beforeBodyWrite方法执行,ture:需要;false:不需要beforeBodyWrite:对response进行具体的处理如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成RestControllerAdvice(basePackagescom。example。demo)publicclassResponseAdviceimplementsResponseBodyAdviceObject{Overridepublicbooleansupports(MethodParameterreturnType,Classlt;?extendsHttpMessageConverterlt;?converterType){如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解returntrue;}OverridepublicObjectbeforeBodyWrite(Objectbody,MethodParameterreturnType,MediaTypeselectedContentType,Classlt;?extendsHttpMessageConverterlt;?selectedConverterType,ServerHttpRequestrequest,ServerHttpResponseresponse){提供一定的灵活度,如果body已经被包装了,就不进行包装if(bodyinstanceofResult){returnbody;}returnResult。success(body);}}
  经过这样改造,既能实现对Controller返回的数据进行统一包装,又不需要对原有代码进行大量的改动处理cannotbecasttojava。lang。String问题
  如果直接使用ResponseBodyAdvice,对于一般的类型都没有问题,当处理字符串类型时,会抛出xxx。包装类cannotbecasttojava。lang。String的类型转换的异常
  在ResponseBodyAdvice实现类中debug发现,只有String类型的selectedConverterType参数值是org。springframework。http。converter。StringHttpMessageConverter,而其他数据类型的值是org。springframework。http。converter。json。MappingJackson2HttpMessageConverterString类型
  img其他类型(如Integer类型)
  现在问题已经较为清晰了,因为我们需要返回一个Result对象
  所以使用MappingJackson2HttpMessageConverter是可以正常转换的
  而使用StringHttpMessageConverter字符串转换器会导致类型转换失败
  现在处理这个问题有两种方式在beforeBodyWrite方法处进行判断,如果返回值是String类型就对Result对象手动进行转换成JSON字符串,另外方便前端使用,最好在RequestMapping中指定ContentTypeRestControllerAdvice(basePackagescom。example。demo)publicclassResponseAdviceimplementsResponseBodyAdviceObject{。。。OverridepublicObjectbeforeBodyWrite(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;}修改HttpMessageConverter实例集合中MappingJackson2HttpMessageConverter的顺序。因为发生上述问题的根源所在是集合中StringHttpMessageConverter的顺序先于MappingJackson2HttpMessageConverter的,调整顺序后即可从根源上解决这个问题网上有不少做法是直接在集合中第一位添加MappingJackson2HttpMessageConverterConfigurationpublicclassWebConfigurationimplementsWebMvcConfigurer{OverridepublicvoidconfigureMessageConverters(ListHttpMessageConverterlt;?converters){converters。add(0,newMappingJackson2HttpMessageConverter());}}诚然,这种方式可以解决问题,但其实问题的根源不是集合中缺少这一个转换器,而是转换器的顺序导致的,所以最合理的做法应该是调整MappingJackson2HttpMessageConverter在集合中的顺序ConfigurationpublicclassWebMvcConfigurationimplementsWebMvcConfigurer{交换MappingJackson2HttpMessageConverter与第一位元素让返回值类型为String的接口能正常返回包装结果paramconvertersinitiallyanemptylistofconvertersOverridepublicvoidconfigureMessageConverters(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参数进行校验需要在入参声明约束的注解
  如果校验失败,会抛出MethodArgumentNotValidException异常RestController(valueprettyTestController)RequestMapping(pretty)ValidatedpublicclassTestController{privateTestServicetestService;GetMapping({num})publicIntegerdetail(PathVariable(num)Min(1)Max(20)Integernum){returnnumnum;}GetMapping(getByEmail)publicTestDTOgetByAccount(RequestParamNotBlankEmailStringemail){TestDTOtestDTOnewTestDTO();testDTO。setEmail(email);returntestDTO;}AutowiredpublicvoidsetTestService(TestServiceprettyTestService){this。testServiceprettyTestService;}}校验原理
  在SpringMVC中,有一个类是RequestResponseBodyMethodProcessor,这个类有两个作用(实际上可以从名字上得到一点启发)用于解析RequestBody标注的参数处理ResponseBody标注方法的返回值
  解析RequestBoyd标注参数的方法是resolveArgumentpublicclassRequestResponseBodyMethodProcessorextendsAbstractMessageConverterMethodProcessor{ThrowsMethodArgumentNotValidExceptionifvalidationfails。throwsHttpMessageNotReadableExceptionif{linkRequestBodyrequired()}is{codetrue}andthereisnobodycontentorifthereisnosuitableconvertertoreadthecontentwith。OverridepublicObjectresolveArgument(MethodParameterparameter,NullableModelAndViewContainermavContainer,NativeWebRequestwebRequest,NullableWebDataBinderFactorybinderFactory)throwsException{parameterparameter。nestedIfOptional();把请求数据封装成标注的DTO对象ObjectargreadWithMessageConverters(webRequest,parameter,parameter。getNestedGenericParameterType());StringnameConventions。getVariableNameForParameter(parameter);if(binderFactory!null){WebDataBinderbinderbinderFactory。createBinder(webRequest,arg,name);if(arg!null){执行数据校验validateIfApplicable(binder,parameter);如果校验不通过,就抛出MethodArgumentNotValidException异常如果我们不自己捕获,那么最终会由DefaultHandlerExceptionResolver捕获处理if(binder。getBindingResult()。hasErrors()isBindExceptionRequired(binder,parameter)){thrownewMethodArgumentNotValidException(parameter,binder。getBindingResult());}}if(mavContainer!null){mavContainer。addAttribute(BindingResult。MODELKEYPREFIXname,binder。getBindingResult());}}returnadaptArgumentIfNecessary(arg,parameter);}}publicabstractclassAbstractMessageConverterMethodArgumentResolverimplementsHandlerMethodArgumentResolver{Validatethebindingtargetifapplicable。pThedefaultimplementationchecksfor{codejavax。validation。Valid},Springs{linkorg。springframework。validation。annotation。Validated},andcustomannotationswhosenamestartswithValid。parambindertheDataBindertobeusedparamparameterthemethodparameterdescriptorsince4。1。5seeisBindExceptionRequiredprotectedvoidvalidateIfApplicable(WebDataBinderbinder,MethodParameterparameter){获取参数上的所有注解Annotation〔〕annotationsparameter。getParameterAnnotations();for(Annotationann:annotations){如果注解中包含了Valid、Validated或者是名字以Valid开头的注解就进行参数校验Object〔〕validationHintsValidationAnnotationUtils。determineValidationHints(ann);if(validationHints!null){实际校验逻辑,最终会调用HibernateValidator执行真正的校验所以SpringValidation是对HibernateValidation的二次封装binder。validate(validationHints);break;}}}}RequestBody参数校验
  Post、Put请求的参数推荐使用RequestBody请求体参数
  对RequestBody参数进行校验需要在DTO对象中加入校验条件后,再搭配Validated即可完成自动校验
  如果校验失败,会抛出ConstraintViolationException异常DTODatapublicclassTestDTO{NotBlankprivateStringuserName;NotBlankLength(min6,max20)privateStringpassword;NotNullEmailprivateStringemail;}ControllerRestController(valueprettyTestController)RequestMapping(pretty)publicclassTestController{privateTestServicetestService;PostMapping(testvalidation)publicvoidtestValidation(RequestBodyValidatedTestDTOtestDTO){this。testService。save(testDTO);}AutowiredpublicvoidsetTestService(TestServicetestService){this。testServicetestService;}}校验原理
  声明约束的方式,注解加到了参数上面,可以比较容易猜测到是使用了AOP对方法进行增强
  而实际上Spring也是通过MethodValidationPostProcessor动态注册AOP切面,然后使用MethodValidationInterceptor对切点方法进行织入增强publicclassMethodValidationPostProcessorextendsAbstractBeanFactoryAwareAdvisingPostProcessorimplementsInitializingBean{指定了创建切面的Bean的注解privateClasslt;?extendsAnnotationvalidatedAnnotationTypeValidated。class;OverridepublicvoidafterPropertiesSet(){为所有Validated标注的Bean创建切面PointcutpointcutnewAnnotationMatchingPointcut(this。validatedAnnotationType,true);创建Advisor进行增强this。advisornewDefaultPointcutAdvisor(pointcut,createMethodValidationAdvice(this。validator));}创建Advice,本质就是一个方法拦截器protectedAdvicecreateMethodValidationAdvice(NullableValidatorvalidator){return(validator!null?newMethodValidationInterceptor(validator):newMethodValidationInterceptor());}}publicclassMethodValidationInterceptorimplementsMethodInterceptor{OverridepublicObjectinvoke(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)DocumentedConstraint(validatedByMobileValidator。class)publicinterfaceMobile{是否允许为空booleanrequired()defaulttrue;校验不通过返回的提示信息Stringmessage()default不是一个手机号码格式;Constraint要求的属性,用于分组校验和扩展,留空就好Classlt;?〔〕groups()default{};Classlt;?extendsPayload〔〕payload()default{};}注解校验器publicclassMobileValidatorimplementsConstraintValidatorMobile,CharSequence{privatebooleanrequiredfalse;privatefinalPatternpatternPattern。compile(1〔34578〕〔09〕{9}34;);验证手机号在验证开始前调用注解里的方法,从而获取到一些注解里的参数paramconstraintAnnotationannotationinstanceforagivenconstraintdeclarationOverridepublicvoidinitialize(MobileconstraintAnnotation){this。requiredconstraintAnnotation。required();}判断参数是否合法paramvalueobjecttovalidateparamcontextcontextinwhichtheconstraintisevaluatedOverridepublicbooleanisValid(CharSequencevalue,ConstraintValidatorContextcontext){if(this。required){验证returnisMobile(value);}if(StringUtils。hasText(value)){验证returnisMobile(value);}returntrue;}privatebooleanisMobile(finalCharSequencestr){Matchermpattern。matcher(str);returnm。matches();}}
  自动校验参数真的是一项非常必要、非常有意义的工作。JSR303提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。
  更多关于Spring参数校验请参考:SpringValidation最佳实践及其实现原理,参数校验没那么简单!自定义异常与统一拦截异常
  原来的代码中可以看到有几个问题抛出的异常不够具体,只是简单地把错误信息放到了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:mp。weixin。qq。comsK5YGdEomTeX92l15Hv0A

炖肉的3个技巧,学会后炖肉软烂鲜香,入味不腥,不学吗炖肉的3个技巧,学会后炖肉软烂鲜香,入味不腥,不学吗小区附近的美食街,隔段时间就有一家新店开张,最近新开了一家卤肉饭,开业很优惠,买一送一,前天带着家人去吃了,点了2份送……中国17岁小将商竣程首次晋级网球大满贯正赛新华社北京1月12日电在12日进行的2023年澳网资格赛决胜轮中,中国17岁小将商竣程以6:3和6:4击败匈牙利选手皮洛斯,成功从资格赛突围,职业生涯首次跻身大满贯正赛。……汪小菲大S离婚,数亿财产怎么分?一直觉得,大S,是那种不会轻易离婚的人。11月22日,汪小菲、大S发布离婚声明,经过汪小菲先生和徐熙媛女士认真、慎重地考虑,两人已决定和平解除婚姻关系,并与近日办理了相关……谢霆锋,终于明白王菲为什么爱你了男人四十,能有多帅?谢霆锋三个字就是最好的答案。最近,电影《怒火重案》上映,那个出走半生的厨子谢霆锋,终于回归了。凭借潇洒狠辣的打戏,和危险迷人的微笑,41岁……轮回!(太美了)。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。顺风逆风,那是岁月的感悟春去春回,那是别致的风景轮回就是……连胜两大夺冠热门!北京队能否阻击辽宁队?林书豪郭艾伦巅峰对决CBA第二阶段即将来临,将于长春赛区举行,揭幕战由北京队对阵辽宁队,第一阶段北京队排名第9,而辽宁队高居榜首,不过北京队战胜过广东队、浙江队、上海队等夺冠热门,辽宁队只遇到过广……500元以下的无线耳机推荐500元以下的无线耳机可选择的空间不多,因为无线耳机音质是有门槛的。200500元价位并没有能够综合上超越漫步者TWS1经典版的真无线耳机,即便是漫步者自家的TWS2和TWS5……打疯了!狂轰14101100横扫7届世锦赛冠军北京时间11月3日凌晨,2021斯诺克英格兰公开赛第一轮继续进行。一场焦点比赛中,7届世锦赛冠军台球皇帝斯蒂芬亨德利,04被克里斯韦克林横扫。此役,韦克林状态开挂,轰出2杆破百……可预防痴呆延缓衰老!少吃好处多多,吃多少合适?吃了吗?这是我们见面经常打的招呼,足以说明吃在我们心中的分量。谁都需要吃饭,但是我们早已过了饥不果腹的苦难日子,如今,我们需要抵制各种美吃了吗?这是我们见面经常打的招呼,……8个养肝小妙招按时休息中医的子午流注中讲到肝胆在晚上11点至凌晨3点最兴盛,各个脏腑的血液都经过肝,此刻肝脏的解毒作用也达到了最高峰。养肝的最佳方式就是好好休息。食补我国的……轻松三步,解决孩子分房难的问题学龄前两大难题:一个是入园分离焦虑,一个就是分房睡了。随着孩子的一天天长大,孩子总要学会去独立,分房睡有利于让孩子形成独立的人格,锻炼孩子独立,而且有利于孩子两性意识的形成,但……他绕不开鲁迅,我们都绕不开文樊成最近,一段鲁迅之孙周令飞接受采访的视频火遍全网。起因是,在不久前鲁迅逝世85周年纪念日,也即在上海举办的第四届鲁迅文化周开幕式上,周令飞出席活动并致辞,因其酷似鲁迅……
中国和朝鲜之间有着巨大差距,朝鲜美女导游这么举例中国和朝鲜之间有着巨大差距,朝鲜美女导游这么举例。去朝鲜旅游,你会发现旅游团所带领的游客,基本上都是清一色的老年人,之所以会有这一特点,主要是因为许多年轻人认为,朝……腌雪里蕻,用错盐会发黄!母亲教我老方法,又脆又绿,好吃放不坏腌雪里蕻,用错盐会发黄!母亲教我老方法,又脆又绿,好吃放不坏雪里蕻是一种常见的蔬菜,这是北方的叫法,南方叫雪菜,它是芥菜的变种,最适合腌制食用,做成后也叫辣菜,可以炒着吃……早财经丨中国女篮获世界杯亚军申万研究所副所长被立案侦查特斯拉NO。1北京时间10月1日,在2022女篮世界杯决赛中,中国女篮不敌美国女篮,获得世界杯亚军。追平历史最佳战绩,上一次是1994年世锦赛(世界杯前身)夺得银牌。国际篮联官方宣布……昆明占地2000的清朝老宅,竟有72道门,门里有门,进去会迷据考古研究,早在三万年前滇池一带就有人类在此生息繁衍。公元前278年,滇国建立,昆明城成为都城。滇池,对于昆明人来说意义非凡,它不仅仅只是一个地标景点,还是昆明历史文化的……河北60岁大爷自学酿酒,酿出的酒水5元一斤没人要,到底是咋回河北60岁大爷自学酿酒,酿出的酒水5元一斤没人要,到底是咋回事其实在白酒刚开始出世的时候,几乎家家户户都会酿制,在唐宋时期开始有专门卖酒水的商铺,并且是门庭若市。现在的话……微信有钱还绑定了银行卡,那这3个地方记得检查,避免钱财受损自从手机支付普及以来,支付宝、微信都成为了年轻人使用最多的APP,其中微信作为一款支付兼社交的软件,全球用户数量更是突破了12亿之多。当然了,无论是支付宝,还是微信的使用都需要……浅浅遇,淡淡忘这个世界上,没有什么是永远,唱一曲风花雪月,吟一阕岁月静好,烟火、流年、红尘、沧桑,浅浅遇,淡淡忘!如果有来生,愿做一株草,没有情绪,没有期待,没有疲惫,也没有无奈,一半……全明星一般都是顶薪球员,有人一边拿底薪一边入选全明星吗?正所谓落地的凤凰不如鸡,一般来说沦为底薪也就意味着该球员已经失去了过往的辉煌,这类球员有可能连工作都是朝不保夕,就比如德怀特霍华德和卡梅隆安东尼至今都还没有找到下家,但有的底薪……励志语录Day21。不想认命,就去拼命,我始终相信付出就会有收获,或大或小,或迟或早,始终不会辜负你的努力。有一种落差是,你总是羡慕别人的成功,自己却不敢开始。2。自律的人不一定优秀,但……微山岛上的汉墓死亡并不可怕,活着的人不必担忧悲伤文杨建东烟波浩渺的微山湖南部有个凸起不高的小山头,远远望去仿佛漂浮在湖面上的巨轮,商代称微山,明代形成大湖就叫微山岛。电影《铁道游击队》的插曲《弹起我心爱的土琵琶》……常吃发酵食品,有助于减少体内炎症,医生建议这6种食物可多吃在中国文化的传统饮食习惯中,可谓是有许许多多食物,拥有非常悠久的历史,其中就有发酵食物。什么叫发酵食物呢?简单来说就是通过某些手段将食物本身的一些营养刺激出来,并且经过长……为什么女性机器人这么受欢迎?买家除了不能生孩子,其他都能做说到机器人这个话题,很多人都抱有一定的争议性,觉得机器人的出现,能够解决我们的问题解放我们的双手,给我们的生活带来众多的优势。(此处已添加小程序,请到今日头条客户端查看)……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网