当前位置:网站首页>How to write controller layer code gracefully?
How to write controller layer code gracefully?
2022-06-24 11:35:00 【Program ape DD_】

Source of the article :http://suo.nz/1KaLX0
Preface
This article mainly introduces controller The treatment of layer , A complete back-end request is made by 4 Part of it is made up of :
Address of the interface ( That is to say URL Address )
Request mode ( It is commonly get、set, Of course, put、delete)
Request data (request, Yes head Follow body)
The response data (response)
This article will address the following 3 A question :
When a request is received , How to check parameters gracefully
How to uniformly process the return response data
Received request , How to handle exceptions thrown when processing business logic
Controller Layer parameter receiving ( It's so basic , You can skip )
Common requests are divided into get Follow post Two kinds of :
@RestController
@RequestMapping("/product/product-info")
public class ProductInfoController {
@Autowired
ProductInfoService productInfoService;
@GetMapping("/findById")
public ProductInfoQueryVo findById(Integer id) {
...
}
@PostMapping("/page")
public IPage findPage(Page page, ProductInfoQueryVo vo) {
...
}
}RestController: I've explained before ,@[email protected]+ResponseBody.
Add this note ,springboot I will treat this class as controller To deal with , Then put all the returned parameters into ResponseBody in .
@RequestMapping: Prefix requested , That is all that should be Controller All requests under the need to add /product/product-info The prefix of .
@GetMapping("/findById"): Mark this is a get request , And need to pass /findById Address to access .
@PostMapping("/page"): Empathy , It means it's a post request .
Parameters : As for the parameters , Just write ProductInfoQueryVo, From the front json The request will be assigned to the corresponding object by mapping , For example, ask to write ,productId Will be automatically mapped to vo Corresponding attributes .
size : 1
current : 1
productId : 1
productName : Soak the foot Unified status code
| Returns the format
In order to have a good relationship with the front-end sister , We usually need to wrap the data returned from the back end , Add a status code , State information , In this way, the front-end sister can receive data according to different status codes , Determine the response data status , Whether it is successful or abnormal is displayed differently .
Of course, this gives you more opportunities to communicate with your front-end sister , Suppose we make an agreement 1000 It means success .
If you don't package , Then the returned data looks like this :
{
"productId": 1,
"productName": " Soak the foot ",
"productPrice": 100.00,
"productDescription": " Chinese medicine feet soaking and massage ",
"productStatus": 0,
}It looks like this after packaging :
{
"code": 1000,
"msg": " The request is successful ",
"data": {
"productId": 1,
"productName": " Soak the foot ",
"productPrice": 100.00,
"productDescription": " Chinese medicine feet soaking and massage ",
"productStatus": 0,
}
}| encapsulation ResultVo
These status codes must be prepared in advance , How to make it up ? Write a constant 1000? Or just write it dead 1000?
If you want to write in this way, you will really read the book for nothing , Writing the status code is, of course, an enumeration :
① First, define a status code interface , All status codes need to implement it , Only with standards can we do things :
public interface StatusCode {
public int getCode();
public String getMsg();
}② Then go to the front sister , Make an appointment with him for the status code ( This may be your only Agreement ) Enumerating classes , Of course not setter The method , So we can't use @Data Note the , We need to use it. @Getter.
@Getter
public enum ResultCode implements StatusCode{
SUCCESS(1000, " The request is successful "),
FAILED(1001, " request was aborted "),
VALIDATE_ERROR(1002, " Parameter verification failed "),
RESPONSE_PACK_ERROR(1003, "response Return packaging failure ");
private int code;
private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}③ Write enumeration classes , Start writing ResultVo Packaging class , We preset several default methods , For example, if it succeeds, it will be passed in by default object That's all right. , We automatically pack it into success.
@Data
public class ResultVo {
// Status code
private int code;
// State information
private String msg;
// Returns the object
private Object data;
// Manually set the return vo
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// The success status code is returned by default , Data objects
public ResultVo(Object data) {
this.code = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMsg();
this.data = data;
}
// Returns the specified status code , Data objects
public ResultVo(StatusCode statusCode, Object data) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = data;
}
// Only the status code is returned
public ResultVo(StatusCode statusCode) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = null;
}
}Use , Now the return is definitely not return data; It's so simple , But needs new ResultVo(data);
@PostMapping("/findByVo")
public ResultVo findByVo(@Validated ProductInfoVo vo) {
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(vo, productInfo);
return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}Finally, the data with the status code will be returned .
Unified verification
| The original way
Suppose you have an add ProductInfo The interface of , When there is no unified verification , We need to do this .
@Data
public class ProductInfoVo {
// Name of commodity
private String productName;
// commodity price
private BigDecimal productPrice;
// On the shelf status
private Integer productStatus;
}@PostMapping("/findByVo")
public ProductInfo findByVo(ProductInfoVo vo) {
if (StringUtils.isNotBlank(vo.getProductName())) {
throw new APIException(" Commodity name cannot be empty ");
}
if (null != vo.getProductPrice() && vo.getProductPrice().compareTo(new BigDecimal(0)) < 0) {
throw new APIException(" Commodity prices cannot be negative ");
}
...
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(vo, productInfo);
return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}this if People who write are stupid , Can you bear it ? I can't bear it .
| @Validated Parameter checking
Fortunately, there are @Validated, It is also a necessary medicine for checking parameters . With @Validated We just need to vo Add a little note on it , The verification function can be completed .
@Data
public class ProductInfoVo {
@NotNull(message = " The product name cannot be empty ")
private String productName;
@Min(value = 0, message = " The commodity price cannot be negative ")
private BigDecimal productPrice;
private Integer productStatus;
}@PostMapping("/findByVo")
public ProductInfo findByVo(@Validated ProductInfoVo vo) {
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(vo, productInfo);
return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}Run it , What happens if the parameters are wrong ?
We deliberately sent a price for -1 The parameters of the past :
productName : Soak the foot
productPrice : -1
productStatus : 1{
"timestamp": "2020-04-19T03:06:37.268+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Min.productInfoVo.productPrice",
"Min.productPrice",
"Min.java.math.BigDecimal",
"Min"
],
"arguments": [
{
"codes": [
"productInfoVo.productPrice",
"productPrice"
],
"defaultMessage": "productPrice",
"code": "productPrice"
},
0
],
"defaultMessage": " The commodity price cannot be negative ",
"objectName": "productInfoVo",
"field": "productPrice",
"rejectedValue": -1,
"bindingFailure": false,
"code": "Min"
}
],
"message": "Validation failed for object\u003d\u0027productInfoVo\u0027. Error count: 1",
"trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors\nField error in object \u0027productInfoVo\u0027 on field \u0027productPrice\u0027: rejected value [-1]; codes [Min.productInfoVo.productPrice,Min.productPrice,Min.java.math.BigDecimal,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [productInfoVo.productPrice,productPrice]; arguments []; default message [productPrice],0]; default message [ The commodity price cannot be negative ]\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:124)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1594)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:830)\n",
"path": "/leilema/product/product-info/findByVo"
}Is it a success ? Although the parameters were successfully verified , An exception is also returned , And bring them up. " The commodity price cannot be negative " Information about .
But if you return to the front end like this , The front sister came over with a knife , The status code agreed in that year , You are a heartless person. You forget when you say you forget ?
The user experience is less than or equal to 0 ah ! So we need to optimize , Every time something goes wrong , Write the status code automatically , Live up to your sister's promise !
| Optimize exception handling
First, let's see what exceptions are thrown by the validation parameters :
Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errorsWe see that the code throws org.springframework.validation.BindException Binding exception for , So our idea is AOP Intercept all controller, Then the exceptions are intercepted uniformly , encapsulate ! perfect !

