淺析Java如何優(yōu)雅的設(shè)計(jì)接口狀態(tài)碼和異常
一、前言
目前大多互聯(lián)網(wǎng)應(yīng)用后端輸出數(shù)據(jù)協(xié)議都是使用HTTP協(xié)議+JSON數(shù)據(jù)格式,HTTP協(xié)議里定義了一系列的狀態(tài)碼用來表明請(qǐng)求的狀態(tài),如常用的200表示請(qǐng)求正常,404表示請(qǐng)求的資源不存在。由于這些狀態(tài)數(shù)量是有限的,無法完整的表達(dá)我們業(yè)務(wù)中的各種狀態(tài),所以一般會(huì)在返回的JSON中增加業(yè)務(wù)狀態(tài)碼,如請(qǐng)求參數(shù)不對(duì)、用戶狀態(tài)禁用、用戶名密碼錯(cuò)誤等。首先要搞清楚HTTP狀態(tài)碼和我們業(yè)務(wù)狀態(tài)的關(guān)系, 我們看一個(gè)簡單的HTTP協(xié)議報(bào)文:
GET http://localhost/test?id=2
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 11 Mar 2024 06:42:39 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"code": "OK",
"message": "OK",
"data": {
"id": 2,
"userName": "test3@8531.cn"
}
}
上面是一個(gè)簡單的返回JSON數(shù)據(jù)的GET請(qǐng)求,其中響應(yīng)頭中的HTTP/1.1 200表明HTTP狀態(tài)碼是200,響應(yīng)Body中的"code": "OK",是我們業(yè)務(wù)里定義的狀態(tài)碼。
HTTP狀態(tài)碼
HTTP 狀態(tài)碼是由 HTTP 協(xié)議定義的,用于表示 Web 服務(wù)器對(duì)請(qǐng)求的響應(yīng)狀態(tài),每一個(gè)狀態(tài)碼都有特定的含義。雖然開發(fā)者可以自定義 HTTP 狀態(tài)碼,但并不推薦這樣做,因?yàn)檫@可能會(huì)引起混淆或者與將來的 HTTP 規(guī)范相沖突。HTTP 狀態(tài)碼的值是三位數(shù)字,其中第一位數(shù)字表示響應(yīng)類別,目前有以下五個(gè)類別:
- 1xx:表示請(qǐng)求已被接收,需要繼續(xù)處理;
- 2xx:表示請(qǐng)求已成功被服務(wù)器接收、理解、并接受;
- 3xx:重定向,需要客戶端采取進(jìn)一步的操作才能完成請(qǐng)求;
- 4xx:客戶端錯(cuò)誤,表示請(qǐng)求包含語法錯(cuò)誤或者無法完成請(qǐng)求;
- 5xx:服務(wù)器錯(cuò)誤,服務(wù)器在處理請(qǐng)求的過程中發(fā)生了錯(cuò)誤。
HTTP狀態(tài)碼有非常多的作用:
- 服務(wù)器通知客戶端:狀態(tài)碼用于指示網(wǎng)頁請(qǐng)求的處理結(jié)果,幫助客戶端了解發(fā)生了什么事件;
- 便于程序處理:三位數(shù)字的狀態(tài)碼便于自動(dòng)化程序和腳本解析和處理響應(yīng)結(jié)果;
- 便于用戶理解:狀態(tài)消息(狀態(tài)碼后面的文本)為用戶提供關(guān)于響應(yīng)的額外信息,幫助用戶理解發(fā)生了什么問題;
- 指導(dǎo)后續(xù)操作:例如,301狀態(tài)碼表示資源已永久移動(dòng)到新地址,客戶端應(yīng)使用新地址重新發(fā)送請(qǐng)求;
- 便于監(jiān)控報(bào)警:通過監(jiān)控分析nginx的接口請(qǐng)求日志,可以監(jiān)控到服務(wù)異常從而發(fā)送報(bào)警消息。
業(yè)務(wù)狀態(tài)碼
業(yè)務(wù)狀態(tài)碼是在 HTTP 狀態(tài)碼之上,由應(yīng)用程序自身定義的,以反映特定業(yè)務(wù)邏輯的狀態(tài)。這些狀態(tài)碼可以針對(duì)不同的操作不同的條件提供更詳細(xì)更具體的信息,以便客戶端能夠更好地理解和處理業(yè)務(wù)流程,根據(jù)不同的狀態(tài)碼采取相應(yīng)的處理措施。業(yè)務(wù)狀態(tài)碼的主要作用有:
- 方便與前端開發(fā)對(duì)接:前端在請(qǐng)求接口時(shí)通過判斷非正常業(yè)務(wù)狀態(tài)碼,可以給用戶對(duì)應(yīng)的提示;
- 方便對(duì)業(yè)務(wù)更進(jìn)一步的監(jiān)控:比如用戶名密碼錯(cuò)誤用USER_PASSWORD_ERROR,可以分析日志,監(jiān)控用戶登錄錯(cuò)誤的請(qǐng)求,這個(gè)監(jiān)控是HTTP狀態(tài)碼無法實(shí)現(xiàn)的;
- 提升用戶操作體驗(yàn):良好的錯(cuò)誤提示可以大大提升用戶體驗(yàn),有些系統(tǒng)用戶操作失敗全部提示“系統(tǒng)異常”,如果是“用戶名不存在”、“用戶密碼錯(cuò)誤”等提示用戶體驗(yàn)就非常好了。
HTTP狀態(tài)碼和業(yè)務(wù)狀態(tài)碼的關(guān)系
業(yè)務(wù)狀態(tài)碼應(yīng)該是包含的HTTP狀態(tài)碼,在實(shí)際項(xiàng)目開發(fā)中很多開發(fā)者定義了很多業(yè)務(wù)狀態(tài)碼,但是所有接口請(qǐng)求都是返回http狀態(tài)碼為200,這是很不好的,應(yīng)該是當(dāng)HTTP狀態(tài)碼中能表達(dá)業(yè)務(wù)請(qǐng)求的狀態(tài)時(shí)應(yīng)該返回對(duì)應(yīng)的HTTP狀態(tài)碼,HTTP狀態(tài)碼無法表達(dá)業(yè)務(wù)狀態(tài)時(shí)才自定義業(yè)務(wù)狀態(tài)碼。我們參考《Google API Design Guide (谷歌API設(shè)計(jì)指南)中文版》看看大廠業(yè)務(wù)狀態(tài)碼是如何定義的:下面是一個(gè)表格,其中包含google.rpc.Code中定義的所有g(shù)RPC錯(cuò)誤代碼及其原因的簡短說明:
| HTTP | RPC | 描述 |
|---|---|---|
| 200 | OK | 沒有錯(cuò)誤 |
| 400 | INVALID_ARGUMENT | 客戶端指定了無效的參數(shù)。 檢查錯(cuò)誤消息和錯(cuò)誤詳細(xì)信息以獲取更多信息。 |
| 400 | FAILED_PRECONDITION | 請(qǐng)求不能在當(dāng)前系統(tǒng)狀態(tài)下執(zhí)行,例如刪除非空目錄。 |
| 400 | OUT_OF_RANGE | 客戶端指定了無效的范圍。 |
| 401 | UNAUTHENTICATED | 由于遺失,無效或過期的OAuth令牌而導(dǎo)致請(qǐng)求未通過身份驗(yàn)證。 |
| 403 | PERMISSION_DENIED | 客戶端沒有足夠的權(quán)限。這可能是因?yàn)镺Auth令牌沒有正確的范圍,客戶端沒有權(quán)限,或者客戶端項(xiàng)目尚未啟用API。 |
| 404 | NOT_FOUND | 找不到指定的資源,或者該請(qǐng)求被未公開的原因(例如白名單)拒絕。 |
| 409 | ABORTED | 并發(fā)沖突,例如讀-修改-寫沖突。 |
| 409 | ALREADY_EXISTS | 客戶端嘗試創(chuàng)建的資源已存在。 |
| 429 | RESOURCE_EXHAUSTED | 資源配額達(dá)到速率限制。 客戶端應(yīng)該查找google.rpc.QuotaFailure錯(cuò)誤詳細(xì)信息以獲取更多信息。 |
| 499 | CANCELLED | 客戶端取消請(qǐng)求 |
| 500 | DATA_LOSS | 不可恢復(fù)的數(shù)據(jù)丟失或數(shù)據(jù)損壞。 客戶端應(yīng)該向用戶報(bào)告錯(cuò)誤。 |
| 500 | UNKNOWN | 未知的服務(wù)器錯(cuò)誤。 通常是服務(wù)器錯(cuò)誤。 |
| 500 | INTERNAL | 內(nèi)部服務(wù)錯(cuò)誤。 通常是服務(wù)器錯(cuò)誤。 |
| 501 | NOT_IMPLEMENTED | 服務(wù)器未實(shí)現(xiàn)該API方法。 |
| 503 | UNAVAILABLE | 暫停服務(wù)。通常是服務(wù)器已經(jīng)關(guān)閉。 |
| 504 | DEADLINE_EXCEEDED | 已超過請(qǐng)求期限。如果重復(fù)發(fā)生,請(qǐng)考慮降低請(qǐng)求的復(fù)雜性。 |
從Google定義的RPC狀態(tài)碼可以看出業(yè)務(wù)狀態(tài)碼里很多都使用了HTTP狀態(tài)碼,這樣通過監(jiān)控HTTP狀態(tài)碼也可以反映出業(yè)務(wù)的某些狀態(tài)。
如何設(shè)計(jì)一套優(yōu)雅的狀態(tài)碼
"工欲善其事,必先利其器"、“磨刀不誤砍柴工”,狀態(tài)碼的設(shè)計(jì)是非?;A(chǔ)的工作,在很多項(xiàng)目開發(fā)過程中剛開始時(shí)項(xiàng)目比較急也沒有考慮統(tǒng)一狀態(tài)碼,等項(xiàng)目做好后發(fā)現(xiàn)船已經(jīng)太大了,沒法掉頭了,很多錯(cuò)的東西就將錯(cuò)就錯(cuò),這也增加了項(xiàng)目的后期維護(hù)成本,后面接手的人不了解代碼歷史也往往會(huì)吐槽前人代碼寫的垃圾。在項(xiàng)目開始時(shí)就應(yīng)該將這些基礎(chǔ)的東西規(guī)范好,這對(duì)后面的開發(fā)者來說用著也方便,項(xiàng)目也好維護(hù)。那么如何設(shè)計(jì)一套優(yōu)雅的狀態(tài)碼呢?我覺得有以下幾點(diǎn):
- 統(tǒng)一:狀態(tài)碼的編碼和命名風(fēng)格及接口返回參數(shù)要統(tǒng)一,不能每個(gè)人搞一種風(fēng)格,每個(gè)人定義一套狀態(tài)碼,狀態(tài)碼要全局唯一,特別是微服務(wù)模式開發(fā),有可能每個(gè)人開發(fā)的代碼都不是一個(gè)GIT,做到統(tǒng)一管理就非常重要了;
- 兼容HTTP狀態(tài)碼:HTTP狀態(tài)碼是全球通用的,你返回個(gè)404知道HTTP協(xié)議的人都知道是什么意思;
- 要可讀:你返回一個(gè)
USERNAME_NOT_EXIST和10002,很明顯USERNAME_NOT_EXIST一眼就看出是用戶不存在的意思了; - 要方便維護(hù):狀態(tài)碼越來越多,后面隨著業(yè)務(wù)發(fā)展,添加擴(kuò)展?fàn)顟B(tài)碼應(yīng)該要很方便。
二、設(shè)計(jì)步驟
總體設(shè)計(jì)思路
業(yè)務(wù)狀態(tài)碼統(tǒng)一使用code枚舉返回,可讀性強(qiáng),返回格式如下:
HTTP/1.1 200
{
"code": "OK",
"message": "OK",
"data": {
"id": 2,
"userName": "test3@8531.cn"
}
}
其中code 業(yè)務(wù)狀態(tài)碼主要分為三類:
- HTTP狀態(tài)碼:三位數(shù),對(duì)應(yīng)
org.springframework.http.HttpStatus中定義的狀態(tài)碼; - 公共狀態(tài)碼: 四位數(shù),對(duì)應(yīng)編碼1XXX開頭,枚舉COMM_*,對(duì)應(yīng)公共異常如參數(shù)錯(cuò)誤;
- 業(yè)務(wù)狀態(tài)碼: 五位數(shù),各業(yè)務(wù)模塊自定義,如用戶中心10XXX,枚舉USER_XXX,訂單中心20XXX,枚舉ORDER_XXX等等。

