牛刀小试.开胃菜·代码思路·9-7
牛刀小试.开胃菜·代码思路·9-7
当线索转商机时,使用规则来进行自动分配,没有按照规则来进行自动分配
bug描述
bug描述
1️⃣ ⚠️当线索转商机时,使用规则来进行自动分配,没有按照规则来进行自动分配
期望效果:意向学科是java的自动分配给lisi,意向学科是前端的自动分配给lisi1

2️⃣ ⚠️点击转商机

我们期望按照规则来进行自动分配
规则有:
- 意向学科是java的分配给lisi商机专员
- 意向学科是前端的分配给lisi1商机专员
- 如果没有匹配到规则则不分配等待管理员和主管来进行分配
3️⃣ ⚠️现象:我们使用lifeng即主管来进行登录,

思路:
思路:
- 通过F12判断在转商机的时候访问了什么接口
- 通过全局搜索定位后端代码的位置
- 找到对应分配的逻辑在哪
提示:
@Autowired注入的static mapper是否为空
参考AdminStrategy部分的代码,看admin的策略是如何实现的
@ConditionalOnProperty(name = "rule.transfor", havingValue = "rule")
的作用是 读取yml配置文件中的rule.transfor字段里的内容,havingValue = “rule” 就是比较rule.transfor的值是否是rule,如果比对成功,则使用这个类作为接口的实现类
#admin策略 导入和转换全部交由admin来处理,由admin来分配线索和规则
#rule策略 交由规则引擎来处理,项目一阶段不处理,单讲admin模式
rule:
transfor: rule #转商机时的自动分配方式--rule基于规则来分片 admin先分配给管理员再进行二次分配
clue:
import: rule #导入时的自动分配方式--rule基于规则来分片 admin先分配给管理员再进行二次分配
实现步骤:
1.前端触发
通过F12能够看到对应的接口名
Request URL: http://localhost/dev-api/clues/clue/changeBusiness/9672
Request Method: PUT
Status Code: 200 OK
Remote Address: 127.0.0.1:80
Referrer Policy: strict-origin-when-cross-origin
2.定位后端代码
请求的后端接口在TbClueController下:
/**
* 线索转商机
* @param id
* @return
*/
@PreAuthorize("@ss.hasPermi('clues:clue:changeBusiness')")
@Log(title = "线索转商机", businessType = BusinessType.UPDATE)
@PutMapping("/changeBusiness/{id}")
public AjaxResult changeBusiness(@PathVariable Long id) {
return toAjax(tbBusinessService.changeBusiness(id));
}
3.查看对应的逻辑
跟进到service层中TbBusinessServiceImpl
/**
* 转商机的方法
* @param clueId
* @return
*/
@Override
public int changeBusiness(Long clueId) {
//查询出线索对应的数据
TbClue tbClue = tbClueMapper.selectTbClueById(clueId);
//重置状态为转商机
tbClueMapper.resetNextTimeAndStatus(clueId, TbClue.StatusType.TOBUSINESS.getValue());
//构建商机对象
TbBusiness tbBusiness = new TbBusiness();
BeanUtils.copyProperties(tbClue, tbBusiness);
// yg提示:设置跟进状态为未跟进
tbBusiness.setStatus(TbBusiness.StatusType.UNFOLLOWED.getValue());
tbBusiness.setClueId(clueId);
tbBusiness.setNextTime(null);
tbBusiness.setCreateBy(SecurityUtils.getUsername());
Date now=DateUtils.getNowDate();
tbBusiness.setCreateTime(now);
//添加商机数据
int rows = tbBusinessMapper.insertTbBusiness(tbBusiness);
//基于规则来进行分配
Integer transForBusiness = rule.transforBusiness(tbBusiness);
if (transForBusiness != 0) {
return transForBusiness;
} else {
return rows;
}
}
在这里我们看到了我们首先往商机表里添加了一条数据,并且设置了对应的状态是待跟进状态
3.1 策略模式
然后这里有一个rule.transforBusiness
方法传入的是我们的商机对象
我们来看下这个rule是怎么拿到的
/**
* 商机Service业务层处理
* @date 2021-04-25
*/
@Service
public class TbBusinessServiceImpl implements ITbBusinessService {
@Autowired
private Rule rule;
我们发现Rule是我们使用@Autowired
注入进来的
我们点击跟进进入到了Rule里,发现是一个接口,并没有对应的实现逻辑 👈 ⚠️
package com.huike.business.strategy;
import com.huike.business.domain.TbBusiness;
/**
* 线索转商机使用的规则
*/
public interface Rule {
Integer transforBusiness(TbBusiness tbBusiness);
}
而我们点击对应方法去寻找对应的实现类的时候我们发现一共有两个实现类

这两个实现类 这两个实现类都实现了这个接口,而我们是注入的这个接口,具体我们是使用哪个类作为我们的实现类呢?❓
我们进入到具体的实现类总哪个都可以
我们发现每个实现类上都有一个@ConditionalOnProperty
注解
/**
* admin 处理策略
*
* 由admin来处理所有的线索导入和转商机的数据
*
* 全部导入到admin 统一由admin来处理所有的线索
* exchange 转商机的时候统一转换到admin,再由admin来统一分片商机
*/
@ConditionalOnProperty(name = "rule.transfor", havingValue = "admin")
@Service("BusinessAdminStrategy")
public class AdminStrategy implements Rule {
/**
* rule 处理策略
* 根据规则系统自动分配对应的商机专员来处理
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
*/
@ConditionalOnProperty(name = "rule.transfor", havingValue = "rule")
@Service("BussinessruleStrategy")
public class RuleStrategy implements Rule {
@ConditionalOnProperty含义
该注解的主要意义是,读取配置文件application.yml文件中的rule.transfor字段,判断字段的内容与havingValue里的内容进行对比,如果配置的内容与havingValue里的内容相同,则springboot会基于配置的内容,选择对应类作为接口的实现类
4.bug分析
我们回顾我们的bug描述:当线索转商机时,使用规则来进行自动分配,没有按照规则来进行自动分配
注意:是使用规则来进行自动分配 ⚠️
我们来看对应的两个实现类,哪一个是根据规则来进行分配的
我们期望的规则是这样的
- 意向学科是java的分配给lisi商机专员
- 意向学科是前端的分配给lisi1商机专员
- 如果没有匹配到规则则不分配等待管理员和主管来进行分配
很明显RuleStrategy是按照这个规则来进行分配的
package com.huike.business.strategy.impl;
/**
* rule 处理策略
* 根据规则系统自动分配对应的商机专员来处理
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
* 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
*/
@ConditionalOnProperty(name = "rule.transfor", havingValue = "rule")
@Service("BussinessruleStrategy")
public class RuleStrategy implements Rule {
@Autowired
private static TbAssignRecordMapper assignRecordMapper;
@Autowired
private static SysUserMapper userMapper;
@Autowired
private SysDictDataMapper dictDataMapper;
private static SysUser lisi = new SysUser();
private static SysUser lisi1 = new SysUser();
//内存中JAVA学科的内容--提前预加载在内存中
private static SysDictData subjectJAVA = new SysDictData();
//内存中前端学科的内容--提前预加载在内存中
private static SysDictData subjectHtml = new SysDictData();
static{
try{
//空间换时间的方式将数据库中的学科读取到内存中
//预加载学科数据到内存中
List<SysDictData> course_subject = dictDataMapper.selectDictDataByType("course_subject");
for (SysDictData index: course_subject) {
//找到java和前端两个学科对应的数值
if(index.getDictLabel().equals("Java")){
subjectJAVA = index;
}
if(index.getDictLabel().equals("前端")){
subjectHtml = index;
}
}
//预加载lisi和lisi1的数据到内存中
lisi = userMapper.selectUserByName("lisi");
lisi1 = userMapper.selectUserByName("lisi1");
}catch (Exception e){
}
}
/**
* 定义一些规则来自动分配
*
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
* 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
* @param tbBusiness
* @return
*/
@Override
public Integer transforBusiness(TbBusiness tbBusiness) {
//注意处理空指针的问题
if(tbBusiness.getSubject().equals(subjectJAVA.getDictLabel())){
//如果意向学科是java--分配给lisi
return distribute(tbBusiness,lisi);
}else if(tbBusiness.getSubject().equals(subjectHtml.getDictLabel())){
//如果意向学科是前端--分配给lisi1
return distribute(tbBusiness,lisi1);
}else{
//不进行分配,直接添加-----即待分配状态
return 1;
}
}
/**
* 分配商机给具体用户的方法
* @param business
* @param user
* @return
*/
private int distribute(TbBusiness business,SysUser user){
TbAssignRecord tbAssignRecord =new TbAssignRecord();
tbAssignRecord.setAssignId(business.getId());
tbAssignRecord.setUserId(user.getUserId());
tbAssignRecord.setUserName(user.getUserName());
tbAssignRecord.setDeptId(user.getDeptId());
tbAssignRecord.setCreateBy(SecurityUtils.getUsername());
tbAssignRecord.setCreateTime(DateUtils.getNowDate());
tbAssignRecord.setType(TbAssignRecord.RecordType.BUSNIESS.getValue());
business.setNextTime(null);
return assignRecordMapper.insertAssignRecord(tbAssignRecord);
}
}
那么由于havingValue里的内容是rule那么我们在application.yml里配置的rule.transfor就应该是rule
#admin策略 导入和转换全部交由admin来处理,由admin来分配线索和规则
#rule策略 交由规则引擎来处理,项目一阶段不处理,单讲admin模式
rule:
transfor: rule #转商机时的自动分配方式--rule基于规则来分配 admin先分配给管理员再进行二次分配
clue:
import: rule #导入时的自动分配方式--rule基于规则来分配 admin先分配给管理员再进行二次分配
5.规则逻辑
5.1规则介绍
规则内容如下:
- 根据规则系统自动分配对应的商机专员来处理
- 1.意向学科是java的分配给lisi商机专员
- 2.意向学科是前端的分配给lisi1商机专员
- 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
5.2 空间换时间
@Autowired
private static TbAssignRecordMapper assignRecordMapper;
@Autowired
private static SysUserMapper userMapper;
@Autowired
private SysDictDataMapper dictDataMapper;
private static SysUser lisi = new SysUser();
private static SysUser lisi1 = new SysUser();
//内存中JAVA学科的内容--提前预加载在内存中
private static SysDictData subjectJAVA = new SysDictData();
//内存中前端学科的内容--提前预加载在内存中
private static SysDictData subjectHtml = new SysDictData();
static{
try{
//空间换时间的方式将数据库中的学科读取到内存中
//预加载学科数据到内存中
List<SysDictData> course_subject = dictDataMapper.selectDictDataByType("course_subject");
for (SysDictData index: course_subject) {
//找到java和前端两个学科对应的数值
if(index.getDictLabel().equals("Java")){
subjectJAVA = index;
}
if(index.getDictLabel().equals("前端")){
subjectHtml = index;
}
}
//预加载lisi和lisi1的数据到内存中
lisi = userMapper.selectUserByName("lisi");
lisi1 = userMapper.selectUserByName("lisi1");
}catch (Exception e){
}
}
在该类的成员位置定义了很多的静态资源
并且使用了一个静态代码块的形式来对静态资源进行赋值,提前预加载了
前端,JAVA到成员属性位置,提前加载了lisi和lisi1用户到成员属性位置
这些静态资源存在类的成员位置
static静态资源具有哪些特点:
在初次类加载时,就会被初始化执行一次(且一次),因此可以利用静态代码块来优化程序的性能
为所有实例对象所共享
注意:这种通过类或项目启动提前预加载一部分数据到内存或缓存中的方式叫空间换时间
指的是牺牲一部分内存空间,以换取代码的执行性能的提升
5.3静态资源注入的问题
原作者在查询lisi
和lisi1
的数据的时候是通过数据库进行查询的,这里查询能否正常查询呢?
带着这些疑问我们可以进行debug调试一下,由于是静态资源
是随着类的加载而加载的,我们将断点打在
静态代码块的第一行,然后重启项目,即可进入断点(为什么?@ConditionalOnProperty(name = "rule.transfor", havingValue = "rule"),springboot会将RuleStrategy作为Rule的实现类进行加载,只要这个类被加载了,那么就会执行静态代码块里的内容)

lisi和lisi1的数据就不可能通过静态代码块的方式进行赋值,怎么解决?
5.4静态资源注入的问题解决方案
- 为什么我们使用@Autowire注入静态资源会失败
静态成员属于类的,当类加载器加载静态变量时,Spring上下文尚未加载。所以类加载器不会在bean中正确注入静态类,并且会失败
- 使用@PostConstruct来实现空间换时间
2.1 参考同类代码的编写方式:
如果你完全不清楚这一块该怎么做,通过代码的注释和我们任务中给的提示,可以去找AdminStrategy这个实现类,看这部分代码是怎么做的,就会发现@PostConstruct注解
2.2 了解@PostConstruct
首先@PostConstruct注解是JAVA提供的,他的作用是用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
5.5 利用@PostConstruct预加载数据
使用@PostConstruct修饰一个方法,在这个方法中去查询数据库,将lisi和lisi1的数据提前加载到内存中
类:RuleStrategy
@Autowired
private TbAssignRecordMapper assignRecordMapper;
@Autowired
private SysUserMapper userMapper;
@Autowired
private SysDictDataMapper dictDataMapper;
private static SysUser lisi = new SysUser();
private static SysUser lisi1 = new SysUser();
//内存中JAVA学科的内容--提前预加载在内存中
private static SysDictData subjectJAVA = new SysDictData();
//内存中前端学科的内容--提前预加载在内存中
private static SysDictData subjectHtml = new SysDictData();
@PostConstruct
public void init() {
//空间换时间的方式将数据库中的学科读取到内存中
//预加载学科数据到内存中
List<SysDictData> course_subject = dictDataMapper.selectDictDataByType("course_subject");
for (SysDictData index: course_subject) {
//找到java和前端两个学科对应的数值
if(index.getDictLabel().equals("Java")){
subjectJAVA = index;
}
if(index.getDictLabel().equals("前端")){
subjectHtml = index;
}
}
//预加载lisi和lisi1的数据到内存中
lisi = userMapper.selectUserByName("lisi");
lisi1 = userMapper.selectUserByName("lisi1");
}
到此完成数据的预加载步骤
5.6规则执行
规则的执行逻辑如下代码,主要是根据商机对象里的信息,与内存中的值进行对比
类:RuleStrategy
/**
* 定义一些规则来自动分配
*
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
* 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
* @param tbBusiness
* @return
*/
@Override
public Integer transforBusiness(TbBusiness tbBusiness) {
//注意处理空指针的问题
if(tbBusiness.getSubject().equals(subjectJAVA.getDictLabel())){
//如果意向学科是java--分配给lisi
return distribute(tbBusiness,lisi);
}else if(tbBusiness.getSubject().equals(subjectHtml.getDictLabel())){
//如果意向学科是前端--分配给lisi1
return distribute(tbBusiness,lisi1);
}else{
//不进行分配,直接添加-----即待分配状态
return 1;
}
}
/**
* 分配商机给具体用户的方法
* @param business
* @param user
* @return
*/
private int distribute(TbBusiness business,SysUser user){
TbAssignRecord tbAssignRecord =new TbAssignRecord();
tbAssignRecord.setAssignId(business.getId());
tbAssignRecord.setUserId(user.getUserId());
tbAssignRecord.setUserName(user.getUserName());
tbAssignRecord.setDeptId(user.getDeptId());
tbAssignRecord.setCreateBy(SecurityUtils.getUsername());
tbAssignRecord.setCreateTime(DateUtils.getNowDate());
tbAssignRecord.setType(TbAssignRecord.RecordType.BUSNIESS.getValue());
business.setNextTime(null);
return assignRecordMapper.insertAssignRecord(tbAssignRecord);
}
在transforBusiness方法中,对比了学科是否是JAVA还是前端,如果是JAVA调用distribute方法
将其分配给lisi,如果是lisi1则调用distribute方法分配给lisi1,如果都没有命中则什么也不作直接返回,
//注意处理空指针的问题
if(tbBusiness.getSubject().equals(subjectJAVA.getDictLabel())){
//如果意向学科是java--分配给lisi
return distribute(tbBusiness,lisi);
}else if(tbBusiness.getSubject().equals(subjectHtml.getDictLabel())){
//如果意向学科是前端--分配给lisi1
return distribute(tbBusiness,lisi1);
}else{
//不进行分配,不添加分配记录-----即待分配状态
return 1;
}
5.7 规则执行部分bug分析
注意:
1.我们比较的顺序应该调换一下,因为tbBusiness有可能为空 null.equals(xxx)会报错
2.比较的内容也有问题
商机对象中的学科是字典值,而学科对象其实是一个字典值
对应的表是sys_dict_data

而我们getDictLabel获取的是字典的中文名那么那中文名和字典里的值进行对比是永远也对比不上的
需要调整
类:RuleStrategy
/**
* 定义一些规则来自动分配
*
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
* 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
* @param tbBusiness
* @return
*/
@Override
public Integer transforBusiness(TbBusiness tbBusiness) {
//注意处理空指针的问题
if(subjectJAVA.getDictValue().equals(tbBusiness.getSubject())){
//如果意向学科是java--分配给lisi
return distribute(tbBusiness,lisi);
}else if(subjectHtml.getDictValue().equals(tbBusiness.getSubject())){
//如果意向学科是前端--分配给lisi1
return distribute(tbBusiness,lisi1);
}else{
//不进行分配,直接添加-----即待分配状态
return 1;
}
}
完整的RuleStrategy
package com.huike.business.strategy.impl;
import com.huike.business.domain.TbBusiness;
import com.huike.business.strategy.Rule;
import com.huike.clues.domain.TbAssignRecord;
import com.huike.clues.mapper.SysDictDataMapper;
import com.huike.clues.mapper.SysUserMapper;
import com.huike.clues.mapper.TbAssignRecordMapper;
import com.huike.clues.service.ITbCourseService;
import com.huike.common.core.domain.entity.SysDictData;
import com.huike.common.core.domain.entity.SysUser;
import com.huike.common.utils.DateUtils;
import com.huike.common.utils.DictUtils;
import com.huike.common.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* rule 处理策略
* 根据规则系统自动分配对应的商机专员来处理
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
*/
@ConditionalOnProperty(name = "rule.transfor", havingValue = "rule")
@Service("BussinessruleStrategy")
public class RuleStrategy implements Rule {
@Autowired
private TbAssignRecordMapper assignRecordMapper;
@Autowired
private SysUserMapper userMapper;
@Autowired
private SysDictDataMapper dictDataMapper;
private static SysUser lisi = new SysUser();
private static SysUser lisi1 = new SysUser();
//内存中JAVA学科的内容--提前预加载在内存中
private static SysDictData subjectJAVA = new SysDictData();
//内存中前端学科的内容--提前预加载在内存中
private static SysDictData subjectHtml = new SysDictData();
@PostConstruct
public void init() {
//空间换时间的方式将数据库中的学科读取到内存中
//预加载学科数据到内存中
List<SysDictData> course_subject = dictDataMapper.selectDictDataByType("course_subject");
for (SysDictData index: course_subject) {
//找到java和前端两个学科对应的数值
if(index.getDictLabel().equals("Java")){
subjectJAVA = index;
}
if(index.getDictLabel().equals("前端")){
subjectHtml = index;
}
}
//预加载lisi和lisi1的数据到内存中
lisi = userMapper.selectUserByName("lisi");
lisi1 = userMapper.selectUserByName("lisi1");
}
/**
* 定义一些规则来自动分配
*
* 1.意向学科是java的分配给lisi商机专员
* 2.意向学科是前端的分配给lisi1商机专员
* 3.如果没有匹配到规则则不分配等待管理员和主管来进行分配
* @param tbBusiness
* @return
*/
@Override
public Integer transforBusiness(TbBusiness tbBusiness) {
//注意处理空指针的问题
if(subjectJAVA.getDictValue().equals(tbBusiness.getSubject())){
//如果意向学科是java--分配给lisi
return distribute(tbBusiness,lisi);
}else if(subjectHtml.getDictValue().equals(tbBusiness.getSubject())){
//如果意向学科是前端--分配给lisi1
return distribute(tbBusiness,lisi1);
}else{
//不进行分配,直接添加-----即待分配状态
return 1;
}
}
/**
* 分配商机给具体用户的方法
* 这部分代码学生不用研究,未预制bug
* @param business
* @param user
* @return
*/
private int distribute(TbBusiness business,SysUser user){
TbAssignRecord tbAssignRecord =new TbAssignRecord();
tbAssignRecord.setAssignId(business.getId());
tbAssignRecord.setUserId(user.getUserId());
tbAssignRecord.setUserName(user.getUserName());
tbAssignRecord.setDeptId(user.getDeptId());
tbAssignRecord.setCreateBy(SecurityUtils.getUsername());
tbAssignRecord.setCreateTime(DateUtils.getNowDate());
tbAssignRecord.setType(TbAssignRecord.RecordType.BUSNIESS.getValue());
business.setNextTime(null);
return assignRecordMapper.insertAssignRecord(tbAssignRecord);
}
}