首页 > 其他分享 >02-运费服务

02-运费服务

时间:2024-04-17 20:35:09浏览次数:28  
标签:02 exception kg 运费 db store sl 服务 模板

1. 需求分析

运费计算微服务是核心的微服务,不能出现计算错误,毕竟是钱挂钩的。接到开发任务后,首先需要了解需求,再动手开发。

运费的计算是分不同地区的,比如:同城、省内、跨省,计算规则是不一样的,所以针对不同的类型需要设置不同的运费规则,这其实就是所谓的模板。

1.1 模板列表

产品需求中的运费模板列表:

轻抛系数名称解释:

在计算运费时,包裹有两个维度,体积和重量,二者谁大取谁进行计算,但是体积和重量不是一个单位怎么比较呢?一般的做法就是将体积转化成重量,公式:体积 / 轻抛系数 = 重量,这样就可以比较了。

也就是说,相同的体积,轻抛系数越大计算出的重量就越小,反之就越大。

1.2 计费规则

重量计算方法:

取重量和体积两者间较大的数值,体积计算方法:长(cm) × 宽(cm) × 高(cm) / 轻抛系数

普快:

  • 同城互寄:12000
  • 城内寄件:12000
  • 跨省寄件:12000
  • 经济区互寄(京津翼、江浙沪皖、川渝):6000
  • 经济区互寄(黑吉辽):9000

计费重量小数点规则:

  • 不满 1kg,按 1kg 计费;
  • 10kg 以下:以 0.1kg 为计重单位,四舍五入保留 1 位小数;
  • 10-100kg:续重以 0.5kg 为计重单位,不足 0.5kg 按 0.5kg 算,四舍五入保留 1 位小数;
  • 100kg 及以上:四舍五入取整;

举例:

8.4kg按照8.4kg收费

8.5kg按照8.5kg收费

8.8kg按照8.8kg收费

18.1kg按照18.5kg收费

18.5kg按照18.5kg收费

18.7kg按照19kg收费

108.4kg按照108kg收费

108.5kg按照109kg收费

108.6kg按照109kg收费

总运费小数点规则:按四舍五入计算,精确到小数点后一位


价格:(目前只有普快)

同城寄:首重(1.0kg)13.0元,续重2.0元/kg

省内寄:首重(1.0kg)14.0元,续重2.0元/kg

跨省寄:首重(1.0kg)18.0元,续重5.0元/kg

经济区互寄(江浙沪皖):首重(1.0kg)12.0元,续重3.0元/kg

经济区互寄(京津翼):首重(1.0kg)12.0元,续重3.0元/kg

经济区互寄(黑吉辽):首重(1.0kg)16.0元,续重6.0元/kg

经济区互寄(川渝):首重(1.0kg)18.0元,续重8.0元/kg


模板不可重复设置,需确保唯一值。

如已设置同城寄、跨省寄、省内寄,则只可修改,不可再新增。

如已设置经济区互寄某个城市,下次添加不可再关联此经济区城市。

1.3 新增模板

运费模板有 4 种类型,分别为:

  • 同城寄:同城寄件运费计算模板,全国统一定价
  • 省内寄:省内寄件运费计算模板,全国统一定价
  • 跨省寄:不同省份间的运费计算模板,全国统一定价
  • 经济区互寄:4个经济区(京津翼、江沪浙皖、川渝、黑吉辽),经济区间寄件可设置优惠价格。

a. 全国范围

此模板为「同城寄/省内寄/跨省」三个类型的运费模板

模板类型:可选择同城寄/省内寄/跨省/经济区互寄

运送类型:可选择运送类型,目前业务只支持普快

关联城市:同城寄/省内寄/跨省:全国统一定价(如上图)

首重价格:保留小数点后一位,可输入1-999 间任意数值

续重价格:保留小数点后一位,可输入1-999 间任意数值

轻抛系数:整数,可输入1-99999 间,任意数值

b. 经济区互寄

此模板「经济区互寄」类型的运费模板

模板类型:经济区互寄

