范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

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

  目录一个优秀的Controller层逻辑从现状看问题改造 Controller 层逻辑总结
  一个优秀的Controller层逻辑
  说到 Controller,相信大家都不陌生,它可以很方便地对外提供数据接口。它的定位,我认为是「不可或缺的配角」,说它不可或缺是因为无论是传统的三层架构还是现在的COLA架构,Controller 层依旧有一席之地,说明他的必要性;说它是配角是因为 Controller 层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求
  从现状看问题
  Controller 主要的工作有以下几项
  接收请求并解析参数
  调用 Service 执行具体的业务代码(可能包含参数校验)
  捕获业务逻辑异常做出反馈
  业务逻辑执行成功做出响应//DTO
  @Data
  public class TestDTO {
  private Integer num;
  private String type;
  }
  //Service
  @Service
  public class TestService {
  public Double service(TestDTO testDTO) throws Exception {
  if (testDTO.getNum <= 0) {
  throw new Exception("输入的数字需要大于0");
  }
  if (testDTO.getType.equals("square")) {
  return Math.pow(testDTO.getNum, 2);
  }
  if (testDTO.getType.equals("factorial")) {
  double result = 1;
  int num = testDTO.getNum;
  while (num > 1) {
  result = result * num;
  num -= 1;
  }
  return result;
  }
  throw new Exception("未识别的算法");
  }
  }
  //Controller
  @RestController
  public class TestController {
  private TestService testService;
  @PostMapping("/test")
  public Double test(@RequestBody TestDTO testDTO) {
  try {
  Double result = this.testService.service(testDTO);
  return result;
  } catch (Exception e) {
  throw new RuntimeException(e);
  }
  }
  @Autowired
  public DTOid setTestService(TestService testService) {
  this.testService = testService;
  }
  }
  如果真的按照上面所列的工作项来开发 Controller 代码会有几个问题
  参数校验过多地耦合了业务代码,违背单一职责原则
  可能在多个业务中都抛出同一个异常,导致代码重复
  各种异常反馈和成功响应格式不统一,接口对接不友好
  改造 Controller 层逻辑
  统一返回结构
  统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为 就判断成功与否,因为有些接口的设计就是如此),使用一个状态码、状态信息就能清楚地了解接口调用情况//定义返回数据结构
  public interface IResult {
  Integer getCode;
  String getMessage;
  }
  //常用结果的枚举
  public enum ResultEnum implements IResult {
  SUCCESS(2001, "接口调用成功"),
  VALIDATE_FAILED(2002, "参数校验失败"),
  COMMON_FAILED(2003, "接口调用失败"),
  FORBIDDEN(2004, "没有权限访问资源");
  private Integer code;
  private String message;
  //省略get、set方法和构造方法
  }
  //统一返回数据结构
  @Data
  @NoArgsConstructor
  @AllArgsConstructor
  public class Result {
  private Integer code;
  private String message;
  private T data;
  public static  Result success(T data) {
  return new Result<>(ResultEnum.SUCCESS.getCode, ResultEnum.SUCCESS.getMessage, data);
  }
  public static  Result success(String message, T data) {
  return new Result<>(ResultEnum.SUCCESS.getCode, message, data);
  }
  public static Result<?> failed {
  return new Result<>(ResultEnum.COMMON_FAILED.getCode, ResultEnum.COMMON_FAILED.getMessage, );
  }
  public static Result<?> failed(String message) {
  return new Result<>(ResultEnum.COMMON_FAILED.getCode, message, );
  }
  public static Result<?> failed(IResult errorResult) {
  return new Result<>(errorResult.getCode, errorResult.getMessage, );
  }
  public static  Result instance(Integer code, String message, T data) {
  Result result = new Result<>;
  result.setCode(code);
  result.setMessage(message);
  result.setData(data);
  return result;
  }
  }
  统一返回结构后,在 Controller 中就可以使用了,但是每一个 Controller 都写这么一段最终封装的逻辑,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构
  统一包装处理
  Spring 中提供了一个类  ResponseBodyAdvice,能帮助我们实现上述需求
  ResponseBodyAdvice是对 Controller 返回的内容在HttpMessageConverter进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。那这样就可以把统一包装的工作放到这个类里面。public interface ResponseBodyAdvice {
  boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
  @able
  T beforeBodyWrite(@able T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
  }
  supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
  beforeBodyWrite:对 response 进行具体的处理// 如果引入了swagger或knife4j的文档生成组件,这里需要仅扫描自己项目的包,否则文档无法正常生成
  @RestControllerAdvice(basePackages = "com.example.demo")
  public class ResponseAdvice implements ResponseBodyAdvice {
  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
  // 如果不需要进行封装的,可以添加一些校验手段,比如添加标记排除的注解
  return true;
  }
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
  // 提供一定的灵活度,如果body已经被包装了,就不进行包装
  if (body instanceof Result) {
  return body;
  }
  return Result.success(body);
  }
  }
  经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动
  处理 cannot be cast to java.lang.String 问题
  如果直接使用ResponseBodyAdvice,对于一般的类型都没有问题,当处理字符串类型时,会抛出  
