往期文章
- 《从零开始的毕业设计》-ELS中台快递物流调度系统(一)搭建项目环境 - 掘金 (juejin.cn)
- 《从零开始的毕业设计》-ELS中台快递物流调度系统(三)基本物流地址功能 - 掘金 (juejin.cn)
前倾回顾
在上一篇文章中,我们搭建好了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直接生成。
主键选择
主键我们选择使用雪花算法,这时候可能会有人问!
Q:你这破毕业设计,也需要使用雪花算法?直接数据库自增字段不就行了吗!
我只能说,这位同学,格局就小了。
你用雪花算法,你答辩的时候不就多一样东西跟老师吹牛b了嘛,甚至还可以显得专业一点,为了业务需求,魔改一下雪花算法的机器码部分,腾出2个bit来作为标志位。这不就高大上了吗。
RESTful风格API
对于接口的设计,我们采用RESTful接口设计,显得专业一点,RESTful只是一个行为规范,落实下来非常地简单,一个接口访问可以归纳为:
/版本号/资源名/(如果有指定资源名则指定)指定资源
比如我们要查询id为1的order订单 我们的接口就是:/v1/order/1 v1是我们指定的版本,日后版本迭代可能会有v2、v3....等等
我们F12看一下掘金的API请求路径,也可以看到RESTful风格
对于RESTful接口我们需要注意:
请求
- 路径携带版本号
- 名词作为资源名,不要出现动词
- 用 @PathVariable来解析指定的资源名字
- 通过请求方法GET、POST、PUT、DELETE来识别动作
响应
封装好一个ResultVO,用作返回包装
@Data
public class ResultVO<T> {
private Integer code;
private String msg;
private T data;
}
事务
这里我就直接用声明式事务,也就是我们常用的 @Transactional 了,但是其实我们在真正的项目当中,是建议使用编程式事务的。
主要是因为声明式事务:
- 不够灵活,面对大事务无法拆分
- 事务的粒度太大
- 使用不当,容易产生事务失效
由于我是小项目,做着玩所以就直接用Transactional了
这边还是介绍一下编程式事务的优点吧
编程式事务
要想使用编程式使用,我们需要用到一个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
数据库
日志
日志部分就先简单弄一弄,后面可能会去用log4j,顺便蹭一蹭去年12月那个log4j2的漏洞,到时候答辩又有画图可以跟导师聊!
仓库地址
github:ashlee618/ELS (github.com)
后续待完成
- 订单接口的幂等性
- 用设计模式去处理订单的状态
- 订单和交易模块解耦(交易我打算就不对接第三方了,默认交易成功,直接摆烂!)