異常設(shè)計(jì)
異常設(shè)計(jì)UML圖

ApiException定義異常接口,有獲取狀態(tài)碼和提示消息兩個(gè)方法,其它所有異常實(shí)現(xiàn)該接口;HttpException主要用于HTTP狀態(tài)的異常,其中用了Spring自帶的HttpStatus,如果是HTTP狀態(tài)碼能表達(dá)業(yè)務(wù)代碼可直接new HttpException(HttpStatus.XXX);CommException公共業(yè)務(wù)異常,如參數(shù)沒有傳、文件不存在、數(shù)據(jù)超界等;UserException用戶模塊的狀態(tài)碼,如用戶不存在、用戶密碼錯(cuò)誤、手機(jī)號(hào)已被使用等等;OrderException訂單模塊狀態(tài)碼,如訂單超時(shí)、訂單已取消等等。- 如果還是其它模塊自定義類XXXException和XXXCodeEnum,并分配好狀態(tài)碼段和枚舉開頭,就可以使?fàn)顟B(tài)碼全局唯一了。
ApiException
public interface ApiException {
String getCode();
String getMessage();
}
HttpException
public class HttpException extends RuntimeException implements ApiException{
@Getter
private final HttpStatus httpStatus;
private final String message;
public HttpException(HttpStatus apiCode) {
this(apiCode, apiCode.getReasonPhrase());
}
public HttpException(HttpStatus httpStatus, String message) {
this.httpStatus = httpStatus;
this.message = message;
}
@Override
public String getCode() {
return httpStatus.name();
}
@Override
public String getMessage() {
return message;
}
}
CommException
public class CommException extends HttpException implements ApiException{
private CommCodeEnum commCodeEnum;
public CommException(CommCodeEnum commCodeEnum) {
super(commCodeEnum.getHttpStatus());
this.commCodeEnum=commCodeEnum;
}
public CommException(CommCodeEnum commCodeEnum, String message) {
super(commCodeEnum.getHttpStatus(), message);
this.commCodeEnum=commCodeEnum;
}
@Override
public String getCode() {
return commCodeEnum.getEnumName();
}
@Override
public String getMessage() {
return commCodeEnum.getName();
}
}UserException
public class UserException extends HttpException implements ApiException {
private UserCodeEnum userCodeEnum;
public UserException(UserCodeEnum userCodeEnum) {
super(userCodeEnum.getHttpStatus());
this.userCodeEnum=userCodeEnum;
}
public UserException(UserCodeEnum userCodeEnum, String message) {
super(userCodeEnum.getHttpStatus(), message);
this.userCodeEnum=userCodeEnum;
}
@Override
public String getCode() {
return userCodeEnum.getEnumName();
}
@Override
public String getMessage() {
return userCodeEnum.getName();
}
}OrderException
public class OrderException extends HttpException implements ApiException {
private OrderCodeEnum oderCodeEnum;
public OrderException(OrderCodeEnum oderCodeEnum) {
super(oderCodeEnum.getHttpStatus());
this.oderCodeEnum=oderCodeEnum;
}
public OrderException(OrderCodeEnum oderCodeEnum, String message) {
super(oderCodeEnum.getHttpStatus(), message);
this.oderCodeEnum=oderCodeEnum;
}
@Override
public String getCode() {
return oderCodeEnum.getEnumName();
}
@Override
public String getMessage() {
return oderCodeEnum.getName();
}
}異常枚舉設(shè)計(jì)
BaseEnum
如何優(yōu)雅的處理枚舉可以參考我的另一篇文章《項(xiàng)目中如何優(yōu)雅的使用枚舉》
public interface BaseEnum {
int getCode();
String getName();
String getEnumName();
}
CommCodeEnum
public enum CommCodeEnum implements BaseEnum {
INVALID_ARGUMENT(HttpStatus.OK,600, "參數(shù)錯(cuò)誤"),
;
//公共錯(cuò)誤碼6xx
private int code;
@Getter
private HttpStatus httpStatus;
private String name;
CommCodeEnum(HttpStatus httpStatus, Integer code, String name) {
this.httpStatus = httpStatus;
this.code = code;
this.name = name;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getName() {
return name;
}
@Override
public String getEnumName() {
return this.name();
}
}
UserCodeEnum
在定義業(yè)務(wù)狀態(tài)碼時(shí),需要注意的是有一個(gè)HttpStatus參數(shù),如果我們覺得該業(yè)務(wù)出錯(cuò)了接口不應(yīng)該返回HTTP 200,就可以設(shè)置成對(duì)應(yīng)的HTTP狀態(tài)碼,如用戶名不存在,可以理解為HTTP 狀態(tài)碼里的404資源不存在,這樣我們就設(shè)置成USERNAME_NOT_EXIST(HttpStatus.NOT_FOUND,10002, "用戶名不存在")這樣做的好處是在監(jiān)控HTTP狀態(tài)碼異常時(shí)也可以監(jiān)控到業(yè)務(wù)出問題了。
public enum UserCodeEnum implements BaseEnum {
USERNAME_EXIST(HttpStatus.OK,10001, "用戶名已存在"),
USERNAME_NOT_EXIST(HttpStatus.NOT_FOUND,10002, "用戶名不存在"),
USERNAME_DISABLE(HttpStatus.OK,10003, "用戶被禁用"),
;
@Getter
private HttpStatus httpStatus;
//用戶模塊錯(cuò)誤碼10xxx
private int code;
private String name;
}
OrderCodeEnum
public enum OrderCodeEnum implements BaseEnum {
ORDER_CANCELLED(HttpStatus.OK,20001, "訂閱已取消"),
ORDER_TIMEOUT(HttpStatus.OK,20002, "訂閱已超時(shí)"),
;
//訂單模塊錯(cuò)誤碼20xxx
private int code;
@Getter
private HttpStatus httpStatus;
private String name;
}
三、統(tǒng)一接口返回
為了達(dá)到統(tǒng)一的接口數(shù)據(jù)返回格式,我們需要定義統(tǒng)一的接口返回類ApiResult,其它定義的code業(yè)務(wù)狀態(tài)碼,message提示消息和業(yè)務(wù)數(shù)據(jù)data參數(shù)。
統(tǒng)一接口返回包裝
@NoArgsConstructor
@Data
public class ApiResult {
private String code;
private String message;
private Object data;
public ApiResult(BaseEnum apiCode, Map<String, Object> data) {
this.code = apiCode.getEnumName();
this.message = apiCode.getName();
this.data = data;
}
public ApiResult(HttpStatus httpStatus, Object data) {
this.code = httpStatus.name();
this.message = httpStatus.getReasonPhrase();
this.data = data;
}
public ApiResult(HttpStatus httpStatus,String message, Object data) {
this.code = httpStatus.name();
this.message = message;
this.data = data;
}
public ApiResult(BaseEnum apiCode, String explanation, Map<String, Object> data) {
this.code = apiCode.getEnumName();
this.message = apiCode.getName() + (explanation != null ? "【" + explanation + "】" : "");
this.data = data;
}
}
公共Controller
為了方便Controller返回統(tǒng)一的數(shù)據(jù)格式,我可以定義BaseController,重載多種返回?cái)?shù)據(jù)格式,業(yè)務(wù)Controller只需繼承BaseController就可以直接調(diào)用 return renderOk(data)返回統(tǒng)一的接口數(shù)據(jù)格式。
public abstract class BaseController {
@Resource
protected HttpServletRequest httpRequest;
protected ResponseEntity<ApiResult> renderOk() {
return renderOk(null);
}
protected ResponseEntity<ApiResult> renderOk(Map<String, Object> data) {
ApiResult apiResult = new ApiResult(HttpStatus.OK, data);
return new ResponseEntity<>(apiResult, HttpStatus.OK);
}
protected ResponseEntity<ApiResult> renderOk(Object data) {
ApiResult apiResult = new ApiResult(HttpStatus.OK, data);
return new ResponseEntity<>(apiResult, HttpStatus.OK);
}
protected ResponseEntity<ApiResult> renderError(HttpStatus apiCode) {
ApiResult apiResult = new ApiResult(apiCode, null);
return new ResponseEntity<>(apiResult, HttpStatus.OK);
}
protected ResponseEntity<ApiResult> renderError(BaseEnum apiCode) {
ApiResult apiResult = new ApiResult(apiCode, null);
return new ResponseEntity<>(apiResult, HttpStatus.OK);
}
protected ResponseEntity<Map<String, Object>> renderError(HttpException httpException) {
Map<String, Object> map = ImmutableMap.of("code", httpException.getCode(), "message", httpException.getMessage());
return new ResponseEntity<>(map, httpException.getHttpStatus());
}
protected ResponseEntity<ApiResult> renderError(HttpException httpException, Object data) {
ApiResult apiResult = new ApiResult(httpException.getHttpStatus(), data);
return new ResponseEntity<>(apiResult, httpException.getHttpStatus());
}
protected ResponseEntity<Map<String, Object>> render(Map<String, Object> map) {
return new ResponseEntity<>(map, HttpStatus.OK);
}
}
四、統(tǒng)一異常攔截
針對(duì)代碼中的非正常行業(yè),可以統(tǒng)一使用拋?zhàn)远x異常的方式,這樣只需配置一個(gè)統(tǒng)一的異常攔截器,統(tǒng)一返回狀態(tài)碼。
@Slf4j
@ControllerAdvice
public class ErrorHandler extends BaseController {
@ExceptionHandler(value = {HttpException.class})
public ResponseEntity<Map<String, Object>> httpException(HttpException ex) {
log.error("{}", ex);
return renderError(ex);
}
}
五、測(cè)試
這樣我們使用狀態(tài)碼就比較簡單了,主要分為三種:
- 公共異常:
throw new CommException(CommCodeEnum.INVALID_ARGUMENT); - http狀態(tài)碼類異常:
throw new HttpException(HttpStatus.GATEWAY_TIMEOUT); - 業(yè)務(wù)類異常:如用戶
throw new UserException(UserCodeEnum.USERNAME_NOT_EXIST),訂單throw new OrderException(OrderCodeEnum.ORDER_TIMEOUT, "請(qǐng)求訂單超時(shí)")等。
@RestController
public class UserController extends BaseController {
@Resource
private UserService userService;
@GetMapping("/test")
public ResponseEntity<ApiResult> getById(@RequestParam Long id) {
//1.公共異常
if (id == null) {
throw new CommException(CommCodeEnum.INVALID_ARGUMENT);
}
//2.http協(xié)議異常
User user = null;
try {
user = userService.selectById(id);
int b = 1 / 0;
} catch (Exception exception) {
throw new HttpException(HttpStatus.GATEWAY_TIMEOUT);
}
//3.用戶模塊異常
if (user == null) {
// throw new UserException(UserCodeEnum.USERNAME_NOT_EXIST);
}
//4.訂單模塊異常
try {
//調(diào)用訂單
} catch (Exception e) {
//throw new OrderException(OrderCodeEnum.ORDER_TIMEOUT, "請(qǐng)求訂單超時(shí)");
}
return renderOk(user);
}
}
正常返回狀態(tài)碼
接口正常返回統(tǒng)一使用code:OK,http狀態(tài)碼為200,

