失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > JSD-2204-(业务逻辑开发)-更新订单状态的功能-搜索功能-Quartz-Day12

JSD-2204-(业务逻辑开发)-更新订单状态的功能-搜索功能-Quartz-Day12

时间:2019-05-29 02:15:54

相关推荐

JSD-2204-(业务逻辑开发)-更新订单状态的功能-搜索功能-Quartz-Day12

1.开发更新订单状态的功能

1.1订单的状态码

我们电商上面订单的状态修改是非常普通的业务

随着商品的购买流程,订单的状态有

状态:

0=未支付

1=已关闭(超时未支付)

2=已取消

3=已支付

4=已签收

5=已拒收

6=退款处理中

7=已退款

1.2开发更新订单状态的持久层

修改订单状态就是根据订单id修改订单的state

我们随着业务的发展,订单可能需要更多修改的需求

订单的列(字段)比较多,如果每个字段修改,都需要编写一个方法的话,那么方法的数量会非常多

如果我们编写一个方法,能够接收订单对象的实体类参数(OmsOrder)

我们要实现可以根据OmsOrder对象的实际数据来实现动态的修改要修改的字段

Mybatis中可以通过编写动态修改sql语句完成这个需求

OmsOrderMapper接口添加方法

// 动态修改订单的sql,参数是omsOrder实体类对象// 对象中必须有id,id不可修改,其它属性不为空就修改其它属性值int updateOrderById(OmsOrder order);

OmsOrderMapper.xml编写sql

<!--动态修改订单的sql,参数是omsOrder实体类对象对象中必须有id,id不可修改,其它属性不为空就修改其它属性值--><!--omsOrder对象除id外的某个属性有值时,下面的动态sql语句,就有通过判断,来生成修改对应列的代码--><!--Mybatis框架动态sql标签<set>效果如下1.在<set>标签的位置生成一个set关键字2.在<set></set>标签的范围内,动态生成的sql语句,如果最后一个字符是","就删除它--><update id="updateOrderById">update oms_order<set><if test="contactName!=null">contact_name=#{contactName},</if><if test="mobilePhone!=null">mobile_phone=#{mobilePhone},</if><if test="telephone!=null">telephone=#{telephone},</if><if test="streetCode!=null">street_code=#{streetCode},</if><if test="streetName!=null">street_name=#{streetName},</if><if test="detailedAddress!=null">detailed_address=#{detailedAddress},</if><if test="tag!=null">tag=#{tag},</if><if test="paymentType!=null">payment_type=#{paymentType},</if><if test="state!=null">state=#{state},</if><if test="rewardPoint!=null">reward_point=#{rewardPoint},</if><if test="amountOfOriginalPrice!=null">amount_of_original_price=#{amountOfOriginalPrice},</if><if test="amountOfFreight!=null">amount_of_freight=#{amountOfFreight},</if><if test="amountOfDiscount!=null">amount_of_discount=#{amountOfDiscount},</if><if test="amountOfActualPay!=null">amount_of_actual_pay=#{amountOfActualPay},</if><if test="gmtPay!=null">gmt_pay=#{gmtPay},</if></set>whereid=#{id}</update>

1.3开发修改订单状态的业务逻辑层

OmsOrderServiceImpl

// 根据订单id 修改订单状态的业务逻辑层方法@Overridepublic void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {// 参数OrderStateUpdateDTO包含订单id和要修改的状态码// 将参数属性值赋值给OmsOrder类型对象,以便持久层调用OmsOrder order=new OmsOrder();BeanUtils.copyProperties(orderStateUpdateDTO,order);// 调用持久层方法omsOrderMapper.updateOrderById(order);}

启动Nacos\Seata

启动Order

测试时根据实际数据库订单id,修改knife4j的数据然后再运行

运行后查看数据库中订单状态列是否修改

2.搜索功能

2.1Elasticsearch加载数据

我们要想完成高效的搜索任务,需要ES的支持

因为数据库的模糊查询效率太低了

我们在前端页面中完成的搜索是从ES中搜索数据

这样就要求,我们在查询之前,需要先将商品信息(spu)保存到ES中

一开始我们使用最原始的办法:从数据库查询出数据之后新增到ES中

2.2确认实体类

搜索功能编写在mall-search模块中

它使用的实体类在cn.tedu.mall.pojo.search.eneity包下SpuForElastic

这个类有四个字段是具备分词功能的

所以支持我们使用这4个字段进行查询

