Java应用结构规范

538次阅读  |  发布于2年以前

序言

在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大“痛点”,本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考。下面按分层结构、分层明细、调用关系、各层规范和通用代码工具展开介绍。

一 分层结构

通过调用业务层服务,处理前端的请求。

提供封装好的能力,并通过对能力进行组装、编排,进行业务逻辑处理。

对底层数据源进行增删改查操作。

定义暴露给其他应用的接口。

定义暴露给外部的公共类。

通过调用业务层服务,处理外部应用的请求。

二 分层明细

web(前端请求层)

子包 描述
controller 对接前端的控制器
model 前端请求相关的实体类
request 前端传入的请求
vo 返回给前端的实体类
convert controller请求转化为service请求的转化类、 dto转化为vo的转化类

biz(业务层)

子包 描述
service 查询服务和域服务
query 查询服务
fulfilOrder 举例:履约单服务
ability 域能力
fulfilOrder 举例:履约单域能力
manager 对应底层数据模型的通用逻辑处理器
remote 外部服务
message producer 消息发送器
diamond 动态配置
tair 缓存服务
config 业务层配置项,如:bean配置、hsf配置
common 内部公共类
constansts 仅内部使用的常量
convert dto和do的转化器、service请求转化为manager请求的转化器
enums 仅内部使用的枚举
model.dto 用于业务处理的实体类载体
model.request service和ability的请求类
model.option 查询的拓展条件,用于判断返回值的填充内容
utils 工具类

dal(数据层)

子包 描述
mapper 数据处理器
adb adb的数据处理器
tddl xdb的数据处理器
model 前端请求相关的实体类
dataobject 数据实体类
query 数据查询条件
config 数据层配置项,如:mybatis配置、tddl配置、sequence配置

client(外部请求层)

子包 描述
api 暴露给外部的hsf接口

common(外部公共层)

子包 描述
constansts 暴露给外部的常量
enums 暴露给外部的枚举
exception 暴露给外部的异常
model 暴露给外部的实体类
dto 暴露给外部的dto
request 暴露给外部的请求
result 暴露给外部的返回结果
to 暴露给外部的消息实体

facade(外观层)

子包 描述
api impl hsf的实现类
convert dto和外部dto的转化器、外部的请求转化为service请求的转化器
message
listener 消息的监听器
consumer 消息的处理器

start(启动类) qatest(测试类)

三 调用关系

注意点:

四 各层规范

web(前端请求层)

biz(业务层)

dal(数据层)

common(外部公共层)

facade(外观层)

五 通用代码和工具

web(前端请求层)

@RestControllerAdvice
public class RestExceptionHandler {


    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public Result system(HttpServletRequest req, Exception e) {
        AllLoggers.EXCEPTION.error("RestExceptionHandler.system|servlet:{}|method:{}|code:{}|msg:{}",
                req.getServletPath(),req.getMethod(), e.getMessage(), e);
        return Result.error(ResultCode.BASE.SYSTEM_ERROR);
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public Result business(HttpServletRequest req, BusinessException e) {
        AllLoggers.EXCEPTION.error("RestExceptionHandler.business|servlet:{}|method:{}|code:{}|msg:{}",
                req.getServletPath(),req.getMethod(), e.getMessage(), e);
        return Result.error(e.getErrorCode(), e.getErrorMessage());
    }
}

biz(业务层)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public interface AllLoggers {

    /**
     * 应用日志
     */
    Logger APPLICATION = LoggerFactory.getLogger("APPLICATION");

    /**
     * 异常日志
     */
    Logger EXCEPTION = LoggerFactory.getLogger("EXCEPTION");

    /**
     * 业务日志
     */
    Logger BIZ = LoggerFactory.getLogger("BIZ");

    /**
     * hsf日志
     */
    Logger HSF = LoggerFactory.getLogger("HSF");

    /**
     * 入口日志
     */
    Logger MTOP = LoggerFactory.getLogger("MTOP");

}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- https://github.com/spring-projects/spring-boot/blob/v1.5.13.RELEASE/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <property resource="application.properties"></property>
    <property name="APP_NAME" value="toms" />
    <property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs" />
    <property name="LOG_FILE" value="${LOG_PATH}/toms-root.log" />

