Java 單體服務開發指南(下)

      網友投稿 654 2025-04-04

      5、分環境配置

      類型

      dev

      test

      uat

      prod

      環境定義:

      public class EnvConstant { public static final String ENV_DEV = "dev"; public static final String ENV_TEST = "test"; public static final String ENV_UAT = "uat"; // similar to staging public static final String ENV_PROD = "prod"; }

      環境配置:

      // environment related configuration @Data @Builder public class EnvConfig { private String name; private boolean debug; private String externalApex; private String internalApex; private String scheme; @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) private static Map map; static { map = new HashMap(); EnvConfig envConfig = EnvConfig.builder().name(EnvConstant.ENV_DEV) .debug(true) .externalApex("staffjoy-v2.local") .internalApex(EnvConstant.ENV_DEV) .scheme("http") .build(); map.put(EnvConstant.ENV_DEV, envConfig); envConfig = EnvConfig.builder().name(EnvConstant.ENV_TEST) .debug(true) .externalApex("staffjoy-v2.local") .internalApex(EnvConstant.ENV_DEV) .scheme("http") .build(); map.put(EnvConstant.ENV_TEST, envConfig); // for aliyun k8s demo, enable debug and use http and staffjoy-uat.local // in real world, disable debug and use http and staffjoy-uat.xyz in UAT environment envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT) .debug(true) .externalApex("dusan-uat.local") .internalApex(EnvConstant.ENV_UAT) .scheme("http") .build(); map.put(EnvConstant.ENV_UAT, envConfig); // envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT) // .debug(false) // .externalApex("staffjoy-uat.xyz") // .internalApex(EnvConstant.ENV_UAT) // .scheme("https") // .build(); // map.put(EnvConstant.ENV_UAT, envConfig); envConfig = EnvConfig.builder().name(EnvConstant.ENV_PROD) .debug(false) .externalApex("dunsan.com") .internalApex(EnvConstant.ENV_PROD) .scheme("https") .build(); map.put(EnvConstant.ENV_PROD, envConfig); } public static EnvConfig getEnvConfg(String env) { EnvConfig envConfig = map.get(env); if (envConfig == null) { envConfig = map.get(EnvConstant.ENV_DEV); } return envConfig; } }

      開發測試環境禁用 Sentry 異常日志:

      @Aspect @Slf4j public class SentryClientAspect { @Autowired EnvConfig envConfig; @Around("execution(* io.sentry.SentryClient.send*(..))") public void around(ProceedingJoinPoint joinPoint) throws Throwable { // no sentry logging in debug mode if (envConfig.isDebug()) { log.debug("no sentry logging in debug mode"); return; } joinPoint.proceed(); } }

      Sentry 是統一的異常管理平臺,支持異常事件的收集、展示、告警等功能。

      6、異步調用處理

      ThreadPoolTaskExecutor:

      AsyncExecutor 配置:

      Configuration @EnableAsync @Import(value = {StaffjoyRestConfig.class}) @SuppressWarnings(value = "Duplicates") public class AppConfig { public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor"; @Bean(name=ASYNC_EXECUTOR_NAME) public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new ContextCopyingDecorator()); executor.setCorePoolSize(3); executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize(); return executor; } }

      Async 標注:

      @Async(AppConfig.ASYNC_EXECUTOR_NAME) public void trackEventAsync(String userId, String eventName) { if (envConfig.isDebug()) { logger.debug("intercom disabled in dev & test environment"); return; } Event event = new Event() .setUserID(userId) .setEventName("v2_" + eventName) .setCreatedAt(Instant.now().toEpochMilli()); try { Event.create(event); } catch (Exception ex) { String errMsg = "fail to create event on Intercom"; handleException(logger, ex, errMsg); throw new ServiceException(errMsg, ex); } logger.debug("updated intercom"); }

      線程上下文拷貝:

      // https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor public class ContextCopyingDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { RequestAttributes context = RequestContextHolder.currentRequestAttributes(); return () -> { try { RequestContextHolder.setRequestAttributes(context); runnable.run(); } finally { RequestContextHolder.resetRequestAttributes(); } }; } }

      @Configuration @EnableAsync @Import(value = {StaffjoyRestConfig.class}) @SuppressWarnings(value = "Duplicates") public class AppConfig { public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor"; @Bean(name=ASYNC_EXECUTOR_NAME) public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // for passing in request scope context executor.setTaskDecorator(new ContextCopyingDecorator()); executor.setCorePoolSize(3); executor.setMaxPoolSize(5); executor.setQueueCapacity(100); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setThreadNamePrefix("AsyncThread-"); executor.initialize(); return executor; } }

      7、Swagger 配置

      io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2

      @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() //為當前包下controller生成API文檔 .apis(RequestHandlerSelectors.basePackage("com.7d.PmsBrand.controller")) .paths(PathSelectors.any()) .build() .apiInfo(apiEndPointsInfo()) .useDefaultResponseMessages(false); } private ApiInfo apiEndPointsInfo() { return new ApiInfoBuilder().title("PmsBrand REST API") .description("7d Account REST API") .contact(new Contact("7d", "https://zuozewei.blog.csdn.net", "zuozewei@hotmail.com")) .license("The MIT License") .licenseUrl("https://opensource.org/licenses/MIT") .version("V2") .build(); } }

      給 Controller 添加 Swagger 注解:

      /** * 品牌管理Controller */ @Api(tags = "PmsBrandController", description = "商品品牌管理") @Controller @RequestMapping("/brand") public class PmsBrandController { @Autowired private PmsBrandService brandService; private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class); @ApiOperation("獲取所有品牌列表") @RequestMapping(value = "listAll", method = RequestMethod.GET) @ResponseBody public CommonResult> getBrandList() { return CommonResult.success(brandService.listAllBrand()); } @ApiOperation("添加品牌") @RequestMapping(value = "/create", method = RequestMethod.POST) @ResponseBody public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) { CommonResult commonResult; int count = brandService.createBrand(pmsBrand); if (count == 1) { commonResult = CommonResult.success(pmsBrand); LOGGER.debug("createBrand success:{}", pmsBrand); } else { commonResult = CommonResult.failed("操作失敗"); LOGGER.debug("createBrand failed:{}", pmsBrand); } return commonResult; } @ApiOperation("更新指定id品牌信息") @RequestMapping(value = "/update/{id}", method = RequestMethod.POST) @ResponseBody public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrandDto, BindingResult result) { CommonResult commonResult; int count = brandService.updateBrand(id, pmsBrandDto); if (count == 1) { commonResult = CommonResult.success(pmsBrandDto); LOGGER.debug("updateBrand success:{}", pmsBrandDto); } else { commonResult = CommonResult.failed("操作失敗"); LOGGER.debug("updateBrand failed:{}", pmsBrandDto); } return commonResult; } @ApiOperation("刪除指定id的品牌") @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET) @ResponseBody public CommonResult deleteBrand(@PathVariable("id") Long id) { int count = brandService.deleteBrand(id); if (count == 1) { LOGGER.debug("deleteBrand success :id={}", id); return CommonResult.success(null); } else { LOGGER.debug("deleteBrand failed :id={}", id); return CommonResult.failed("操作失敗"); } } @ApiOperation("分頁查詢品牌列表") @RequestMapping(value = "/list", method = RequestMethod.GET) @ResponseBody public CommonResult> listBrand(@RequestParam(value = "pageNum", defaultValue = "1") @ApiParam("頁碼") Integer pageNum, @RequestParam(value = "pageSize", defaultValue = "3") @ApiParam("每頁數量") Integer pageSize) { List brandList = brandService.listBrand(pageNum, pageSize); return CommonResult.success(CommonPage.restPage(brandList)); } @ApiOperation("獲取指定id的品牌詳情") @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public CommonResult brand(@PathVariable("id") Long id) { return CommonResult.success(brandService.getBrand(id)); } }

      修改 MyBatis Generator 注釋的生成規則:

      CommentGenerator為MyBatis Generator的自定義注釋生成器,修改addFieldComment方法使其生成Swagger的@ApiModelProperty注解來取代原來的方法注釋,添加addJavaFileComment方法,使其能在import中導入@ApiModelProperty,否則需要手動導入該類,在需要生成大量實體類時,是一件非常麻煩的事。

      /** * 自定義注釋生成器 */ public class CommentGenerator extends DefaultCommentGenerator { private boolean addRemarkComments = false; private static final String EXAMPLE_SUFFIX="Example"; private static final String API_MODEL_PROPERTY_FULL_CLASS_NAME="io.swagger.annotations.ApiModelProperty"; /** * 設置用戶配置的參數 */ @Override public void addConfigurationProperties(Properties properties) { super.addConfigurationProperties(properties); this.addRemarkComments = StringUtility.isTrue(properties.getProperty("addRemarkComments")); } /** * 給字段添加注釋 */ @Override public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) { String remarks = introspectedColumn.getRemarks(); //根據參數和備注信息判斷是否添加備注信息 if(addRemarkComments&&StringUtility.stringHasValue(remarks)){ // addFieldJavaDoc(field, remarks); //數據庫中特殊字符需要轉義 if(remarks.contains("\"")){ remarks = remarks.replace("\"","'"); } //給model的字段添加swagger注解 field.addJavaDocLine("@ApiModelProperty(value = \""+remarks+"\")"); } } /** * 給model的字段添加注釋 */ private void addFieldJavaDoc(Field field, String remarks) { //文檔注釋開始 field.addJavaDocLine("/**"); //獲取數據庫字段的備注信息 String[] remarkLines = remarks.split(System.getProperty("line.separator")); for(String remarkLine:remarkLines){ field.addJavaDocLine(" * "+remarkLine); } addJavadocTag(field, false); field.addJavaDocLine(" */"); } @Override public void addJavaFileComment(CompilationUnit compilationUnit) { super.addJavaFileComment(compilationUnit); //只在model中添加swagger注解類的導入 if(!compilationUnit.isJavaInterface()&&!compilationUnit.getType().getFullyQualifiedName().contains(EXAMPLE_SUFFIX)){ compilationUnit.addImportedType(new FullyQualifiedJavaType(API_MODEL_PROPERTY_FULL_CLASS_NAME)); } } }

      運行代碼生成器重新生成 mbg 包中的代碼:

      運行com.7d.mall.tiny.mbg.Generator 的 main方法,重新生成 mbg 中的代碼,可以看到 PmsBrand 類中已經自動根據數據庫注釋添加了@ApiModelProperty注解

      8、前后端分離跨域

      CORS全稱Cross-Origin Resource Sharing,意為跨域資源共享。當一個資源去訪問另一個不同域名或者同域名不同端口的資源時,就會發出跨域請求。如果此時另一個資源不允許其進行跨域資源訪問,那么訪問的那個資源就會遇到跨域問題。

      覆蓋默認的CorsFilter

      添加GlobalCorsConfig配置文件來允許跨域訪問。

      設置 SpringSecurity 允許 OPTIONS 請求訪問

      在SecurityConfig類的configure(HttpSecurity httpSecurity) 方法中添加如下代碼。

      .antMatchers(HttpMethod.OPTIONS)//跨域請求會先進行一次options請求 .permitAll()

      9、統一訪問日志記錄

      AOP 通過在 controller 層建一個切面來實現接口訪問的統一日志記錄。

      添加日志信息封裝類 WebLog

      用于封裝需要記錄的日志信息,包括操作的描述、時間、消耗時間、url、請求參數和返回結果等信息。

      /** * Controller層的日志封裝類 */ public class WebLog { /** * 操作描述 */ private String description; /** * 操作用戶 */ private String username; /** * 操作時間 */ private Long startTime; /** * 消耗時間 */ private Integer spendTime; /** * 根路徑 */ private String basePath; /** * URI */ private String uri; /** * URL */ private String url; /** * 請求類型 */ private String method; /** * IP地址 */ private String ip; /** * 請求參數 */ private Object parameter; /** * 請求返回的結果 */ private Object result; //省略了getter,setter方法 }

      添加切面類 WebLogAspect:

      定義了一個日志切面,在環繞通知中獲取日志需要的信息,并應用到controller層中所有的public方法中去。

      /** * 統一日志處理切面 */ @Aspect @Component @Order(1) public class WebLogAspect { private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class); @Pointcut("execution(public * com.dunsan.mall.tiny.controller.*.*(..))") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { } @AfterReturning(value = "webLog()", returning = "ret") public void doAfterReturning(Object ret) throws Throwable { } @Around("webLog()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); //獲取當前請求對象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //記錄請求信息 WebLog webLog = new WebLog(); Object result = joinPoint.proceed(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method.isAnnotationPresent(ApiOperation.class)) { ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); webLog.setDescription(apiOperation.value()); } long endTime = System.currentTimeMillis(); String urlStr = request.getRequestURL().toString(); webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); webLog.setIp(request.getRemoteUser()); webLog.setMethod(request.getMethod()); webLog.setParameter(getParameter(method, joinPoint.getArgs())); webLog.setResult(result); webLog.setSpendTime((int) (endTime - startTime)); webLog.setStartTime(startTime); webLog.setUri(request.getRequestURI()); webLog.setUrl(request.getRequestURL().toString()); LOGGER.info("{}", JSONUtil.parse(webLog)); return result; } /** * 根據方法和傳入的參數獲取請求參數 */ private Object getParameter(Method method, Object[] args) { List argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //將RequestBody注解修飾的參數作為請求參數 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } //將RequestParam注解修飾的參數作為請求參數 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); if (requestParam != null) { Map map = new HashMap<>(); String key = parameters[i].getName(); if (!StringUtils.isEmpty(requestParam.value())) { key = requestParam.value(); } map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } }

      10、打包方式

      jar

      docker

      服務配置文件處理方式:

      對于各個項目分環境部署,最麻煩的就是配置文件的問題,不同的環境需要加載不同的配置,好在 Spring Boot 框架加載配置是非常方便的,我們可以針對不同的環境分別配置不同的配置文件,這里有兩個地方要注意一下:

      構建鏡像的時候,盡量實現一個鏡像支持所有環境(即所有配置都打到一個鏡像里面去),在容器啟動時指定加載哪個環境配置即可,例如:在部署容器時指定 args: ["–spring.profiles.active=prod"] 參數啟動。

      盡量不要每個環境打出來一個鏡像版本,傳統方式在構建的時候指定 -D prod 配置 Profile 來指定加載哪個配置,來生成不同的產物 jar,容器化部署后不需要這樣,那樣后期控制各鏡像版本發布會比較麻煩。

      鏡像可以分為基礎鏡像和應用鏡像:

      基礎鏡像要求體積盡量小,方便拉取,同時安裝一些必要的軟件,方便后期進入容器內排查問題,我們需要準備好服務運行的底層系統鏡像,比如 Centos、Ubuntu 等常見 Linux 操作系統,然后基于該系統鏡像,構建服務運行需要的環境鏡像,比如一些常見組合:Centos + Jdk、Centos + Jdk + Tomcat、Centos + nginx 等,由于不同的服務運行依賴的環境版本不一定一致,所以還需要制作不同版本的環境鏡像,例如如下基礎鏡像版本。

      Centos6.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:6.5_1.8

      Centos7.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:7.5_1.8

      Centos7.5 + Jdk1.7: registry.docker.com/baseimg/centos-jdk:7.5_1.7

      Centos7 + Tomcat8 + Jdk1.8: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_8.5_1.8

      Centos7 + Nginx: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_1.10.2

      這樣,就可以標識該基礎鏡像的系統版本及軟件版本,方便后邊選擇對應的基礎鏡像來構建應用鏡像

      有了上邊的基礎鏡像后,就很容易構建出對應的應用鏡像了,例如一個簡單的應用鏡像 Dockerfile 如下:

      FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8 COPY app-name.jar /opt/project/app.jar EXPOSE 8080 ENTRYPOINT ["/java", "-jar", "/opt/project/app.jar"]

      當然,這里我建議使用另一種方式來啟動服務,將啟動命令放在統一 shell 啟動腳本執行,例如如下Dockerfile 示例:

      FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8 COPY app-name.jar /opt/project/app.jar COPY entrypoint.sh /opt/project/entrypoint.sh EXPOSE 8080 ENTRYPOINT ["/bin/sh", "/opt/project/entrypoint.sh"]

      將服務啟動命令配置到 entrypoint.sh,這樣我們可以擴展做很多事情,比如啟動服務前做一些初始化操作等,還可以向容器傳遞參數到腳本執行一些特殊操作,而且這里變成腳本來啟動,這樣后續構建鏡像基本不需要改 Dockerfile 了。

      #!/bin/bash # do other things here java -jar $JAVA_OPTS /opt/project/app.jar > /dev/null 2>&1

      上邊示例中,我們就注入 $JAVA_OPTS 環境變量,來優化 JVM 參數,還可以傳遞一個變量,這個變量大家應該就猜到了,就是服務啟動加載哪個配置文件參數,例如:–spring.profiles.active=prod

      十、技術選型(參考)

      1、代碼生成工具

      MyBatis Generator

      MyBatis Generator是 MyBatis 的代碼生成器,支持為 MyBatis 的所有版本生成代碼。非常容易及快速生成 Mybatis 的Java POJO文件及數據庫 Mapping 文件。

      2、核心框架

      SpringBoot 2.x

      SpringBoot 它使用“習慣優于配置”(項目中存在大量的配置,此外還內置一個習慣性的配置,讓你無須手動進行配置)的理念讓 Java 項目快速運行起來。使用 SpringBoot 很容易創建一個獨立運行(運行 Jar ,內嵌 Servlet 容器)、準生產級別的基于 Spring 的框架項目,使用 SpringBoot 你可以不用或者只需要很少的 Spring 配置。

      用白話來理解,就是 SpringBoot 其實不是什么新框架,它默認配置了很多框架的使用方式,就像 Maven 整合了所有的 Jar 包,SpringBoot 整合了幾乎所有的框架。

      官網:https://spring.io/projects/spring-boot

      3、日志框架

      Logback

      LogBack 是 Log4j 的改良版本,比 Log4j 擁有更多的特性,同時也帶來很大性能提升,同時天然支持SLF4J。

      LogBack 官方建議配合 Slf4j 使用,這樣可以靈活地替換底層日志框架。

      官網:http://logback.qos.ch/

      4、持久層框架

      Mybatis 3

      MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。

      5、連接池

      阿里 druid

      Druid 是一個關系型數據庫連接池,它是阿里巴巴的一個開源項目。Druid 支持所有 JDBC 兼容數據庫,包括了Oracle、MySQL、PostgreSQL、SQL Server、H2等。

      Druid 在監控、可擴展性、穩定性和性能方面具有明顯的優勢。通過 Druid 提供的監控功能,可以實時觀察數據庫連接池和SQL查詢的工作情況。使用 Druid 連接池在一定程度上可以提高數據訪問效率。

      官網:https://druid.apache.org/

      6、SQL攔截工具

      P6Spy

      p6spy 是一個開源項目,通常使用它來跟蹤數據庫操作,查看程序運行過程中執行的sql語句。

      官網:https://github.com/p6spy/p6spy

      7、多數據源啟動器

      dynamic-datasource-spring-boot-starter

      dynamic-datasource-spring-boot-starter 是一個基于 springboot 的快速集成多數據源的啟動器。

      其支持 Jdk 1.7+, SpringBoot 1.4.x、1.5.x、 2.0.x。

      官網:https://github.com/baomidou/dynamic-datasource-spring-boot-starter

      8、分頁插件

      MyBatis PageHelper

      MyBatis PageHelper 實現了通用的分頁查詢,其支持的數據有,mysql、Oracle、DB2、PostgreSQL等主流的數據庫。

      github: https://github.com/pagehelper/Mybatis-PageHelper

      PageHelper.startPage(pageNum, pageSize); //之后進行查詢操作將自動進行分頁 List brandList = brandMapper.selectByExample(new PmsBrandExample()); //通過構造PageInfo對象獲取分頁信息,如當前頁碼,總頁數,總條數 PageInfo pageInfo = new PageInfo(list);

      9、API文檔

      swagger2.0

      Swagger是一款Restful 接口的文檔在線自動生成、功能測試框架。一個規范和完整的框架,用于生成、描述、調用和可視化Restful 風格的Web服務,加上Swagger-UI,可以有很好的呈現。

      十一、開發環境(推薦)

      1、開發插件

      Lombok

      Lombok 項目是一個 Java 庫,它會自動插入您的編輯器和構建工具中,從而使您的Java更加生動有趣。

      永遠不要再寫另一個 getter 或 equals 方法,帶有一個注釋的您的類有一個功能全面的生成器,自動化您的日志記錄變量等等。

      官網:https://projectlombok.org/

      Hutool

      Hutool 是一個小而全的Java工具類庫,它幫助我們簡化每一行代碼,避免重復造輪子。如果你有需要用到某些工具類的時候,不妨在 Hutool 里面找找。

      官網:https://www.hutool.cn/

      2、JDK

      SUN JDK1.8及以上

      3、構建工具

      Java 單體服務開發指南(下)

      Maven 3.5.4及以上

      Maven 作為一個構建工具,不僅能幫我們自動化構建,還能夠抽象構建過程,提供構建任務實現;它跨平臺,對外提供了一致的操作接口,這一切足以使它成為優秀的、流行的構建工具。

      Maven 不僅是構建工具,還是一個依賴管理工具和項目管理工具,它提供了中央倉庫,能幫助我們自動下載構件。

      官網:https://maven.apache.org/

      4、Git 不限

      5、數據庫

      MySQL 5.7及以上

      Navicat Premium 11.2.7及以上

      MySQL是一個關系型數據庫管理系統,由瑞典 MySQL AB 公司開發,目前屬于 Oracle 旗下產品。MySQL 是最流行的關系型數據庫管理系統之一,在 WEB 應用方面,MySQL是最好的 RDBMS (Relational Database Management System,關系數據庫管理系統) 應用軟件之一。

      MySQL是一種關系數據庫管理系統,關系數據庫將數據保存在不同的表中,而不是將所有數據放在一個大倉庫內,這樣就增加了速度并提高了靈活性。

      MySQL所使用的 SQL 語言是用于訪問數據庫的最常用標準化語言。MySQL 軟件采用了雙授權政策,分為社區版和商業版,由于其體積小、速度快、總體擁有成本低,尤其是開放源碼這一特點,一般中小型網站的開發都選擇 MySQL 作為網站數據庫。

      ----- 摘抄自百度百科

      官網:https://www.mysql.com/

      6、IDE

      IntelliJ IDEA 2020.1

      推薦插件:

      Free MyBatis plugin:對MyBatis的xml具有強大的提示功能,同時可以關聯mapper接口和mapper.xml中的sql實現。

      Lombok plugin:Lombok為Java語言添加了非常有趣的附加功能,你可以不用再為實體類手寫getter,setter等方法,通過一個注解即可擁有。

      MyBatis Log Plugin:把Mybatis輸出的SQL日志還原成完整的SQL語句。

      RestfulToolkit:一套Restful服務開發輔助工具集,提供了項目中的接口概覽信息,可以根據URL跳轉到對應的接口方法中去,內置了HTTP請求工具,對請求方法做了一些增強功能。

      GsonFormat:這款插件可以把JSON格式的字符串轉化為實體類,當我們要根據JSON字符串來創建實體類的時候用起來很方便。

      Grep Console:一款幫你分析控制臺日志的插件,可以對不同級別的日志進行不同顏色的高亮顯示,還可以用來按關鍵字搜索日志內容。

      Alibaba Java Coding Guidelines:阿里巴巴《Java 開發手冊》配套插件,可以實時檢測代碼中不符合手冊規約的地方,助你碼出高效,碼出質量。

      Maven Helper:解決Maven依賴沖突的好幫手,可以快速查找項目中的依賴沖突,并予以解決

      Statistic:一款代碼統計工具,可以用來統計當前項目中代碼的行數和大小。

      Vue.js:Vue.js支持插件,可以根據模板創建.vue文件,也可以對Vue相關代碼進行智能提示。

      element:Element-UI支持插件,可以對Element-UI中的標簽進行智能提示,有了它就不用盲寫相關代碼了!

      7、其他工具

      Postman:API接口調試工具。

      https://www.postman.com/

      PowerDesigner:數據庫設計工具,平時用來設計數據庫表,設計完成之后可以直接導出數據庫表

      RedisDesktop:Redis可視化工具,平時用來查看和管理Redis緩存中的數據,有時候需要清空緩存的時候就用到它了。

      https://rdm.dev/

      Robomongo:MongoDB可視化工具,平時用來查看和管理MongoDB中的數據。

      https://robomongo.org/download

      X-shell:一款強大的安全終端模擬軟件,可以用來連接和管理遠程linux服務器。-

      https://www.netsarang.com/zh/all-downloads/

      ProcessOn:作圖工具,可以用來制作思維導圖和流程圖。

      processon.com

      Snipaste:一款好用的截屏工具。

      www.snipaste.com

      十二、代碼提交規范

      1、基本原則

      Git 代碼完整提交正確姿勢,建議先 Commit,再 Pull,最后 Push;

      代碼提交前,保證本地編譯通過;

      代碼提交時,保證代碼、文件完整提交,不要把本地測試代碼、配置提交上去了;

      代碼每次獨立的功能、模塊修改,都 Commit 到本地(不急每次都 Push);

      創建本地開發分支,完成后合并到特性分支,特性分支不可 push。

      2、提交注釋規則

      格式:[type: description] #[相關任務編號]

      2.1、type

      fix: 修復bug

      add: 新功能

      update: 更新

      style : 代碼格式改變

      test: 增加測試代碼

      revert: 撤銷上一次的commit

      build: 構建工具或構建過程等的變動,如:gulp 換成了 webpack,webpack 升級等

      2.2、description

      description 是對本次提交的簡短描述;

      不超過50個字符;

      推薦以動詞開頭,如: 設置、修改、增加、刪減、撤銷等。

      3、示例

      fix:修復登錄正確提示不準確缺陷 #demo-1243 add:添加登錄攔截校驗功能 #demo-1240 update:刪除登陸彈出框提示 #demo-1241 test:增加控制接口測試用例 #demo-1242

      關聯任務單/缺陷單編號,例如:“demo-124”;

      《java開發手冊》v1.7.0 嵩山版:

      https://github.com/zuozewei/blog-example/blob/master/Java-api-test/12-Alibaba Java Coding Guidelines/《java開發手冊》v1.7.0 嵩山版.pdf

      參考資料:

      [1]:《java開發手冊》v1.7.0 嵩山版》

      [2]:《Spring Boot & Kubernetes 云原生微服務實踐》

      Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:工作任務表(跟蹤表)提前(工作事項跟蹤表)
      下一篇:excel2003插入序列自動編號的教程
      相關文章
      亚洲一级片在线观看| 亚洲福利视频网站| 亚洲精品亚洲人成在线| 国产色在线|亚洲| 亚洲av无码久久忘忧草| 亚洲国产精品综合久久2007| 久久丫精品国产亚洲av| 久久亚洲精品无码AV红樱桃| 亚洲av日韩av无码| 久久精品国产亚洲av成人| 亚洲国产精品无码专区| 日韩亚洲人成在线综合日本| 亚洲AV无码一区二区三区系列| 日韩亚洲欧洲在线com91tv| 亚洲AV无码一区二区三区系列 | 日韩亚洲国产高清免费视频| 7777久久亚洲中文字幕| 日韩亚洲国产高清免费视频| 亚洲精品第一国产综合亚AV| 久久亚洲色WWW成人欧美| 激情婷婷成人亚洲综合| 亚洲国产精品成人网址天堂| 亚洲人午夜射精精品日韩| 国产亚洲情侣一区二区无码AV| 亚洲无线码在线一区观看| 国产av无码专区亚洲av果冻传媒| 亚洲情综合五月天| 久久久久亚洲AV片无码| 亚洲视频免费一区| 色噜噜亚洲男人的天堂| 亚洲国产精品网站在线播放| 成a人片亚洲日本久久| 亚洲人成色7777在线观看不卡| 国产成人麻豆亚洲综合无码精品| 国产亚洲A∨片在线观看| 亚洲国产精品久久久久婷婷老年| 91亚洲视频在线观看| 亚洲人成网站在线播放2019| 噜噜噜亚洲色成人网站| 亚洲欧洲日产国码av系列天堂| 亚洲成人午夜在线|