xxx.包装类 cannot be cast to java.lang.String
的类型转换的异常   在ResponseBodyAdvice 实现类中 debug 发现,只有 String 类型的 selectedConverterType参数值是
org.springframework.http.converter.StringHttpMessageConverter
,而其他数据类型的值是
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
  String 类型   其他类型 (如 Integer 类型)   现在问题已经较为清晰了,因为我们需要返回一个 Result对象   所以使用
MappingJackson2HttpMessageConverter
是可以正常转换的   而使用 StringHttpMessageConverter字符串转换器会导致类型转换失败   现在处理这个问题有两种方式   在 beforeBodyWrite方法处进行判断,如果返回值是 String 类型就对Result对象手动进行转换成 JSON 字符串,另外方便前端使用,最好在 @RequestMapping中指定 ContentType@RestControllerAdvice(basePackages = "com.example.demo")   public class ResponseAdvice implements ResponseBodyAdvice {   ...   @Override   public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {   // 提供一定的灵活度,如果body已经被包装了,就不进行包装   if (body instanceof Result) {   return body;   }   // 如果返回值是String类型,那就手动把Result对象转换成JSON字符串   if (body instanceof String) {   try {   return this.objectMapper.writeValueAsString(Result.success(body));   } catch (JsonProcessingException e) {   throw new RuntimeException(e);   }   }   return Result.success(body);   }   ...   }   @GetMapping(value = "/returnString", produces = "application/json; charset=UTF-8")   public String returnString {   return "success";   }   修改 HttpMessageConverter实例集合中MappingJackson2HttpMessageConverter的顺序。因为发生上述问题的根源所在是集合中StringHttpMessageConverter的顺序先于
MappingJackson2HttpMessageConverter
的,调整顺序后即可从根源上解决这个问题   网上有不少做法是直接在集合中第一位添加
MappingJackson2HttpMessageConverter
@Configuration   public class WebConfiguration implements WebMvcConfigurer {   @Override   public void configureMessageConverters(List> converters) {   converters.add(0, new MappingJackson2HttpMessageConverter);   }   }   诚然,这种方式可以解决问题,但其实问题的根源不是集合中缺少这一个转换器,而是转换器的顺序导致的,所以最合理的做法应该是调整
MappingJackson2HttpMessageConverter
在集合中的顺序@Configuration   public class WebMvcConfiguration implements WebMvcConfigurer {   /**   * 交换MappingJackson2HttpMessageConverter与第一位元素   * 让返回值类型为String的接口能正常返回包装结果   *   * @param converters initially an empty list of converters   */   @Override   public void configureMessageConverters(List> converters) {   for (int i = 0; i < converters.size; i++) {   if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {   MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converters.get(i);   converters.set(i, converters.get(0));   converters.set(0, mappingJackson2HttpMessageConverter);   break;   }   }   }   }   参数校验   Java API 的规范 JSR303定义了校验的标准validation-api,其中一个比较出名的实现是hibernate validation,spring validation是对其的二次封装,常用于 SpringMVC 的参数自动校验,参数校验的代码就不需要再与业务逻辑代码进行耦合了   @PathVariable 和 @RequestParam 参数校验   Get 请求的参数接收一般依赖这两个注解,但是处于 url 有长度限制和代码的可维护性,超过 5 个参数尽量用实体来传参   对 @PathVariable 和 @RequestParam 参数进行校验需要在入参声明约束的注解   如果校验失败,会抛出 MethodArgumentNotValidException异常@RestController(value = "prettyTestController")   @RequestMapping("/pretty")   @Validated   public class TestController {   private TestService testService;   @GetMapping("/{num}")   public Integer detail(@PathVariable("num") @Min(1) @Max(20) Integer num) {   return num * num;   }   @GetMapping("/getByEmail")   public TestDTO getByAccount(@RequestParam @NotBlank @Email String email) {   TestDTO testDTO = new TestDTO;   testDTO.setEmail(email);   return testDTO;   }   @Autowired   public void setTestService(TestService prettyTestService) {   this.testService = prettyTestService;   }   }   校验原理   在 SpringMVC 中,有一个类是 RequestResponseBodyMethodProcessor,这个类有两个作用(实际上可以从名字上得到一点启发)   用于解析 @RequestBody 标注的参数   处理 @ResponseBody 标注方法的返回值   解析 @RequestBoyd 标注参数的方法是resolveArgumentpublic class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {   /**   * Throws MethodArgumentNotValidException if validation fails.   * @throws HttpMessageNotReadableException if {@link RequestBody#required}   * is {@code true} and there is no body content or if there is no suitable   * converter to read the content with.   */   @Override   public Object resolveArgument(MethodParameter parameter, @able ModelAndViewContainer mavContainer,   NativeWebRequest webRequest, @able WebDataBinderFactory binderFactory) throws Exception {   parameter = parameter.nestedIfOptional;   //把请求数据封装成标注的DTO对象   Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType);   String name = Conventions.getVariableNameForParameter(parameter);   if (binderFactory != ) {   WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);   if (arg != ) {   //执行数据校验   validateIfApplicable(binder, parameter);   //如果校验不通过,就抛出MethodArgumentNotValidException异常   //如果我们不自己捕获,那么最终会由DefaultHandlerExceptionResolver捕获处理   if (binder.getBindingResult.hasErrors && isBindExceptionRequired(binder, parameter)) {   throw new MethodArgumentNotValidException(parameter, binder.getBindingResult);   }   }   if (mavContainer != ) {   mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult);   }   }   return adaptArgumentIfNecessary(arg, parameter);   }   }   public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {   /**   * Validate the binding target if applicable.   *

The default implementation checks for {@code @javax.validation.Valid},   * Spring"s {@link org.springframework.validation.annotation.Validated},   * and custom annotations whose name starts with "Valid".   * @param binder the DataBinder to be used   * @param parameter the method parameter descriptor   * @since 4.1.5   * @see #isBindExceptionRequired   */   protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {   //获取参数上的所有注解   Annotation annotations = parameter.getParameterAnnotations;   for (Annotation ann : annotations) {   //如果注解中包含了@Valid、@Validated或者是名字以Valid开头的注解就进行参数校验   Object validationHints = ValidationAnnotationUtils.determineValidationHints(ann);   if (validationHints != ) {   //实际校验逻辑,最终会调用Hibernate Validator执行真正的校验   //所以Spring Validation是对Hibernate Validation的二次封装   binder.validate(validationHints);   break;   }   }   }   }   @RequestBody 参数校验   Post、Put 请求的参数推荐使用 @RequestBody 请求体参数   对 @RequestBody 参数进行校验需要在 DTO 对象中加入校验条件后,再搭配 @Validated 即可完成自动校验   如果校验失败,会抛出 ConstraintViolationException异常//DTO   @Data   public class TestDTO {   @NotBlank   private String userName;   @NotBlank   @Length(min = 6, max = 20)   private String password;   @Not   @Email   private String email;   }   //Controller   @RestController(value = "prettyTestController")   @RequestMapping("/pretty")   public class TestController {   private TestService testService;   @PostMapping("/test-validation")   public void testValidation(@RequestBody @Validated TestDTO testDTO) {   this.testService.save(testDTO);   }   @Autowired   public void setTestService(TestService testService) {   this.testService = testService;   }   }校验原理   声明约束的方式,注解加到了参数上面,可以比较容易猜测到是使用了 AOP 对方法进行增强   而实际上 Spring 也是通过 MethodValidationPostProcessor动态注册 AOP 切面,然后使用MethodValidationInterceptor对切点方法进行织入增强public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {   //指定了创建切面的Bean的注解   private Class<? extends Annotation> validatedAnnotationType = Validated.class;   @Override   public void afterPropertiesSet {   //为所有@Validated标注的Bean创建切面   Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);   //创建Advisor进行增强   this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));   }   //创建Advice,本质就是一个方法拦截器   protected Advice createMethodValidationAdvice(@able Validator validator) {   return (validator != ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor);   }   }   public class MethodValidationInterceptor implements MethodInterceptor {   @Override   public Object invoke(MethodInvocation invocation) throws Throwable {   //无需增强的方法,直接跳过   if (isFactoryBeanMetadataMethod(invocation.getMethod)) {   return invocation.proceed;   }   Class<?> groups = determineValidationGroups(invocation);   ExecutableValidator execVal = this.validator.forExecutables;   Method methodToValidate = invocation.getMethod;   Set> result;   try {   //方法入参校验,最终还是委托给Hibernate Validator来校验   //所以Spring Validation是对Hibernate Validation的二次封装   result = execVal.validateParameters(   invocation.getThis, methodToValidate, invocation.getArguments, groups);   }   catch (IllegalArgumentException ex) {   ...   }   //校验不通过抛出ConstraintViolationException异常   if (!result.isEmpty) {   throw new ConstraintViolationException(result);   }   //Controller方法调用   Object returnValue = invocation.proceed;   //下面是对返回值做校验,流程和上面大概一样   result = execVal.validateReturnValue(invocation.getThis, methodToValidate, returnValue, groups);   if (!result.isEmpty) {   throw new ConstraintViolationException(result);   }   return returnValue;   }   }   自定义校验规则   有些时候 JSR303标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则   自定义校验规则需要做两件事情   自定义注解类,定义错误信息和一些其他需要的内容   注解校验器,定义判定规则//自定义注解类   @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})   @Retention(RetentionPolicy.RUNTIME)   @Documented   @Constraint(validatedBy = MobileValidator.class)   public @interface Mobile {   /**   * 是否允许为空   */   boolean required default true;   /**   * 校验不通过返回的提示信息   */   String message default "不是一个手机号码格式";   /**   * Constraint要求的属性,用于分组校验和扩展,留空就好   */   Class<?> groups default {};   Class<? extends Payload> payload default {};   }   //注解校验器   public class MobileValidator implements ConstraintValidator {   private boolean required = false;   private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$"); // 验证手机号   /**   * 在验证开始前调用注解里的方法,从而获取到一些注解里的参数   *   * @param constraintAnnotation annotation instance for a given constraint declaration   */   @Override   public void initialize(Mobile constraintAnnotation) {   this.required = constraintAnnotation.required;   }   /**   * 判断参数是否合法   *   * @param value object to validate   * @param context context in which the constraint is evaluated   */   @Override   public boolean isValid(CharSequence value, ConstraintValidatorContext context) {   if (this.required) {   // 验证   return isMobile(value);   }   if (StringUtils.hasText(value)) {   // 验证   return isMobile(value);   }   return true;   }   private boolean isMobile(final CharSequence str) {   Matcher m = pattern.matcher(str);   return m.matches;   }   }   自动校验参数真的是一项非常必要、非常有意义的工作。JSR303提供了丰富的参数校验规则,再加上复杂业务的自定义校验规则,完全把参数校验和业务逻辑解耦开,代码更加简洁,符合单一职责原则。   自定义异常与统一拦截异常   原来的代码中可以看到有几个问题   抛出的异常不够具体,只是简单地把错误信息放到了 Exception 中   抛出异常后,Controller 不能具体地根据异常做出反馈   虽然做了参数自动校验,但是异常返回结构和正常返回结构不一致   自定义异常是为了后面统一拦截异常时,对业务中的异常有更加细颗粒度的区分,拦截时针对不同的异常作出不同的响应   而统一拦截异常的目的一个是为了可以与前面定义下来的统一包装返回结构能对应上,另一个是我们希望无论系统发生什么异常,Http 的状态码都要是 200 ,尽可能由业务来区分系统的异常//自定义异常   public class ForbiddenException extends RuntimeException {   public ForbiddenException(String message) {   super(message);   }   }   //自定义异常   public class BusinessException extends RuntimeException {   public BusinessException(String message) {   super(message);   }   }   //统一拦截异常   @RestControllerAdvice(basePackages = "com.example.demo")   public class ExceptionAdvice {   /**   * 捕获 {@code BusinessException} 异常   */   @ExceptionHandler({BusinessException.class})   public Result<?> handleBusinessException(BusinessException ex) {   return Result.failed(ex.getMessage);   }   /**   * 捕获 {@code ForbiddenException} 异常   */   @ExceptionHandler({ForbiddenException.class})   public Result<?> handleForbiddenException(ForbiddenException ex) {   return Result.failed(ResultEnum.FORBIDDEN);   }   /**   * {@code @RequestBody} 参数校验不通过时抛出的异常处理   */   @ExceptionHandler({MethodArgumentNotValidException.class})   public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {   BindingResult bindingResult = ex.getBindingResult;   StringBuilder sb = new StringBuilder("校验失败:");   for (FieldError fieldError : bindingResult.getFieldErrors) {   sb.append(fieldError.getField).append(":").append(fieldError.getDefaultMessage).append(", ");   }   String msg = sb.toString;   if (StringUtils.hasText(msg)) {   return Result.failed(ResultEnum.VALIDATE_FAILED.getCode, msg);   }   return Result.failed(ResultEnum.VALIDATE_FAILED);   }   /**   * {@code @PathVariable} 和 {@code @RequestParam} 参数校验不通过时抛出的异常处理   */   @ExceptionHandler({ConstraintViolationException.class})   public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {   if (StringUtils.hasText(ex.getMessage)) {   return Result.failed(ResultEnum.VALIDATE_FAILED.getCode, ex.getMessage);   }   return Result.failed(ResultEnum.VALIDATE_FAILED);   }   /**   * 顶级异常捕获并统一处理,当其他异常无法处理时候选择使用   */   @ExceptionHandler({Exception.class})   public Result<?> handle(Exception ex) {   return Result.failed(ex.getMessage);   }   }   总结   做好了这一切改动后,可以发现 Controller 的代码变得非常简洁,可以很清楚地知道每一个参数、每一个 DTO 的校验规则,可以很明确地看到每一个 Controller 方法返回的是什么数据,也可以方便每一个异常应该如何进行反馈   这一套操作下来后,我们能更加专注于业务逻辑的开发,代码简洁、功能完善,何乐而不为呢?   来源:https://juejin.cn/post/7123091045071454238


助力国货崛起,辛选集团创始人辛巴多维度布局直播电商行业日前,工信部等五部门联合发布数字化助力消费品工业三品行动方案(20222025年),提出挖掘中国文化中国记忆中华老字号等传统文化基因和非物质文化遗产,加强新生消费群体消费取向研究,游戏收入暴跌40,但英伟达其实没那么糟糕英伟达糟糕的业绩,其实是被库存闪了腰。尽管早有预期,但英伟达糟糕的业绩表现,依然给寒冬中的半导体市场泼了一大盆冰水。美东时间8月24日盘后,英伟达正式发布截至2022年7月31日的德信服务上半年收入为约人民币4。85亿元中国网地产讯8月25日,德信服务发布2022年半年度业绩报告。报告期内,收入为约人民币4。85亿元,同比增加6。3毛利润为约人民币1。54亿元,同比下降6。9毛利率为31。8,较2李嘉诚是哪一年开始搞房地产的?1971年李嘉诚进入房地产行业常听说李嘉诚从事房地产生意,可李嘉诚在1950年创业时开的是塑胶工厂。那么,李嘉诚是何时进军房地产行业的?真实答案1971年6月内地网络上的自媒体和内雅居乐再偿还超10亿元贷款,公司经营现多重积极因素自8月进入偿债高峰期以来,雅居乐集团控股有限公司(雅居乐,3383。HK)已连续数次发出公告,内容均为公司按时偿还到期债务。8月25日,雅居乐再次公告,已将足够资金存入信贷代理人指安得财富失业断供暴雷,未来3年普通人如何自保?最近几天,很多网友被华为创始人任正非的一篇内部文章刷了屏,文章的标题为整个公司的经营方针要从追求规模转向追求利润和现金流,原文很长,但核心思想就三个1未来十年会很艰难,全球经济会持五菱汽车披露2022年半年报转型阵痛过后,曙光在前8月24日,港股上市公司五菱汽车(00305。HK)发布2022年半年度业绩报告。上半年,公司实现营业收入62。75亿元,同比下降12。4净亏损1。39亿元,较去年同期大幅增加。期每体拜仁82巴萨后,曾试图1200万欧签下佩德里直播吧9月15日讯据每日体育报透露,拜仁曾在2020年82战胜巴萨后,有意1200万欧元签下巴萨中场佩德里。报道称,当时巴萨并不愿意听取拜仁对佩德里的报价,他们希望留下球员。而对于生态经济添活力荆楚大地谱新篇来源中国经济网行走荆楚大地,处处勃勃生机。这些年来,湖北多地着力推动生态经济加快发展。近日,稳中求进高质量发展网络主题宣传活动采访团来到湖北的十堰丹江口和神农架林区,实地调研这些地演员李国麟够硬,晒出两张照片并配文丢人,暗指吃里扒外的人人是一种复杂的社会性动物,一个人存在的意义绝不只在于生命本身。任何生命的逝去都是值得惋惜的,但必须要考虑生命之外的附加社会价值,在表达惋惜的时候需要寻找到一个合适的方式。明星艺人同不要米切尔了!4年1。2亿续约未来核心,尼克斯也要拼一把了今夏2019届的众多球员达成了提前续约,包括效力于鹈鹕队的锡安威廉森和效力于灰熊队的莫兰特,如今2019届的第三人,探花秀RJ巴雷特同样是和自己的球队达成了一份提前续约合同。根据知
微信新功能支付宝极速模式这霸王条款无效华为拿下德国市场微信iOS版拍照功能升级据媒体报道,最近iOS版微信(v8。0。31)通过热更新的方式,对拍照功能进行了升级,上线新版拍照界面,并且对微距拍摄进行了支持,解决了近距离拍摄物体时照片国产安卓手表健康功能不输苹果,年底换新首先了解这三款!当前的环境中,大家最为关心的应该就是自己和家人的健康了。而在日常生活中,能够随身携带和使用的此类产品并不多。但在越来越热的智能手表中,健康监测却也是主要产品力之一,可以为大家提供心近十六万打在脸上效果到底如何?视频加载中杨女士这里这里这里,你们自己看有没有效果。16万打在脸上,而且是一次性打完,打完之后前后就差两天,14号打的,16号拍的照片,很清楚的,几乎是一点效果都没有,为零的,你们每日一味抗癌中药射干,治疗咽喉肿痛效果极佳,建议收藏射干品种来源鸢尾科植物射干的干燥根茎。春初刚发芽或秋末茎叶枯萎时采挖,挖出后除去泥沙,摊开晾至表面干燥。别名黄远鬼扇夜干紫良姜萹竹黄花萹蓄仙人掌扇把草乌要乌扇乌吹乌蒲草姜风翼地萹竹哈亚花絮世界杯期间的网红小王子到底是不是王子?拉伊卜小王子备受关注世界杯历史上最贵东道主卡塔尔队早早出局,但本届世界杯的新晋网红在中国的热度仍在噌噌噌上涨。他就是人称拉伊卜小王子的阿卜杜勒拉赫曼法哈德阿勒萨尼。进驻中国社交平台外贸万亿之城组团出海抢单背后线下参展效果好,明年展会展位一位难求2022年12月18日,浙江宁波,舟山港穿山港区作业场景图据视觉中国红星新闻记者宋昕泽蔡晓仪蓝婧实习生刘中梅编辑官莉实习编辑罗宇婷12月6日,浙江宁波百团千企万人拓市场促招引行动首后背发凉,4种原因,8种中成药6个穴位来解决后背发凉,4种原因,8种中成药6个穴位来解决经常后背发凉,尤其是后背两肩胛骨之间这特别凉的,最常见有四个原因及治疗第一个因为后背肩胛骨缝这个位置正好上焦,心阳不振的人经常会出现像心常用工具软件推荐优秀的软件可以极大提高我们的生产力和效率,本文给大家推荐一些优秀的工具软件。文件搜索everything网址httpswww。voidtools。comzhcneverthing是Eclipse常用开发配置Eclipse常用开发配置1。编码配置1。1输出中文乱码问题1。2Java文件中文乱码2。切换JDK修改JRE3。错误找不到或无法加载主类4。修改字体大小4。1修改编辑窗口字体大小浅谈细胞株开发中,3种常用细胞单克隆方法细胞株开发是抗体药物CMC的起点和基础,在细胞株开发过程中,由于大批量生产必须采用基因和表型方面高度均一化的单克隆,以防止细胞在传代过程中出现过度增殖,加之监管机构仅接受采用克隆细AI四小龙云从科技难破盈利困局前三季亏损逼近去年整年,降本增效效果寥寥本文来源时代周报作者郑栩彤近日,一款名为ChatGPT的AI聊天机器人风靡全球,不仅能与人流畅对话,还能写代码找BUG创作文学,连钢铁侠马斯克体验后都表示震惊。在AI行业,这是近年