目录
学习目标:
客户流失实现和客户流失页面和客户暂缓流失列表的增删改查
学习内容:
客户流失实现:
客户流失的思想:
流失客户定义: 客户自创建起,超过六个月未域企业产生任何订单或者客户最后下单日期距离现在超过六个月的客户定义为流失客户 规则分析: 1.客户数据录入到系统的时间距离现在的时间超过六个月 (前提条件) 2.客户与公司没有产生(已支付)订单记录||客户最后下单时间距离现在时间超过六个月 2.1查询没有订单记录的客户 2.2 查询最后的订单时间距离现在超过六个月的客户 实现思路: 查询2.1与2.2的客户并去除重复的记录 具体实现: 用反向查询来等级啊与查询2.1和2.2的记录 查询产生过订单记录并且最后的下单时间距离现在时间不超过六个与的客户,然后再从客户表中排除这些客户 ps:在(1)的基础上满足(2)的情况 更新客户流失状态 1.查询待流失的客户数据 2.将流失客户数据批量提案极爱到客户流失表中 3.批量更新客户的流失状态 0=正常客户 1=流失客户
dao层
根据思路来书写查询语句
//查询带流失的客户
List<Customer> queryLossCustomers();
//通过客户ID批量跟新客户流失状态
int updateCustomerStateByIds(List<Integer> lossCustomerIds);
//通过客户ID查询最后一条订单记录
CustomerOrder queryLossCustomerOrderByCustomerId(Integer id);
<!-- 查询待流失的客户 -->
<select id="queryLossCustomers" resultType="com.xxxx.crm.vo.Customer">
SELECT
*
FROM
t_customer c
WHERE
is_valid = 1
AND state = 0
AND DATE_ADD(
c.create_date,
INTERVAL 6 MONTH
) < NOW()
AND c.id NOT IN (
SELECT DISTINCT
cus_id
FROM
t_customer_order o
WHERE
is_valid = 1
AND state = 1
AND DATE_ADD(
o.order_date,
INTERVAL 6 MONTH
) > NOW()
)
</select>
<!-- 批量跟新客户流失状态 -->
<update id="updateCustomerStateByIds" >
update
t_customer
set
state = 1
where
id in
<foreach collection="list" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</update>
<!-- 查询指定客户最后一条订单记录 -->
<select id="queryLossCustomerOrderByCustomerId" parameterType="int" resultType="com.xxxx.crm.vo.CustomerOrder">
SELECT
*
FROM
t_customer_order
WHERE
is_valid = 1
AND cus_id = {customerId}
ORDER BY
order_date DESC
LIMIT 1
</select>
<!-- 批量添加 -->
<insert id="insertBatch">
insert into
t_customer_loss (cus_no, cus_name, cus_manager, last_order_time, confirm_loss_time, state, loss_reason, is_valid, create_date, update_date)
values
<foreach collection="list" separator="," item="item">
(#{item.cusNo},#{item.cusName},#{item.cusManager},#{item.lastOrderTime},#{item.confirmLossTime},#{item.state},#{item.lossReason},1,now(),now())
</foreach>
</insert>
service层
先把所有的流失客户查出来,然后再把这些数据放到流失表中,最后跟新客户状态为流失
/**
* 更新客户流失状态
* 1.查询待流失的客户数据
* 2.将流失客户数据批量提案极爱到客户流失表中
* 3.批量更新客户的流失状态 0=正常客户 1=流失客户
* @author QQ星
*
* @param
* @return void
* @Date 2022/4/20 21:45
*/
@Transactional(propagation = Propagation.REQUIRED)
public void updateCustomerState(){
/* 1.查询待流失的客户数据 */
List<Customer> lossCustomerList = customerMapper.queryLossCustomers();
/* 2.将流失客户数据批量添加到客户流失表中 */
//判断流失客户数据是否存在
if (lossCustomerList !=null && lossCustomerList.size()>0){
//定义集合 用来接受流失客户ID
List<Integer> lossCustomerIds = new ArrayList<>();
//定义流失客户列表
List<CustomerLoss> customerLossList = new ArrayList<>();
//遍历查询到的流失客户数据
lossCustomerList.forEach(customer -> {
// 定义流失客户对象
CustomerLoss customerLoss = new CustomerLoss();
// 创建时间 系统当前时间
customerLoss.setCreateDate(new Date());
// 客户经理
customerLoss.setCusManager(customer.getCusManager());
// 客户名称
customerLoss.setCusName(customer.getName());
// 客户编号
customerLoss.setCusNo(customer.getKhno());
// 是否有效 1=有效
customerLoss.setIsValid(1);
// 修改时间 系统当前时间
customerLoss.setUpdateDate(new Date());
// 客户流失状态 0=暂缓流失状态 1=确认流失状态
customerLoss.setState(0);
//客户最后下单时间
//通过客户ID查询最后的订单记录(最后一条订单记录)
CustomerOrder customerOrder = customerOrderMapper.queryLossCustomerOrderByCustomerId(customer.getId());
//判断客户订单是否存在,如果存在,则设置最后下单时间
if(customerOrder !=null){
customerLoss.setLastOrderTime(customerOrder.getOrderDate());
}
//把流失客户对象设置到对应的集合中
customerLossList.add(customerLoss);
//将流失客户id设置到哦对应的集合里
lossCustomerIds.add(customer.getId());
});
//批量添加流失客户记录
AssertUtil.isTrue(customerLossMapper.inserBatch(customerLossList) !=customerLossList.size(),"客户流失数据转移失败");
/* 3.批量更新客户的流失状态 */
AssertUtil.isTrue(customerMapper.updateCustomerStateByIds(lossCustomerIds)!=lossCustomerIds.size(),"更新客户失败");
}
}
}
出现问题:
每次都需要把数据放到loss表中,那万一有傻呗天天刷新天天放数据效率太低,根据流失客户比较久的这个特性来设置一个任务来定时任务
/**
* 定时任务
* @author QQ星
*
* @Date 2022/4/21 23:30
*/
@Component
public class JobTask {
@Resource
private CustomerService customerService;
/**
* 每月最后一天的23:42分执行
* @author QQ星
*
* @param
* @return void
* @Date 2022/4/21 23:44
*/
//测试是否有效
//@Scheduled(cron = "0/2 * * * * ?")
@Scheduled(cron = "0 42 23 L * ? ")
public void job(){
System.out.println("定时任务开始执行 --> " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//调用需要被定时执行的方法
customerService.updateCustomerState();
}
}
遇到问题:
Spring默认定时@Scheduled不支持L关键字
解决方法:
客户流失管理和暂缓流失列表:
思想:
添加暂缓数据 1. 参数校验 流失客户ID lossId 非空,数据存在 暂缓措施内容 measure 非空 2. 设置参数的默认值 是否有效 默认有效,1 创建时间 系统当前时间 修改时间 系统当前时间 3. 执行添加操作,判断受影响的行数 修改暂缓数据 1. 参数校验 主键ID id 非空,数据存在 流失客户ID lossId 非空,数据存在 暂缓措施内容 measure 非空 2. 设置参数的默认值 修改时间 系统当前时间 3. 执行修改操作,判断受影响的行数 删除暂缓数据 1. 判断id是否为空,且数据存在 2. 设置isvalid为0 3. 执行更新操作,判断受影响的行数 更新流失客户的流失状态 1. 参数校验 判断id非空且对应的数据存在 流失原因非空 2. 设置参数的默认值 设置流失状态 state=1 0=暂缓流失,1=确认流失 流失原因 客户流失时间 系统当前时间 更新时间 系统当前时间 3. 执行更新操作,判断受影响的行数
可参考客户的开发计划
DAO层
加入两个多条件查询,用来查询列表
<!-- 多条件查询 -->
<select id="selectByParams" parameterType="com.xxxx.crm.query.CustomerLossQuery" resultType="com.xxxx.crm.vo.CustomerLoss">
select
<include refid="Base_Column_List"></include>
from
t_customer_loss
<where>
is_valid = 1
<if test="null != customerNo and customerNo != ''">
and cus_no = #{customerNo}
</if>
<if test="null != customerName and customerName != ''">
and cus_name like concat('%',#{customerName},'%')
</if>
<if test="null != state">
and state = #{state}
</if>
</where>
</select>
<!-- 多条件查询 -->
<select id="selectByParams" parameterType="com.xxxx.crm.query.CustomerReprieveQuery" resultType="com.xxxx.crm.vo.CustomerReprieve">
select
<include refid="Base_Column_List"></include>
from
t_customer_reprieve
<where>
is_valid = 1
<if test="null != lossId">
and loss_id = #{lossId}
</if>
</where>
</select>
service层
CustomerLoss层只有一个分页查询与CustomerReprieve中分页查询一样,创建两个query供前端传给后端即可
public class CustomerLossQuery extends BaseQuery {
// 客户编号
private String customerNo;
// 客户名称
private String customerName;
// 流失状态 0=暂缓流失状态 1=确认流失状态
private Integer state;
public String getCustomerNo() {
return customerNo;
}
public void setCustomerNo(String customerNo) {
this.customerNo = customerNo;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
}
public class CustomerReprieveQuery extends BaseQuery {
//流失客户Id
private Integer lossId;
public Integer getLossId() {
return lossId;
}
public void setLossId(Integer lossId) {
this.lossId = lossId;
}
}
@Service
public class CustomerReprieveService extends BaseService<CustomerReprieve,Integer> {
@Resource
private CustomerReprieveMapper customerReprieveMapper;
@Resource
private CustomerLossMapper customerLossMapper;
/**
* 分页查询流失客户暂缓操作的列表
* @author QQ星
*
* @param customerReprieveQuery
* @return java.util.Map<java.lang.String,java.lang.Object>
* @Date 2022/4/26 22:06
*/
public Map<String, Object> queryCustomerReprieveByParams(CustomerReprieveQuery customerReprieveQuery) {
Map<String, Object> map = new HashMap<>();
// 开启分页
PageHelper.startPage(customerReprieveQuery.getPage(), customerReprieveQuery.getLimit());
// 得到对应分页对象
PageInfo<CustomerReprieve> pageInfo = new PageInfo<>(customerReprieveMapper.selectByParams(customerReprieveQuery));
// 设置map对象
map.put("code",0);
map.put("msg","success");
map.put("count",pageInfo.getTotal());
// 设置分页好的列表
map.put("data",pageInfo.getList());
return map;
}
/**
* 添加暂缓数据
* 1. 参数校验
* 流失客户ID lossId
* 非空,数据存在
* 暂缓措施内容 measure
* 非空
* 2. 设置参数的默认值
* 是否有效
* 默认有效,1
* 创建时间
* 系统当前时间
* 修改时间
* 系统当前时间
* 3. 执行添加操作,判断受影响的行数
* @author QQ星
*
* @param customerReprieve
* @return void
* @Date 2022/4/26 22:55
*/
@Transactional(propagation = Propagation.REQUIRED)
public void addCustomerRepr(CustomerReprieve customerReprieve) {
/* 1. 参数校验 */
checkParams(customerReprieve.getLossId(), customerReprieve.getMeasure());
/* 2. 设置参数的默认值 */
customerReprieve.setIsValid(1);
customerReprieve.setCreateDate(new Date());
customerReprieve.setUpdateDate(new Date());
/* 3. 执行添加操作,判断受影响的行数 */
AssertUtil.isTrue(customerReprieveMapper.insertSelective(customerReprieve) < 1, "添加暂缓数据失败!");
}
/**
* 参数校检
* @author QQ星
*
* @param lossId
* @param measure
* @return void
* @Date 2022/4/26 23:01
*/
private void checkParams(Integer lossId, String measure) {
// 流失客户ID lossId 非空,数据存在
AssertUtil.isTrue(null == lossId
|| customerLossMapper.selectByPrimaryKey(lossId) == null, "流失客户记录不存在!");
// 暂缓措施内容 measure 非空
AssertUtil.isTrue(StringUtils.isBlank(measure), "暂缓措施内容不能为空!");
}
/**
* 修改暂缓数据
* 1. 参数校验
* 主键ID id
* 非空,数据存在
* 流失客户ID lossId
* 非空,数据存在
* 暂缓措施内容 measure
* 非空
* 2. 设置参数的默认值
* 修改时间
* 系统当前时间
* 3. 执行修改操作,判断受影响的行数
* @author QQ星
*
* @param customerReprieve
* @return void
* @Date 2022/4/26 23:14
*/
@Transactional(propagation = Propagation.REQUIRED)
public void updateCustomerRepr(CustomerReprieve customerReprieve) {
/* 1. 参数校验 */
// 主键ID id
AssertUtil.isTrue(null == customerReprieve.getId()
|| customerReprieveMapper.selectByPrimaryKey(customerReprieve.getId()) == null, "待更新记录不存在!");
// 参数校验
checkParams(customerReprieve.getLossId(), customerReprieve.getMeasure());
/* 2. 设置参数的默认值 */
customerReprieve.setUpdateDate(new Date());
/* 3. 执行修改操作,判断受影响的行数 */
AssertUtil.isTrue(customerReprieveMapper.updateByPrimaryKeySelective(customerReprieve) < 1, "修改暂缓数据失败!");
}
/**
* 删除暂缓数据
* 1. 判断id是否为空,且数据存在
* 2. 设置isvalid为0
* 3. 执行更新操作,判断受影响的行数
* @author QQ星
*
* @param id
* @return void
* @Date 2022/4/26 23:35
*/
@Transactional(propagation = Propagation.REQUIRED)
public void deleteCustomerRepr(Integer id) {
// 判断id是否为空
AssertUtil.isTrue(null == id, "待删除记录不存在!");
// 通过id查询暂缓数据
CustomerReprieve customerReprieve = customerReprieveMapper.selectByPrimaryKey(id);
// 判断数据是否存在
AssertUtil.isTrue(null == customerReprieve, "待删除记录不存在!");
// 设置isValid
customerReprieve.setIsValid(0);
customerReprieve.setUpdateDate(new Date());
// 执行更新操作,判断受影响的行数
AssertUtil.isTrue(customerReprieveMapper.updateByPrimaryKeySelective(customerReprieve) < 1, "删除暂缓数据失败!");
}
}
Controller层
在CustomerLossController层中书写首页和流失客户列表加上打开添加暂缓按钮的界面
@RequestMapping("customer_loss")
@Controller
public class CustomerLossController extends BaseController {
@Resource
CustomerLossService customerLossService;
/**
* 进入客户流失管理页面
* @author QQ星
*
* @param
* @return java.lang.String
* @Date 2022/4/26 0:27
*/
@RequestMapping("index")
public String index(){
return "customerLoss/customer_loss";
}
/**
* 分页条件查询流失客户列表
* @author QQ星
*
* @param customerLossQuery
* @return java.util.Map<java.lang.String,java.lang.Object>
* @Date 2022/4/26 1:14
*/
@RequestMapping("list")
@ResponseBody
public Map<String, Object> queryCustomerLossByParams(CustomerLossQuery customerLossQuery) {
return customerLossService.queryCustomerLossByParams(customerLossQuery);
}
/**
* 打开添加暂缓页面
* @author QQ星
*
* @param lossId
* @param model
* @return java.lang.String
* @Date 2022/4/26 21:49
*/
@RequestMapping("toCustomerLossPage")
public String toCustomerLossPage(Integer lossId, Model model) {
// 通过流失客户的ID查询对应流失客户的记录
CustomerLoss customerLoss = customerLossService.selectByPrimaryKey(lossId);
// 将流失客户对应的数据存到请求域中
model.addAttribute("customerLoss", customerLoss);
return "customerLoss/customer_rep";
}
}
在CustomerReprieveController层中书写增删改查操做以及打开添加删除页面
@RequestMapping("customer_rep")
@Controller
public class CustomerReprieveController extends BaseController {
@Resource
private CustomerReprieveService customerReprieveService;
/**
* 分页查询流失客户暂缓操作列表
* @author QQ星
*
* @param customerReprieveQuery
* @return java.util.Map<java.lang.String,java.lang.Object>
* @Date 2022/4/26 22:05
*/
@RequestMapping("list")
@ResponseBody
public Map<String, Object> queryCustomerReprieveByParams(CustomerReprieveQuery customerReprieveQuery) {
return customerReprieveService.queryCustomerReprieveByParams(customerReprieveQuery);
}
/**
* 添加暂缓数据
* @author QQ星
*
* @param customerReprieve
* @return com.xxxx.crm.base.ResultInfo
* @Date 2022/4/26 22:51
*/
@PostMapping("add")
@ResponseBody
public ResultInfo addCustomerRepr(CustomerReprieve customerReprieve) {
customerReprieveService.addCustomerRepr(customerReprieve);
return success("添加暂缓数据成功!");
}
/**
* 跟新暂缓数据
* @author QQ星
*
* @param customerReprieve
* @return com.xxxx.crm.base.ResultInfo
* @Date 2022/4/26 23:11
*/
@PostMapping("update")
@ResponseBody
public ResultInfo updateCustomerRepr(CustomerReprieve customerReprieve) {
customerReprieveService.updateCustomerRepr(customerReprieve);
return success("修改暂缓数据成功!");
}
/**
* 打开添加/修改暂缓数据的页面
* @author QQ星
*
* @param lossId
* @param request
* @param id
* @return java.lang.String
* @Date 2022/4/26 23:25
*/
@RequestMapping("toAddOrUpdateCustomerReprPage")
public String toAddOrUpdateCustomerReprPage(Integer lossId, HttpServletRequest request, Integer id) {
// 将流失客户ID存到作用域中
request.setAttribute("lossId", lossId);
// 判断ID是否为空
if (id != null) {
// 通过主键ID查询暂缓数据
CustomerReprieve customerRep = customerReprieveService.selectByPrimaryKey(id);
// 设置到请求域中
request.setAttribute("customerRep", customerRep);
}
return "customerLoss/customer_rep_add_update";
}
/**
* 删除暂缓数据
* @author QQ星
*
* @param id
* @return com.xxxx.crm.base.ResultInfo
* @Date 2022/4/26 23:28
*/
@PostMapping("delete")
@ResponseBody
public ResultInfo updateCustomerRepr(Integer id) {
customerReprieveService.deleteCustomerRepr(id);
return success("删除暂缓数据成功!");
}
}
确认流失操作
service层:
/**
* 更新流失客户的流失状态
* 1. 参数校验
* 判断id非空且对应的数据存在
* 流失原因非空
* 2. 设置参数的默认值
* 设置流失状态 state=1 0=暂缓流失,1=确认流失
* 流失原因
* 客户流失时间 系统当前时间
* 更新时间 系统当前时间
* 3. 执行更新操作,判断受影响的行数
* @author QQ星
*
* @param id
* @param lossReason
* @return void
* @Date 2022/4/26 00:30
*/
@Transactional(propagation = Propagation.REQUIRED)
public void updateCustomerLossStateById(Integer id, String lossReason) {
/* 1. 参数校验 */
// 判断id非空
AssertUtil.isTrue(null == id, "待确认流失的客户不存在!");
// 通过id查询流失客户的记录
CustomerLoss customerLoss = customerLossMapper.selectByPrimaryKey(id);
// 判断流失客户记录是否存在
AssertUtil.isTrue(null == customerLoss, "待确认流失的客户不存在!");
// 流失原因非空
AssertUtil.isTrue(StringUtils.isBlank(lossReason), "流失原因不能为空!");
/* 2. 设置参数的默认值 */
// 设置流失状态 state=1 0=暂缓流失,1=确认流失
customerLoss.setState(1);
// 设置流失原因
customerLoss.setLossReason(lossReason);
// 客户流失时间 系统当前时间
customerLoss.setConfirmLossTime(new Date());
// 更新时间 系统当前时间
customerLoss.setUpdateDate(new Date());
/* 3. 执行更新操作,判断受影响的行数 */
AssertUtil.isTrue(customerLossMapper.updateByPrimaryKeySelective(customerLoss) < 1, "确认流失失败!");
}
controller层:
/**
* 确认流失
* @author QQ星
*
* @param id
* @param lossReason
* @return com.xxxx.crm.base.ResultInfo
* @Date 2022/4/26 00:11
*/
@PostMapping("updateCustomerLossStateById")
@ResponseBody
public ResultInfo updateCustomerLossStateById(Integer id, String lossReason) {
customerLossService.updateCustomerLossStateById(id, lossReason);
return success("确认流失成功!");
}