1. 学习目标

2. CRM 系统概念与项目开发流程

2. 1. CRM 基本概念

圈内存在这么一句话:“世上本来没有 CRM,大家的生意越来越难做了,才有了 CRM。” 在同质化竞 争时代,顾客资产尤为重要,新时代在呼唤 CRM。
CRM 系统即客户关系管理系统, 顾名思义就是管理公司与客户之间的关系。 是一种以"客户关系一对 一理论"为基础,旨在改善企业与客户之间关系的新型管理机制。客户关系管理的定义是:企业为提高核心竞争力,利用相应的信息技术以及互联网技术来协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理方式,向客户提供创新式的个性化的客户交互和服务的过程。 其最终目标是吸引新客户、 保留老客户以及将已有客户转为忠实客户,增加公司市场份额。
CRM 的实施目标就是通过全面提升企业业务流程的管理来降低企业成本,通过提供更快速和周到的优质服务来吸引和保持更多的客户。作为一种新型管理机制,CRM 极大地改善了企业与客户之间的关系, 应用于企业的市场营销、销售、服务与技术支持等与客户相关的领域。

2. 2. CRM 分类

根据客户的类型不同,CRM 可以分为 B to B CRM 及 B to C CRM。 BtoB CRM 中管理的客户是企业 客户,而 B to C CRM 管理的客户则是个人客户。提供企业产品销售和服务的企业需要的 B to B 的CRM,也就是市面上大部分 CRM 的内容。而提供个人及家庭消费的企业需要的是 B to C 的 CRM。
根据 CRM 管理侧重点不同又分为操作性和分析型 CRM。大部分 CRM 为操作型 CRM,支持CRM的日 常作业流程的每个环节,而分析型 CRM 则偏重于数据分析。

2. 3. 企业项目开发流程

  1. 产品组根据市场调研或商户同事的反馈提出 idea,设计出原型然后跟市场, 商户同事进行确认
  2. UI 设计组和开发组一起讨论,确定方案是否可行
  3. UI 组根据产品组提供的原型稿做出设计稿,与产品和开发确认
  4. 开发组根据产品的原型稿(看逻辑)和UI组的设计稿(看界面)编写代码其中当然也会来回跟设计, 产品 同学进行确认和沟通
  5. 代码编写完毕后提交给测试组. 然后再提交上线
  6. 后期的数据跟踪和优化

这就是一个产品研发的大致流程。其中开发的责任就是选用合适的框架技术来完成产品所提供的需求 以及设计所提供的效果。

3. CRM 系统模块划分

3. 1. 系统功能模块图

3. 2. 模块功能描述

3. 2. 1. 基础模块

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

3. 2. 2. 营销管理

营销机会管理 :企业客户的质询需求所建立的信息录入功能,方便销售人员进行后续的客户需求跟 踪。

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

3. 2. 3. 客户管理

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

3. 2. 4. 服务管理

服务管理是针对客户而开发的功能,针对客户要求,Crm 提供客户相应的信息质询,反馈与投诉功能,提高企业对于客户的服务质量。
营销开发计划 :开发计划是根据营销机会而来,对于企业质询的客户,会有相应的销售人员对于该客户进行具

3. 2. 5. 数据报表

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

3. 2. 6. 系统管理

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

4. CRM 系统数据库设计

CRM 系统根据产品的原型稿以及UI组的设计稿,接下来就要设计数据库, 一般在大公司通常会有专门的DBA, 这时我们可以不要考虑数据库表设计, 但是也要能够读懂或者了解DBA的设计思路方便在程序开发阶段不会出现问题,一般关系型数据库表设计满足三范式的设计即可,表名设计做到见名知意最 好。

5. 项目环境搭建与测试

5. 1. 项目技术栈

5. 2. 环境搭建与测试

5. 2. 1. 新建项目

在 IDEA 中,新建 SpringBoot 项目,项目名设置为 crm

5. 2. 2. 引入坐标 & 插件

在 pom.xml 文件中,添加项目集成环境所需要的依赖坐标与插件

<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.2.RELEASE</version></parent><dependencies><!-- web 环境 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- freemarker --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!-- 测试环境 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><!-- 分页插件 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.2.13</version></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- c3p0 --><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version></dependency><!-- commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency><!-- json --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><!-- DevTools 热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.2</version><configuration><source>11</source><target>11</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration><configurationFile>src/main/resources/generatorConfig.xml</configurationFile><verbose>true</verbose><overwrite>true</overwrite></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><!-- 如果没有该配置,热部署的devtools不生效 --><fork>true</fork></configuration></plugin></plugins></build>

5. 2. 3. 添加配置文件

src/main/resources 目录下新建 application.yml 配置文件,内容如下:

