Java 单体服务开发指南(下)

网友投稿 532 2022-05-30

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、构建工具

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。

Java 单体服务开发指南(下)

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小时内删除侵权内容。

上一篇:怎么解决dede首页网址自动加上index.html
下一篇:什么是单点登录?
相关文章