【一行代码的故事---阿里云短信发送&保存日志到es】

前言

最近优化代码,总结了一点个人的开发经验。由于我这人写代码喜欢偷懒,奉承着能少写一行代码,绝不多写一行的宗旨开发。

一. 一行代码的初衷

增删改查大量的业务代码,充斥着setter和getter,一个函数中仅仅只有一处地方还好,多了就会让代码看起了很浑厚,不简洁;
对接了大量的第三方接口,如果要详细的设计每一个接口,势必会造成代码的大量冗余,不简洁;
大量不同的service有相同的方法,仅仅是入参和返回结果不同,做着重复的工作,时间久了就不耐烦了。

二. 思考如何简化实现

每次打印日志时,我们都会定义一个log,然后通过这个log能打印各种异常,然后我就在想,它是怎么实现,怎么就能一行代码就把日志打印出来。然后我各种翻源码,没找到个所以然来,干脆放弃。
但是此时我注意到了枚举,一切都要从这个枚举说起。

三. 实现

建造者模式。建造者模式可以优化系统中大量的setter,使代码变的十分简洁。实现也很简单,如果你想偷懒,可以直接使用Lombok包中@Builder注解即可。
定义第三方接口的请求接口方法,主要重心放在处理返回结果中。
使用枚举处理第三方提供的服务

四. 实例

这里着重讲解使用枚举一行代码处理第三方提供的服务。

系统日志保存到message-send服务的es服务器中
正常来说,请求第三方服务,肯定要通过第三方服务提供的地址,访问他们的接口。这里我们不得不定义一个函数专门用来处理这些操作,然后再在需要的地方调用这个函数。这里又要涉及很多东西,比如每次

请求都要创建httpClient,区分日志的类型,每种日志类型的打印的参数不一致,参数个数也不一致,导致参数模板可能就需要好几个。打印日志报错影响到主业务流程怎么办等等一些列问题。已开始我尝试用了这种

方式处理,发现代码量有点大,要定义很多东西。所以我使用了枚举,具体方法如下:

a. 定义ES服务客户端 ElasticClient,这个客户端的主要作用就是封装请求es服务的接口以及请求参数

@Data
public class ElasticClient implements ElasticRequestApi {

    private static final Logger log = LoggerFactory.getLogger(ElasticClient.class);

    /**
     * 请求uri
     */
    private String uri;

    /**
     * 用户密匙
     */
    private String appKey;

    /**
     * 用户秘钥
     */
    private String secret;

    /**
     * 项目名称
     */
    private String projectName;

    /**
     * rest客户端
     */
    private RestTemplate restTemplate;

    /**
     * 日志id
     */
    private String logId;

    /**
     * 日志等级
     */
    private String logLevel;

    /**
     * 打印时间
     */
    private String printTime;

    /**
     * 系统环境
     */
    private String env;

    ElasticClient(ElasticClient.Builder builder) {
        this.appKey = builder.appKey;
        this.secret = builder.secret;
        this.uri = builder.uri;
        this.restTemplate = builder.restTemplate;
        this.projectName = builder.project;
        this.logId = builder.logId;
        this.logLevel = builder.logLevel;
        this.logLevel = builder.logLevel;
        this.printTime = builder.printTime;
        this.env = builder.env;
    }

