日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747


 

前言

準確點說,這不是《從零打造項目》系列的第一篇文章,模版代碼生成的那個項目講解算是第一篇,當時就打算做一套項目腳手架,為后續進行項目練習做準備。因時間及個人經驗問題,一直拖到現在才繼續實施該計劃,希望這次能順利完成。

每個項目中都會有一些共用的代碼,我們稱之為項目的基礎設施,隨拿隨用。本文主要介紹 SpringBoot 項目中的一些基礎設施,后續還會詳細介紹 SpringBoot 分別結合 MyBatis、MybatisPlus、JPA 這三種 ORM 框架進行項目搭建,加深大家對項目的掌握能力。

因內容篇幅過長,本來這些基礎設施代碼應該分布在未來的三篇文章中,被提取出來,專門寫一篇文章來介紹。

SpringBoot項目基礎代碼引入依賴org.springframework.bootspring-boot-starter-parent2.6.31.81.2.735.5.18.0.192.1.44.1.5struct.version>1.4.2.Final1.18.20org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-aoporg.springframework.bootspring-boot-starter-securityorg.springframework.bootspring-boot-starter-validationcom.alibabafastjson${fastJSON.version}cn.hutoolhutool-all${hutool.version}org.projectlomboklombok${org.projectlombok.version}trueorg.springframework.bootspring-boot-starter-testtestMySQLmysql-connector-JAVA${mysql.version}runtimeorg.springframework.dataspring-data-commons2.4.6org.springdocspringdoc-openapi-ui1.6.9com.alibabadruid-spring-boot-starter1.1.18org.Mapstructmapstruct${org.mapstruct.version}org.mapstructmapstruct-processor${org.mapstruct.version}org.springframework.bootspring-boot-maven-plugin復制代碼

有些依賴不一定是最新版本,而且你看到這篇文章時,可能已經發布了新版本,到時候可以先模仿著將項目跑起來后,再根據自己的需求來升級各項依賴,有問題咱再解決問題。

日志請求切面

項目進入聯調階段,服務層的接口需要和協議層進行交互,協議層需要將入參[json字符串]組裝成服務層所需的 json 字符串,組裝的過程中很容易出錯。入參出錯導致接口調試失敗問題在聯調中出現很多次,因此就想寫一個請求日志切面把入參信息打印一下,同時協議層調用服務層接口名稱對不上也出現了幾次,通過請求日志切面就可以知道上層是否有沒有發起調用,方便前后端甩鍋還能拿出證據。

首先定義一個請求日志類,記錄一些關鍵信息。

