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

举报
zuozewei 发表于 2021/12/13 09:29:49 2021/12/13
【摘要】 Java 单体服务开发指南(下)

在这里插入图片描述

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<String, EnvConfig> map;

    static {
        map = new HashMap<String, EnvConfig>();
        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 配置

         <!-- Swagger -->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger2</artifactId>
                <version>2.9.2</version>
            </dependency>
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-swagger-ui</artifactId>
                <version>2.9.2</version>
            </dependency>
@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<List<PmsBrand>> 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<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                        @ApiParam("页码") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "3")
                                                        @ApiParam("每页数量") Integer pageSize) {
        List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
        return CommonResult.success(CommonPage.restPage(brandList));
    }

    @ApiOperation("获取指定id的品牌详情")
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult<PmsBrand> 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<Object> 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<String, Object> 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 等,由于不同的服务运行依赖的环境版本不一定一致,所以还需要制作不同版本的环境镜像,例如如下基础镜像版本。

这样,就可以标识该基础镜像的系统版本及软件版本,方便后边选择对应的基础镜像来构建应用镜像

有了上边的基础镜像后,就很容易构建出对应的应用镜像了,例如一个简单的应用镜像 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 $1  > /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<PmsBrand> brandList = brandMapper.selectByExample(new PmsBrandExample());
//通过构造PageInfo对象获取分页信息,如当前页码,总页数,总条数
PageInfo<PmsBrand> pageInfo = new PageInfo<PmsBrand>(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接口调试工具。
  • PowerDesigner:数据库设计工具,平时用来设计数据库表,设计完成之后可以直接导出数据库表
  • RedisDesktop:Redis可视化工具,平时用来查看和管理Redis缓存中的数据,有时候需要清空缓存的时候就用到它了。
  • Robomongo:MongoDB可视化工具,平时用来查看和管理MongoDB中的数据。
  • X-shell:一款强大的安全终端模拟软件,可以用来连接和管理远程Linux服务器。-
  • ProcessOn:作图工具,可以用来制作思维导图和流程图。
  • Snipaste:一款好用的截屏工具。

十二、代码提交规范

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 嵩山版:

参考资料:

  • [1]:《java开发手册》v1.7.0 嵩山版》
  • [2]:《Spring Boot & Kubernetes 云原生微服务实践》

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。