    @Override
    public void executePostSaveLog(String logLevel, String message) {
        List<LogInfoEntity> logInfoEntities = new ArrayList<>();
        LogInfoEntity logInfoEntity = new LogInfoEntity();
        String logId = parseStringByFormat(new Date(), "yyyyMMddHHmmssSSS") + RandomUtils.nextInt(10000, 99999);
        logInfoEntity.setLogId(this.projectName + "-" + logId);
        logInfoEntity.setLogLevel(logLevel);
        logInfoEntity.setLogMsg(message.substring(0, Math.min(30000, message.length())));
        logInfoEntity.setProjectName(this.projectName);
        logInfoEntity.setPrintTime(parseStringByFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
        HttpHeaders headers = new HttpHeaders();
        logInfoEntities.add(logInfoEntity);
        SaveEntityForm form = new SaveEntityForm();
        form.setLogEntities(logInfoEntities);
        HttpEntity<SaveEntityForm> httpEntity = new HttpEntity<>(form, headers);
        String uri = this.uri + "es/log/commit/" + this.env;
        ResponseEntity<Object> exchange = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, Object.class);
        if (exchange.getStatusCodeValue() != 200) {
            log.error("保存日志到es失败 res -> {}", exchange);
        }
    }

    public static void main(String[] args) {
        System.out.println("ssss".getBytes(StandardCharsets.UTF_8).length);
        System.out.println("ssss".length());
    }

    public static String parseStringByFormat(Date date, String format) {
        if (date == null || StringUtils.isBlank(format)) {
            throw new BizException("日期转化参数不能为空");
        }
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(date);
    }

    @Override
    public LogInfoEntity executeGetLog(String logId) {
        ResponseEntity<LogInfoEntity> forEntity = restTemplate.getForEntity(this.uri + "/getLog/" + logId, LogInfoEntity.class);
        if (forEntity.getStatusCodeValue() == 200) {
            return forEntity.getBody();
        }
        return null;
    }

    public ElasticClient logLevel(String logLevel) {
        this.logLevel = logLevel;
        return this;
    }

    public static final class Builder {

        private String uri;

        private String appKey;

        private String secret;

        private RestTemplate restTemplate;

        private String project;

        private String logId;

        private String logLevel;

        private String printTime;

        private String env;

        public ElasticClient.Builder uri(String uri) {
            this.uri = uri;
            return this;
        }

        public ElasticClient.Builder appKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public ElasticClient.Builder secret(String secret) {
            this.secret = secret;
            return this;
        }

        public ElasticClient.Builder restTemplate(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
            return this;
        }

        public ElasticClient.Builder project(String project) {
            this.project = project;
            return this;
        }

        public ElasticClient.Builder logId(String logId) {
            this.logId = logId;
            return this;
        }

        public ElasticClient.Builder logLevel(String logLevel) {
            this.logLevel = logLevel;
            return this;
        }

        public ElasticClient.Builder printTime(String printTime) {
            this.printTime = printTime;
            return this;
        }

        public ElasticClient.Builder env(String env) {
            this.env = env;
            return this;
        }

        public Builder() {
            this.appKey = "";
            this.secret = "";
            this.uri = "http://localhost:8090/";
            this.restTemplate = new RestTemplate();
            this.project = "ALL";
            this.logId = this.project + "-" + UUID.randomUUID().toString().replaceAll("-", "");
            this.logLevel = "INFO";
            this.printTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            this.env = "dev";
        }

        public Builder(ElasticClient elasticClient) {
            this.uri = elasticClient.uri;
            this.appKey = elasticClient.appKey;
            this.secret = elasticClient.secret;
            this.restTemplate = elasticClient.restTemplate;
            this.project = elasticClient.projectName;
            this.logId = elasticClient.logId;
            this.logLevel = elasticClient.logLevel;
            this.printTime = elasticClient.printTime;
            this.env = elasticClient.env;
        }

        public ElasticClient build() {
            return new ElasticClient(this);
        }
    }
}

b. 创建EliasticClient配置ElasticClientConfig,用于注入es客户端以及httpClient

@Configuration
public class ElasticClientConfig {

    @Resource
    private ElasticLogProperties elasticLogProperties;

    @Bean
    public HttpClientConnectionManager httpClientConnectionManager() {
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        poolingHttpClientConnectionManager.setMaxTotal(500);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(50);
        return poolingHttpClientConnectionManager;
    }