/*** SPU名称*/@Field(name = "name",type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")@ApiModelProperty(value="SPU名称")private String name;//...../*** 标题*/@Field(name="title",type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")@ApiModelProperty(value="标题")private String title;/*** 简介*/@Field(name="description",type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")@ApiModelProperty(value="简介")private String description;//...../*** 类别名称(冗余)*/@Field(name="category_name",type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")@ApiModelProperty(value="类别名称(冗余)")private String categoryName;//.....

2.3开发ES的持久层

我们仍然使用SpringDataElasticsearch框架来操作ES

按照SpringData的规范,我们创建包repository

在这个包中创建SpuForElasticRepository接口,代码如下

// SpuForElastic实体类操作ES的持久层接口// 一定要继承父接口,才能具备SpringData提供的基本增删改查功能@Repositorypublic interface SpuForElasticRepository extendsElasticsearchRepository<SpuForElastic,Long> {}

这个接口提供了批量新增到ES数据的方法

但是要想获得数据库中的所有pms_spu表的数据,必须连接数据库查询这些数据

但是search模块是负责管理ES的,所以需要Dubbo调用Product模块获取所有数据

2.4product模块提供的查询功能

我们需要使用Dubbo调用product的业务逻辑层获得数据库pms_spu表的数据

经过观察发现业务逻辑逻辑层调用ForFrontSpuServiceImpl类中

具有一个getSpuByPage的方法

他分页查询所有spu信息

@Overridepublic JsonPage<Spu> getSpuByPage(Integer pageNum, Integer pageSize) {PageHelper.startPage(pageNum,pageSize);List<Spu> list=spuMapper.findAllList();return JsonPage.restPage(new PageInfo<>(list));}

分页的原因是因为一般加载到ES中的数据量非常大(几十万上百万条),

我们不可能一次性将所有数据查询出来,增到ES中,必须分批分次

分页查询就是典型的分批查询,每次查询一部分数据,通过循环遍历,将每页数据都新增到ES中

2.5Search模块执行加载

mall-search-webapi模块创建service.impl包

包中创建SearchServiceImpl类,用于将数据库中的数据加载到ES中

代码如下

