# CRM **Repository Path**: hantw/CRM ## Basic Information - **Project Name**: CRM - **Description**: 企业客户关系管理系统实战-中信CRM - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 12 - **Created**: 2025-01-21 - **Last Updated**: 2025-01-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # CRM #### 介绍 企业客户关系管理系统实战-中信CRM #### 软件架构 本系统采用的技术栈有:LayUI + Freemarker + MySql8.0 + Java8 + Mybatis3 + Spring5.X + SpringMVC + SpringBoot #### 安装教程 1. xxxx 2. xxxx 3. xxxx #### 使用说明 1. 克隆代码到本地,使用 IntelliJ IDEA 开发工具打开 2. 根据 application.yml 配置文件创建数据库 crm , 并执行 dababase 目录下的 crm.sql 数据库脚本数据 3. 运行项目,通过 http://localhost:8080/crm/index 访问登陆页,登陆账号 admin、密码 123456 #### 项目展示 ##### 登录页 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0728/233333_eebd1914_5474516.png "登录页.png") ##### 首页 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0728/233526_d8f0290e_5474516.png "首页.png") ##### 营销机会管理页 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0728/233553_8ea3d0c3_5474516.png "营销机会管理页.png") #### 提交日志细节记录 1.【1.本地初始化】 1)新建一个Maven的maven-archetype-quickstart项目 2)check out码云仓库中新建的CRM项目到本地,加入check out下来的内容到新建的quickstart crm项目中,整合修改后,做本地初始化提交 2.【2.项目环境搭建与测试】 1)更新readme.md 2)加入所需的依赖坐标、配置application.yml文件、新建一些文件夹 3)引入 base 包,添加系统登录,主页面转发代码 4)添加静态资源、添加视图模板.ftl 5)添加SpringBoot启动类,测试localhost:8080/crm/index能访问到登录页 3.【3.理解项目的结构,格式化一下.ftl页面文件】 1)通过@ModelAttribute在方法上注解,说明他是一个返回给模型的参数,这里是请求域参数 4.【4.用户登录(核心思路分析)】 1)在resources目录下,新建readme目录存放,思路分析文件 01-用户模块.txt 5.【5.用户登录(准备工作)】 1)加入工具类和自定义异常类的包,utils包和exception包 2)加入mybatis逆向工程配置文件 generatorConfig.xml,并修改数据库驱动路径、数据库账号密码等信息 3)准备数据库,创建crm数据库,导入crm.sql数据库脚本文件 4)使用mybatis-generator生成Mybatis代码。能够生成 vo 类、能生成 mapper 映射文件(其中包括基本的增删改查功能)、能生成 mapper 接口。 5)maven中配置执行命令 mybatis-generator:generate -e,执行后生成第4)点说到的内容 6)注意:如果生成的实体字段少了或者不对应,可能是跳库了,需要处理一下 6.【6.用户登录(后端代码实现)】 1)新建的controller和service都要继承基类BaseController和BaseService,还需要添加注解@Controller和@Service 2)使用逆向工程生成的对应实体类的Mapper也要继承基类BaseMapper 3)在Controller层中注入对应的Service层对象,这里使用@Resources注解标注 4)在Service层中注入Dao层,这里的Dao层是xxxMapper,同样使用@Resource注解标注 5)定义一个Dao层的接口查询方法,然后在对应的.xml映射文件中书写sql语句 6)登录表单是通过AJAX来请求的,返回的是一个对象,而不是返回页面,所以在Controller层的方法中需要添加@ResponseBody注解 7)另外还封装了一个UserModel类,用于存放返回需要的用户字段内容,而不是返回User的全部字段,因为有些字段我们不需要 7.【7.用户登录(后端代码测试)】 1)在启动类中加入@MapperScan注解扫描dao层的Mapper接口 2)使用逆向工程生成的UserMapper.xml文件出问题,我就把不需要的注释掉了,就解决了 3)对于Get请求,我们可以很方便的通过浏览器来测试,但POST请求我们就不行了,可以使用Postman 4)在UserController类上加上父请求路径 @RequestMapping("user") 5)逆向工程generatorConfig.xml文件生成完一个实体类后,设置tableName="" domainObjectName=""为空,放置再次运行他时覆盖已写好的代码 8.【8.用户登录(前端代码实现)】 1)LayUI触发Submit事件的使用; 2)ajax请求中的参数名,要与controller中对应方法的参数参数名一致; 3)前端js中可以使用console.log(result);输入打印信息到浏览器的console控制台中; 4)ajax发送的是POST请求,对应controller层的方法要使用@PostMapping("login")注解 5)登录成功需要使用JQuery设置cookie记录登录状态,并在前端ajax中使用window.location.href = ctx + "/main";跳转到首页 9.【9.用户登录(细节处理)】 1)将存放的cookie中的userId进行加密,通过后台加密后返回 2)使用工具类UserIDBase64对userId进行加密,修改相应的UseModel和前端的ajax 3)登录成功后,跳转到main页面,在/main对应的方法中查询一个对象,用来设置main页面的登录用户名 4)在cookie中,我们存放了userId,又因为每次请求时,浏览器都会把cookie传递到服务器,我们就通过CookieUtil工具类来获取cookie中的userId,然后通过userId来查找一个user对象,用来在main页面中显示 5)这里查询到的user对象要设置到session中,从session中取值显示在main页面的右上角用户名 6)通过$.cookie("userIdStr",result.result.userIdStr, {expires:7});加了expires的参数来设置cookie的生效时间 10.【10.修改密码(核心思路分析)】 1)通过ajax发起请求,后端返回数据ResultInfo,后端主要是各种判断 11.【11.修改密码(后端代码实现)】 1)UserMapper.xml由于之前我注释掉了,所以现在在有需要的地方打开注释,让其可用 2)做添加、修改、删除操作的时候都要在对应的方法上加上事务的注解@Transactional(propagation = Propagation.REQUIRED) 12.【12.修改密码(前端代码实现)】 1)页面跳转是通过后端提供一个controller方法,方法里使用请求转发跳转到目标页面【重要】 2)ajax请求参数的key值要与controller层中对应方法的参数名保持一致 3)修改密码成功后,需要清空cookie数据,跳转到登录页面,使用JQuery清空cookie,需要domain参数值,才能确保是我的cookie,这里在正式环境中需要修改domain的值 4)使用父窗口跳转window.parent.location.href = ctx + "/index";跳转到登录页面 13.【13.用户退出】 1)用户退出登录需要先清空cookie,再以父窗口的方式跳转到登录页面 14.【14.全局异常统一处理】 1)添加一个全局异常处理类GlobalExceptionResolver,想要这个异常处理类生效,需要加上@Component注解,把它交给IOC处理,让IOC进行实例化 2)返回数据使用alibaba的fastjson转为Json后,使用流往外输出 3)加了全局异常统一处理后,每次在需要捕获异常的地方就都统统不用了,我们也就只维护这个全局异常统一处理类就行了,并且该类也提供了默认的异常信息 4)然后我们就将之前在UserController中捕获异常的处理语句注释掉,就不需要了 15.【15.非法请求拦截】 1)拦截器实现有两种方式:一种是实现接口,另一种是继承适配器HandlerInterceptorAdapter 2)使用封装好的方法从浏览器传回的cookie中获取userId,判断是否是登录状态 3)重定向到登录页面的写法:ModelAndView mv = new ModelAndView("redirect:/index"); return mv; 4)配置类需要加上 @Configuration 注解,并继承WebMvcConfigurerAdapter重写addInterceptors()方法,设置需要被拦截的资源和需要被放行的资源 5)在方法上加上 @Bean 注解的含义是:将方法的返回值交给IOC维护 6)未登录状态下去访问其他资源就会被拦截掉 16.【16.登录不成功没有提示信息的修复】 1)因为加了全局异常统一处理后,我就注释掉了原来在UserController中被try-catch包围的代码,那么相应的提示信息也就没有了 2)为了有提示信息,我又把提示信息放到了try-catch代码块中,问题就修复了 17.【17.记住我功能(记住密码功能)】 1)cookie默认关闭浏览器就会失效,所以需要设置失效的时间,这样在关闭浏览器后重新打开,在有效时间内就不需要重新登录了 2)JQuery的prop()方法设置或返回被选元素的属性和值 18.【18.营销管理模块-营销机会管理-准备工作】 1)修改逆向工程文件,使其生成t_sale_chance表对应的SaleChance实体类、dao层的SaleChanceMapper接口、对应的SaleChanceMapper.xml文件 2)修改的是逆向工程generatorConfig.xml文件里最后面的<table tableName="t_sale_chance" domainObjectName="SaleChance" ...> 3)然后就是dao层、service层、controller层的准备工作,继承对应的基类,加上@Service、@Controller注解,注入需要的对象 4)在IDEA中创建MySQL的连接,连接到MySQL数据库,就可以直接在IDEA中操作数据库了 19.【19.营销机会管理-营销机会数据查询操作-后端代码实现】 1)主要是SaleChanceMapper.xml中查询语句的编写 20.【20.营销机会管理-营销机会数据查询操作-进入营销管理页面】 1)点击营销机会管理能进入到对应的页面 21.【21.营销管理模块-营销机会数据查询操作-加载数据表格】 1)每一个模板文件.ftl都有与之对应的js文件 2)加载数据表格的显示是使用LayUI的数据表格组件,需要给它规定好的json数据 3)请求数据的URL别忘了加上ctx,如:, url: ctx + '/sale_chance/list' // 访问数据的URL(后台的数据接口) 4)表格的 field 要与数据的字段名一一对应 5)在实体类的字段上,加入注解格式化时间 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 22.【22.营销管理模块-营销机会数据查询操作-多条件查询与数据显示】 1)通过JQuery的属性选择器来搜索文本框的值 23.【23.营销管理模块-营销机会数据添加操作-思路分析】 1)修改表格字段的显示顺序 2)执行添加、修改、删除操作时,需要加上@Transactional(propagation = Propagation.REQUIRED)事物注解,当出现问题时可以回滚,保证数据的完整性 24.【24.营销管理模块-营销机会数据添加操作-后端代码实现】 1)使用PhoneUtil工具类判断手机号码格式是否正确 2)使用了Enum枚举类 DevResult和StateStatus 来代表分配状态的值,和开发状态的值 25.【25.营销管理模块-营销机会数据添加操作-打开添加营销机会弹出层】 1)可以通过console.log(data)打印册数输出数据,便于数据的操作 2)LayUI里面是可以直接写带有html标签的内容的 3)通过后台的controller跳转到添加营销机会页面 4)弹出层最大化时如果要在父窗口中显示,那么就要加上layui. 如:layui.layer.open({ 26.【26.营销管理模块-营销机会数据添加操作-关闭弹出层】 1)每个.ftl模板文件都会准备一个与之对应的js文件 2)通过点击取消按钮,关闭添加营销机会数据弹出层 27.【27.营销管理模块-营销机会数据添加操作-前端代码实现】 1)监听表单submit事件,使用 layui的 form.on() 2)在表单提交时,通过 data.filed 可以获取到表单的所有字段 3)刷新父窗口,重新加载表格数据,使用 parent.location.reload(); 刷新页面 4)修复了原来查询营销机会数据sql语句查询出的数据不全问题,把inner join 改为 left join 28.【28.营销管理模块-营销机会数据更新操作-思路分析】 1)需要更新数据的id要通过隐藏域传递到后台 2)默认值的设置 29.【29.营销管理模块-营销机会数据更新操作-后端代码实现】 30.【30.营销管理模块-营销机会数据更新操作-打开更新营销机会弹出层】 1)通过前端检查源代码,可以查看到设置在隐藏域中的值 31.【31.营销管理模块-营销机会数据更新操作-前端代码实现】 1)我们通过JQuery的属性选择器来获取到隐藏域中的值 2)复用代码,添加和编辑共用一个页面,只需要判断不同情况的选择即可 32.【32.营销管理模块-营销机会数据更新操作-加载下拉框】 1)获取指派人下拉框的数据,也就是查询在t_role表中角色为销售的人,加载到指派人下拉框中 2)注意这里sql语句返回值的写法