    @Bean
    public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setConnectionManager(httpClientConnectionManager);
        return httpClientBuilder.build();
    }

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpComponentsClientHttpRequestFactory.setHttpClient(httpClient);
        httpComponentsClientHttpRequestFactory.setConnectTimeout(20 * 1000);
        httpComponentsClientHttpRequestFactory.setReadTimeout(20 * 1000);
        httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(20 * 1000);
        return httpComponentsClientHttpRequestFactory;
    }

    @Bean("restTemplateForEs")
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(clientHttpRequestFactory);
        return restTemplate;
    }

    @Bean
    public ElasticClient elasticClient(@Qualifier("restTemplateForEs") RestTemplate restTemplate) {
        return new ElasticClient.Builder()
                .restTemplate(restTemplate)
                .uri(elasticLogProperties.getUri())
                .project(elasticLogProperties.getProjectName())
                .env(elasticLogProperties.getEnv())
                .build();
    }
}

c. 定义访问es服务的接口,ElasticClient实现这个接口,并提供具体的实现

public interface ElasticRequestApi {

    /**
     * 保存日志到es
     *
     * @param logLevel 日志级别
     * @param message  日志
     */
    void executePostSaveLog(String logLevel, String message);

    /**
     * 获取日志信息
     *
     * @param logId 日志id
     * @return 日志信息
     */
    LogInfoEntity executeGetLog(String logId);
}
/**
 * es客户端
 * <p>
 * 构建请求日志client,封装请求接口
 * 可通过@Bean配置交由spring管理,具体配置可参见{@link com.cheapp.work2.common.elastic.ElasticClientConfig}
 * </p>
 * <p>
 * Use: 通过{@link com.cheapp.work2.common.elastic.Log}获取日志等级,然后调用方法{@link com.cheapp.work2.common.elastic.Log#print(Logger, String)} ()}打印日志。
 * 该方法通过参数Logger打印对应的日志信息并调用{@link com.cheapp.work2.common.elastic.Log#execute(String)}请求
 * {@link com.cheapp.work2.common.elastic.ElasticClient#executePostSaveLog(String, String)}接口,保存日志到es。
 * </p>
 *
 * @author baiye
 * @since 2021/6/4 5:48 下午
 **/
@Data
public class ElasticClient implements ElasticRequestApi {

    private static final Logger log = LoggerFactory.getLogger(ElasticClient.class);

    /**
     * 请求uri
     */
    private String uri;

    /**
     * 用户密匙
     */
    private String appKey;

    /**
     * 用户秘钥
     */
    private String secret;

    /**
     * 项目名称
     */
    private String projectName;

    /**
     * rest客户端
     */
    private RestTemplate restTemplate;

    /**
     * 日志id
     */
    private String logId;

    /**
     * 日志等级
     */
    private String logLevel;

    /**
     * 打印时间
     */
    private String printTime;

    /**
     * 系统环境
     */
    private String env;

    ElasticClient(ElasticClient.Builder builder) {
        this.appKey = builder.appKey;
        this.secret = builder.secret;
        this.uri = builder.uri;
        this.restTemplate = builder.restTemplate;
        this.projectName = builder.project;
        this.logId = builder.logId;
        this.logLevel = builder.logLevel;
        this.logLevel = builder.logLevel;
        this.printTime = builder.printTime;
        this.env = builder.env;
    }

