《从零开始的毕业设计》-ELS中台快递物流调度系统(二)基本订单功能

855 阅读4分钟

往期文章

前倾回顾

在上一篇文章中,我们搭建好了ELS的项目工程,并且调试好了服务之间的通信能力。那么在本篇文章中就要实现简单的订单能力。主要是使用到了Mybatis-plus、事务、RESTful风格的API

订单能力

Mybatis-plus

订单能力中,我们希望他具有:

  • 生成订单
  • 查询订单
  • 更新订单
  • 删除订单 四个基本能力,也就是CRUD

作者就直接使用Mybatis-plus来快速构造Service和Mapper部分,主要是因为Mybatis-plus有一个代码生成器,可以快速帮助我们省掉大量时间。


<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>
<!--Mybatis-plus代码生成器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>
<!--注解校验部分-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>${velocity.version}</version>
</dependency>

代码生成器

这里把我用的代码生成器模板贴在上面,同学们以后只需要根据自己业务稍微改改,就能用了。

public class hmfcode {

    public static void main(String[] args) {

        // 需要构建一个 代码自动生成器 对象

        AutoGenerator mpg = new AutoGenerator();
        // 配置策略

        // 1、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath+"/ELS-logistics-order/src/main/java");
        gc.setAuthor("洪敏锋");
        gc.setOpen(false);
        gc.setFileOverride(false);// 是否覆盖
        gc.setServiceName("%sService"); // 去Service的I前缀
        gc.setIdType(IdType.AUTO);
        gc.setDateType(DateType.ONLY_DATE);
        gc.setSwagger2(true);
        mpg.setGlobalConfig(gc);
        // 5.自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        cfg.setFileCreate((configBuilder, fileType, filePath) -> {
            //如果是Entity则直接返回true表示写文件
            if (fileType == FileType.ENTITY) {
                return true;
            }
            //否则先判断文件是否存在
            File file = new File(filePath);
            boolean exist = file.exists();
            if (!exist) {
                file.getParentFile().mkdirs();
            }
            //文件不存在或者全局配置的fileOverride为true才写文件
            return !exist || configBuilder.getGlobalConfig().isFileOverride();
        });
        mpg.setCfg(cfg);

        //2、设置数据源
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://127.0.0.1:3306/kuaidi?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);
        //3、包的配置
        PackageConfig pc = new PackageConfig();

        pc.setParent("com.example.elsorder");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setController("controller");
        
        mpg.setPackageInfo(pc);
        //4、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("logistics_order_package","logistics_order","logistics_order_item");
        // 设置要映射的表名
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        // 自动lombok;
        //逻辑删除
        strategy.setLogicDeleteFieldName("is_delete");

        // 自动填充配置
        TableFill isDeleted = new TableFill("is_delete", FieldFill.INSERT);
        TableFill status = new TableFill("status", FieldFill.INSERT);
        TableFill version = new TableFill("version", FieldFill.INSERT);
        TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
        TableFill gmtCreate = new TableFill("gmt_created", FieldFill.INSERT);

        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(gmtCreate);
        tableFills.add(gmtModified);
        tableFills.add(isDeleted);
        tableFills.add(status);
        tableFills.add(version);

        strategy.setTableFillList(tableFills);
        strategy.setVersionFieldName("version");
        strategy.setRestControllerStyle(true);
        strategy.setControllerMappingHyphenStyle(true);

        mpg.setStrategy(strategy);
        mpg.execute(); //执行
    }
}

然后运行一下这个java文件,Mybatis-plus就可以帮我们把Service和Mapper直接生成。

image.png

主键选择

主键我们选择使用雪花算法,这时候可能会有人问!

Q:你这破毕业设计,也需要使用雪花算法?直接数据库自增字段不就行了吗!

image.png

我只能说,这位同学,格局就小了。

你用雪花算法,你答辩的时候不就多一样东西跟老师吹牛b了嘛,甚至还可以显得专业一点,为了业务需求,魔改一下雪花算法的机器码部分,腾出2个bit来作为标志位。这不就高大上了吗。

RESTful风格API

对于接口的设计,我们采用RESTful接口设计,显得专业一点,RESTful只是一个行为规范,落实下来非常地简单,一个接口访问可以归纳为:

/版本号/资源名/(如果有指定资源名则指定)指定资源

比如我们要查询id为1的order订单 我们的接口就是:/v1/order/1 v1是我们指定的版本,日后版本迭代可能会有v2、v3....等等

我们F12看一下掘金的API请求路径,也可以看到RESTful风格

1641887556(1).jpg