Play with you perfect , Such a stupid operation springboot Don't you know ?spring mvc Of course I know , So it provides us with a @RestControllerAdvice To enhance all @RestController, And then use @ExceptionHandler annotation , You can intercept the corresponding exception .
Here we intercept BindException.class Just fine . Finally, before returning , Let's wrap the exception information , Package as ResultVo, Of course, keep up ResultCode.VALIDATE_ERROR Exception status code of .
So the front-end sister sees VALIDATE_ERROR The status code , The pop-up window of data verification exception will be called to prompt the user where the data has not been filled .
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class})
public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
// Get... From the exception object ObjectError object
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
}
}Let's see the effect , perfect .1002 The status code agreed with the front-end sister :
{
"code": 1002,
"msg": " Parameter verification failed ",
"data": " The commodity price cannot be negative "
}Unified response
| Unified packaging response
Look back controller Layer return :
return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));I'm not happy about it , Who has time to write every day new ResultVo(data) ah , I just want to return an entity ! I don't care how to achieve it !
All right. , That's it AOP Intercept all Controller, Again @After I will help you to package it when you are ready .

I'm afraid it didn't hurt enough last time ,springboot Don't you know such an operation ?
@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response yes ResultVo type , Or annotated NotControllerResponseAdvice No packaging
return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String Type cannot be packaged directly
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// Package data in ResultVo Convert from inside to back json String to return
return objectMapper.writeValueAsString(new ResultVo(data));
} catch (JsonProcessingException e) {
throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// Otherwise, it is directly packaged into ResultVo return
return new ResultVo(data);
}
}①@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) Automatically scan all the specified packages controller, stay Response Unified processing shall be carried out when .
② rewrite supports Method , in other words , When the return type is already ResultVo 了 , Then there is no need to encapsulate , When not equal to ResultVo Only when beforeBodyWrite Method , The effect is the same as that of the filter .
③ Finally, rewrite our encapsulation method beforeBodyWrite, Note that except for String The return value of is a bit special , Cannot be encapsulated directly into json, We need to do something special , Other direct new ResultVo(data); Just ok 了 .
Finish off work , Look at the effect :
@PostMapping("/findByVo")
public ProductInfo findByVo(@Validated ProductInfoVo vo) {
ProductInfo productInfo = new ProductInfo();
BeanUtils.copyProperties(vo, productInfo);
return productInfoService.getOne(new QueryWrapper(productInfo));
}At this point, even if we return to po, The received return is in the standard format , Brother Kaifa showed a happy smile .
{
"code": 1000,
"msg": " The request is successful ",
"data": {
"productId": 1,
"productName": " Soak the foot ",
"productPrice": 100.00,
"productDescription": " Chinese medicine feet soaking and massage ",
"productStatus": 0,
...
}
}| NOT Unified response
Reason for not opening unified response : Brother Kaifa is happy , But other systems are not happy . for instance : A health detection function is integrated in our project , That is, the goods .
@RestController
public class HealthController {
@GetMapping("/health")
public String health() {
return "success";
}
}The company has deployed a set of tools to verify the survival status of all systems , This tool sends messages regularly get Request to our system :
“ brother , Are you dead ?”
“ I'm not dead , roll ”
“ brother , Are you dead ?”
“ I'm not dead , roll ”
Yes ,web The essence of the project is the repeater . Once the sent request is not responded , Will send a message to the person in charge ( Enterprise wechat or SMS ), Your system is dead ! Hurry back to check bug Well !
Just to give you a sense . Every time I see me, I shake , morning 6 spot ! I tm!!!!!