    @Override
    public void executePostSaveLog(String logLevel, String message) {
        List<LogInfoEntity> logInfoEntities = new ArrayList<>();
        LogInfoEntity logInfoEntity = new LogInfoEntity();
        String logId = parseStringByFormat(new Date(), "yyyyMMddHHmmssSSS") + RandomUtils.nextInt(10000, 99999);
        logInfoEntity.setLogId(this.projectName + "-" + logId);
        logInfoEntity.setLogLevel(logLevel);
        logInfoEntity.setLogMsg(message.substring(0, Math.min(30000, message.length())));
        logInfoEntity.setProjectName(this.projectName);
        logInfoEntity.setPrintTime(parseStringByFormat(new Date(), "yyyy-MM-dd HH:mm:ss"));
        HttpHeaders headers = new HttpHeaders();
        logInfoEntities.add(logInfoEntity);
        SaveEntityForm form = new SaveEntityForm();
        form.setLogEntities(logInfoEntities);
        HttpEntity<SaveEntityForm> httpEntity = new HttpEntity<>(form, headers);
        String uri = this.uri + "es/log/commit/" + this.env;
        ResponseEntity<Object> exchange = restTemplate.exchange(uri, HttpMethod.POST, httpEntity, Object.class);
        if (exchange.getStatusCodeValue() != 200) {
            log.error("保存日志到es失败 res -> {}", exchange);
        }
    }

    public static void main(String[] args) {
        System.out.println("ssss".getBytes(StandardCharsets.UTF_8).length);
        System.out.println("ssss".length());
    }

    public static String parseStringByFormat(Date date, String format) {
        if (date == null || StringUtils.isBlank(format)) {
            throw new BizException("日期转化参数不能为空");
        }
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(date);
    }

    @Override
    public LogInfoEntity executeGetLog(String logId) {
        ResponseEntity<LogInfoEntity> forEntity = restTemplate.getForEntity(this.uri + "/getLog/" + logId, LogInfoEntity.class);
        if (forEntity.getStatusCodeValue() == 200) {
            return forEntity.getBody();
        }
        return null;
    }

    public ElasticClient logLevel(String logLevel) {
        this.logLevel = logLevel;
        return this;
    }

    public static final class Builder {

        private String uri;

        private String appKey;

        private String secret;

        private RestTemplate restTemplate;

        private String project;

        private String logId;

        private String logLevel;

        private String printTime;

        private String env;

        public ElasticClient.Builder uri(String uri) {
            this.uri = uri;
            return this;
        }

        public ElasticClient.Builder appKey(String appKey) {
            this.appKey = appKey;
            return this;
        }

        public ElasticClient.Builder secret(String secret) {
            this.secret = secret;
            return this;
        }

        public ElasticClient.Builder restTemplate(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
            return this;
        }

        public ElasticClient.Builder project(String project) {
            this.project = project;
            return this;
        }

        public ElasticClient.Builder logId(String logId) {
            this.logId = logId;
            return this;
        }

        public ElasticClient.Builder logLevel(String logLevel) {
            this.logLevel = logLevel;
            return this;
        }

        public ElasticClient.Builder printTime(String printTime) {
            this.printTime = printTime;
            return this;
        }

        public ElasticClient.Builder env(String env) {
            this.env = env;
            return this;
        }

        public Builder() {
            this.appKey = "";
            this.secret = "";
            this.uri = "http://localhost:8090/";
            this.restTemplate = new RestTemplate();
            this.project = "ALL";
            this.logId = this.project + "-" + UUID.randomUUID().toString().replaceAll("-", "");
            this.logLevel = "INFO";
            this.printTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            this.env = "dev";
        }

        public Builder(ElasticClient elasticClient) {
            this.uri = elasticClient.uri;
            this.appKey = elasticClient.appKey;
            this.secret = elasticClient.secret;
            this.restTemplate = elasticClient.restTemplate;
            this.project = elasticClient.projectName;
            this.logId = elasticClient.logId;
            this.logLevel = elasticClient.logLevel;
            this.printTime = elasticClient.printTime;
            this.env = elasticClient.env;
        }

        public ElasticClient build() {
            return new ElasticClient(this);
        }
    }
}

d. 定义日志枚举,并实现打印日志和上传日志到es的方法,由于枚举类中无法直接注入服务,所以采用内部类的方式加载服务

public enum Log {
    DEBUG,
    INFO,
    WARN,
    ERROR,
    ;

    private static final Logger log = LoggerFactory.getLogger(Log.class);
    private ElasticClient elasticClient;

