• java项目——CRM客户管理系统(SpringBoot+MyBatis)


    目录

    CRM客户管理系统

    本人负责后端的开发(Java)

    一.对CRM的项目的简单描述

    1.什么是CRM

    CRM系统即客户关系管理系统,是指企业用CRM技术来管理与客户之间的关系。他的目标是缩减销售周期和销售成本,增加收入,寻找扩展业务所需的新的市场和渠道以及提高客户的价值,满意度,营利性和忠实度。CRM项目的实施可以分为3步,即应用业务集成。业务诗句分析和决策执行。

    2.CRM开发环境和技术

    <1> 项目业务介绍

    客户关系管理是指企业为提高核心竞争力,利用相应的技术信息以及互联网技术协调企业与顾客间在消费,营销和服务上的交互,从而提升其管理方式,向客户提供创新式的个性化的客户交互和服务的过程,其最终目标是吸引新客户,保留老客户以及将已有客户转为忠实客户,增加市场

    <2>开发环境

    项目名称:CRM客户管理系统
    系统作用:公司客户关系管理,潜在客户开发及订单合同管理
    开发环境:IDEA Windows10 jdk1.8 Maven Mysql8
    需要的工具:postman fiddler抓包工具或浏览器开发者工具

    <3>开发技术

    前端:LayUI freeMaker
    后端:Spring SpringMVC SpringBoot MyBatis Maven MySQL8 Linux CentOS ECharts(折线和饼状图)权限管理 定时任务调度(quartz)CentOS Lombok

    二.项目准备及模块分析

    1.模块分析总览

    1.基础模块:包含系统基本的用户登录,退出,记住我,密码修改等基本操作。

    2.营销管理:
    营销机会管理:企业客户的质询需求所建立的信息录入功能
    客户开发计划:开发计划是根据营销机会而来,对于企业质询的客户,会有相应的销售人员对于该客户
    进行具体的沟通交流,此时对于整个 Crm 系统而言,通过营销开发计划来进行相应的信息管理,提高
    客户的购买企业产品的可能性。

    3.客户管理:
    客户信息管理 :Crm 系统中完整记录客户信息来源的数据、企业与客户交往、客户订单查询等信息录
    入功能,方便企业与客户进行相应的信息交流与后续合作。
    客户流失管理 :Crm 通过一定规则机制所定义的流失客户(无效客户),通过该规则可以有效管理客
    户信息资源,提高营销开发的效率。

    4.服务管理:服务管理是针对客户而开发的功能,针对客户要求,Crm 提供客户相应的信息质询,反馈与投诉功能,
    提高企业对于客户的服务质量。

    5.数据报表:
    Crm 提供的数据报表功能能够帮助企业了解客户整体分布,了解客户开发结果整体信息,从而帮助企业
    整体调整客户开发计划,提高企业的在市场中的竞争力度。

    6.系统管理:系统管理包含常量字典维护工作,以及权限管理模块,Crm 权限管理是基于角色的一种权限控制,基于
    RBAC 实现基于角色的权限控制,通过不同角色的用户登录该系统后展示系统不同的操作功能,从而达
    到对不同角色完成不同操作功能。

    在这里插入图片描述

    2.项目前期环境的搭建

    1.创建SpringBoot项目,导入依赖(见源代码)
    2.在src/main/resources 目录下新建 application.yml 配置文件

    ## 端口号  上下文路径
    server:
      port: 8080
      servlet:
        context-path: /crm
    
    ## 数据源配置
    spring:
      datasource:
        type: com.mchange.v2.c3p0.ComboPooledDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/crm?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: 
        password: 
    
    
      ## freemarker
      freemarker:
        suffix: .ftl
        content-type: text/html
        charset: UTF-8
        template-loader-path: classpath:/views/
    
    
      ## 启用热部署
      devtools:
        restart:
          enabled: true
          additional-paths: src/main/java
    
    ## mybatis 配置
    mybatis:
      mapper-locations: classpath:/mappers/*.xml
      type-aliases-package: org.example.crm.vo;org.example.crm.query;org.example.crm.dto
      configuration:
        map-underscore-to-camel-case: true
    
    ## pageHelper 分页
    pagehelper:
      helper-dialect: mysql
    
    ## 设置 dao 日志打印级别
    logging:
      level:
        org:
          example:
            crm:
              dao: debug
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    3.新建 org.example.crm.controller 包,添加系统登录,主页面转发代码 。
    4.添加静态资源:在 src/main/resources 目录下新建 public 目录,存放系统相关静态资源文件,拷贝静态文件内容到
    public 目录。
    5.添加视图模板:在 src/main/resources 目录下新建 views 目录,添加 index.ftl、main.ftl 等文件。 (具体视图文件详见
    相关目录)
    6.添加启动类:在 org.example.crm 包下新建 Starter.java ,添加启动项目相关代码如下:

    package org.example.crm;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableScheduling;
    
    @SpringBootApplication
    @MapperScan("org.example.crm.dao")
    //启用定时任务
    @EnableScheduling
    public class Starter {
        public static void main(String[] args) {
            SpringApplication.run(Starter.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7.添加Base包:主要用户对Controller,Service Dao层的统一控制,BaseQuery用于控制按条件搜索的对象,ResultInfo是后端返回的对象的统一封装

    项目搭建结构:
    在这里插入图片描述
    8.准备MyBatis代码统一生成工具(generatorConfig.xml)
    这里注意:工具有点缺陷,每次需要改工具作用的表名

    使用mybatis-generator生成Mybatis代码。能够生成 vo 类、能生成 mapper 映射文件(其中包括基本
    的增删改查功能)、能生成 mapper 接口。
    命令: mybatis-generator:generate -e

    
    
    
    
    
        
        
        
    
            
                
                
                
                
            
    
            
            
            
    
            
            
                
            
    
    
    
            
            
                
                
                
                
            
    
    
            
            
                
            
    
    
            
            
                
            
    
    
    
           
            
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    9.导入工具类,主要有:根据Cookie获取作用域,登录成功返回userIdStr加密,Md5协议加密,判断电话号码的格式,userID加解密等
    在这里插入图片描述

    三.项目的正式开发

    主要讲解核心模块的核心代码

    1.用户管理模块

    <1>.表结构分析

    在这里插入图片描述

    <2>.用户登录

    定义UserModel类,用于用户登录成功返回的用户信息,用来设置前端的Cookie

    @Getter
    @Setter
    public class UserVo {
        //private Integer userId;
        //存放在前端cookie中加密后的Id
        private String userIdStr;
        private String userName;
        private String trueName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    设置cookie

    layer.msg("登录成功!", function () {
                            // 判断用户是否选择记住密码(判断复选框是否被选中,如果选中,则设置cookie对象7天生效)
                            if ($("#rememberMe").prop("checked")) {
                                // 选中,则设置cookie对象7天生效
                                // 将用户信息设置到cookie中
                                $.cookie("userIdStr", result.result.userIdStr, {expires: 7});
                                $.cookie("userName", result.result.userName, {expires: 7});
                                $.cookie("trueName", result.result.trueName, {expires: 7});
                            } else {
                                // 将用户信息设置到cookie中
                                $.cookie("userIdStr", result.result.userIdStr);
                                $.cookie("userName", result.result.userName);
                                $.cookie("trueName", result.result.trueName);
                            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    退出登录时,删除前端Cookie即可

    <3>全局统一的异常处理及非法请求的拦截

    (1)统一的异常处理

    全局异常实现思路:
    控制层的方法返回的内容两种情况

    1. 视图:视图异常
    2. Json:方法执行错误 返回错误json信息

    全局异常拦截器的实现,简化了try-catch代码
    实现 HandlerExceptionResolver 接口 ,处理应用程序异常信息

    (2)非法请求拦截

    对于后端菜单资源,这里要求用户必须进行登录来保护 web 资源的安全性,此时引入非法请求拦截功
    能。
    实现思路:
    判断用户是否是登录状态
    获取Cookie对象,解析用户ID的值
    如果用户ID不为空,且在数据库中存在对应的用户记录,表示请求合法
    否则,请求不合法,进行拦截,重定向到登录页面

    定义拦截器:在新建 interceptors 包,创建 NoLoginInterceptor 类,并继承 HandlerInterceptorAdapter 适配器,
    实现拦截器功能。

    /**
     * 非法访问拦截
     * 继承HandlerInterceptorAdapter适配器
     */
    
    public class NoLoginInterceptor extends HandlerInterceptorAdapter {
        @Autowired
        private UserMapper userMapper;
    
        /**
         * 拦截用户是否是登录状态
         * 在目标方法(资源)执行前执行的方法
         * 返回boolean
         * 如果为true,表示目标方法可用被执行
         * 如果为false,表示阻止目标方法执行
         *
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //获取cookie中的用户Id
            Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
            //判断用户Id是否为空,且数据库中是否存在改userId的记录
            if (userId == null || userMapper.selectByPrimaryKey(userId) == null) {
                //抛出未登录异常
                throw new NoLoginException();
            }
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    全局异常类配置:在全局异常处理类中引入未登录异常判断

    /**
     * 全局异常统一处理
     */
    @Component
    public class GlobalExceptionResolver implements HandlerExceptionResolver {
        /**
         * 异常处理方法
         * 方法的返回值:
         * 1. 返回视图
         * 2. 返回数据(JSON数据)
         * 

    * 如何判断方法的返回值? * 通过方法上是否声明@ResponseBody注解 * 如果未声明,则表示返回视图 * 如果声明了,则表示返回数据 * * @param request request请求对象 * @param response response响应对象 * @param handler 方法对象 * @param ex 异常对象 * @return org.springframework.web.servlet.ModelAndView */ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { /** * 非法请求拦截 * 判断是否抛出未登录异常 * 如果抛出该异常,则要求用户登录,重定向跳转到登录页面 */ if (ex instanceof NoLoginException) { // 重定向到登录页面 ModelAndView mv = new ModelAndView("redirect:/index"); return mv; } /** * 设置默认异常处理(返回视图) */ ModelAndView modelAndView = new ModelAndView("error"); // 设置异常信息 modelAndView.addObject("code", 500); modelAndView.addObject("msg", "系统异常,请重试..."); // 判断HandlerMethod if (handler instanceof HandlerMethod) { // 类型转换 HandlerMethod handlerMethod = (HandlerMethod) handler; // 获取方法上声明的@ResponseBody注解对象 ResponseBody responseBody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class); // 判断ResponseBody对象是否为空 (如果对象为空,则表示返回的事视图;如果不为空,则表示返回的事数据) if (responseBody == null) { /** * 方法返回视图 */ // 判断异常类型 if (ex instanceof ParamsException) { ParamsException p = (ParamsException) ex; // 设置异常信息 modelAndView.addObject("code", p.getCode()); modelAndView.addObject("msg", p.getMsg()); } else if (ex instanceof AuthException) { // 认证异常 AuthException a = (AuthException) ex; // 设置异常信息 modelAndView.addObject("code", a.getCode()); modelAndView.addObject("msg", a.getMsg()); } return modelAndView; } else { /** * 方法返回数据 */ // 设置默认的异常处理 ResultInfo resultInfo = new ResultInfo(); resultInfo.setCode(500); resultInfo.setMsg("异常异常,请重试!"); // 判断异常类型是否是自定义异常 if (ex instanceof ParamsException) { ParamsException p = (ParamsException) ex; resultInfo.setCode(p.getCode()); resultInfo.setMsg(p.getMsg()); } else if (ex instanceof AuthException) { // 认证异常 AuthException a = (AuthException) ex; resultInfo.setCode(a.getCode()); resultInfo.setMsg(a.getMsg()); } // 设置响应类型及编码格式(响应JSON格式的数据) response.setContentType("application/json;charset=UTF-8"); // 得到字符输出流 PrintWriter out = null; try { // 得到输出流 out = response.getWriter(); // 将需要返回的对象转换成JOSN格式的字符 String json = JSON.toJSONString(resultInfo); // 输出数据 out.write(json); } catch (IOException e) { e.printStackTrace(); } finally { // 如果对象不为空,则关闭 if (out != null) { out.close(); } } return null; } } return modelAndView; } }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    拦截器生效配置:

    @Configuration//配置类
    public class MvcConfig extends WebMvcConfigurerAdapter {
        @Bean//将方法的返回值交给IOC
        public NoLoginInterceptor noLoginInterceptor() {
            return new NoLoginInterceptor();
        }
    
        /**
         * 添加拦截器
         *
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //需要实现了拦截器功能的实例对象 NoLoginInterceptor
            registry.addInterceptor(noLoginInterceptor())
                    //设置需要被拦截的资源
                    .addPathPatterns("/**")
                    // 设置不需要被拦截的资源
                    .excludePathPatterns("/css/**", "/images/**", "/js/**", "/lib/**")
                    .excludePathPatterns("/index", "/user/login");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    测试拦截效果:
    当 Cookie 中的用户ID不存在时,访问 main 页面,会自动跳转到登录页面

    <3>记住我功能

    记住我功能核心在于当用户上次登录时如果点击了记住我,下次在重新打开浏览器时可以不用选择登
    录,此时可以借助拦截器 + cookie 来实现,当用户在登录时,如果用户点击了记住我功能,默认设置
    cookie存储时间为7天即可。

    2.营销管理模块(CRUD操作,见源码)

    <1>功能开发及表结构分析

    功能开发:
    在这里插入图片描述
    表结构:
    在这里插入图片描述
    这里注意:时间格式化:

     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        @DateTimeFormat(pattern = "yyyy-MM-dd") // 如果传递的参数是Date类型,要求传入的时间字符串的格式
        private Date planDate;
    
    • 1
    • 2
    • 3

    3.权限管理模块(CRUD操作见源码)

    基本概念:RBAC是基于角色的访问控制( Role-Based Access Control )在RBAC中,权限与角色相关联,用户
    通过扮演适当的角色从而得到这些角色的权限。这样管理都是层级相互依赖的,权限赋予给角色,角色
    又赋予用户,这样的权限设计很清楚,管理起来很方便。

    <1>.模块功能及表的结构设计

    功能模块:
    在这里插入图片描述

    表结构设计:
    从上面实体对应关系分析,权限表设计分为以下基本的五张表结构:用户表(t_user)、角色表(t_role)、
    t_user_role(用户角色表)、资源表(t_module)、权限表(t_permission)
    用户和角色间一对一关系,角色和权限间一对一关系,建立t_user_role和t_permission中间表
    表结构关系如下:

    在这里插入图片描述
    在这里插入图片描述

    <2>角色权限功能

    当完成角色权限添加功能后,下一步就是对角色操作的资源进行认证操作,这里对于认证包含两块:

    1. 菜单级别显示控制
    2. 后端方法访问控制

    查询出改用户所拥有的角色,然后根据角色查询出拥有的权限码,具体实现如下:

     @RequestMapping("main")
        public String main(HttpServletRequest request) {
            //通过获取cookie用户ID
            Integer userId = LoginUserUtil.releaseUserIdFromCookie(request);
            //查询用户对象,设置session作用域
            User user = userService.selectByPrimaryKey(userId);
            request.getSession().setAttribute("user", user);
    
            //通过当前登录用户ID,查询当前登录用户拥有的资源列表(查询对应的资源授权码)
            List permissions = null;
            permissions = permissionService.queryUserHasRoleHasPermissionByUserId(userId);
            //将集合设置作用域中(Session作用域)
            request.getSession().setAttribute("permissions", permissions);
    
            return "main";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (1).菜单级别显示控制

    系统根据登录用户扮演的不同角色来对登录用户操作的菜单进行动态控制显示操作,这里显示的控制使
    用freemarker指令+内建函数实现
    例如:
    在这里插入图片描述
    会根据权限码,来显示菜单内容:
    在这里插入图片描述

    (2).后端方法级别访问控制(AOP+注解实现)

    实现了菜单级别显示控制,但最终客户端有可能会通过浏览器来输入资源地址从而越过ui界面来访问后
    端资源,所以接下来加入控制方法级别资源的访问控制操作,这里使用aop+自定义注解实现

    自定义注解类:表示资源所需的权限码

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    /**
     * 定义方法需要的对应资源的权限码
     */
    public @interface RequiredPermission {
        //权限码
        String code() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法级别使用注解:
    例如:
    在这里插入图片描述
    定义aop切面类 拦截指定注解标注的方法:

    @Component
    @Aspect
    public class PermissionProxy {
    
        @Resource
        private HttpSession session;
    
        /**
         * 切面会拦截指定包下的指定注解
         * 拦截com.xxxx.crm.annoation的RequiredPermission注解
         *
         * @param pjp
         * @return java.lang.Object
         */
        @Around(value = "@annotation(org.example.crm.annotation.RequiredPermission)")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            Object result = null;
            // 得到当前登录用户拥有的权限 (session作用域)
            List permissions = (List) session.getAttribute("permissions");
            // 判断用户是否拥有权限
            if (null == permissions || permissions.size() < 1) {
                // 抛出认证异常
                throw new AuthException();
            }
    
            // 得到对应的目标
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            // 得到方法上的注解
            RequiredPermission requiredPermission = methodSignature.getMethod().getDeclaredAnnotation(RequiredPermission.class);
            // 判断注解上对应的状态码
            if (!(permissions.contains(requiredPermission.code()))) {
                // 如果权限中不包含当前方法上注解指定的权限码,则抛出异常
                throw new AuthException();
            }
    
            result = pjp.proceed();
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.客户管理模块(CRUD操作见源码)

    <1>.模块功能及表结构设计

    模块功能:
    在这里插入图片描述
    表结构设计:
    t_customer 客户表、t_customer_contact 客户交往记录表、t_customer_linkman 客户联系人表、t_customer_order 客户订单表、t_order_details 订单详情表
    在这里插入图片描述
    在这里插入图片描述

    <2>.定时器

    当实现了客户数据转移业务逻辑代码后,这里需要思考一个问题:客户数据量的问题随着时间的积累,流失的客户数据可能就比较大,如果数据的获取在用户查询时进行,此时后端对于数据的查询就会变得很慢,此时可以使用我们之前讲到的定时任务来处理,后台通过定时器来对流失客户数据定时进行转移处理,从而当前端用户查询时只需到客户流失表查询流失数据即可。

    增加定时器服务:

    /**
     * 定时任务的执行
     */
    @Component
    public class JobTask {
        @Autowired
        private CustomerService customerService;
    
        //cron表达式
        //每两秒执行一次
        //@Scheduled(cron = "0/2 * * * * ?")
    
        //从六月开始,每个月执行一次
        @Scheduled(cron = "* * * * 6/1 ? ")
        public void job() {
            //调用需要被执行的方法
            //开始执行定时任务
            System.out.println("开始执行定时器任务");
            customerService.updateCustomerState();
            System.out.println("定时器任务执行完成");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Starter开启定时任务环境配置:

    @SpringBootApplication
    @MapperScan("org.example.crm.dao")
    //启用定时任务
    @EnableScheduling
    public class Starter {
        public static void main(String[] args) {
            SpringApplication.run(Starter.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.服务管理(CRUD操作见源码)

    <1>功能实现及表结构设计

    功能实现:
    在这里插入图片描述
    表结构设计:
    在这里插入图片描述

    <2>服务实现

    这里对于服务管理服务的创建,分配,处理与反馈后端代码实现放在同一个方法中进行处理,同时方便对于服务状态值统一处理,这里定义 CustomerServeStatus 枚举类来实现。

    /**
     * 客户服务状态枚举类
     */
    public enum CustomerServeStatus {
        // 创建
        CREATED("fw_001"),
        // 分配
        ASSIGNED("fw_002"),
        // 处理
        PROCED("fw_003"),
        // 反馈
        FEED_BACK("fw_004"),
        // 归档
        ARCHIVED("fw_005");
    
        private String state;
    
        CustomerServeStatus(String state) {
            this.state = state;
        }
    
        public String getState() {
            return state;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    6.统计报表管理(CRUD见源码)

    <1>.功能实现

    功能实现:
    在这里插入图片描述

    <2>.使用ECharts对数据进行分析

    ECharts官网:添加链接描述

    折线图数据返回实现:

     /**
         * 查询客户构成 (折线图)
         * @return
         */
        public Map countCustomerMake() {
            Map map = new HashMap<>();
            // 查询客户构成数据的列表
            List> dataList = customerMapper.countCustomerMake();
            // 折线图X轴数据  数组
            List data1 = new ArrayList<>();
            // 折线图Y轴数据  数组
            List data2 = new ArrayList<>();
    
            // 判断数据列表 循环设置数据
            if (dataList != null && dataList.size() > 0) {
                for (int i = 0; i < dataList.size(); i++) {
                    data1.add(dataList.get(i).get("level").toString());
                    data2.add(Integer.parseInt(dataList.get(i).get("total").toString()));
                }
            }
            // 将X轴的数据集合与Y轴的数据集合,设置到map中
            map.put("data1", data1);
            map.put("data2", data2);
    
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述
    饼状图数据返回实现:

        public Map countCustomerMake02() {
            Map map = new HashMap<>();
            // 查询客户构成数据的列表
            List> dataList = customerMapper.countCustomerMake();
            // 饼状图数据   数组(数组中是字符串)
            List data1 = new ArrayList<>();
            // 饼状图的数据  数组(数组中是对象)
            List> data2 = new ArrayList<>();
    
            // 判断数据列表 循环设置数据
            if (dataList != null && dataList.size() > 0) {
                // 遍历集合
                for (int i = 0; i < dataList.size(); i++) {
                    //饼状图数据, 数组(数组中是字符串
                    data1.add(dataList.get(i).get("level").toString());
                    //饼状图数据 数组(数组中是对象)
                    Map dataMap = new HashMap<>();
                    dataMap.put("name", dataList.get(i).get("level"));
                    dataMap.put("value", dataList.get(i).get("total"));
                    data2.add(dataMap);
                }
            }
    
            // 将X轴的数据集合与Y轴的数据集合,设置到map中
            map.put("data1", data1);
            map.put("data2", data2);
    
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    欢迎Star CRM客户管理系统

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    信创就用国产的 Solon Java Framework,v2.6.0 发布
    java毕业设计城市垃圾分类管理系统mybatis+源码+调试部署+系统+数据库+lw
    React设计思路
    使用Element UI 的el-pagination组件实现分页效果
    搭建RabbitMQ消息服务,整合SpringBoot实现收发消息
    设计模式之适配器模式
    python基础语法(七)
    Weighted Switch Controller--Jmeter插件推荐Chapter2
    网络安全70部学员第二阶段项目验收顺利结束
    Windows 有趣功能集锦
  • 原文地址:https://blog.csdn.net/m0_67401417/article/details/126061297