对于RESTful接口我们需要注意:

请求

  • 路径携带版本号
  • 名词作为资源名,不要出现动词
  • @PathVariable来解析指定的资源名字
  • 通过请求方法GET、POST、PUT、DELETE来识别动作

响应

封装好一个ResultVO,用作返回包装

@Data
public class ResultVO<T> {
    private Integer code;
    private String msg;
    private T data;
}

事务

这里我就直接用声明式事务,也就是我们常用的 @Transactional 了,但是其实我们在真正的项目当中,是建议使用编程式事务的。

主要是因为声明式事务:

  • 不够灵活,面对大事务无法拆分
  • 事务的粒度太大
  • 使用不当,容易产生事务失效

由于我是小项目,做着玩所以就直接用Transactional了

image.png

这边还是介绍一下编程式事务的优点吧

编程式事务

要想使用编程式使用,我们需要用到一个TransactionTemplate,类似RestTemplate的一个东东。

@Autowired
public TransactionTemplate transactionTemplate;

public void save(){
    queryData1();
    queryData2();
    transactionTemplate.execute((status)->{
        //需要执行的事务方法
        dosave();
        return Boolean.TRUE;
    });
}

这里我们可以对save方法进行拆分,把耗时并且不要重要的queryData1()和queryData2()操作放到事务外面,让事务的粒度更细,更灵活。

订单CRUD

最后就是编写简单的Controller层的CRUD代码了,很快啊,CRUD就给他弄好了

/**
 *
 * @author 洪敏锋
 * @since 2022-01-04
 */
@RestController
@RequestMapping("/logistics-order")
public class LogisticsOrderController {
    @Autowired
    public LogisticsOrderServiceImpl service;

    public static final Logger logger = LoggerFactory.getLogger(LogisticsOrderController.class);
    /***
     * 创建订单
     * @param order
     * @return
     */
    @PostMapping("/v1/order")
    @Transactional(rollbackFor = RuntimeException.class)
    public ResultVO creatOrder(@RequestBody LogisticsOrder order) {
        ResultVO result = new ResultVO();
        result.setCode(200);
        boolean save = false;
        logger.info("开始创建订单");
        try{
            save = service.save(order);
            logger.info("创建订单完成,id:{}",order.getId());
        }catch (Exception e){
            logger.error("创建订单失败,id:{}",order.getId(),e);
        }
        result.setMsg(save == true ? "true" : "false");
        return result;
    }

    /***
     * 查询订单
     * @param id
     * @return
     */
    @GetMapping("/v1/order/{id}")
    public ResultVO getOrder(@PathVariable("id") long id){
        ResultVO resultVO = new ResultVO();
        resultVO.setCode(200);
        resultVO.setData(service.getById(id));
        return resultVO;
    }

    /***
     * 删除订单
     * @param id
     * @return
     */
    @DeleteMapping("/v1/order/{id}")
    @Transactional(rollbackFor = RuntimeException.class)
    public ResultVO deleteOrder(@PathVariable("id") long id){
        ResultVO result = new ResultVO();
        result.setCode(200);
        result.setData(service.removeById(id));
        boolean delete = false;
        logger.info("开始删除订单");
        try{
            delete = service.removeById(id);
            logger.info("删除订单完成,id:{}",id);
        }catch (Exception e){
            logger.error("删除订单失败,id:{}",id,e);
        }
        result.setMsg(delete == true ? "true" : "false");
        return result;
    }

    /***
     * 更新订单
     * @param order
     * @return
     */
    @PutMapping("/v1/order")
    @Transactional(rollbackFor = RuntimeException.class)
    public ResultVO updateOrder(@RequestBody LogisticsOrder order){
        ResultVO result = new ResultVO();
        result.setCode(200);
        boolean update = false;
        logger.info("开始更新订单");
        try{
            update = service.updateById(order);
            logger.info("更新订单完成,id:{}",order.getId());
        }catch (Exception e){
            logger.error("更新订单失败,id:{}",order.getId(),e);
        }
        result.setMsg(update == true ? "true" : "false");
        return result;
    }
}

结尾

效果展示

Postman

image.png

数据库

image-20220107210025288

日志

image.png

日志部分就先简单弄一弄,后面可能会去用log4j,顺便蹭一蹭去年12月那个log4j2的漏洞,到时候答辩又有画图可以跟导师聊!

仓库地址

github:ashlee618/ELS (github.com)

后续待完成

  • 订单接口的幂等性
  • 用设计模式去处理订单的状态
  • 订单和交易模块解耦(交易我打算就不对接第三方了,默认交易成功,直接摆烂!)