    <appender name="APPLICATION"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}/toms-root.log</file>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logs_saved/toms-root.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>1GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!--业务日志-->
    <appender name="TOMS-BIZ-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-biz.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-biz.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--hsf日志-->
    <appender name="TOMS-HSF-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-hsf.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-hsf.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 通用错误日志 -->
    <appender name="TOMS-ERROR-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-error.log</File>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <maxHistory>5</maxHistory>
            <maxFileSize>2GB</maxFileSize>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 异常日志 -->
    <appender name="TOMS-EXCEPTION-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}/toms-exception.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs_saved/toms-exception.%d{yyyy-MM-dd}.log</FileNamePattern>
            <maxHistory>5</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <logger name="HSF" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-HSF-APPENDER"/>
    </logger>

    <logger name="BIZ" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-BIZ-APPENDER"/>
        <appender-ref ref="TOMS-ERROR-APPENDER"/>
    </logger>

    <logger name="EXCEPTION" level="${logback.info.level}" additivity="false">
        <appender-ref ref="TOMS-EXCEPTION-APPENDER"/>
        <appender-ref ref="TOMS-ERROR-APPENDER"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>
public class UnitConvertUtils {

    /**
     * 米和千米的进率
     */
    public static final double RATE_OF_METRE_AND_KILOMETRE = 1000d;
    public static final int INT_RATE_OF_METRE_AND_KILOMETRE = 1000;

    /**
     * 分和元的进率
     */
    public static final double RATE_OF_FEN_AND_YUAN = 100d;

    /**
     * 立方厘米和立方米的进率
     */
    public static final double INT_RATE_OF_CM3_AND_M3 = 1000000d;

    /**
     * 米转千米
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Double convertMetre2Kilometre(Long toConvert) {
        if (toConvert == null) {
            return null;
        }
        return toConvert / RATE_OF_METRE_AND_KILOMETRE;
    }

    /**
     * 千米转米
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertKilometre2Metre(Double toConvert) {
        if (toConvert == null) {
            return null;
        }

        BigDecimal bigDecimal = BigDecimal.valueOf(toConvert);
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_METRE_AND_KILOMETRE);

        return bigDecimal.multiply(factorBigDecimal).longValue();
    }

    /**
     * 元转分
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertYuan2Fen(Double toConvert) {
        if (toConvert == null) {
            return null;
        }

        BigDecimal bigDecimal = BigDecimal.valueOf(toConvert);
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);

        return bigDecimal.multiply(factorBigDecimal).longValue();
    }

    /**
     * 元转分
     *
     * @param toConvert
     * @return 异常返回null
     */
    public static Long convertYuan2Fen(String toConvert) {
        if (toConvert == null) {
            return null;
        }

        BigDecimal bigDecimal = BigDecimal.valueOf(ConvertUtils.convertString2Double(toConvert));
        BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);

        return bigDecimal.multiply(factorBigDecimal).longValue();
    }

    /**
     * 分转元
     *
     * @param price
     * @return
     */
    public static String convertFen2Yuan(Long price) {
        if (price == null) {
            return null;
        }

        return BigDecimal.valueOf(price).divide(new BigDecimal(RATE_OF_FEN_AND_YUAN)).toString();
    }

    /**
     * 里程米转换为千米
     *
     * @param distance
     * @return
     */
    public static Double meter2Kilometer(Long distance) {
        if (distance == null) {
            return null;
        }

        BigDecimal meter = BigDecimal.valueOf(distance);
        BigDecimal kilometer = meter.divide(new BigDecimal(INT_RATE_OF_METRE_AND_KILOMETRE));
        return kilometer.doubleValue();
    }

    /**
     * 立方厘米转立方米
     *
     * @param volume
     * @return
     */
    public static String convertCm32M3(Long volume) {
        if (volume == null) {
            return null;
        }

        return BigDecimal.valueOf(volume).divide(new BigDecimal(INT_RATE_OF_CM3_AND_M3)).toString();
    }

}

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8