    private NacosDynamicProperties nacosDynamicProperties;

    public void print(Logger log, String msg) {
        if (nacosDynamicProperties.isEsLogPrintOn()) {
            threadPoolExecutor.execute(() -> execute(msg));
        }
        switch (this.name()) {
            case "DEBUG":
                log.debug(msg);
                break;
            case "INFO":
                log.info(msg);
                break;
            case "WARN":
                log.warn(msg);
                break;
            case "ERROR":
                log.error(msg);
                break;
        }
    }

    public void print(Logger log, String v, Object... obj) {
        if (nacosDynamicProperties.isEsLogPrintOn()) {
            String msg = getMsg(v, obj);
            threadPoolExecutor.execute(() -> execute(msg));
        }
        switch (this.name()) {
            case "DEBUG":
                log.debug(v, obj);
                break;
            case "INFO":
                log.info(v, obj);
                break;
            case "WARN":
                log.warn(v, obj);
                break;
            case "ERROR":
                log.error(v, obj);
                break;
        }
    }

    private static String getMsg(String v, Object... objects) {
        String[] split = v.split("\\{}");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < split.length; i++) {
            if (i > objects.length - 1) {
                sb.append(split[i]);
                continue;
            }
            sb.append(split[i]).append(objects[i]);
        }
        return sb.toString();
    }

    protected void execute(String msg) {
        elasticClient.logLevel(this.name()).executePostSaveLog(this.name(), msg);
    }

    @Component
    public static class ServiceInjector {
        public ServiceInjector(@Qualifier("elasticClient") ElasticClient elasticClient,
                               @Qualifier("nacosDynamicProperties") NacosDynamicProperties nacosDynamicProperties) {
            log.info("init log service!");
            DEBUG.elasticClient = elasticClient;
            DEBUG.nacosDynamicProperties = nacosDynamicProperties;
            INFO.elasticClient = elasticClient;
            INFO.nacosDynamicProperties = nacosDynamicProperties;
            WARN.elasticClient = elasticClient;
            WARN.nacosDynamicProperties = nacosDynamicProperties;
            ERROR.elasticClient = elasticClient;
            ERROR.nacosDynamicProperties = nacosDynamicProperties;
        }
    }

    private static final int availableProcessors = Runtime.getRuntime().availableProcessors();
    private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            availableProcessors,
            20,
            100L,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>());

e. 使用如下图,我们只需要像正常的日志使用即可,在打印日志的同时也会把日志上传到es服务器,即使上传失败报错也不会影响主流程业务,这里要注意尽量在业务代码中使用,如果能明确知道配置的加载顺序,保证ESClient在之前加载,也可在初始化配置中使用。 在这里插入图片描述

  1. aliyun短信发送服务

这里直接描述怎么用枚举一行代码发送短信

a. 配置一个短信模板的枚举,枚举包括短信模板code和短信参数

public enum AliMessage {

    /**
     * 登录注册短信通知
     */
    LOGIN_REG_TEMPLATE("", "{\"code\":\"%s\"}"),

    /**
     * 邀请新员工短信模板
     */
    INVITE_NEW_STAFF_TEMPLATE("", "{\"corpName\":\"%s\"}"),

    /**
     * 预约参观短信模板
     */
    VISIT_TEMPLATE("", "{\"username\": \"%s\", \"phoneNo\":\"%s\", \"remark\":\"%s\"}"),

    /**
     * 报修短信模板
     */
    REPAIR_TEMPLATE("", "{\"username\": \"%s\", \"phoneNo\":\"%s\", \"repairType\": \"%s\", \"repairDesc\":\"%s\"}"),

    /**
     * 企业管理员变更通知
     */
    CORP_ACCOUNT_TEMPLATE("", "{\"corpName\":\"%s\",\"adminNo\":\"%s\", \"password\":\"%s\"}"),