ok , Can't , He's the boss , What people want to return is not :
{
"code": 1000,
"msg": " The request is successful ",
"data": "success"
}Only one return is required success, The standards set by others cannot be changed because of your system . As the saying goes , If you can't change the environment , Then you can only let me ****
Add no encapsulation annotation : Because percent 99 Your request still needs packaging , Only a few don't need , Write the filter on the package ? It is not very easy to maintain , Then add a note . Add this note to all that do not need packaging .
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}Then filter the method containing this annotation on our enhanced filtering method :
@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response yes ResultVo type , Or annotated NotControllerResponseAdvice No packaging
return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
|| methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
...Finally, add notes on the methods that do not need packaging :
@RestController
public class HealthController {
@GetMapping("/health")
@NotControllerResponseAdvice
public String health() {
return "success";
}
}At this time, it will not be automatically encapsulated , Others without comments are still automatically packaged :

Unification exception
Each system has its own business exceptions , For example, the inventory cannot be less than 0 A subclass , This exception is not a program exception , It is an exception caused by a business operation , We also need to arrange the business exception status code , And write a special exception class , Finally, the exception interception just learned is handled uniformly , And log .
① Exception status code enumeration , Since it is a status code , Then we must implement our standard interface StatusCode.
@Getter
public enum AppCode implements StatusCode {
APP_ERROR(2000, " Business exceptions "),
PRICE_ERROR(2001, " The price is abnormal ");
private int code;
private String msg;
AppCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}② Exception class , It needs to be emphasized here ,code representative AppCode Exception status code of , That is to say 2000;msg Represents business exceptions , This is just a big category , Generally, the front end will be placed in the pop-up window title On ; Last super(message); This is the detail of the throw , Display in pop-up window at the front end , stay ResultVo Keep it in data in .
@Getter
public class APIException extends RuntimeException {
private int code;
private String msg;
// Manual setting exception
public APIException(StatusCode statusCode, String message) {
// message Used to set the details of the error thrown by the user , for example : Current price -5, Less than 0
super(message);
// Status code
this.code = statusCode.getCode();
// Status code matching msg
this.msg = statusCode.getMsg();
}
// Default exception uses APP_ERROR Status code
public APIException(String message) {
super(message);
this.code = AppCode.APP_ERROR.getCode();
this.msg = AppCode.APP_ERROR.getMsg();
}
}③ Finally, intercept the unified exception , So no matter in service Layer or controller layer , Developers just throw API abnormal , There is no need to return the relationship to the front end , There is no need to care about the printing of logs .
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class})
public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
// Get... From the exception object ObjectError object
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
}
@ExceptionHandler(APIException.class)
public ResultVo APIExceptionHandler(APIException e) {
// log.error(e.getMessage(), e); Since the logging framework has not been integrated yet , For the time being , write TODO
return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
}
}④ Finally using , Our code just needs to be written like this .
if (null == orderMaster) {
throw new APIException(AppCode.ORDER_NOT_EXIST, " The order number does not exist :" + orderId);
}{
"code": 2003,
"msg": " The order does not exist ",
"data": " The order number does not exist :1998"
}It will automatically throw out AppCode.ORDER_NOT_EXIST State code response , And the order number with exception details does not exist :xxxx.