## 端口号  上下文路径
server:port: 8080servlet:context-path: /crm## 数据源配置
spring:datasource:type: com.mchange.v2.c3p0.ComboPooledDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/crm?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8username: rootpassword: 123456## freemarkerfreemarker:suffix: .ftlcontent-type: text/htmlcharset: UTF-8template-loader-path: classpath:/views/## 启用热部署devtools:restart:enabled: trueadditional-paths: src/main/java## mybatis 配置
mybatis:mapper-locations: classpath:/mappers/*.xmltype-aliases-package: com.xxxx.crm.vo;com.xxxx.crm.query;com.xxxx.crm.dtoconfiguration:map-underscore-to-camel-case: true## pageHelper 分页
pagehelper:helper-dialect: mysql## 设置 dao 日志打印级别
logging:level:com:xxxx:crm:dao: debug

5. 2. 4. 添加视图转发

新建 com.xxxx.crm.controller 包,添加系统登录,主页面转发代码 (这里先引入 base 包,具体文件 见相关目录)

package com.xxxx.crm.controller;import com.xxxx.crm.base.BaseController;
import com.xxxx.crm.service.UserService;
import com.xxxx.crm.utils.LoginUserUtil;
import com.xxxx.crm.vo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@Controller
public class IndexController extends BaseController {@Resourceprivate UserService userService;/*** 系统登录页* @return*/@RequestMapping("index")public String index(){return "index";}// 系统界面欢迎页@RequestMapping("welcome")public String welcome(){return "welcome";}/*** 后端管理主页面* @return*/@RequestMapping("main")public String main() {return "main";}
}

5. 2. 5. 添加静态资源

在 src/main/resources 目录下新建 public 目录,存放系统相关静态资源文件,拷贝静态文件内容到public 目录。

5. 2. 6. 添加视图模板

在 src/main/resources 目录下新建 views 目录,添加 index.ftl、main.ftl 等文件。 (具体视图文件详见 相关目录)

5. 2. 7. 添加应用启动类

在 com.xxxx.crm 包下新建 Starter.java ,添加启动项目相关代码如下:

package com.xxxx;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Hello world!*/
@SpringBootApplication
@MapperScan("com.xxxx.crm.dao")
public class App {public static void main(String[] args) {SpringApplication.run(App.class);}
}

5. 2. 8. 项目目录结构

5. 2. 9. 浏览器访问

Chrome浏览器访问登录页地址:http://localhost:8080/crm/index
Chrome浏览器访问系统主页地址:http://localhost:8080/crm/main

6. 用户登录功能实现

6. 1. 准备工作

6. 1. 1. 工具类与自定义异常类

将工具类与自定义异常类,拷贝到项目中。 (这里拷贝 utils 包和 exceptions 包,具体文件见相关目录)

6. 1. 2. 自动生成代码

6. 1. 2. 1. generatorConfig.xml

在 src/main/resources 目录下,添加 generatorConfig.xml 配置文件。(需要修改数据库驱动路径、数据库账号密码等信息。)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!-- 数据库驱动路径:在左侧project边栏的External Libraries中找到mysql的驱动下的jar包,右键选择copy path --><classPathEntrylocation="E:\.m2\maven_repository\mysql\mysql-connector-java\8.0.18\mysql-connector-java-8.0.18.jar"/><!-- context 是逆向工程的主要配置信息,id:起个名字,targetRuntime:设置生成的文件适用于哪个mybatis版本 --><context id="DB2Tables" targetRuntime="MyBatis3"><!--optional,指在创建class时,对注释进行控制--><commentGenerator><!-- 是否去除日期那行注释 --><property name="suppressDate" value="true"/><!-- 是否去除自动生成的注释 true:是 : false:否 --><property name="suppressAllComments" value="true"/></commentGenerator><!-- 数据库链接地址账号密码 --><jdbcConnectiondriverClass="com.mysql.cj.jdbc.Driver"connectionURL="jdbc:mysql://127.0.0.1:3306/crm?serverTimezone=GMT%2B8"userId="root"password="123456"></jdbcConnection><!--java类型处理器用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和NUMERIC数据类型;true:使用 BigDecimal对应DECIMAL和NUMERIC数据类型false:默认,把JDBC DECIMAL和NUMERIC类型解析为Integer--><javaTypeResolver><property name="forceBigDecimals" value="false"/></javaTypeResolver><!-- 生成Model类存放位置 --><javaModelGenerator targetPackage="com.xxxx.crm.vo" targetProject="src/main/java"><!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,生成的类放在这个package下,默认为false --><property name="enableSubPackages" value="true"/><!-- 设置是否在getter方法中,对String类型字段调用trim()方法 --><property name="trimStrings" value="true"/></javaModelGenerator><!--生成映射文件存放位置--><sqlMapGenerator targetPackage="mappers" targetProject="src/main/resources"><property name="enableSubPackages" value="true"/></sqlMapGenerator><!--生成Dao类存放位置--><javaClientGenerator type="XMLMAPPER" targetPackage="com.xxxx.crm.dao" targetProject="src/main/java"><property name="enableSubPackages" value="true"/></javaClientGenerator><!-- 数据库的表名与对应的实体类的名称,tableName是数据库中的表名,domainObjectName是生成的JAVA模型名 --><!--用完可以注释掉,防止错按--><!-- <table tableName="t_user" domainObjectName="User"enableCountByExample="false" enableUpdateByExample="false"enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>--></context>
</generatorConfiguration>
6. 1. 2. 2. 执行命令

