目录
学习目标:
客户流失实现和客户流失页面和客户暂缓流失列表的增删改查
学习内容:
客户流失实现:
客户流失的思想:
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关键字
解决方法:
客户流失管理和暂缓流失列表:
思想:
可参考客户的开发计划
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("确认流失成功!");
}