@Service@Slf4jpublic class SearchServiceImpl implements ISearchService {// dubbo调用product模块查询所有spu@DubboReferenceprivate IForFrontSpuService dubboSpuService;// 将查询出的spu对象新增到ES中@Autowiredprivate SpuForElasticRepository spuRepository;// 先循环调用dubbo分页查询数据库// 将分页查询出的spu对象新增到ES@Overridepublic void loadSpuByPage() {// 我们并不知道本次分页从查询的总页数// 所以是典型的先执行后判断,推荐使用do-whileint i=1;// 循环次数,从1开始,同时代表页码int pages=0; // 总页数,在第一次运行之后才能知道具体值,默认值赋0即可(不赋值也可以)do{// dubbo 分页查询页码指定的数据JsonPage<Spu> spus=dubboSpuService.getSpuByPage(i,2);// 需要将JsonPage类型中的数据转换为List<SpuForElastic>,以便新增到ESList<SpuForElastic> esSpus=new ArrayList<>();// 遍历spus,进行转换并新增到esSpusfor(Spu spu : spus.getList()){SpuForElastic esSpu=new SpuForElastic();BeanUtils.copyProperties(spu,esSpu);// 将赋值好的esSpu添加到esSpus中esSpus.add(esSpu);}// esSpus集合中已经添加了当前页数据,执行新增到ESspuRepository.saveAll(esSpus);log.info("成功加载第{}页数据",i);// 为下次循环准备i++;pages=spus.getTotalPage();}while(i<=pages);}@Overridepublic JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize) {return null;}}

创建测试类

@SpringBootTestpublic class LoadTest {@Autowiredprivate ISearchService searchService;@Testvoid loadData(){searchService.loadSpuByPage();System.out.println("ok");}}

运行测试前保证

Nacos\Seata\\ES启动

启动product模块

运行测试,没有报错即可

2.6验证ES中的数据

我们再通过连接ES来进行全查

检验上面执行的加载工作是否达到效果

仍然在测试类中,在编写一个方法,使用SpringData提供的全查方法查询后遍历输出

检查输出内容,代码如下

@Autowiredprivate SpuForElasticRepository repository;@Testvoid showData(){Iterable<SpuForElastic> spus=repository.findAll();spus.forEach(spu -> System.out.println(spu));}

2.7搜索功能的实现

电商网站一定会有按用户输入的关键字进行搜索的功能

这样的搜索都是搜索ES查询到的结果

上面章节中,我们已经将所有spu信息保存到了ES中

下面通过查询逻辑将搜索结果显示出来

2.7.1编写SpringData自定义查询

如果我们按照关键字"手机"进行搜索

可以在Repository接口中编写自定义方法

@Repositorypublic interface SpuForElasticRepository extendsElasticsearchRepository<SpuForElastic,Long> {// 查询title字段包含指定关键字的spu数据Iterable<SpuForElastic> querySpuForElasticsByTitleMatches(String title);}

上面的查询可以通过测试类测试

// 根据title查询数据@Testvoid getSpuByTitle(){Iterable<SpuForElastic> it=repository.querySpuForElasticsByTitleMatches("手机");it.forEach(e -> System.out.println(e));}

尤其需要关注ES是否已经启动

不需要其它项目的支持,直接运行测试即可

我们业务中需要4个字段的条件查询

SpringData也支持我们在代码中编写查询语句,一避免过长的方法名

@Query("{\n" +" \"bool\": {\n" +"\"should\": [\n" +" { \"match\": { \"name\": \"?0\"}},\n" +" { \"match\": { \"title\": \"?0\"}},\n" +" { \"match\": { \"description\": \"?0\"}},\n" +" { \"match\": { \"category_name\": \"?0\"}}\n" +" ]\n" +"}\n" +"}")// 上面指定了查询语句,这个方法的方法名就随意定义了Iterable<SpuForElastic> querySearch(String keyword);

测试代码

// 根据title查询数据@Testvoid getSpuByTitle(){// Iterable<SpuForElastic> it=//repository.querySpuForElasticsByTitleMatches("手机华为小米");Iterable<SpuForElastic> it=repository.querySearch("手机");it.forEach(e -> System.out.println(e));}

在实际开发中

我们数据库中的数据和Elasticsearch中的数据还存在同步问题

为了保持数据库中的数据和Elasticsearch中的数据一致

我们可以使用下面的办法

1.在所有对spu表进行增删改的操作代码运行后,也对ES中的数据进行相同的操作

​但是会有比较多的代码要编写,而且有比较明显的事务处理问题

2.实际上业界使用Elasticsearch有一个组合叫ELK,其中L(logstash)可以实现自动同步数据库和ES的信息

后面学习过程中,我们会在Linux虚拟机的学习中使用它

实际运行查询的逻辑是需要分页的

所以要按照SpringData支持的分页查询格式修改上面的查询代码

@Query("{\n" +" \"bool\": {\n" +"\"should\": [\n" +" { \"match\": { \"name\": \"?0\"}},\n" +" { \"match\": { \"title\": \"?0\"}},\n" +" { \"match\": { \"description\": \"?0\"}},\n" +" { \"match\": { \"category_name\": \"?0\"}}\n" +" ]\n" +"}\n" +"}")// 上面指定了查询语句,这个方法的方法名就随意定义了//↓↓↓ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓Page<SpuForElastic> querySearch(String keyword, Pageable pageable);

修改方法的定义,原有的调用会报错,注释掉调用即可!

2.7.2开发搜索功能的业务逻辑层

提供的项目搜索功能接口的返回值泛型为虚拟机版本的ES实体类

要先修改一下

ISearchService

public interface ISearchService {// ES分页查询spu的方法// ↓↓↓↓↓↓↓↓↓↓↓↓↓JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize)//......}

SearchServiceImpl类添加实现方法如下

@Overridepublic JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize) {// 根据参数中分页的数据,执行分页,注意SpringData分页参数设置0是第一页,所以要减1Page<SpuForElastic> spus=spuRepository.querySearch(keyword,PageRequest.of(page-1,pageSize));// 当前方法需要返回JsonPage类型,我们需要将spus对象进行转换// 我们使用就地转换的方式,也可以在JsonPage类中添加转换方法来调用JsonPage<SpuForElastic> jsonPage=new JsonPage<>();// 赋值相关分页信息jsonPage.setPage(page);jsonPage.setPageSize(pageSize);//总条数和总页数jsonPage.setTotal(spus.getTotalElements());jsonPage.setTotalPage(spus.getTotalPages());// 将查询到的数据赋值到jsonPagejsonPage.setList(spus.getContent());// 最后别忘了返回return jsonPage;}

2.7.3开发控制层代码

创建controller包

包中创建SearchController编写搜索方法,代码如下

@RestController@RequestMapping("/search")@Api(tags = "搜索模块")public class SearchController {@Autowiredprivate ISearchService searchService;// 搜索模块的功能非常直接,就是搜索// 所以它的url路径可以尽量短// @GetMapping后面不写任何内容,意思就是只适用类上定义的路径即可访问// 访问路径:localhost:10008/search@GetMapping@ApiOperation("根据用户输入的关键字分页查询spu")@ApiImplicitParams({@ApiImplicitParam(value = "搜索关键字",name="keyword", dataType="string"),@ApiImplicitParam(value = "页码",name="page", dataType="int"),@ApiImplicitParam(value = "每页条数",name="pageSize", dataType="int")})public JsonResult<JsonPage<SpuForElastic>> searchByKeyword(String keyword,Integer page, Integer pageSize){JsonPage<SpuForElastic> jsonPage=searchService.search(keyword,page,pageSize);return JsonResult.ok(jsonPage);}}

保证Nacos\seata\ES启动

因为当前search项目过滤器解析JWT所以需要登录才能访问

启动search模块

建议启动passport模块去进行登录获得jwt

复制JWT后,粘贴到10008模块的全局参数,启动search模块

再测试http://localhost:10008/doc.html

3.Quartz

3.1什么是Quartz

quartz:石英钟的意思

是一个当今市面上流行的高效的任务调度管理工具

所谓"调度"就是制定好的什么时间做什么事情的计划

由OpenSymphony开源组织开发

Symphony:交响乐

是java编写的,我们使用时需要导入依赖即可

3.2为什么需要Quartz

所谓"调度"就是制定好的什么时间做什么事情的计划

我们使用过的最简单的调度方法就是Timer

但是Timer的调度功能过于单一,只能是指定时间的延时调用和周期运行

而Quartz可以更详细的指定时间,进行计划调用

3.3Quartz核心组件

调度器:Scheduler

任务:job

触发器:Trigger

调度器来配置\计划什么时间触发什么任务

简单来说就是调度器规定什么时间做什么事情

job(工作\任务):Quartz 实现过程中是一个接口,接口中有一个方法execute(执行的意思)

​我们创建一个类,实现这个接口,在方法中编写要进行的操作(执行具体任务)

​我们还需要一个JobDetail的类型的对象,Quartz每次执行job时

​会实例化job类型对象,去调用这个方法,JobDetail是用来描述Job实现类的静态信息,

​比如任务运行时在Quartz中的名称

Trigger(触发器):能够描述触发指定job的规则,分为简单触发和复杂触发

简单触发可以使用SimplTrigger实现类.功能类似timer

复杂触发可以使用CronTrigger实现类,内部利用cron表达式描述各种复杂的时间调度计划

Scheduler(调度器):一个可以规定哪个触发器绑定哪个job的容器

在调度器中保存全部的Quartz保存的任务

SpringBoot框架下,添加Quartz依赖后,调度器由SpringBoot管理,我们不需要编写

3.4Cron表达式

表示9月3日凌晨4点的cron表达式

0 0 4 3 9?

* 表示任何值,如果在分的字段上编写*,表示每分钟都会触发

, 是个分割符如果秒字段我想20秒和40秒时触发两次就写 20,40

- 表示一个区间 秒字段5-10 表示 5,6,7,8,9,10

/ 表示递增触发 秒字段 5/10表示5秒开始每隔10秒触发一次

日字段编写1/3表示从每月1日起每隔3天触发一次

? 表示不确定值, 因为我们在定日期时,一般确定日期就不确定是周几,相反确定周几时就不确定日期

L 表示last最后的意思,我们可以设置当月的最后一天,就会在日字段用L表示,

周字段使用L表示最后一周,一般会和1-7的数字组合

例如6L表示本月最后一周的周五

W (work)表示最近的工作日(单纯的周一到周五) 如果日字段编写15W表示

每月15日最近的工作日触发,如果15日是周六就14日触发,如果15日是周日就16日触发

​LW通常一起使用,表示本月的最后一个工作日

# 表示第几个,只能使用在周字段上 6#3表示每月的第三个周五

如果#后面数字写大了,是一个不存在的日期,那就不运行了

适合设计在母亲节或父亲节这样的日期运行

网络上可用的Cron表达式生成器很多

推荐一个Cron - 在线Cron表达式生成器

未来每年的母亲节(时间9点)

0 0 9?5 1#3

未来每个月离15日最近的工作日(时间9点)

0 0 9 15W * ?

双11的0时触发

0 0 0 11 11?

每月10日最近的工作日9点发工资

0 0 9 10W * ?

3.5SpringBoot使用Quartz

SpringBoot框架下使用Quartz格式还是非常固定的

我们选用之前学习微服务的项目csmall减少对大项目的影响

首先添加依赖

我们选项csmall-stock-webapi模块pom文件

<!-- SpringBoot整合Quartz依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>

先编写要执行的任务

当前项目模块中创建quartz包

包中创建一个QuartzJob的类,实现Job接口

代码如下

public class QuartzJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {// 实现一个简单的任务做演示// 例如输出当前时间System.out.println("-------------------"+ LocalDateTime.now() +"--------------------");}}

上面编写的是Job接口的实现类,要想运行还需要将它封装为JobDetail对象保存在Spring容器中

还有要创建一个Trigger设置要运行的时机,也保存到Spring容器中

在quartz包下,再创建一个QuartzConfig类,其中编写它们的调度绑定关系

这个格式是固定的,后面再需要绑定,直接套用即可

// 要配置Quartz的调度器Scheduler// 调度器由SpringBoot管理,所以就变成了配置Spring@Configurationpublic class QuartzConfig {// 配置的核心是向Spring容器保存一个job和保存一个Trigger// 创建一个封装Job对象的类型JobDetail// 使用@Bean注解标记的方法将这个对象保存到Spring容器@Beanpublic JobDetail showTime(){System.out.println("jobDetail保存到Spring容器");//newJob方法就是在绑定要运行的Job接口实现类,需要实现类的反射做参数return JobBuilder.newJob(QuartzJob.class)// 给当前JobDetail对象在调度环境中起名.withIdentity("dateTime")// 即使没有触发器绑定当前JobDetail对象,也不会被删除.storeDurably().build();}// 下面是触发器的声明,也会保存到Spring容器中// 它能够设置job的运行时机@Beanpublic Trigger showTimeTrigger(){System.out.println("Trigger保存到Spring容器中");// 定义Cron表达式CronScheduleBuilder cron=CronScheduleBuilder.cronSchedule("0 47 16 2 9 ?");return TriggerBuilder.newTrigger()// 绑定要运行的JobDetail对象.forJob(showTime())// 为触发器起名.withIdentity("dateTrigger")// 绑定cron表达式.withSchedule(cron).build();}}

Nacos\Seata需要启动

其他服务和软件都可以关闭

然后启动csmall-stock-webapi模块

观察控制台输出

3.6课上作业

在csmall-stock项目中写

利用Quart实现

每隔2分钟运行一次添加库存的操作,PC100号商品的库存添加10

public class QuartzAddStock implements Job {@Autowiredprivate IStockService stockService;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {StockReduceCountDTO countDTO=new StockReduceCountDTO();countDTO.setCommodityCode("PC100");countDTO.setReduceCount(-10);stockService.reduceCommodityCount(countDTO);System.out.println("定时增加库存完成!");}}

@Beanpublic JobDetail addStock(){return JobBuilder.newJob(QuartzAddStock.class).withIdentity("addStock").storeDurably().build();}@Beanpublic Trigger addStockTrigger(){CronScheduleBuilder cron=CronScheduleBuilder.cronSchedule("0 0/2 * * * ?");return TriggerBuilder.newTrigger().forJob(addStock()).withIdentity("addStockTrigger").withSchedule(cron).build();}

4.Redis 强化

4.1缓存使用原则

什么时候,什么样的数据能够保存在Redis中?

1.数据量不能太大

2.使用越频繁,Redis保存这个数据越值得

3.保存在Redis中的数据一般不会是数据库中频繁修改的

4.2缓存淘汰策略

Redis将数据保存在内存中,内存的容量是有限的

如果Redis服务器的内存已经全满,现在还需要向Redis中保存新的数据,如何操作,就是缓存淘汰策略

noeviction:返回错误**(默认)**

如果我们不想让它发生错误,就可以设置它将满足某些条件的信息删除后,再将新的信息保存

allkeys-random:所有数据中随机删除数据volatile-random:有过期时间的数据中随机删除数据volatile-ttl:删除剩余有效时间最少的数据allkeys-lru:所有数据中删除上次使用时间距离现在最久的数据volatile-lru:有过期时间的数据中删除上次使用时间距离现在最久的数据allkeys-lfu:所有数据中删除使用频率最少的volatile-lfu:有过期时间的数据中删除使用频率最少的

如果觉得《JSD-2204-(业务逻辑开发)-更新订单状态的功能-搜索功能-Quartz-Day12》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。