使用mybatis-generator生成Mybatis代码。能够生成 vo 类、能生成 mapper 映射文件(其中包括基

本的增删改查功能)、能生成 mapper 接口。

命令:mybatis-generator:generate -e

6. 2. 核心思路分析

前台

  1. 获取用户输入的数据

  2. 校验用户输入的数据

  3. 发送ajax请求到后台

  4. 接收后台返回的数据ResultInfo(封装UserModel,将数据存放在cookie中,保持登录状态)

后台

  1. 接收参数

  2. 校验参数是否为空 如果为空,抛异常

  3. 通过用户名查询数据库数据 如果未查到,抛异常(用户不存在)

  4. 校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验) 如果不一致,抛异常(密码错误)

  5. 封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)

分层思想

controller

  1. 接收参数,调用service

service

  1. 校验参数是否为空 如果为空,抛异常

  2. 调用dao层查询通过用户名查询数据库数据 如果未查到,抛异常(用户不存在)

  3. 校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验) 如果不一致,抛异常(密码错误)

  4. 封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)

dao

通过用户名查询数据库数据

6. 3. 核心代码实现

6. 3. 1. UserModel

定义 UserModel 实体类,用来返回登录成功后的用户信息

package com.xxxx.crm.query;public class UserModel {//private Integer userId;private String userId;private String userName;private String trueName;/*public Integer getUserId() {return userId;}public void setUserId(Integer userId) {this.userId = userId;}
*/public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getTrueName() {return trueName;}public void setTrueName(String trueName) {this.trueName = trueName;}
}

6. 3. 2. UserService

用户登录具体的业务逻辑的实现