运送类型:可选择运送类型,目前业务只支持普快

关联城市:经济区互寄:可设置单个或多个经济区价格(如上图)

首重价格:保留小数点后一位,可输入 1-999 间任意数值

续重价格:保留小数点后一位,可输入 1-999 间任意数值

轻抛系数:整数,可输入 1-99999 间,任意数值

1.4 运费模板表

运费模板是需要存储到表中的,所以首先需要设计表结构,具体表结构语句如下:

CREATE TABLE `sl_carriage` (
  `id` bigint NOT NULL COMMENT '运费模板id',
  `template_type` tinyint NOT NULL COMMENT '模板类型,1-同城寄 2-省内寄 3-经济区互寄 4-跨省',
  `transport_type` tinyint NOT NULL COMMENT '运送类型,1-普快 2-特快',
  `associated_city` varchar(20) NOT NULL COMMENT '关联城市,1-全国 2-京津冀 3-江浙沪 4-川渝 5-黑吉辽',
  `first_weight` double NOT NULL COMMENT '首重价格',
  `continuous_weight` double NOT NULL DEFAULT '1' COMMENT '续重价格',
  `light_throwing_coefficient` int NOT NULL COMMENT '轻抛系数',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='运费模板表';

由于该表数据比较少,所以就不需要添加索引字段了。

2. 开发环境

2.1 工程规范

在神领物流项目中,微服务代码是独立的工程(非聚合项目结构),这样更适合多团队间的协作,在部署方面更加的独立方便。

1 个微服务需要创建 3 个工程,分别是:

  • sl-express-ms-xxx-api(定义 Feign 接口)
  • sl-express-ms-xxx-domain(定义 DTO、枚举对象)
  • sl-express-ms-xxx-service(微服务的实现)

它们之间的依赖关系如下:

2.2 拉取代码

需要拉取的工程有 3 个:

工程名 Git 地址
sl-express-ms-carriage-domain http://git.sl-express.com/sl/sl-express-ms-carriage-domain
sl-express-ms-carriage-api http://git.sl-express.com/sl/sl-express-ms-carriage-api
sl-express-ms-carriage-service http://git.sl-express.com/sl/sl-express-ms-carriage-service

2.3 代码规范

a. DTO 对象

在神领物流项目中,微服务之间的对象传输都使用 DTO,命名规范:XxxxDTO(DTO 必须大写),并且将 DTO 类放置到 domain 工程中,如下:

DTO 类中统一使用 lombok 的 @Data 注解进行标注。

b. 数据校验

微服务之间的接口调用,对于传输的数据是需要做校验的,一般校验方式有 2 种:

(1)采用 hibernate-validator 注解方式校验

(2)在程序中通过 if 进行判断

我们采用哪一种方式呢?实际上在项目中,我们采用二者结合的方式进行校验。

对于第一种方式的补充说明:

(1)在 Controller 中需要在类声明上增加 @Validated 注解,来开启校验。

(2)对于表单、url 参数校验,在 Controller 方法入参上增加校验规则。

(3)对于 @RequestBody 的校验,校验规则写的 DTO 对象中,统一通过 Spring 的 AOP 进行校验,具体在 common 工程中的 com.sl.transport.common.aspect.ValidatedAspect 实现:

c. 自定义异常

在神领物流项目中,我们统一做了自定义异常的处理。定义了 2 个异常:

  • com.sl.transport.common.exception.SLWebException 用于前后端交互时抛出的异常
    @Data
    public class SLWebException extends RuntimeException {
    
        /** 异常中的信息 */
        private String msg;
    
        /** 业务状态码,规则:4位数,从1001开始递增 */
        private int code = 1001;
    
        /** http状态码,按照http协议规范,如:200,201,400等 */
        private int status = 500;
        
        ...
        
    }
    
  • com.sl.transport.common.exception.SLException 用于微服务之前接口调用抛出的异常
    @Data
    public class SLWebException extends RuntimeException {
    
        /** 异常中的信息 */
        private String msg;
    
        /** 业务状态码,规则:异常弹窗状态码 */
        private int code = 1;
    
        /** http状态码,按照http协议规范,如:200,201,400等 */
        private int status = 200;
        
        ...
    
    }
    

这两个异常的区别在于 code、status 的值不同。

疑问:为什么不使用一个,而是要设置两个?

这个主要是前端和后端的设计不同,一般在微服务间接口调用时会采用标准的 RESTful 方式,按照 RESTful 的规范响应的状态码要使用标准的 HTTP 状态码,成功 200、失败 500、没有权限 401 等。

而前后端进行交互时,一般都是响应 200,即使出错也是 200,只是响应结果中通过 msg 和 code 进行表达是否成功。

基于以上的场景,所以设置了两个异常类。

【统一异常处理】具体的业务逻辑在 com.sl.transport.common.handler.GlobalExceptionHandler 中实现。

package com.sl.transport.common.handler;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 参数校验失败异常
     *
     * @param exception 校验失败异常
     * @return 响应数据
     */
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<Object> handle(ValidationException exception) {
        List<String> errors = null;
        if (exception instanceof ConstraintViolationException) {
            ConstraintViolationException exs = (ConstraintViolationException) exception;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            errors = violations.stream()
                    .map(ConstraintViolation::getMessage).collect(Collectors.toList());
        }
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("参数校验失败异常 -> ", exception);
        }
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(MapUtil.<String, Object>builder()
                        .put("code", HttpStatus.BAD_REQUEST.value())
                        .put("msg", errors)
                        .build());
    }

    /**
     * 自定义异常处理
     *
     * @param exception 自定义异常
     * @return 响应数据
     */
    @ExceptionHandler(SLException.class)
    public ResponseEntity<Object> handle(SLException exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("自定义异常处理 -> ", exception);
        }
        return ResponseEntity.status(exception.getStatus())
                .body(MapUtil.<String, Object>builder()
                        .put("code", exception.getCode())
                        .put("msg", exception.getMsg())
                        .build());
    }

    /**
     * web自定义异常处理
     * 用于统一封装VO对象返回前端
     * @param exception web自定义异常
     * @return 响应数据
     */
    @ExceptionHandler(SLWebException.class)
    public ResponseEntity<Object> handle(SLWebException exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("自定义异常处理 -> ", exception);
        }
        JSONObject jsonObject = JSONUtil.parseObj(exception);
        return ResponseEntity.ok(MapUtil.<String, Object>builder()
                        .put("code", exception.getCode())
                        .put("msg", jsonObject.getStr("msg"))
                        .build());
    }

    /**
     * 其他未知异常
     *
     * @param exception 未知异常
     * @return 响应数据
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handle(Exception exception) {
        if (ObjectUtil.isNotEmpty(exception.getCause())) {
            log.error("其他未知异常 -> ", exception);
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(MapUtil.<String, Object>builder()
                        .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
                        .put("msg", ExceptionUtil.stacktraceToString(exception))
                        .build());
    }

}

d. @Resource

在项目中,涉及到注入 Spring 容器中 bean 对象时,均使用 @Resource,目前 IDEA 不推荐使用 @Autowired,原因是它是 Spring 提供,并非是 Java 标准,而 @Resource是 Java 标准中定义的,建议使用。

如果想要使用 @Autowired 的话,建议通过构造器注入。

两者区别:

  • @Autowired:默认是 ByType,可以使用 @Qualifier 指定 Name,可以对构造器、方法、参数、字段使用。
  • @Resource:默认 ByName,如果找不到则 ByType,只能对方法、字段使用,不能用于构造器。
  • @Autowired 是 Spring 提供的,@Resource 是 JSR-250 提供的。
  • 总结:基本上 @Resource 可以完全替代 @Autowired。

2.4 配置文件

a. SpringBoot 配置

文件 说明
bootstrap.yml 通用配置项,服务名、日志文件、swagger配置等
bootstrap-local.yml 多环境配置,本地开发环境
bootstrap-stu.yml 多环境配置,学生101环境
bootstrap-test.yml 多环境配置,开发组测试环境

对于配置文件的补充说明:

  • 关于 swagger 的配置,统一在 com.sl.transport.common.properties.SwaggerConfigProperties 中读取,并且在 com.sl.transport.common.config.Knife4jConfiguration 中进行了初始化 Knife4j。
  • spring.profiles.active 默认 local,部署发布到 101 机器,在 Jenkins 中发布时设置为 stu。
    docker run -d -p $SERVER_PORT:8080 --name $SERVER_NAME -e SERVER_PORT=8080 -e SPRING_CLOUD_NACOS_DISCOVERY_IP=${SPRING_CLOUD_NACOS_DISCOVERY_IP} -e  SPRING_CLOUD_NACOS_DISCOVERY_PORT=${port} -e SPRING_PROFILES_ACTIVE=stu $SERVER_NAME:$SERVER_VERSION
    

通过环境变量的方式配置了 spring.profiles.active、发布到注册中心的 ip 和端口。

规则:环境变量统一采用大写字母,不允许使用 .- 符号,采用下划线“_”取代点“.”,减号“-”直接删除。

为了与 101 环境中服务互通,所以在 local 环境中固定设置了注册到注册中心的服务地址。

具体的一些项目配置统一使用 Nacos 的配置中心管理,并且在这里使用 Nacos 的共享配置机制,这样可以在多个项目中共享相同的配置。

b. Seata 配置

shared-spring-seata.yml:

seata:
  registry:
    type: nacos
    nacos:
      server-addr: 192.168.150.101:8848
      namespace: ecae68ba-7b43-4473-a980-4ddeb6157bdc
      group: DEFAULT_GROUP
      application: seata-server
      username: nacos
      password: nacos
  tx-service-group: sl-seata # 事务组名称
  service:
    vgroup-mapping: # 事务组与cluster的映射关系
      sl-seata: default

seata 服务的配置(seata-server.properties):

# 指定seata存储的数据库
store.mode = db
store.db.datasource = druid
store.db.dbType = mysql
store.db.driverClassName = com.mysql.cj.jdbc.Driver
store.db.url = jdbc:mysql://192.168.150.101:3306/seata?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
store.db.user = root
store.db.password = 123
store.db.minConn = 5
store.db.maxConn = 100
store.db.globalTable = global_table
store.db.branchTable = branch_table
store.db.lockTable = lock_table
store.db.distributedLockTable = distributed_lock
store.db.queryLimit = 100
store.db.maxWait = 5000

seata 服务地址:http://seata.sl-express.com 账号信息:seata/seata

c. MySQL 配置

spring:
  datasource: # 数据库的配置
    driver-class-name: ${jdbc.driver:com.mysql.cj.jdbc.Driver}
    url: ${jdbc.url}
    username: ${jdbc.username}
    password: ${jdbc.password}

具体的配置项在每个微服务自己的配置文件中,例如运费服务:

jdbc.url = jdbc:mysql://192.168.150.101:3306/sl_carriage?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
jdbc.username = root
jdbc.password = 123

需要说明的是,${jdbc.driver:com.mysql.cj.jdbc.Driver} 这种写法冒号后面的是默认值,如果不配置 jdbc.driver 就采用默认值。

d. MP 配置

mybatis-plus:
  configuration:
    # 在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

在配置文件中指定的默认的 id 策略为 ASSIGN_ID,只当插入对象 ID 为空时,自动填充雪花 id。

e. Log 配置

项目中统一使用 logback 日志框架,其配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。-->
<!--scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。-->
<!--debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。-->
<configuration debug="false" scan="false" scanPeriod="60 seconds">
    <springProperty scope="context" name="appName" source="spring.application.name"/>
    <!--文件名-->
    <property name="logback.appname" value="${appName}"/>
    <!--文件位置-->
    <property name="logback.logdir" value="/data/logs"/>
    <!-- 定义控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] - %-5level - %logger{50} - %msg%n</pattern>
        </layout>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </filter>
        <File>${logback.logdir}/${logback.appname}/${logback.appname}.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${logback.logdir}/${logback.appname}/${logback.appname}.%d{yyyy-MM-dd}.log.zip</FileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
        </encoder>
    </appender>
    <!--evel:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,-->
    <!--不能设置为INHERITED或者同义词NULL。默认是DEBUG。-->
    <root level="INFO">
        <appender-ref ref="stdout"/>
    </root>
</configuration>

3. 业务实现

// TODO

标签:02,exception,kg,运费,db,store,sl,服务,模板
From: https://www.cnblogs.com/liujiaqi1101/p/18141728

相关文章

  • P10282
    思路首先想到一个\(n^{4}\)的dp,观察数据范围,发现这应该是一个\(n^{3}\)的算法,考虑如何优化。首先把转移方程写出来\(dp_{i,j}=\sum_{0\leii\lei-1,0\lejj\lej-1,\overline{a_{ii+1}...a{i}}\le\overline{b_{jj+1}...b{j}}}dp_{ii,jj}\),发现都不太好优化。首先枚举\(i\),\(......
  • 2024.4.8
    <?xmlversion="1.0"encoding="utf-8"?><ScrollViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.androi......
  • 2024.4.7
    <?xmlversion="1.0"encoding="utf-8"?><ScrollViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.androi......
  • 2024.4.13
    <?xmlversion="1.0"encoding="utf-8"?><ScrollViewxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.androi......
  • 2024.4.5 软工日报
    packagecom.example.sub_find;importandroid.content.Intent;importandroid.os.Bundle;importandroid.view.View;importandroid.widget.Button;importandroid.widget.TextView;importandroid.widget.Toast;importandroidx.appcompat.app.AppCompatActivity;importand......
  • ICPC2023南京站题解(A C D F G I L M)
    本场金牌线为7题前一半。做出8题可稳金牌,这里是难度前8题的题解。ICPC2023南京站I:签到题。#include<bits/stdc++.h>#definelllonglong#defineQWQcout<<"QwQ"<<endl;#defineFOR()llle=e[u].size();for(lli=0;i<le;i++)usingnamespacestd;constllN=501010;......
  • 服务器raid卡,守护数据安全,赋能新质生产力
    RAID卡,全称为独立冗余磁盘阵列卡,在数据中心、服务器、网络存储等领域得到广泛应用,RAID卡通过不同的RAID级别实现数据容错和冗余。例如,RAID0主要适用于需要高速数据传输但对数据安全要求不高的场景,如数据的缓存;RAID1使用镜像备份确保数据不因硬盘故障而丢失。然而市面上的Raid卡......
  • 【专题】2024年中国跨境进口电商B2C行业消费趋势白皮书报告合集PDF分享(附原数据表)
    原文链接:https://tecdat.cn/?p=35940原文出处:拓端数据部落公众号报告合集指出,随着中国经济开放程度的加深和全球化的推进,跨境进口电商行业规模持续扩大,抖音电商全球购等平台成为国际品牌进入中国市场的重要跳板。消费者对品质生活的追求日益提升,对产品的专业性和真实性要求更加......
  • MySQL服务无法启动 服务没有报告任何错误
    安装MYSQL后启动服务出现错误在启动MySQL服务时出现该报错解决方法:将原本在MySQL根目录下的my.ini文件移动到bin目录下(my.ini文件参考:这里)  删除根目录下的data目录  管理员方式运行命令行并移动到mysql中的bin目录下(cd+目录命令)  移除MySQL服务执......
  • 实景三维技术在社区服务与管理领域的应用
    随着科技的不断发展,实景三维技术已经成为了社区服务与管理领域的一项重要工具。实景三维技术可以通过高精度的三维建模技术,将现实世界中的场景、物体以及人物进行数字化重建,使得人们可以在计算机中实现对现实世界的全方位、多角度的观察和分析。在社区服务与管理领域,实景三维技......