The development efficiency of the back-end brother , Front end sister gets 2003 Status code , Call the corresponding warning pop-up ,title Write that the order does not exist ,body Detailed information records " The order number does not exist :1998". At the same time, the log is automatically printed !666! My old friend likes it three times in a row !
We have created a high-quality technical exchange group , With good people , I will be excellent myself , hurriedly Click Add group , Enjoy growing up together . in addition , If you want to change jobs recently , Years ago, I spent 2 A wave of large factory classics were collected in a week , Those who are ready to change jobs after the festival can Click here to get !
Recommended reading
··································
Hello , I'm a procedural ape DD,10 Old driver developed in 、 Alibaba cloud MVP、 Tencent cloud TVP、 I have published books and started a business 、 State-owned enterprises 4 In the Internet 6 year . From ordinary developers to architects 、 Then to the partner . Come all the way , My deepest feeling is that I must keep learning and pay attention to the frontier . As long as you can hold on , Think more 、 Don't complain 、 Do it frequently , It's easy to overtake on a curve ! therefore , Don't ask me what I'm doing now, whether it's in time . If you are optimistic about one thing , It must be persistence to see hope , Instead of sticking to it when you see hope . believe me , Just stick to it , You must be better than now ! If you don't have any direction , You can pay attention to me first , Some cutting-edge information is often shared here , Help you accumulate the capital to overtake on the curve .
边栏推荐
- [technical tutorial] national standard protocol platform easygbs cascading supports customized national standard channels
- Oxylabs live online: website capture demo
- qt -- QTabWidget 中支持拖拽TabBar项
- [net action!] Cos data escort helps SMEs avoid content security risks!
- Istio best practice: graceful termination
- "Write once, run at all ends", Qualcomm released AI software stack!
- Programmers spend most of their time not writing code, but...
- 把騰訊搬到雲上,治愈了他們的技術焦慮
- 工具及方法 - 在Source Insight中使用代码格式化工具
- 历史上的今天:图灵诞生日;互联网奠基人出生;Reddit 上线
猜你喜欢

图片的可视化呈现有效增强大屏吸引力

Programmers spend most of their time not writing code, but...

Visual presentation of pictures effectively enhances the attraction of large screen

Understanding of homogeneous coordinates

Shell脚本(.sh文件)如何执行完毕之后不自动关闭、闪退?

How to develop hospital information system (his) with SMS notification and voice function

保险APP适老化服务评测分析2022第06期
[Architect (Part 41)] installation of server development and connection to redis database

qt -- QTabWidget 中支持拖拽TabBar项

程序员大部分时间不是写代码,而是。。。
随机推荐
Why does the virtual machine Ping the host but not the virtual machine
Any 与 TypeVar,让 IDE 的自动补全更好用
2008R2 precautions for configuring L2TP pre shared key VPN
Influence of DEX optimization on arouter lookup path
Jenkins performance test
≥ 2012r2 configure IIS FTP
RPM installation percona5.7.34
Attribute observer didset and willset in swift of swiftui swift internal skill
The record of 1300+ times of listing and the pursuit of ultimate happiness
【老卫搞机】090期:键盘?主机?全功能键盘主机!
可变参数模板实现max(接受多个参数,两种实现方式)
Self cleaning Manual of mining Trojan horse
Redis
@Requestbody annotation
怎么可以打新债 开户是安全的吗
I pushed my younger brother into Tencent. Look at his benchmark resume!
程序员大部分时间不是写代码,而是。。。
怎么申请打新债 开户是安全的吗
Analysis and understanding of Jieba stutter word segmentation principle HMM application in Chinese word segmentation and partial code reading
图片的可视化呈现有效增强大屏吸引力