    /**
     * 客户租赁场地到期通知
     */
    CONTRACT_EXPIRE_TEMPLATE("", ""),

    /**
     * 缴款单出账通知
     */
    BILL_TEMPLATE("", "{\"billName\":\"%s\",\"adminNo\":\"%s\"}"),

    /**
     * 预约参观办公室短信模板
     */
    VISIT_OFFICE_TEMPLATE("", "{\"username\": \"%s\", \"phoneNo\":\"%s\", \"officeName\":\"%s\", \"remark\":\"%s\"}"),

    ;

    private String tempCode;

    private String paramJson;

    AliMessage() {
    }

    AliMessage(String tempCode, String paramJson) {
        this.tempCode = tempCode;
        this.paramJson = paramJson;
    }

    private static final Logger log = LoggerFactory.getLogger(AliMessage.class);

    private AliMessageService aliMessageService;

    public Boolean send(String phoneNo, Object... objs) {
        Object[] no = new Object[objs.length];
        if (objs.length > 0) {
            for (int i = 0; i < objs.length; i++) {
                String str = objs[i].toString();
                if (str.length() > 25) {
                    str = str.substring(0, 22) + "...";
                }
                no[i] = str;
            }
        }
        SendMessageForm form = new SendMessageForm();
        form.setPhoneNo(phoneNo);
        form.setParamJson(StringUtils.isNotBlank(this.paramJson) ? String.format(this.paramJson, no) : "");
        form.setTemplateCode(this.tempCode);
        SendMessageVo sendMessageVo = aliMessageService.sendMessage(form);
        log.info("短信发送信息 -> {}", sendMessageVo);
        if (!"OK".equals(sendMessageVo.getCode())) {
            throw Result.toBizException(ResultCodeEnum.SEND_MESSAGE_FAIL, sendMessageVo.getMessage());
        }
        return true;
    }

    @Component
    public static class ServiceInjector {
        public ServiceInjector(AliMessageService aliMessageService) {
            LOGIN_REG_TEMPLATE.aliMessageService = aliMessageService;
            INVITE_NEW_STAFF_TEMPLATE.aliMessageService = aliMessageService;
            VISIT_TEMPLATE.aliMessageService = aliMessageService;
            REPAIR_TEMPLATE.aliMessageService = aliMessageService;
            CORP_ACCOUNT_TEMPLATE.aliMessageService = aliMessageService;
            CONTRACT_EXPIRE_TEMPLATE.aliMessageService = aliMessageService;
            BILL_TEMPLATE.aliMessageService = aliMessageService;
            VISIT_OFFICE_TEMPLATE.aliMessageService = aliMessageService;
        }
    }
}

b. 定义一个发送短信的方法即可

    public Boolean send(String phoneNo, Object... objs) {
        Object[] no = new Object[objs.length];
        if (objs.length > 0) {
            for (int i = 0; i < objs.length; i++) {
                String str = objs[i].toString();
                if (str.length() > 25) {
                    str = str.substring(0, 22) + "...";
                }
                no[i] = str;
            }
        }
        SendMessageForm form = new SendMessageForm();
        form.setPhoneNo(phoneNo);
        form.setParamJson(StringUtils.isNotBlank(this.paramJson) ? String.format(this.paramJson, no) : "");
        form.setTemplateCode(this.tempCode);
        SendMessageVo sendMessageVo = aliMessageService.sendMessage(form);
        log.info("短信发送信息 -> {}", sendMessageVo);
        if (!"OK".equals(sendMessageVo.getCode())) {
            throw Result.toBizException(ResultCodeEnum.SEND_MESSAGE_FAIL, sendMessageVo.getMessage());
        }
        return true;
    }

c. 使用
在这里插入图片描述

五. 总结

以前使用枚举仅仅是作为业务状态的多种模板提供选择的作用,现在可以通过枚举简化代码处理各种服务,代码入侵基本为0。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值