package com.xxxx.crm.service;import com.xxxx.crm.base.BaseService;
import com.xxxx.crm.base.ResultInfo;
import com.xxxx.crm.dao.UserMapper;
import com.xxxx.crm.query.UserModel;
import com.xxxx.crm.utils.AssertUtil;
import com.xxxx.crm.utils.Md5Util;
import com.xxxx.crm.utils.UserIDBase64;
import com.xxxx.crm.vo.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Date;@Service
public class UserService extends BaseService<User, Integer> {@Resourceprivate UserMapper userMapper;/*** 用户登录* 2.校验参数是否为空* 如果为空,抛异常* 3.调用dao层查询通过用户名查询数据库数据* 如果未查到,抛异常(用户不存在)* 4.校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验)* 如果不一致,抛异常(密码错误)* 5.封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)*/public ResultInfo loginCheck(String userName, String userPwd) {//校验参数是否为空checkLoginData(userName, userPwd);//调用dao层查询通过用户名查询数据库数据,判断账号是否存在User user = userMapper.queryUserByName(userName);AssertUtil.isTrue(user == null, "账号不存在");//校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验)checkLoginPwd(user.getUserPwd(),userPwd);//封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)ResultInfo resultInfo = buildResultInfo(user);//封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)/*ResultInfo resultInfo = new ResultInfo();UserModel userModel = new UserModel();userModel.setUserId(user.getId());userModel.setUserName(user.getUserName());userModel.setTrueName(user.getTrueName());resultInfo.setResult(userModel);*/return resultInfo;}/*** 准备前台cookie需要的数  usermodel* @param user*/private ResultInfo buildResultInfo(User user) {ResultInfo resultInfo = new ResultInfo();//封装userMdel  cookie需要的数据UserModel userModel = new UserModel();//将userid加密String id = UserIDBase64.encoderUserID(user.getId());userModel.setUserId(id);userModel.setUserName(user.getUserName());userModel.setTrueName(user.getTrueName());resultInfo.setResult(userModel);return resultInfo;}private void checkLoginPwd(String dbPwd, String userPwd) {//将传来的密码加密再校验String encodePwd = Md5Util.encode(userPwd);//校验AssertUtil.isTrue(!encodePwd.equals(dbPwd), "用户密码错误");}/*** 用户登录参数非空校验** @param userName* @param userPwd*/private void checkLoginData(String userName, String userPwd) {AssertUtil.isTrue(StringUtils.isBlank(userName), "用户名不能为空");AssertUtil.isTrue(StringUtils.isBlank(userPwd), "密码不能为空");}
}

6. 3. 3. UserMapper

在 UserMapper 接口类中定义对应的查询方法

package com.xxxx.crm.dao;import com.xxxx.crm.base.BaseMapper;
import com.xxxx.crm.vo.User;public interface UserMapper extends BaseMapper<User,Integer> {//通过用户名称查询数据public User queryUserByName(String name);
}

6. 3. 4. UserMapper.xml

配置查询对应的 SQL 语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xxxx.crm.dao.UserMapper" ><resultMap id="BaseResultMap" type="com.xxxx.crm.vo.User" ><id column="id" property="id" jdbcType="INTEGER" /><result column="user_name" property="userName" jdbcType="VARCHAR" /><result column="user_pwd" property="userPwd" jdbcType="VARCHAR" /><result column="true_name" property="trueName" jdbcType="VARCHAR" /><result column="email" property="email" jdbcType="VARCHAR" /><result column="phone" property="phone" jdbcType="VARCHAR" /><result column="is_valid" property="isValid" jdbcType="INTEGER" /><result column="create_date" property="createDate" jdbcType="TIMESTAMP" /><result column="update_date" property="updateDate" jdbcType="TIMESTAMP" /></resultMap><sql id="Base_Column_List" >id, user_name, user_pwd, true_name, email, phone, is_valid, create_date, update_date</sql><select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >select <include refid="Base_Column_List" />from t_userwhere id = #{id,jdbcType=INTEGER}</select><delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >delete from t_userwhere id = #{id,jdbcType=INTEGER}</delete><insert id="insert" parameterType="com.xxxx.crm.vo.User" >insert into t_user (id, user_name, user_pwd, true_name, email, phone, is_valid, create_date, update_date)values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{userPwd,jdbcType=VARCHAR}, #{trueName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR}, #{isValid,jdbcType=INTEGER}, #{createDate,jdbcType=TIMESTAMP}, #{updateDate,jdbcType=TIMESTAMP})</insert><insert id="insertSelective" parameterType="com.xxxx.crm.vo.User" >insert into t_user<trim prefix="(" suffix=")" suffixOverrides="," ><if test="id != null" >id,</if><if test="userName != null" >user_name,</if><if test="userPwd != null" >user_pwd,</if><if test="trueName != null" >true_name,</if><if test="email != null" >email,</if><if test="phone != null" >phone,</if><if test="isValid != null" >is_valid,</if><if test="createDate != null" >create_date,</if><if test="updateDate != null" >update_date,</if></trim><trim prefix="values (" suffix=")" suffixOverrides="," ><if test="id != null" >#{id,jdbcType=INTEGER},</if><if test="userName != null" >#{userName,jdbcType=VARCHAR},</if><if test="userPwd != null" >#{userPwd,jdbcType=VARCHAR},</if><if test="trueName != null" >#{trueName,jdbcType=VARCHAR},</if><if test="email != null" >#{email,jdbcType=VARCHAR},</if><if test="phone != null" >#{phone,jdbcType=VARCHAR},</if><if test="isValid != null" >#{isValid,jdbcType=INTEGER},</if><if test="createDate != null" >#{createDate,jdbcType=TIMESTAMP},</if><if test="updateDate != null" >#{updateDate,jdbcType=TIMESTAMP},</if></trim></insert><update id="updateByPrimaryKeySelective" parameterType="com.xxxx.crm.vo.User" >update t_user<set ><if test="userName != null" >user_name = #{userName,jdbcType=VARCHAR},</if><if test="userPwd != null" >user_pwd = #{userPwd,jdbcType=VARCHAR},</if><if test="trueName != null" >true_name = #{trueName,jdbcType=VARCHAR},</if><if test="email != null" >email = #{email,jdbcType=VARCHAR},</if><if test="phone != null" >phone = #{phone,jdbcType=VARCHAR},</if><if test="isValid != null" >is_valid = #{isValid,jdbcType=INTEGER},</if><if test="createDate != null" >create_date = #{createDate,jdbcType=TIMESTAMP},</if><if test="updateDate != null" >update_date = #{updateDate,jdbcType=TIMESTAMP},</if></set>where id = #{id,jdbcType=INTEGER}</update><update id="updateByPrimaryKey" parameterType="com.xxxx.crm.vo.User" >update t_userset user_name = #{userName,jdbcType=VARCHAR},user_pwd = #{userPwd,jdbcType=VARCHAR},true_name = #{trueName,jdbcType=VARCHAR},email = #{email,jdbcType=VARCHAR},phone = #{phone,jdbcType=VARCHAR},is_valid = #{isValid,jdbcType=INTEGER},create_date = #{createDate,jdbcType=TIMESTAMP},update_date = #{updateDate,jdbcType=TIMESTAMP}where id = #{id,jdbcType=INTEGER}</update><select id="queryUserByName" parameterType="String" resultType="user">select * from t_user where is_valid = 1 and user_name=#{name}</select>
</mapper>

6. 3. 5. UserController

控制层定义接口,对接前台。Controller 层调用 Service 层 userLogin 方法,捕获 service 方法的异

常,获取登录结果,并将 ResultInfo 对象通过 JSON 格式响应给客户端。

package com.xxxx.crm.controller;import com.xxxx.crm.base.BaseController;
import com.xxxx.crm.base.ResultInfo;
import com.xxxx.crm.exceptions.ParamsException;
import com.xxxx.crm.service.UserService;
import com.xxxx.crm.utils.LoginUserUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@Controller
@RequestMapping("user")
public class UserController extends BaseController {@Resourceprivate UserService userService;/***  用户登录* @param userName* @param userPwd*/@PostMapping("login")@ResponseBodypublic ResultInfo login(String userName,String userPwd) {// return userService.loginCheck(userName, userPwd);ResultInfo resultInfo = new ResultInfo();try {resultInfo = userService.loginCheck(userName, userPwd);} catch (ParamsException e) {e.printStackTrace();resultInfo.setCode(400);resultInfo.setMsg(e.getMsg());} catch (Exception e) {e.printStackTrace();resultInfo.setCode(500);resultInfo.setMsg("登录失败");}return resultInfo;}}

6. 3. 6. Starter

修改启动类,在启动类上添加 @MapperScan 注解,设置扫描包范围。

package com.xxxx;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** Hello world!*/
@SpringBootApplication
@MapperScan("com.xxxx.crm.dao")
public class App {public static void main(String[] args) {SpringApplication.run(App.class);}
}

6.3.7. PostMan 测试

利用 Postman 工具,对用户登录的接口进行测试。

6.3.8. 前端登录功能实现

index.ftl 添加对应 index.js,使用 layui 表单组件实现表单提交操作。登录成功后,如果

参考API:https://www.layui.com/doc/modules/form.html#onsubmit

layui.use(['form','jquery','jquery_cookie'], function () {var form = layui.form,layer = layui.layer,$ = layui.jquery,$ = layui.jquery_cookie($);/*** 监听表单的提交*     on监听 submit事件*/form.on("submit(login)",function (data){/*console.log(data.elem);console.log(data.form);*/console.log(data.field) //当前容器的全部表单字段,名值对形式:{name: value}//数据校验 TODO//使用了lay-verify表单验证//发送请求$.ajax({type:"post",url: ctx + "/user/login",data:{userName:data.field.username,userPwd:data.field.password},dataType:'json',success:function (data){if(data.code == 200){//存储cookie/* $.cookie("userId",data.result.userId);$.cookie("userName",data.result.userName);$.cookie("trueName",data.result.trueName);*/$.cookie("userIdStr",data.result.userId);$.cookie("userName",data.result.userName);$.cookie("trueName",data.result.trueName);//记住密码if($("#rememberMe").prop("checked")){$.cookie("userIdStr", data.result.userId, { expires: 7 });$.cookie("userName", data.result.userName, { expires: 7 });$.cookie("trueName", data.result.trueName, { expires: 7 });}//跳转到首页window.location.href = ctx + "/main";}else{layer.msg(data.msg,{icon:5});}}});return false; //阻止表单跳转。如果需要表单跳转,去掉这段即可。});
});

6.3.9. 修改 Cookie 的数据

将 Cookie 中的 userId 的值加密存储。

6.3.10. 主页面显示用户名信息

6.3.11. 启动程序测试登录效果

使用测试账号执行登录操作。(用户名:admin ,密码:123)

7. 密码修改功能实现

7. 1. 核心思路分析

  1. 确保用户是否是登录状态获取cookie中的id 非空 查询数据库

  2. 校验老密码 非空 老密码必须要跟数据库中密码一致

  3. 新密码 非空 新密码不能和原密码一致

  4. 确认密码 非空 确认必须和新密码一致

  5. 执行修改操作,返回ResultInfo

7. 2. UserService

updateUserPassword 方法实现

/** 修改密码*/public void userUpdate(Integer userId,String oldPassword,String newPassword,String confirmPassword){//确保用户是否是登录状态获取cookie中的id 非空 查询数据库AssertUtil.isTrue(userId == null,"用户未登录");User user = userMapper.selectByPrimaryKey(userId);AssertUtil.isTrue(user == null,"用户状态异常");//校验密码数据checkUpdateData(oldPassword,newPassword,confirmPassword,user.getUserPwd());// 执行修改操作,返回ResultInfouser.setUserPwd(Md5Util.encode(newPassword));user.setUpdateDate(new Date());//判断是否修改成功AssertUtil.isTrue(userMapper.updateByPrimaryKeySelective(user) < 1,"密码修改失败");}/**密码校验*          1.确保用户是否是登录状态获取cookie中的id 非空 查询数据库*          2.校验老密码 非空  老密码必须要跟数据库中密码一致*          3.新密码    非空  新密码不能和原密码一致*          4.确认密码  非空  确认必须和新密码一致*          5.执行修改操作,返回ResultInfo* @param oldPassword* @param newPassword* @param confirmPassword* @param dbPassword*/private void checkUpdateData(String oldPassword, String newPassword, String confirmPassword, String dbPassword) {//校验老密码  非空  老密码必须要跟数据库中密码一致AssertUtil.isTrue(StringUtils.isBlank(oldPassword),"原始密码不存在");AssertUtil.isTrue(!dbPassword.equals(Md5Util.encode(oldPassword)),"原始密码错误");//新密码    非空  新密码不能和原密码一致AssertUtil.isTrue(StringUtils.isBlank(newPassword),"新密码不能为空");AssertUtil.isTrue(oldPassword.equals(newPassword),"新密码不能和原密码一致");//确认密码  非空  确认必须和新密码一致AssertUtil.isTrue(StringUtils.isBlank(confirmPassword),"确认密码不能为空");AssertUtil.isTrue(!confirmPassword.equals(newPassword),"确认密码必须和新密码一致");}

7.3. UserController

updateUserPassword 方法实现

/***  修改密码*/@PostMapping("update")@ResponseBodypublic ResultInfo update(HttpServletRequest request, String oldPassword, String newPassword, String confirmPassword){ResultInfo resultInfo = new ResultInfo();//int i = 1/0;//获取登录用户的idint id = LoginUserUtil.releaseUserIdFromCookie(request);//userService.userUpdate(id,oldPassword,newPassword,confirmPassword);try {userService.userUpdate(id,oldPassword,newPassword,confirmPassword);} catch (ParamsException e) {e.printStackTrace();resultInfo.setCode(400);resultInfo.setMsg(e.getMsg());} catch (Exception e) {e.printStackTrace();resultInfo.setCode(500);resultInfo.setMsg("修改密码失败");}// return success();return resultInfo;}

7.4. PostMan 测试

7.4.1. Postman 中添加 Cookie

7.5. 前端核心代码

8. 用户退出功能实现

8. 1. 退出登录

找到 “退出登录” 的元素,并绑定点击事件。当用户点击退出时,清空cookie信息

在 main.js 中,通过类选择器绑定元素的点击事件

9. 全局异常统一处理

9. 1. 全局异常实现思路

控制层的方法返回的内容两种情况

  1. 视图:视图异常

  2. Json:方法执行错误 返回错误json信息

9. 2. 全局异常拦截器实现

实现 HandlerExceptionResolver 接口 ,处理应用程序异常信息

package com.xxxx.crm;import com.alibaba.fastjson.JSON;
import com.xxxx.crm.base.ResultInfo;
import com.xxxx.crm.exceptions.NoLoginException;
import com.xxxx.crm.exceptions.ParamsException;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;@Component
public class  GlobalExceptionResolver implements HandlerExceptionResolver {/*** 控制层的方法返回的内容两种情况*        1. 视图:视图异常*     2. Json:方法执行错误 返回错误json信息* @param request* @param response* @param handler* @param ex* @return*/@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {ModelAndView mv = new ModelAndView();if(ex instanceof NoLoginException){NoLoginException ne = (NoLoginException)ex;// mv.setViewName("index");   目前是直接去找视图//目的是跳转到登录页面   必须通过接口才能显示mv.setViewName("redirect:index");return mv;}//设置默认的异常处理mv.setViewName("error");mv.addObject("code",300);mv.addObject("msg","数据异常,请重试");//判断目标方法返回的是视图还是json数据if(handler instanceof HandlerMethod){//转换成controller方法对象HandlerMethod handlerMethod = (HandlerMethod)handler;//获取responsebody注解对象ResponseBody reponsebody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class);//判断当前方法是否存在responsebody注解if(reponsebody == null){//返回视图的接口异常处理if(ex instanceof ParamsException){ParamsException pe = (ParamsException)ex;mv.addObject("code",pe.getCode());mv.addObject("msg",pe.getMsg());}return mv;}else{//返回json的接口异常处理ResultInfo resultInfo = new ResultInfo();resultInfo.setCode(500);resultInfo.setMsg("系统异常请重试");//判断是否是自定义异常if(ex instanceof ParamsException){ParamsException pe = (ParamsException)ex;resultInfo.setCode(pe.getCode());resultInfo.setMsg(pe.getMsg());}//将resultinfo数据传给前台的ajax回调函数//设置数据传输的类型和编码格式response.setContentType("application/json;charset=utf-8");PrintWriter writer = null;try {//获取输出流writer = response.getWriter();//将数据对象转换成json格式的,传输出去writer.write(JSON.toJSONString(resultInfo));writer.flush();} catch (IOException e) {e.printStackTrace();}finally {if(writer != null){writer.close();}}return null;}}return mv;}
}

9. 3. 消除 try-catch 代码

系统引入全局异常,简化控制层 try-catch 代码

package com.xxxx.crm.controller;import com.xxxx.crm.base.BaseController;
import com.xxxx.crm.base.ResultInfo;
import com.xxxx.crm.exceptions.ParamsException;
import com.xxxx.crm.service.UserService;
import com.xxxx.crm.utils.LoginUserUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;@Controller
@RequestMapping("user")
public class UserController extends BaseController {@Resourceprivate UserService userService;/***  用户登录* @param userName* @param userPwd*/@PostMapping("login")@ResponseBodypublic ResultInfo login(String userName,String userPwd) {return userService.loginCheck(userName, userPwd);/* ResultInfo resultInfo = new ResultInfo();try {resultInfo = userService.loginCheck(userName, userPwd);} catch (ParamsException e) {e.printStackTrace();resultInfo.setCode(400);resultInfo.setMsg(e.getMsg());} catch (Exception e) {e.printStackTrace();resultInfo.setCode(500);resultInfo.setMsg("登录失败");}return resultInfo;*/}/***  修改密码*/@PostMapping("update")@ResponseBodypublic ResultInfo update(HttpServletRequest request, String oldPassword, String newPassword, String confirmPassword){//ResultInfo resultInfo = new ResultInfo();//int i = 1/0;//获取登录用户的idint id = LoginUserUtil.releaseUserIdFromCookie(request);userService.userUpdate(id,oldPassword,newPassword,confirmPassword);/*try {userService.userUpdate(id,oldPassword,newPassword,confirmPassword);} catch (ParamsException e) {e.printStackTrace();resultInfo.setCode(400);resultInfo.setMsg(e.getMsg());} catch (Exception e) {e.printStackTrace();resultInfo.setCode(500);resultInfo.setMsg("修改密码失败");}*/return success();//return resultInfo;}/***  修改密码*//* @PostMapping("update")@ResponseBodypublic ResultInfo update(HttpServletRequest request, String oldPassword, String newPassword, String confirmPassword){int i = 1/0;//获取登录用户的idint id = LoginUserUtil.releaseUserIdFromCookie(request);userService.userUpdate(id,oldPassword,newPassword,confirmPassword);return success();}*///打开修改密码页面@RequestMapping("toPasswordPage")public String toPasswordPage(){//int i = 1/0;return "user/password";}
}

10. 非法请求拦截

对于后端菜单资源,这里要求用户必须进行登录来保护 web 资源的安全性,此时引入非法请求拦截功

能。

10. 1. 实现思路

判断用户是否是登录状态

获取Cookie对象,解析用户ID的值

如果用户ID不为空,且在数据库中存在对应的用户记录,表示请求合法

否则,请求不合法,进行拦截,重定向到登录页面

10. 2. 定义拦截器

在新建 interceptors 包,创建 NoLoginInterceptor 类,并继承 HandlerInterceptorAdapter 适配器,

实现拦截器功能。

package com.xxxx.crm.interceptors;import com.xxxx.crm.exceptions.NoLoginException;
import com.xxxx.crm.service.UserService;
import com.xxxx.crm.utils.LoginUserUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class LoginInterceptor implements HandlerInterceptor {@Resourceprivate UserService userService;/*** 在请求到达目标接口之前,拦截* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//通过 cookie中的userIdStr 判断用户是否是登录状态int id = LoginUserUtil.releaseUserIdFromCookie(request);if(id == 0 || null == userService.selectByPrimaryKey(id)){throw new NoLoginException();}return true; //放行 执行目标接口方法}
}

10. 3. 全局异常类配置

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

package com.xxxx.crm.exceptions;/*** 自定义参数异常*/
public class NoLoginException extends RuntimeException {private Integer code=300;private String msg="用户未登录!";public NoLoginException() {super("用户未登录!");}public NoLoginException(String msg) {super(msg);this.msg = msg;}public NoLoginException(Integer code) {super("用户未登录!");this.code = code;}public NoLoginException(Integer code, String msg) {super(msg);this.code = code;this.msg = msg;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

10. 4. 拦截器生效配置

新建 config 包,添加拦截器生效的配置类

package com.xxxx.crm.config;import com.xxxx.crm.interceptors.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Beanpublic LoginInterceptor createLoginInterceptor(){return new LoginInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(createLoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/index","/user/login","/css/**","/images/**","/js/**","/lib/**");}
}

10. 5. 拦截测试

10. 6. 测试拦截效果

当 Cookie 中的用户ID不存在时,访问 main 页面,会自动跳转到登录页面

11. 记住我功能实现

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

11. 1. 修改 index.ftl

在用户登录表单中添加记住密码的复选框

<#-- 记住我 -->
<div class="layui-form-item"><input type="checkbox" name="rememberMe" id="rememberMe" value="true" lay-skin="primary" title="记住密码">
</div>

11. 2. 修改 index.js

如果用户在登录时,勾选了 “记住我” 的复选框,则在登录成功之后,设置 cookie 的有效期

//记住密码
if($("#rememberMe").prop("checked")){$.cookie("userIdStr", data.result.userId, { expires: 7 });$.cookie("userName", data.result.userName, { expires: 7 });$.cookie("trueName", data.result.trueName, { expires: 7 });
}

CRM - 用户管理相关推荐

  1. 客户资源信息管理后台/机构管理/信息管理/商机管理/产品管理/合同管理/日志管理/预约管理/任务清单/员工管理/资料库管理/统计分析/用户管理/售后日志/系统配置/crm客户管理系统/系统对接管理

    Axure作品介绍:客户资源信息管理后台/机构管理/信息管理/商机管理/产品管理/合同管理/日志管理/预约管理/任务清单/员工管理/资料库管理/统计分析/用户管理/售后日志/系统配置/crm客户管理系 ...

  2. 制作CRM管理系统03(用户管理)

    目录 一.用户管理界面显示用户信息 1.1.前端HTML 1.1.1.通过axios发送请求到Servlet获取用户列表数据 1.1.2.将Servlet响应的数据通过v-for指令显示到table上 ...

  3. 2023年国产CRM客户管理系统软件排名

    目录 一.CRM客户管理系统前10名有哪些 1.纷享销客-国内知名CRM服务商 2.销售易-主打企业级CRM 3.神州云动-老牌CRM 4.金蝶-大厂出品 5.红圈CRM-早期SaaS 6.用友CRM ...

  4. CRM服务管理是什么?如何使企业受益?

    今天,CRM服务管理系统是任何企业的必要工具.这是因为服务和支持部门比以往任何时候都更处于企业成功的最前沿.客户的成功取决于客户服务代表与企业其他部门的协作.产生有价值的客户数据是这个过程中不可或缺的 ...

  5. 医疗行业CRM客户管理解决方案

    医疗行业解决方案    行业背景 不同于别的行业,医院信息化不能止步于内部应用.按照目前国内外普遍认同的划分方法,医院信息化一般要经历三个阶段:以人财物为中心的医院管理信息系统(MIS).以病人为中心 ...

  6. 推荐好用的CRM客户管理软件?

    有没有好用的CRM客户管理软件推荐?综合来看,比较推荐您使用Zoho CRM.在功能方面,Zoho CRM的完整性能和领头羊SF有的一拼,但相同版本的价格还不到三分之一:在本土化方面,Zoho CRM ...

  7. dva + antd + mockjs 实现用户管理

    1.安装dva-cli npm install dva-cli -g 2.创建应用 dva new dvadashboard [dvadashboard为项目名] 3.安装mockjs npm ins ...

  8. Saltstack 用户管理

    最近测试组来了一大波,最为公司测试那必须要有qa环境,测试组老大跟我关系很好,他们组不少人对Linux也很了解,平时弄个东西啥的也能自己搞定,软磨硬泡加上最近真的很忙给他们配置了jenkins和开了q ...

  9. puppet aix之自动化用户管理

    一.    用户组的管理 (一)   Puppet组管理特性 1.   manages_aix_lam 用来管理AIX的LAM(Loadable Authentication Module)系统. 2 ...

  10. OA项目12:系统管理之用户管理

    首注:本学习教程为传智播客汤阳光讲师所公布的免费OA项目视频我的文字版实践笔记,本人用此来加强巩固自己开发知识,如有网友转载,请注明.谢谢. 一 之前在第8节时已经将User实体及映射文件建立好了,所 ...

最新文章

  1. 职业经理人的核心技能
  2. Glass Dragon
  3. 验证字符串是否为汉字
  4. python3函数中lambda/filter/map/reduce的用法
  5. cdoj 秋实大哥搞算数
  6. js一次获取整个表单的数据
  7. 这是我的第一个博客,以后遇到问题一起解决
  8. 小白如何快速入门数学建模
  9. Word中公式输入的快捷键
  10. Jenkins + 钉钉 + SpringBoot 极简入门,一键打包部署项目
  11. 浮点数的表示方法是什么?
  12. Qt OpenGL(08)通过递归细分正二十面体逼近球面
  13. ChinaSkills-高职组网络系统管理大赛-WinSer 2019 互联网网卡检测服务笔记
  14. ssrs订阅_SSRS订阅失败警报
  15. Chapter8:控制系统状态空间分析
  16. PMOS和NMOS的导通特性
  17. API开放赋能,打造DING功能快速抢占上亿用户
  18. Zxing和QR CODE 生成与解析二维码实例(带logo篇)
  19. java的配置环境简介配置教学
  20. 关于VTOL垂直起降模式的参数很详细的介绍

热门文章

  1. @开源镜像站(linux系统:Center OS|Ubuntu|Debian)
  2. Laravel文档梳理8、中间件
  3. Computer programming and database - 方方面面入门
  4. npm i --legacy-peer-deps
  5. 图片批量压缩工具免费版-免费的批量图片压缩工具
  6. 【谨记】PCB画板子的正确步骤(说多了,都是泪啊!)
  7. 2022-2028年全球与中国便携式茶包市场现状及未来发展趋势分析报告
  8. java 同比环比_数据相关概念同比,环比
  9. Github学生认证指北
  10. 大数据时代没有隐私,我们都在裸奔