开发环境
IDE: Myeclipse
Jdk: 1.8
数据库:: MySQL
1.1 目的
客户关系管理系统通过对客户生命周期的有效管理,帮助企业有效管理客户资源、控制销售过 程、缩短销售周期、提高销售成功率;通过对客户相关信息的分析与挖掘,识别客户消费规律 和客户价值,指导企业的部门运作和市场规划,从而提供更加快捷和周到的优质服务,帮助企 业提升客户满意度和忠诚度,最终提高企业市场竞争力。
2、项目演示
项目在线演示地址:登录 - CRM客户关系管理系统1
管理员账号:admin 密码:123456
营销员账号:1001 密码:123456
2.1 开发背景
伴随着CRM客户管理系统在国内的飞速发展,绝大部分的企业都在应用CRM系统来服务自个 的企业,而且取得显著效果,CRM系统强调建立一个以客户为中心的现代企业,以客户价值来 判定市场的需求,满足了将企业的战略从以产品为中心转向以客户为中心的企业需求。应用 CRM最终达到提高企业客户的满意度,减少客户的流失。
3、系统简介
CRM客户关系管理是企业通过提供创新式的个性化客户服务为招揽新客户、保留旧客户、更好 的提供客户服务以及进一步培养企业和客户之间的关系、提升客户忠诚度的利用信息技术来协 调公司与客户间的销售、服务的营销管理方式。
4、业务需求描述
| 业务类别 | 子业务 | 业务详解 |
| 基础模块 | 用户登录 | 用户登录 |
| 退出 | 退出当前登录用户 | |
| 记住密码 | 记住登录用户的账号密码 | |
| 密码修改 | 修改密码 | |
| ... | 其他 | |
| 营销管理模块 | 营销机会管理: | 企业客户的质询(询问)需求所建立的信息录入功能 |
| 客户开发计划 | 开发计划是根据营销机会而来,对于企业质询的客户, 会有相应的销售人员对于该客户进行具体的沟通交流,此时对于整个 CRM系统而言,通过营销开发计划来进行相应的信息管理,提高客户 的购买企业产品的可能性。 | |
| … | ||
| 客户管理模块 | 客户信息管理 | CRM系统中完整记录客户信息来源的数据、企业与 客户交往、客户订单查询等信息录入功能,方便企业与客户进行相应 的信息交流与后续合作。 |
| 客户流失管理 | CRM通过一定规则机制所定义的流失客户(无效客 户),通过该规则可以有效管理客户信息资源,提高营销开发的效率。 | |
| 服务管理模块 | 服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户 相应的信息质询,反馈与投诉功能,提高企业对于客户的服务质量。 | 服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户 相应的信息质询,反馈与投诉功能,提高企业对于客户的服务质量。 |
| 文件中心模块 | 上传合同文件 | 文件格式要求是PDF文件,其他文件不能上传 |
| 查询文件 | 查询所有文件,或根据文件标题进行模糊查询 | |
| 下载文件 | 文件需要提供下载功能 | |
| 系统公告模块 | 发布公告 | 发布系统公告 |
| 删除公告 | 删除公告 | |
| 公告列表 | 列出全部公告 |
5、业务描述
5.1 基础模块
(一)登录
1)业务描述
提供用户登录的功能,登录之后,可以进入主菜单进行操作其他菜单
2)输入
| 账号 | 不低于4位的英文单词组成 |
| 密码 | 长度不低于6位,并且要有英文大写字母 |
| 验证码 | 先识别验证码是否正确 |
3)输出
登录成功,进行页面跳转,跳转到主页面
登录失败,进行页面的跳转,跳转到登录页面,并且提示登录失败的原因
4)业务流程
- 访问登录页面在页面中输入用户名,密码信息,点击登录的按钮,提交数据服务器接收到客户端的请求数据,去查询数据库查询数据库之后,根据查询的结果响应前端前端接受客户端的相应,并给出页面跳转到的操作
(二)退出
1)请求 删除用户登录数据
2)成功跳转到登录页面
(三)记住登录
记住当前登录用户的登录账号和密码
1)加密用户数据
2)保存到session
(四)修改密码
修改用户的登录密码
1)验证输入用户老密码是否正确
2)检查新密码是否符合规则
3)请求 修改
5.2 营销管理模块
(一)营销机会管理
增:
1)前端js验证数据是否符合规则
2)提交数据到后端进行添加处理
3)失败 成功提示并刷新数据
删:
1)根据索引删除数据
改:
1)验证数据是否符合规则
2)根据索引修改数据
查:
1)分页查询数据列出
企业客户的质询(询问)需求所建立的信息录入功能
(二)客户开发计划
开发计划是根据营销机会而来,对于企业质询的客户,
会有相应的销售人员对于该客户进行具体的沟通交流,此时对于整个
CRM系统而言,通过营销开发计划来进行相应的信息管理,提高客户
的购买企业产品的可能性。
5.3 客户管理模块
(一)客户信息管理
增:
1)验证客户数据是否符合规则
2)符合请求数据添加
删:
1)根据索引请求后端进行删除
改:
1)根据索引修改数据 请求前先前端验证
查:
1)分页查询数据列出
CRM系统中完整记录客户信息来源的数据、企业与客户交往、客户订单查询等信息录入功能,方便企业与客户进行相应的信息交流与后续合作。
(二)客户流失管理
CRM通过一定规则机制所定义的流失客户(无效客户),通过该规则可以有效管 理客户 信息资源,提高营销开发的效率。
5.4 服务管理模块
服务管理是针对客户而开发的功能,针对客户要求,CRM提供客户相应的信息质询, 反馈与投诉功能,提高企业对于客户的服务质量。
5.5 文件中心模块
(一)上传合同文件
1)验证文件格式是否是pdf
2)检测文件大小是否符合要求
1、1 上传请求
文件格式要求是PDF文件,其他文件不能上传
1、2 查询文件
1)根据用户选择的查询类型进行模糊查询 (上传时间,上传文件名)
2)查询所有文件,或根据文件标题进行模糊查询
1、3 下载文件
1)下载请求
2)输出文件下载
3)文件需要提供下载功能
5.6 系统公告模块
1、1 添加公告
1)验证公告数据是否符合规则
2)请求后端添加
1、2 删除公告
1)根据索引进行删除
1、3 公告列表
1)分页查询数据列出
6、项目部分代码
aop日志:
package com.crm.aop;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.crm.pojo.Log;
import com.crm.pojo.SessionUser;
import com.crm.service.LogService;
@Aspect //该标签把LoggerAspect类声明为一个切面
@Order(1) //设置切面的优先级:如果有多个切面,可通过设置优先级控制切面的执行顺序(数值越小,优先级越高)
@Component //该标签把LoggerAspect类放到IOC容器中
public class LoggerAspect {
@Autowired
private LogService logService;
@Autowired
private HttpServletRequest request;
private String JSonAOP(String json,String path) {
int start=json.indexOf(path);
int end=json.indexOf(",", start);
if(end==-1) {end=json.length()-1;}//last
return json.substring(start+path.length()+1,end);//+1 =
}
private Log getLog(String Type,String Content,String userId) {
Log log= new Log();
log.setIp(request.getRemoteAddr());
log.setTime(new Date());
log.setType(Type);
log.setContent(Content);
log.setUserId(userId);
return log;
}
@Pointcut("execution(public * com.crm.controller.*Controller.*(..))")
public void declearJoinPointexpression(){}
@Before("declearJoinPointexpression()") //该标签声明次方法是一个前置通知:在目标方法开始之前执行
public void beforMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List
登录控制器 :
package com.crm.controller;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import com.crm.pojo.Marketer;
import com.crm.pojo.SessionUser;
import com.crm.pojo.WebSystem;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.crm.pojo.Admin;
import com.crm.pojo.IRole;
import com.crm.service.AdminService;
import com.crm.service.CustomerProjectService;
import com.crm.service.CustomerService;
import com.crm.service.IRoleService;
import com.crm.service.MarketerService;
import com.crm.service.NoticeService;
import com.crm.service.OrdersService;
import com.crm.service.SystemService;
import com.crm.util.CpachaUtils;
import com.crm.util.EncryptionUtil;
import com.crm.util.Interceptor;
import com.crm.util.JsonUtil;
import com.crm.util.Permission;
@Controller
@RequestMapping("/system")
public class SystemController {
@Autowired
private MarketerService marketerService;
@Autowired
private AdminService adminService;
@Autowired
private SystemService systemService;
@Autowired
private IRoleService roleService;
@Autowired
private CustomerService customerService;
@Autowired
private CustomerProjectService customerProjectService;
@Autowired
private NoticeService noticeService;
@Autowired
private OrdersService ordersService;
@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(ModelAndView model,HttpServletRequest req) {
ServletContext application=req.getServletContext();
WebSystem web=(WebSystem) application.getAttribute("WebSystem");
SessionUser user = (SessionUser) req.getSession().getAttribute("SUser");
if (user != null) {
try {
if(new Interceptor().toeknVer(user)) {//验证成功给它自动跳转到首页去
if(web!=null) {
model.setView(new RedirectView( "/system/index", true, false, true ));
model.addObject("token",EncryptionUtil.getMD5(user.getToken()));
return model;
}else {System.out.println("系统错误:存在非法注入");}
//resp.sendRedirect(req.getContextPath() + "/system/index");
}
} catch (UnsupportedEncodingException e) {
req.getSession().setAttribute("SUser", null);//token验证失败了直接给他清掉
}
}
Object obj=application.getAttribute("permissionData");
if(obj==null) {//初始化api权限
application=Permission.Initialize(application);
}
if(application.getAttribute("WebSystem")==null) {
application.setAttribute("WebSystem", systemService.getSystem());
}
model.setViewName("system/login");
return model;
}
@RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView index(ModelAndView model) {
model.setViewName("system/index");
return model;
}
@RequestMapping(value = "/reg", method = RequestMethod.GET)
public ModelAndView reg(ModelAndView model) {
model.setViewName("system/reg");
return model;
}
@RequestMapping(value = "/WebSet", method = RequestMethod.GET)
public ModelAndView WebSet(ModelAndView model) {
model.addObject("Web",systemService.getSystem());
model.setViewName("system/web_set");
return model;
}
@RequestMapping(value = "/welcome", method = RequestMethod.GET)
public ModelAndView welcome(ModelAndView model) {
Map retMap = new HashMap();
retMap.put("customerSize",customerService.getTotal(new HashMap()));
retMap.put("customerProjectSize",customerProjectService.getTotal(new HashMap()));
retMap.put("marketerSize", marketerService.getTotal(new HashMap()));
retMap.put("ordersSize", ordersService.getTotal(new HashMap()));
Map queryMap = new HashMap();
int noticeSize=noticeService.getTotal(queryMap);
if(noticeSize-10<0) {noticeSize=0;}else {noticeSize-=10;}
queryMap.put("limit", 10);
queryMap.put("offset",noticeSize);
retMap.put("noticeList",noticeService.findList(queryMap));
model.addObject("Data",retMap);
model.setViewName("system/welcome");
return model;
}
@RequestMapping(value = "/login_out", method = RequestMethod.GET)
public String loginOut(HttpServletRequest request) {
request.getSession().setAttribute("SUser", null);
return "redirect:login";
}
@RequestMapping(value = "/permission", method = RequestMethod.GET)
public ModelAndView permission(ModelAndView model,@RequestParam(name = "id", required = false) String id) {
if(id!=null && !id.equals("")) {
model.addObject("id",id);//取当前用户数据
model.setViewName("system/permission/permission");
}else {
model.setViewName("404");
}
return model;
}
@RequestMapping(value = "/permissionEdit", method = RequestMethod.GET)
public ModelAndView permissionEdit(ModelAndView model,
@RequestParam(name = "index", required = false) String index,
@RequestParam(name = "id", required = false) Long id) {
Map
客户控制器:
package com.crm.controller;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSONArray;
import com.crm.pojo.Customer;
import com.crm.service.CustomerService;
import com.crm.service.MarketerService;
import com.crm.util.ExcelUtil;
import jxl.read.biff.BiffException;
@Controller
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
@Autowired
private MarketerService marketerService;
@RequestMapping(value = "/list", method = RequestMethod.GET)
public ModelAndView list(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_list");
return model;
}
@RequestMapping(value = "/listDrain", method = RequestMethod.GET)
public ModelAndView listDrain(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customerdrain_list");
return model;
}
@RequestMapping(value = "/importCustomer", method = RequestMethod.GET)
public ModelAndView importCustomer(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_import");
return model;
}
@RequestMapping(value = "/updateDrain", method = RequestMethod.GET)
public ModelAndView updateDrain(ModelAndView model, @RequestParam(name = "id", required = false) Integer id) {
Map queryMap = new HashMap();
model.addObject("type", 1);// 前端参数类型 1表示修改
if (id != null) {
Customer customerdrain = customerService.findByCustomerId(id);
model.addObject("customerdrain", customerdrain);// 取当前用户数据
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customerdrain_update");
} else {
model.setViewName("404");// 如未传递id直接404 因为在web.xml中设置了404 所以随便指定一个未存在页面即可 *注意并不是说serViewName(404)就代表了404的源地址
}
return model;
}
@RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView add(ModelAndView model) {
Map queryMap = new HashMap();
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_add_update");
model.addObject("type", 2);// 前端类型 1是修改,2是添加
return model;
}
@RequestMapping(value = "/update", method = RequestMethod.GET)
public ModelAndView update(ModelAndView model, @RequestParam(name = "id", required = false) Integer id) {
Map queryMap = new HashMap();
model.addObject("type", 1);// 前端类型 1是修改,2是添加
if (id != null) {
Customer customer = customerService.findByCustomerId(id);
model.addObject("customer", customer);
model.addObject("merkList", marketerService.findList(queryMap));
model.setViewName("customer/customer_add_update");
} else {
model.setViewName("404");
}
return model;
}
@RequestMapping(value = "ApicustomerimportData", method = {RequestMethod.POST})
@ResponseBody
public Map ApicustomerimportData(
@RequestParam(name="marketerId", required = false) Integer marketerId,
@RequestParam(name="customerData", required = false) String data
) {
Map retmap=new HashMap<>();
if(marketerId!=null && marketerId!=0) {
JSonArray dataArr=(JSONArray) JSONArray.parse(data);
for(Object d:dataArr) {
JSonArray a=(JSONArray)d;
Customer customer=new Customer();
customer.setMarketerId(marketerId);
customer.setName(a.get(0).toString());
customer.setAddress(a.get(1).toString());
customer.setTel(a.get(2).toString());
customer.setMemo(a.get(3).toString());
customerService.add(customer);
}
retmap.put("code", 0);
retmap.put("msg", "导入结束");
}else {
retmap.put("code", 201);
retmap.put("msg", "请指定营销员");
}
return retmap;
}
@RequestMapping(value = "/ApiimportExcel", method = {RequestMethod.POST})
@ResponseBody
public Map ApiimportExcel(@RequestParam(name="file", required = false) MultipartFile file) {
Map retmap=new HashMap<>();
if(file!=null) {
CommonsMultipartFile cf= (CommonsMultipartFile)file;
DiskFileItem fi = (DiskFileItem)cf.getFileItem();
File excelFile = fi.getStoreLocation();
try {
String[][] data=ExcelUtil.readSpecifyRows(excelFile);
retmap.put("code",0);
retmap.put("msg","解析成功");
retmap.put("data",data);
} catch (BiffException | IOException e) {
//e.printStackTrace();
retmap.put("code", 201);
retmap.put("msg","文件错误,请提交正确的Excel文件!");
}
}else {
retmap.put("code", 201);
retmap.put("msg","请上传文件");
}
return retmap;
}
@RequestMapping(value = "/ApiGetList", method = RequestMethod.POST)
@ResponseBody
public Map ApiGetList(@RequestParam(name = "name", required = false) String name,
@RequestParam(name = "marketerId", required = false) Integer marketerId,
@RequestParam(name = "tel", required = false) String tel,
@RequestParam(name = "limit", required = false) Integer limit,
@RequestParam(name = "page", required = false) Integer page,
@RequestParam(name = "status", required = false) Integer status
) {
Map queryMap = new HashMap();
Map retMap = new HashMap();
queryMap.put("name", name);
queryMap.put("marketerId", marketerId);
queryMap.put("tel", tel);
queryMap.put("offset", limit * (page - 1));// 对应数据库中的偏移量
queryMap.put("limit", limit * page);// 每页显示记录条数,也就是每页显示的数量
queryMap.put("status",status);//用户状态
List customer = customerService.findList(queryMap);
if (!customer.isEmpty()) {
retMap.put("code", 0);// 状态码 layui中code为0才是成功 注意不是200
retMap.put("msg", "success");// msg
retMap.put("data", customer);// 数据
retMap.put("count", customerService.getTotal(queryMap));// 获取符合结果的总记录数
return retMap;
} else {
retMap.put("code", "201");
retMap.put("msg", "无数据");
return null;
}
}
@RequestMapping(value = "/ApiAdd", method = RequestMethod.POST)
@ResponseBody
public Map ApiAdd(Customer customer) {
Map retMap = new HashMap();
if (customerService.add(customer) > 0) {
retMap.put("code", 0);
retMap.put("msg", "success");
} else {
retMap.put("code", 201);
retMap.put("msg", "error");
}
return retMap;
}
@RequestMapping(value = "/ApiDelete", method = RequestMethod.POST)
@ResponseBody
public Map ApiDelete(@RequestParam(value = "ids[]", required = true) Long[] ids) {
Map retMap = new HashMap();
if (ids == null) {
retMap.put("type", "error");
retMap.put("msg", "请选择要删除的数据!");
return retMap;
}
String idsString = "";
for (Long id : ids) {// 把Long分割并转换为idsString这个是加强for循环,逐个遍历ids的每个元素 直到ids的最后一个遍历完 跳出循环,
// ids大于0,也就是有多个,就把这个ids赋给数组,并循环,在循环里去数组的值,进行多个操作
idsString += id + ",";
}
idsString = idsString.substring(0, idsString.length() - 1);// 截取掉字符串的最后一位
if (customerService.delete(idsString) <= 0) {// 文本框中内容非空时执行删除操作
retMap.put("type", "error");
retMap.put("msg", "删除失败!");
return retMap;
}
retMap.put("type", "success");
retMap.put("msg", "删除成功!");
return retMap;
}
@RequestMapping(value = "/ApiUpdate", method = RequestMethod.POST)
@ResponseBody
public Map ApiUpdate(Customer customer,@RequestParam(value = "statusRaw", required = false) int statusRaw) {
Map retMap = new HashMap();// 输出集合
if(customer.getStatus()!=null && customer.getStatus()==0 && statusRaw==1) {
customer.setLostTime(new Date());
}
if (customerService.update(customer) > 0) {
retMap.put("code", 0);
retMap.put("msg", "success");
} else {
retMap.put("code", 201);
retMap.put("msg", "修改失败");
}
return retMap;
}
}
工具类:
package com.crm.util;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.base64;
import java.util.base64.Decoder;
import java.util.base64.Encoder;
public class EncryptionUtil {
public static String getMD5(String strMD5) {
try {
// 得到一个信息摘要器
MessageDigest digest = MessageDigest.getInstance("md5");
byte[] result = digest.digest(strMD5.getBytes());
StringBuffer buffer = new StringBuffer();
// 把每一个byte 做一个与运算 0xff;
for (byte b : result) {
// 与运算
int number = b & 0xff;// 加盐
String str = Integer.toHexString(number);
if (str.length() == 1) {
buffer.append("0");
}
buffer.append(str);
}
// 标准的md5加密后的结果
return buffer.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
}
}
public static String HloveyRC4(String aInput,String aKey)
{
int[] iS = new int[256];
byte[] iK = new byte[256];
for (int i=0;i<256;i++)
iS[i]=i;
int j = 1;
for (short i= 0;i<256;i++)
{
iK[i]=(byte)aKey.charAt((i % aKey.length()));
}
j=0;
for (int i=0;i<255;i++)
{
j=(j+iS[i]+iK[i]) % 256;
int temp = iS[i];
iS[i]=iS[j];
iS[j]=temp;
}
int i=0;
j=0;
char[] iInputChar = aInput.toCharArray();
char[] iOutputChar = new char[iInputChar.length];
for(short x = 0;x
package com.crm.util;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.alibaba.fastjson.TypeReference;
public class JsonUtil {
public static Map jsonToMap(JSonObject jsonObj) {
return JSONObject.parseObject(jsonObj.toJSonString(), new TypeReference 


