CRM(客户管理)系统是一个将对客户基本信息、联系人信息、交往信息、客户服务信息的充分共享和规范化管理的系统。通过对潜在客户、客户开发过程的追踪和记录,提高新客户的开发能力;客户将要流失时系统及时预警,以便销售人员及时采取措施,降低损失。并能提供相关报表,以便公司高层随时了解公司客户情况。
该系统总共包含了6个功能模块包括:基础数据模块,组织机构模块,权限模块,客户模块,高级业务模块。
其中基础数据模块中的系统设置包含:系统菜单,套餐表,官网展示,登录注册页面,数据字典,系统日志。
组织机构模块中的员工管理包含:部门管理,员工管理,通讯录。
权限模块包含资源管理,权限管理,角色管理,租户分布。
客户模块包含客户开发和客户维护
高级业务模块包含订单管理,合同管理,售后管理,公告,日程管理,数据分析等。
本次开发用到的技术有Spring SpringMVC mybatis shiro layui jquery 框架技术。
系统流程如下图:
本次开发我负责的是权限模块,主要是对用户的权限进行管理,过滤,和用户密码加密。权限模块中主要用到了用户,角色,权限,菜单这几张表。对于权限的管理模块,用户表多对多角色表,角色表多对多权限表,通过用户查询用户所对应的角色,在通过角色查询对应的权限,就能获取该用户的权限。新增用户是也可以同过角色直接设置权限。
在用户注册时还需对用户的密码进行加密,我用的是MD5加密,通过加密加盐的方式使用户的密码更为安全。
权限过滤部分通过,登录用户的权限不同拦截用户所没有的权限,并且隐藏没有权限的栏目。
下面是权限模块的部分代码:
自定义拦截器
/**
* 自己定义的权限拦截器
*/
public class CrmPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
//request:请求对象(包含咱们所有的请求信息)
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
Subject subject = this.getSubject(request, response);
if (subject.getPrincipal() == null) {
this.saveRequestAndRedirectToLogin(request, response);
} else {
//判断是否是Ajax的请求
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
//获取到相应的请求头X-Requested-With:XMLHttpRequest
//只有Ajax请求有这个数据
String xhr =req.getHeader("X-Requested-With");
if("XMLHttpRequest".equals(xhr)){
//设置响应头(告诉前端返回的数据格式)
resp.setContentType("application/json;charset=UTF-8");
//代表是一个Ajax请求,单独处理 返回 {"success":true,"msg":"xxxx"}
resp.getWriter().print("{\"success\":false,\"msg\":\"你没有权限\"}");
}else {
String unauthorizedUrl = this.getUnauthorizedUrl();
if (StringUtils.hasText(unauthorizedUrl)) {
WebUtils.issueRedirect(request, response, unauthorizedUrl);
} else {
WebUtils.toHttp(response).sendError(401);
}
}
}
return false;
}
}
自定义realm
public class CrmRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
@Override
public String getName() {
return "CrmRealm";
}
//解决授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Set<String> permissionSns = new HashSet<>();
//1.拿到当前登录用户
System.out.println("shouquan1");
Employee employee = UserContext.getUser();
//2.根据用户id从数据库中查询权限
Set<Permission> permissions = permissionService.findSnByUser(employee.getId());
//3.返回AuthorizationInfo,并且设置权限与角色
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (Permission permission :permissions) {
System.out.print(permission.getSn()+"====");
permissionSns.add(permission.getSn());
}
//设置权限
authorizationInfo.setStringPermissions(permissionSns);
//返回授权
System.out.println("授权成功");
return authorizationInfo;
}
//解决身份认证登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿到令牌
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//2.拿到用户名
String username = token.getUsername();
//根据用户名拿到密码
Employee employee = employeeService.findByUsername(username);
if (employee == null) {
//密码为空 登录失败
return null;
}
//准备盐值
ByteSource salt=ByteSource.Util.bytes("itsource");
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(employee,employee.getPassword(), salt,getName());
System.out.println("denglu");
return authenticationInfo;
}
权限过滤
/**
* 設置需要登录才能访问的路径 和 需要权限才能访问的路径
*
* 修改此处的代码需要重启服务器
*/
public class FilterChainDefinitionMapBuilder {
@Autowired
private IPermissionService permissionService;
public Map<String,String> createFilterChainDefinitionMap(){
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
//filterChainDefinitionMap.put("/login1","anon");
//设置不需要登录也可以访问的路径 anon:无需登录
// filterChainDefinitionMap.put("/s/login.jsp","anon");
// 放行 点击登录访问的页面
filterChainDefinitionMap.put("/login","anon");
//放行首页
filterChainDefinitionMap.put("/index.jsp","anon");
filterChainDefinitionMap.put("*.js","anon");
filterChainDefinitionMap.put("*.css","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/plugin/**","anon");
filterChainDefinitionMap.put("/static/**","anon");
//设置需要权限访问的路径
//1.为数据为中获取到所有的权限
List<Permission> perms = permissionService.queryAll();
//2.遍历perms,把每个值放进去
perms.forEach(p ->{
System.out.println(p.getUrl()+"====="+p.getSn());
filterChainDefinitionMap.put(p.getUrl(),"crmPerms["+p.getSn()+"]");
});
//设置需要权限访问的路径
//filterChainDefinitionMap.put("/employee/index","perms[employee:index]");
//filterChainDefinitionMap.put("/dept/index","perms[dept:index]");
//设置需要登录才能访问 authc:需要登录
filterChainDefinitionMap.put("/**","authc");
return filterChainDefinitionMap;
}
}
密码加密工具类
public class MD5Util {
public static final String SALT = "itsource";
public static final int HASHITERATIONS = 10;
public static String createMd5Str(String pwd){
SimpleHash hash = new SimpleHash("MD5",pwd,SALT,HASHITERATIONS);
return hash.toString();
}
}
设置用户到session和从session中获取用户
public class UserContext {
public static final String USER_IN_SESSION = "userInSession";
//设置用户到session中
public static void setUser(Employee loginUser){
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute(USER_IN_SESSION,loginUser);
}
//为Session中获取到当前登录用户
public static Employee getUser(){
Subject subject = SecurityUtils.getSubject();
Employee employee = (Employee) subject.getSession().getAttribute(USER_IN_SESSION);
return employee;
}
}