公共異??梢栽O(shè)置非200HTTP狀態(tài)碼
公共異常狀態(tài)碼

HTTP異常 HTTP狀態(tài)碼都是非200
http異常狀態(tài)碼

用戶異常HTTP狀態(tài)碼可以是200也可以非200
用戶異常狀態(tài)碼

訂單異常HTTP狀態(tài)碼可以是200也可以非200
訂單異常狀態(tài)碼

六、總結(jié)
本文介紹了HTTP狀態(tài)碼及業(yè)務(wù)狀態(tài)碼的區(qū)別和作用,提出并實(shí)現(xiàn)一種統(tǒng)一維護(hù)業(yè)務(wù)狀態(tài)碼和HTTP狀態(tài)碼的思路,該思路融合了HTTP狀態(tài)碼,規(guī)范了接口返回格式,統(tǒng)一的業(yè)務(wù)狀態(tài)碼,大大方便了在系統(tǒng)中使用異常和定義狀態(tài)碼。
到此這篇關(guān)于淺析Java如何優(yōu)雅的設(shè)計(jì)接口狀態(tài)碼和異常的文章就介紹到這了,更多相關(guān)Java設(shè)計(jì)接口狀態(tài)碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)從PLC讀取數(shù)據(jù)的常見主流且可行方案
要從PLC讀取數(shù)據(jù),Java本身并不直接支持PLC的通信協(xié)議,所以你需要借助一些中間件或庫來實(shí)現(xiàn),以下是幾種主流且可行的方案,大家可以根據(jù)自己的需要進(jìn)行選擇2026-03-03
一篇文章帶你入門Springboot整合微信登錄與微信支付(附源碼)
微信支付是騰訊公司的支付業(yè)務(wù)品牌,微信支付商戶平臺(tái)支持線下場(chǎng)所、公眾號(hào)、小程序、PC網(wǎng)站、APP、企業(yè)微信等經(jīng)營場(chǎng)景快速接入微信支付。這里一篇文章帶你入門!2021-06-06
IDEA打開項(xiàng)目不顯示或者缺失項(xiàng)目文件目錄的解決方案
這篇文章主要介紹了IDEA打開項(xiàng)目不顯示或者缺失項(xiàng)目文件目錄的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
mybatis?mapper.xml?注釋帶參數(shù)的坑及解決
這篇文章主要介紹了mybatis?mapper.xml?注釋帶參數(shù)的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
maven配置多個(gè)鏡像的實(shí)現(xiàn)方法
這篇文章主要介紹了maven配置多個(gè)鏡像的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例詳解
這篇文章主要介紹了Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例,簡單描述了裝飾者模式的概念、原理及Java使用裝飾者模式的相關(guān)實(shí)現(xiàn)步驟、操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-05-05
淺談java項(xiàng)目與javaweb項(xiàng)目導(dǎo)入jar包的區(qū)別
下面小編就為大家分享一篇淺談java項(xiàng)目與javaweb項(xiàng)目導(dǎo)入jar包的區(qū)別,具有很好的參考價(jià)值。希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-11-11