@Data@EqualsAndHashCode(callSuper = false)public class requestLog {// 請求ipprivate String ip;// 訪問urlprivate String url;// 請求類型private String httpMethod;// 請求方法名(絕對路徑)private String classMethod;// 請求方法描述private String methodDesc;// 請求參數private Object requestParams;// 返回結果private Object result;// 操作時間private Long operateTime;// 消耗時間private Long timeCost;// 錯誤信息private JSONObject errorMessage;復制代碼

然后根據 @Aspect 實現日志切面記錄

@Component@Aspect@Slf4jpublic class RequestLogAspect {@Pointcut("execution(* com.msdn.orm.hresh.controller..*(..))")public void requestServer() {@Around("requestServer()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long start = System.currentTimeMillis();//獲取當前請求對象RequestLog requestLog = getRequestLog();Object result = proceedingJoinPoint.proceed();Signature signature = proceedingJoinPoint.getSignature();// 請求方法名(絕對路徑)requestLog.setClassMethod(String.format("%s.%s", signature.getDeclaringTypeName(),signature.getName()));// 請求參數requestLog.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));// 返回結果requestLog.setResult(result);// 如果返回結果不為null,則從返回結果中剔除返回數據,查看條目數、返回狀態和返回信息等if (!ObjectUtils.isEmpty(result)) {JSONObject jsonObject = JSONUtil.parseobj(result);Object data = jsonObject.get("data");if (!ObjectUtils.isEmpty(data) && data.toString().length() > 200) {// 減少日志記錄量,比如大量查詢結果,沒必要記錄jsonObject.remove("data");requestLog.setResult(jsonObject);// 獲取請求方法的描述注解信息MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();if (method.isAnnotationPresent(Operation.class)) {Operation methodAnnotation = method.getAnnotation(Operation.class);requestLog.setMethodDesc(methodAnnotation.description());// 消耗時間requestLog.setTimeCost(System.currentTimeMillis() - start);log.info("Request Info : {}", JSONUtil.toJsonStr(requestLog));return result;@AfterThrowing(pointcut = "requestServer()", throwing = "e")public void doAfterThrow(JoinPoint joinPoint, Runtimeexception e) {try {RequestLog requestLog = getRequestLog();Signature signature = joinPoint.getSignature();// 請求方法名(絕對路徑)requestLog.setClassMethod(String.format("%s.%s", signature.getDeclaringTypeName(),signature.getName()));// 請求參數requestLog.setRequestParams(getRequestParamsByJoinPoint(joinPoint));StackTraceElement[] stackTrace = e.getStackTrace();// 將異常信息轉換成jsonJSONObject jsonObject = new JSONObject();if (!ObjectUtils.isEmpty(stackTrace)) {StackTraceElement stackTraceElement = stackTrace[0];jsonObject = JSONUtil.parseObj(JSONUtil.toJsonStr(stackTraceElement));// 轉換成jsonjsonObject.set("errorContent", e.getMessage());jsonObject.set("createTime", DateUtil.date());jsonObject.setDateFormat(DatePattern.NORM_DATETIME_PATTERN);jsonObject.set("messageId", IdUtil.fastSimpleUUID());// 獲取IP地址jsonObject.set("serverIp",.NETUtil.getLocalhostStr());requestLog.setErrorMessage(jsonObject);log.error("Error Request Info : {}", JSONUtil.toJsonStr(requestLog));} catch (Exception exception) {log.error(exception.getMessage());private RequestLog getRequestLog() {//獲取當前請求對象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 記錄請求信息(通過Logstash傳入Elasticsearch)RequestLog requestLog = new RequestLog();if (!ObjectUtils.isEmpty(attributes) && !ObjectUtils.isEmpty(attributes.getRequest())) {HttpServletRequest request = attributes.getRequest();// 請求iprequestLog.setIp(request.getRemoteAddr());// 訪問urlrequestLog.setUrl(request.getRequestURL().toString());// 請求類型requestLog.setHttpMethod(request.getMethod());return requestLog;* 根據方法和傳入的參數獲取請求參數* @param proceedingJoinPoint 入參* @return 返回private Map getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {//參數名String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();//參數值Object[] paramValues = proceedingJoinPoint.getArgs();return buildRequestParam(paramNames, paramValues);private Map getRequestParamsByJoinPoint(JoinPoint joinPoint) {try {//參數名String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();//參數值Object[] paramValues = joinPoint.getArgs();return buildRequestParam(paramNames, paramValues);} catch (Exception e) {return new HashMap<>();private Map buildRequestParam(String[] paramNames, Object[] paramValues) {try {Map requestParams = new HashMap<>(paramNames.length);for (int i = 0; i < paramNames.length; i++) {Object value = paramValues[i];//如果是文件對象if (value instanceof MultipartFile) {MultipartFile file = (MultipartFile) value;//獲取文件名value = file.getOriginalFilename();requestParams.put(paramNames[i], value);return requestParams;} catch (Exception e) {return new HashMap<>(1);復制代碼

上述切面是在執行 Controller 方法時,打印出調用方IP、請求URL、HTTP 請求類型、調用的方法名、耗時等。

除了上述這種形式進行日志記錄,還可以自定義注解,

@Target({ElementType.PARAMETER, ElementType.METHOD})//作用于參數或方法上@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface SystemLog {* 日志描述* @returnString description() default "";復制代碼

具體使用為:

@GetMApping(value = "/queryPage")@Operation(description = "獲取用戶分頁列表")@SystemLog(description = "獲取用戶分頁列表")public Result> queryPage(@RequestBody UserQueryPageDTO dto) {Page userVOPage = userService.queryPage(dto);return Result.ok(PageResult.ok(userVOPage));復制代碼

我們只需要修改一下 RequestLogAspect 文件中的 requestServer()方法

@Pointcut("@annotation(com.xxx.annotation.SystemLog)")public void requestServer() {復制代碼

除了方便前后端排查問題,健壯的項目還會做日志分析,這里介紹一種我了解的日志分析系統——ELK(ELasticsearch+Logstash+Kibana),在 RequestLogAspect 文件中可以將日志信息輸出到 ELK 上,本項目不做過多介紹。

除了日志分析,還有一種玩法,如果項目比較復雜,比如說分布式項目,微服務個數過多,一次請求往往需要涉及到多個服務,這樣一來,調用鏈路就會很復雜,一旦出現故障,如何快速定位問題需要考慮。一種解決方案就是在日志記錄時增加一個 traceId 字段,一條調用鏈路上的 traceId 是相同。

全局異常

在日常項目開發中,異常是常見的,雖然 SpringBoot 對于異常有自己的處理方案,但是對于開發人員不夠友好。我們想要友好地拋出異常,針對運行時異常,想要一套全局異常捕獲手段。因此如何處理好異常信息,對我們后續開發至關重要。

1、定義基礎接口類

public interface IError {* 錯誤碼String getResultCode();* 錯誤描述String getResultMsg();復制代碼

2、異常枚舉類

public enum ExceptionEnum implements IError {// 數據操作狀態碼和提示信息定義SUCCESS("200", "操作成功"),VALIDATE_FAILED("400", "參數檢驗失敗"),NOT_FOUND("404", "參數檢驗失敗"),UNAUTHORIZED("401", "暫未登錄或token已經過期"),FORBIDDEN("403", "沒有相關權限"),REQUEST_TIME_OUT("408", "請求時間超時"),INTERNAL_SERVER_ERROR("500", "服務器內部錯誤!"),SERVER_BUSY("503", "服務器正忙,請稍后再試!");* 錯誤碼private String resultCode;* 錯誤描述private String resultMsg;private ExceptionEnum(String resultCode, String resultMsg) {this.resultCode = resultCode;this.resultMsg = resultMsg;@Overridepublic String getResultCode() {return resultCode;@Overridepublic String getResultMsg() {return resultMsg;復制代碼

3、自定義業務異常類

public class BusinessException extends RuntimeException {* 錯誤碼private String errorCode;* 錯誤描述private String errorMsg;public BusinessException() {super();public BusinessException(IError error) {super(error.getResultCode());this.errorCode = error.getResultCode();this.errorMsg = error.getResultMsg();public BusinessException(IError error, Throwable cause) {super(error.getResultCode(), cause);this.errorCode = error.getResultCode();this.errorMsg = error.getResultMsg();public BusinessException(String message) {super(message);public BusinessException(String errorCode, String errorMsg) {super(errorCode);this.errorCode = errorCode;this.errorMsg = errorMsg;public BusinessException(String errorCode, String errorMsg, Throwable cause) {super(errorCode, cause);this.errorCode = errorCode;this.errorMsg = errorMsg;public BusinessException(Throwable cause) {super(cause);public BusinessException(String message, Throwable cause) {super(message, cause);public static void validateFailed(String message) {throw new BusinessException(ExceptionEnum.VALIDATE_FAILED.getResultCode(), message);public static void fail(String message) {throw new BusinessException(message);public static void fail(IError error) {throw new BusinessException(error);public static void fail(String errorCode, String errorMsg) {throw new BusinessException(errorCode, errorMsg);復制代碼

4、全局異常處理類

@ControllerAdvice@Slf4jpublic class GlobalExceptionHandler {* 處理自定義的api異常* @param e* @return@ResponseBody@ExceptionHandler(value = BusinessException.class)public Result handle(BusinessException e) {if (Objects.nonNull(e.getErrorCode())) {log.error("發生業務異常!原因是:{}", e.getErrorMsg());return Result.failed(e.getErrorCode(), e.getErrorMsg());return Result.failed(e.getMessage());* 處理參數驗證失敗異常 基于json格式的數據傳遞,這種傳遞才會拋出MethodArgumentNotValidException異常* @param e* @return@ResponseBody@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handleValidException(MethodArgumentNotValidException e) {BindingResult bindingResult = e.getBindingResult();String message = null;if (bindingResult.hasErrors()) {FieldError fieldError = bindingResult.getFieldError();if (Objects.nonNull(fieldError)) {message = fieldError.getField() + fieldError.getDefaultMessage();return Result.validateFailed(message);* 使用@Validated 來校驗 JavaBean的參數,比如@NotNull、@NotBlank等等; post 請求數據傳遞有兩種方式,一種是基于form-data格式的數據傳遞,這種傳遞才會拋出BindException異常* @param e* @return@ResponseBody@ExceptionHandler(value = BindException.class)public Result handleValidException(BindException e) {BindingResult bindingResult = e.getBindingResult();String message = null;if (bindingResult.hasErrors()) {FieldError fieldError = bindingResult.getFieldError();if (fieldError != null) {message = fieldError.getField() + fieldError.getDefaultMessage();return Result.validateFailed(message);復制代碼統一返回格式

目前比較流行的是基于 json 格式的數據交互。但是 json 只是消息的格式,其中的內容還需要我們自行設計。不管是 HTTP 接口還是 RPC 接口保持返回值格式統一很重要,這將大大降低 client 的開發成本。

定義返回值四要素

 

  • boolean success ;是否成功。
  • T data ;成功時具體返回值,失敗時為 null 。
  • String code ;成功時返回 200 ,失敗時返回具體錯誤碼。
  • String message ;成功時返回 null ,失敗時返回具體錯誤消息。

 

返回對象中會處理分頁結果,普通的查詢結果,異常等信息。

@Data@NoArgsConstructorpublic class Result implements Serializable {private T data;private String code;private String message;private boolean success;protected Result(String code, String message, T data) {this.code = code;this.message = message;this.data = data;this.success = true;protected Result(String code, String message, T data, boolean success) {this(code, message, data);this.success = success;public static Result ok() {return ok((T) null);* 成功返回結果* @param data 獲取的數據* @returnpublic static Result ok(T data) {return new Result<>(ExceptionEnum.SUCCESS.getResultCode(),ExceptionEnum.SUCCESS.getResultMsg(), data);* 成功返回list結果* @param list 獲取的數據* @returnpublic static Result> ok(List list) {Result> listResult = new Result<>(ExceptionEnum.SUCCESS.getResultCode(),ExceptionEnum.SUCCESS.getResultMsg(), list);return listResult;* 成功返回結果* @param data 獲取的數據* @param message 提示信息public static Result ok(T data, String message) {return new Result<>(ExceptionEnum.SUCCESS.getResultCode(), message, data);* 失敗返回結果* @param error 錯誤碼public static Result failed(IError error) {return new Result<>(error.getResultCode(), error.getResultMsg(), null, false);* 失敗返回結果* @param error 錯誤碼* @param message 錯誤信息public static Result failed(IError error, String message) {return new Result<>(error.getResultCode(), message, null, false);* 失敗返回結果* @param errorCode 錯誤碼* @param message 錯誤信息public static Result failed(String errorCode, String message) {return new Result<>(errorCode, message, null, false);* 失敗返回結果* @param message 提示信息public static Result failed(String message) {return new Result<>(ExceptionEnum.INTERNAL_SERVER_ERROR.getResultCode(), message, null, false);* 失敗返回結果public static Result failed() {return failed(ExceptionEnum.INTERNAL_SERVER_ERROR);* 參數驗證失敗返回結果public static Result validateFailed() {return failed(ExceptionEnum.VALIDATE_FAILED);* 參數驗證失敗返回結果* @param message 提示信息public static Result validateFailed(String message) {return new Result<>(ExceptionEnum.VALIDATE_FAILED.getResultCode(), message, null, false);* 未登錄返回結果public static Result unauthorized(T data) {return new Result<>(ExceptionEnum.UNAUTHORIZED.getResultCode(),ExceptionEnum.UNAUTHORIZED.getResultMsg(), data, false);* 未授權返回結果public static Result forbidden(T data) {return new Result<>(ExceptionEnum.FORBIDDEN.getResultCode(),ExceptionEnum.FORBIDDEN.getResultMsg(), data, false);@Overridepublic String toString() {return toJSONString(this);復制代碼對象類型轉換

在項目中,尤其是在服務層,經常要將服務中的 Dto 實體對象轉換為 Entity 對象,以及將 Entity 對象轉換為 VO 對象返回給前端展示。現在市面上有很多這樣的工具包,比如 Spring 框架中就自帶了 BeanUtils,使我們進行這樣的數據操作十分簡單快捷,但當數據量級特別大時,存在性能問題。因此我們要選擇一款優秀的工具——Mapstruct。

關于 Mapstruct 的介紹以及其他對象轉換工具,可以參考這兩篇文章:Apache的BeanUtils、Spring的BeanUtils、Mapstruct、BeanCopier對象拷貝 和 MapStruct 才是王者

定義如下對象類型轉換文件:

@Mapper(componentModel = "spring")public interface UserStruct {@Mapping(target = "jobVOS",source = "jobs")UserVO modelToVO(User record);@Mapping(target = "jobVOS",source = "jobs")List modelToVO(List records);User voToModel(UserVO record);List voToModel(List records);UserDTO modelToDTO(User record);List modelToDTO(List records);User dtoToModel(UserDTO record);List dtoToModel(List records);復制代碼

如果對象中的屬性名不同,可以使用 @Mapping 注解進行聲明,自動生成的 UserStructImpl.class 如下所示,這里只展示部分代碼。

@Componentpublic class UserStructImpl implements UserStruct {@Overridepublic UserVO modelToVO(User record) {if ( record == null ) {return null;UserVO userVO = new UserVO();userVO.setJobVOS( jobListToJobVOList( record.getJobs() ) );userVO.setName( record.getName() );userVO.setAge( record.getAge() );userVO.setAddress( record.getAddress() );return userVO;protected JobVO jobToJobVO(Job job) {if ( job == null ) {return null;JobVO jobVO = new JobVO();jobVO.setName( job.getName() );jobVO.setAddress( job.getAddress() );return jobVO;protected List jobListToJobVOList(List list) {if ( list == null ) {return null;List list1 = new ArrayList( list.size() );for ( Job job : list ) {list1.add( jobToJobVO( job ) );return list1;復制代碼分組校驗和自定義校驗

@Validation是一套幫助我們繼續對傳輸的參數進行數據校驗的注解,通過配置 Validation 可以很輕松的完成對數據的約束。

@Validated作用在類、方法和參數上

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Validated {Class[] value() default {};復制代碼

在項目中我們可能會遇到這樣的場景:新增數據時某些字段需要進行判空校驗,而修改數據時又需要校驗另外一些字段,而且都是用同一個對象來封裝這些字段,為了便于管理及代碼的優雅,我們決定引入分組校驗。

創建分組,區分新增和編輯以及其它情況下的參數校驗。

public interface ValidateGroup {* 新增interface Add extends Default {* 刪除interface Delete {interface Edit extends Default {復制代碼

除了分組校驗,validation 還允許我們自定義校驗器。

@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.PARAMETER})@Constraint(validatedBy = EnumValidatorClass.class)public @interface EnumValidator {String[] value() default {};boolean required() default true;// 校驗枚舉值不存在時的報錯信息String message() default "enum is not found";//將validator進行分類,不同的類group中會執行不同的validator操作Class[] groups() default {};//主要是針對bean,很少使用Class[] payload() default {};復制代碼

其中 EnumValidatorClass 類主要是為了校驗 EnumValidator 注解的,代碼如下:

public class EnumValidatorClass implements ConstraintValidator {private String[] values;@Overridepublic void initialize(EnumValidator enumValidator) {this.values = enumValidator.value();@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {boolean isValid = false;if (value == null) {//當狀態為空時使用默認值return true;for (int i = 0; i < values.length; i++) {if (values[i].equals(String.valueOf(value))) {isValid = true;break;return isValid;復制代碼

后續項目實踐過程中會演示具體使用。

Liquibase

Liquibase 是一個用于跟蹤、管理和應用數據庫變化的開源的數據庫重構工具。它將所有數據庫的變化(包括結構和數據)都保存在 changelog 文件中,便于版本控制,它的目標是提供一種數據庫類型無關的解決方案,通過執行 schema 類型的文件來達到遷移。

目標:

Liquibase 實施端到端CI / CD要求將所有代碼(包括數據庫代碼)檢入版本控制系統,并作為軟件發布過程的一部分進行部署。

1、引入依賴

org.liquibaseliquibase-core4.16.1復制代碼

2、application.yml 配置

spring:liquibase:enabled: truechange-log: classpath:liquibase/master.xml# 記錄版本日志表database-change-log-table: databasechangelog# 記錄版本改變lock表database-change-log-lock-table: databasechangeloglock復制代碼

3、resource 目錄下新建 master.xml 和 changelog 目錄


 

復制代碼

4、運行項目,數據庫中會生成如下兩張表:

 

  • DATABASECHANGELOG 表
  • DATABASECHANGELOGLOCK表

 

因為 yaml 文件中的配置,實際生成的表名為小寫格式。

接下來該研究如何使用 liquibase 了,如果項目所連接的數據庫中目前沒有一個表,那么你可以在網上找一下 changeset 的書寫格式,然后模仿著來建表。如果數據庫中有表,可以先執行 liquibase:generateChangeLog 命令,生成一份現有表的建表語句,文件輸出路徑既可以在 yaml 文件中添加,然后在 pom 文件中讀取 yaml 文件;也可以直接在 pom 文件中添加。

#輸出文件路徑配置outputChangeLogFile: src/main/resources/liquibase/out/out.xml復制代碼

pom.xml

org.liquibaseliquibase-maven-plugin4.16.1src/main/resources/application.ymltrue復制代碼

7、點擊如下任意一個命令


 

然后在控制臺輸入名稱:job_create_table,效果為:


 

內容如下:

復制代碼plugin-生成數據庫修改文檔

雙擊liquibase plugin面板中的liquibase:dbDoc選項,會生成數據庫修改文檔,默認會生成到target目錄中,如下圖所示


 

訪問index.html會展示如下頁面,簡直應有盡有


 

關于 liquibase 的更多有意思的使用,可以花時間再去挖掘一下,這里就不過多介紹了。

一鍵式生成模版代碼

基于 orm-generate 項目可以實現項目模板代碼,集成了三種 ORM 方式:Mybatis、Mybatis-Plus 和 Spring JPA,JPA 是剛集成進來的,該項目去年就已經發布過一版,也成功實現了想要的功能,關于功能介紹可以參考我之前的這篇文章。

運行 orm-generate 項目,在 swagger 上調用 /build 接口,調用參數如下:

"database": "mysql_db","flat": true,"type": "mybatis","group": "hresh","host": "127.0.0.1","module": "orm","password": "root","port": 3306,"table": ["user","job"],"username": "root","tableStartIndex":"0"復制代碼

先將代碼下載下來,解壓出來目錄如下:


 

代碼文件直接移到項目中就行了,稍微修改一下引用就好了。

總結

上述基礎代碼是根據個人經驗總結出來的,可能不夠完美,甚至還缺少一些更有價值的基礎代碼,望大家多多指教。

在實際項目開發中,SpringBoot 基礎代碼和模版生成代碼完全可以作為兩個獨立的項目,供其他業務項目使用,以上代碼僅供參考,應用時可以按需修改。

分享到:
標簽:SpringBoot
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定