伙伴云客服论坛»论坛 S区 S客户管理 查看内容

0 评论

0 收藏

分享

CRM - 用户管理

1. 学习目的

CRM - 用户管理-1.png


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. 企业项目开发流程

    产品组根据市场调研或商户同事的反响提出 idea,设计出原型然后跟市场, 商户同事停止确认UI 设计组和开发组一起讨论,确定方案是否可行UI 组根据产品组提供的原型稿做出设计稿,与产品和开发确认开发组根据产品的原型稿(看逻辑)和UI组的设计稿(看界面)编写代码其中当然也会来回跟设计, 产品 同学停止确认和沟通代码编写完毕后提交给测试组. 然后再提交上线后期的数据跟踪和优化
这就是一个产品研发的大致流程。其中开发的责任就是选用适宜的框架技术来完成产品所提供的需求 以及设计所提供的效果。
3. CRM 系统模块划分

3. 1. 系统功能模块图

3. 2. 模块功能描绘

3. 2. 1. 根底模块

包含系统根本的用户登录,退出,记住我,密码修改等根本操作。
3. 2. 2. 营销管理

营销时机管理 :企业客户的质询需求所建立的信息录入功能,方便销售人员停止后续的客户需求跟 踪。
  1. 营销开发方案 :开发方案是根据营销时机而来,对于企业质询的客户,会有相应的销售人员对于该客户停止具 体的沟通交流,此时对于整个 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 文件中,添加项目集成环境所需要的依赖坐标与插件
  1. <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 配置文件,内容如下:
  1. ## 端口号  上下文途径server:port:8080servlet:context-path: /crm
  2. ## 数据源配置spring:datasource:type: com.mchange.v2.c3p0.ComboPooledDataSource
  3.     driver-class-name: com.mysql.cj.jdbc.Driver
  4.     url: jdbc:mysql://127.0.0.1:3306/crm?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8username: root
  5.     password:123456## freemarkerfreemarker:suffix: .ftl
  6.     content-type: text/html
  7.     charset: UTF-8template-loader-path: classpath:/views/
  8.   ## 启用热部署devtools:restart:enabled:trueadditional-paths: src/main/java
  9. ## mybatis 配置mybatis:mapper-locations: classpath:/mappers/*.xmltype-aliases-package: com.xxxx.crm.vo;com.xxxx.crm.query;com.xxxx.crm.dto
  10.   configuration:map-underscore-to-camel-case:true## pageHelper 分页pagehelper:helper-dialect: mysql
  11. ## 设置 dao 日志打印级别logging:level:com:xxxx:crm:dao: debug
复制代码
5. 2. 4. 添加视图转发

新建 com.xxxx.crm.controller 包,添加系统登录,主页面转发代码 (这里先引入 base 包,详细文件 见相关目录)
  1. packagecom.xxxx.crm.controller;importcom.xxxx.crm.base.BaseController;importcom.xxxx.crm.service.UserService;importcom.xxxx.crm.utils.LoginUserUtil;importcom.xxxx.crm.vo.User;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.RequestMapping;importjavax.annotation.Resource;importjavax.servlet.http.HttpServletRequest;@ControllerpublicclassIndexControllerextendsBaseController{@ResourceprivateUserService userService;/**
  2.      * 系统登录页
  3.      * @return
  4.      */@RequestMapping("index")publicStringindex(){return"index";}// 系统界面欢送页@RequestMapping("welcome")publicStringwelcome(){return"welcome";}/**
  5.      * 后端管理主页面
  6.      * @return
  7.      */@RequestMapping("main")publicStringmain(){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 ,添加启动项目相关代码如下:
  1. packagecom.xxxx;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/**
  2. * Hello world!
  3. */@SpringBootApplication@MapperScan("com.xxxx.crm.dao")publicclassApp{publicstaticvoidmain(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 配置文件。(需要修改数据库驱动途径、数据库账号密码等信息。)
  1. <?xml version="1.0" encoding="UTF-8"?><!DOCTYPEgeneratorConfigurationPUBLIC"-//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版本 --><contextid="DB2Tables"targetRuntime="MyBatis3"><!--optional,指在创建class时,对注释停止控制--><commentGenerator><!-- 是否去除日期那行注释 --><propertyname="suppressDate"value="true"/><!-- 是否去除自动生成的注释 true:是 : false:否 --><propertyname="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><!--
  2.              java类型处置器
  3.                 用于处置DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
  4.                 注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和NUMERIC数据类型;
  5.                 true:使用 BigDecimal对应DECIMAL和NUMERIC数据类型
  6.                 false:默认,把JDBC DECIMAL和NUMERIC类型解析为Integer
  7.         --><javaTypeResolver><propertyname="forceBigDecimals"value="false"/></javaTypeResolver><!-- 生成Model类寄存位置 --><javaModelGeneratortargetPackage="com.xxxx.crm.vo"targetProject="src/main/java"><!-- 在targetPackage的根底上,根据数据库的schema再生成一层package,生成的类放在这个package下,默认为false --><propertyname="enableSubPackages"value="true"/><!-- 设置是否在getter方法中,对String类型字段调用trim()方法 --><propertyname="trimStrings"value="true"/></javaModelGenerator><!--生成映射文件寄存位置--><sqlMapGeneratortargetPackage="mappers"targetProject="src/main/resources"><propertyname="enableSubPackages"value="true"/></sqlMapGenerator><!--生成Dao类寄存位置--><javaClientGeneratortype="XMLMAPPER"targetPackage="com.xxxx.crm.dao"targetProject="src/main/java"><propertyname="enableSubPackages"value="true"/></javaClientGenerator><!-- 数据库的表名与对应的实体类的名称,tableName是数据库中的表名,domainObjectName是生成的JAVA模型名 --><!--用完可以注释掉,防止错按--><!-- <table tableName="t_user" domainObjectName="User"
  8.                enableCountByExample="false" enableUpdateByExample="false"
  9.                enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
  10.         </table>--></context></generatorConfiguration>
复制代码
6. 1. 2. 2. 执行命令

使用mybatis-generator生成Mybatis代码。可以生成 vo 类、能生成 mapper 映射文件(其中包括基
本的增删改查功能)、能生成 mapper 接口。
命令:mybatis-generator:generate -e
6. 2. 核心思路分析

前台
    获取用户输入的数据
    校验用户输入的数据
    发送ajax恳求到后台
    接收后台返回的数据ResultInfo(封装UserModel,将数据寄存在cookie中,坚持登录状态)
后台
    接收参数
    校验参数是否为空 假设为空,抛异常
    通过用户名查询数据库数据 假设未查到,抛异常(用户不存在)
    校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验) 假设不一致,抛异常(密码错误)
    封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)
分层思想
controller
    接收参数,调用service
service
    校验参数是否为空 假设为空,抛异常
    调用dao层查询通过用户名查询数据库数据 假设未查到,抛异常(用户不存在)
    校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验) 假设不一致,抛异常(密码错误)
    封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)
dao
通过用户名查询数据库数据
6. 3. 核心代码实现

6. 3. 1. UserModel

定义 UserModel 实体类,用来返回登录胜利后的用户信息
  1. packagecom.xxxx.crm.query;publicclassUserModel{//private Integer userId;privateString userId;privateString userName;privateString trueName;/*public Integer getUserId() {
  2.         return userId;
  3.     }
  4.     public void setUserId(Integer userId) {
  5.         this.userId = userId;
  6.     }
  7. */publicStringgetUserId(){return userId;}publicvoidsetUserId(String userId){this.userId = userId;}publicStringgetUserName(){return userName;}publicvoidsetUserName(String userName){this.userName = userName;}publicStringgetTrueName(){return trueName;}publicvoidsetTrueName(String trueName){this.trueName = trueName;}}
复制代码
6. 3. 2. UserService

用户登录详细的业务逻辑的实现
  1. packagecom.xxxx.crm.service;importcom.xxxx.crm.base.BaseService;importcom.xxxx.crm.base.ResultInfo;importcom.xxxx.crm.dao.UserMapper;importcom.xxxx.crm.query.UserModel;importcom.xxxx.crm.utils.AssertUtil;importcom.xxxx.crm.utils.Md5Util;importcom.xxxx.crm.utils.UserIDBase64;importcom.xxxx.crm.vo.User;importorg.apache.commons.lang3.StringUtils;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.Date;@ServicepublicclassUserServiceextendsBaseService<User,Integer>{@ResourceprivateUserMapper userMapper;/**
  2.      * 用户登录
  3.      * 2.校验参数是否为空
  4.      * 假设为空,抛异常
  5.      * 3.调用dao层查询通过用户名查询数据库数据
  6.      * 假设未查到,抛异常(用户不存在)
  7.      * 4.校验前台传来的密码和数据库中的密码是否一致 (前台密码加密后再校验)
  8.      * 假设不一致,抛异常(密码错误)
  9.      * 5.封装ResultInfo对象给前台(根据前台需求:usermodel对象封装后传到前台使用)
  10.      */publicResultInfologinCheck(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();
  11.         UserModel userModel = new UserModel();
  12.         userModel.setUserId(user.getId());
  13.         userModel.setUserName(user.getUserName());
  14.         userModel.setTrueName(user.getTrueName());
  15.         resultInfo.setResult(userModel);*/return resultInfo;}/**
  16.      * 准备前台cookie需要的数  usermodel
  17.      * @param user
  18.      */privateResultInfobuildResultInfo(User user){ResultInfo resultInfo =newResultInfo();//封装userMdel  cookie需要的数据UserModel userModel =newUserModel();//将userid加密String id =UserIDBase64.encoderUserID(user.getId());
  19.         userModel.setUserId(id);
  20.         userModel.setUserName(user.getUserName());
  21.         userModel.setTrueName(user.getTrueName());
  22.         resultInfo.setResult(userModel);return resultInfo;}privatevoidcheckLoginPwd(String dbPwd,String userPwd){//将传来的密码加密再校验String encodePwd =Md5Util.encode(userPwd);//校验AssertUtil.isTrue(!encodePwd.equals(dbPwd),"用户密码错误");}/**
  23.      * 用户登录参数非空校验
  24.      *
  25.      * @param userName
  26.      * @param userPwd
  27.      */privatevoidcheckLoginData(String userName,String userPwd){AssertUtil.isTrue(StringUtils.isBlank(userName),"用户名不能为空");AssertUtil.isTrue(StringUtils.isBlank(userPwd),"密码不能为空");}}
复制代码
6. 3. 3. UserMapper

在 UserMapper 接口类中定义对应的查询方法
  1. packagecom.xxxx.crm.dao;importcom.xxxx.crm.base.BaseMapper;importcom.xxxx.crm.vo.User;publicinterfaceUserMapperextendsBaseMapper<User,Integer>{//通过用户名称查询数据publicUserqueryUserByName(String name);}
复制代码
6. 3. 4. UserMapper.xml

配置查询对应的 SQL 语句
  1. <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.xxxx.crm.dao.UserMapper"><resultMapid="BaseResultMap"type="com.xxxx.crm.vo.User"><idcolumn="id"property="id"jdbcType="INTEGER"/><resultcolumn="user_name"property="userName"jdbcType="VARCHAR"/><resultcolumn="user_pwd"property="userPwd"jdbcType="VARCHAR"/><resultcolumn="true_name"property="trueName"jdbcType="VARCHAR"/><resultcolumn="email"property="email"jdbcType="VARCHAR"/><resultcolumn="phone"property="phone"jdbcType="VARCHAR"/><resultcolumn="is_valid"property="isValid"jdbcType="INTEGER"/><resultcolumn="create_date"property="createDate"jdbcType="TIMESTAMP"/><resultcolumn="update_date"property="updateDate"jdbcType="TIMESTAMP"/></resultMap><sqlid="Base_Column_List">
  2.     id, user_name, user_pwd, true_name, email, phone, is_valid, create_date, update_date
  3.   </sql><selectid="selectByPrimaryKey"resultMap="BaseResultMap"parameterType="java.lang.Integer">
  4.     select
  5.     <includerefid="Base_Column_List"/>
  6.     from t_user
  7.     where id = #{id,jdbcType=INTEGER}
  8.   </select><deleteid="deleteByPrimaryKey"parameterType="java.lang.Integer">
  9.     delete from t_user
  10.     where id = #{id,jdbcType=INTEGER}
  11.   </delete><insertid="insert"parameterType="com.xxxx.crm.vo.User">
  12.     insert into t_user (id, user_name, user_pwd,
  13.       true_name, email, phone,
  14.       is_valid, create_date, update_date
  15.       )
  16.     values (#{id,jdbcType=INTEGER}, #{userName,jdbcType=VARCHAR}, #{userPwd,jdbcType=VARCHAR},
  17.       #{trueName,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{phone,jdbcType=VARCHAR},
  18.       #{isValid,jdbcType=INTEGER}, #{createDate,jdbcType=TIMESTAMP}, #{updateDate,jdbcType=TIMESTAMP}
  19.       )
  20.   </insert><insertid="insertSelective"parameterType="com.xxxx.crm.vo.User">
  21.     insert into t_user
  22.     <trimprefix="("suffix=")"suffixOverrides=","><iftest="id != null">
  23.         id,
  24.       </if><iftest="userName != null">
  25.         user_name,
  26.       </if><iftest="userPwd != null">
  27.         user_pwd,
  28.       </if><iftest="trueName != null">
  29.         true_name,
  30.       </if><iftest="email != null">
  31.         email,
  32.       </if><iftest="phone != null">
  33.         phone,
  34.       </if><iftest="isValid != null">
  35.         is_valid,
  36.       </if><iftest="createDate != null">
  37.         create_date,
  38.       </if><iftest="updateDate != null">
  39.         update_date,
  40.       </if></trim><trimprefix="values ("suffix=")"suffixOverrides=","><iftest="id != null">
  41.         #{id,jdbcType=INTEGER},
  42.       </if><iftest="userName != null">
  43.         #{userName,jdbcType=VARCHAR},
  44.       </if><iftest="userPwd != null">
  45.         #{userPwd,jdbcType=VARCHAR},
  46.       </if><iftest="trueName != null">
  47.         #{trueName,jdbcType=VARCHAR},
  48.       </if><iftest="email != null">
  49.         #{email,jdbcType=VARCHAR},
  50.       </if><iftest="phone != null">
  51.         #{phone,jdbcType=VARCHAR},
  52.       </if><iftest="isValid != null">
  53.         #{isValid,jdbcType=INTEGER},
  54.       </if><iftest="createDate != null">
  55.         #{createDate,jdbcType=TIMESTAMP},
  56.       </if><iftest="updateDate != null">
  57.         #{updateDate,jdbcType=TIMESTAMP},
  58.       </if></trim></insert><updateid="updateByPrimaryKeySelective"parameterType="com.xxxx.crm.vo.User">
  59.     update t_user
  60.     <set><iftest="userName != null">
  61.         user_name = #{userName,jdbcType=VARCHAR},
  62.       </if><iftest="userPwd != null">
  63.         user_pwd = #{userPwd,jdbcType=VARCHAR},
  64.       </if><iftest="trueName != null">
  65.         true_name = #{trueName,jdbcType=VARCHAR},
  66.       </if><iftest="email != null">
  67.         email = #{email,jdbcType=VARCHAR},
  68.       </if><iftest="phone != null">
  69.         phone = #{phone,jdbcType=VARCHAR},
  70.       </if><iftest="isValid != null">
  71.         is_valid = #{isValid,jdbcType=INTEGER},
  72.       </if><iftest="createDate != null">
  73.         create_date = #{createDate,jdbcType=TIMESTAMP},
  74.       </if><iftest="updateDate != null">
  75.         update_date = #{updateDate,jdbcType=TIMESTAMP},
  76.       </if></set>
  77.     where id = #{id,jdbcType=INTEGER}
  78.   </update><updateid="updateByPrimaryKey"parameterType="com.xxxx.crm.vo.User">
  79.     update t_user
  80.     set user_name = #{userName,jdbcType=VARCHAR},
  81.       user_pwd = #{userPwd,jdbcType=VARCHAR},
  82.       true_name = #{trueName,jdbcType=VARCHAR},
  83.       email = #{email,jdbcType=VARCHAR},
  84.       phone = #{phone,jdbcType=VARCHAR},
  85.       is_valid = #{isValid,jdbcType=INTEGER},
  86.       create_date = #{createDate,jdbcType=TIMESTAMP},
  87.       update_date = #{updateDate,jdbcType=TIMESTAMP}
  88.     where id = #{id,jdbcType=INTEGER}
  89.   </update><selectid="queryUserByName"parameterType="String"resultType="user">
  90.     select * from t_user where is_valid = 1 and user_name=#{name}
  91.   </select></mapper>
复制代码
6. 3. 5. UserController

控制层定义接口,对接前台。Controller 层调用 Service 层 userLogin 方法,捕获 service 方法的异
常,获取登录结果,并将 ResultInfo 对象通过 JSON 格式响应给客户端。
  1. packagecom.xxxx.crm.controller;importcom.xxxx.crm.base.BaseController;importcom.xxxx.crm.base.ResultInfo;importcom.xxxx.crm.exceptions.ParamsException;importcom.xxxx.crm.service.UserService;importcom.xxxx.crm.utils.LoginUserUtil;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.Mapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.ResponseBody;importjavax.annotation.Resource;importjavax.servlet.http.HttpServletRequest;@Controller@RequestMapping("user")publicclassUserControllerextendsBaseController{@ResourceprivateUserService userService;/**
  2.      *  用户登录
  3.      * @param userName
  4.      * @param userPwd
  5.      */@PostMapping("login")@ResponseBodypublicResultInfologin(String userName,String userPwd){// return userService.loginCheck(userName, userPwd);ResultInfo resultInfo =newResultInfo();try{
  6.             resultInfo = userService.loginCheck(userName, userPwd);}catch(ParamsException e){
  7.             e.printStackTrace();
  8.             resultInfo.setCode(400);
  9.             resultInfo.setMsg(e.getMsg());}catch(Exception e){
  10.             e.printStackTrace();
  11.             resultInfo.setCode(500);
  12.             resultInfo.setMsg("登录失败");}return resultInfo;}}
复制代码
6. 3. 6. Starter

修改启动类,在启动类上添加 @MapperScan 注解,设置扫描包范围。
  1. packagecom.xxxx;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/**
  2. * Hello world!
  3. */@SpringBootApplication@MapperScan("com.xxxx.crm.dao")publicclassApp{publicstaticvoidmain(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
  1. layui.use(['form','jquery','jquery_cookie'], function (){var form = layui.form,
  2.         layer = layui.layer,
  3.         $ = layui.jquery,
  4.         $ = layui.jquery_cookie($);/**
  5.      * 监听表单的提交
  6.      *     on监听 submit事件
  7.      */
  8.     form.on("submit(login)",function (data){/*console.log(data.elem);
  9.         console.log(data.form);*/
  10.         console.log(data.field)//当前容器的全部表单字段,名值对形式:{name: value}//数据校验 TODO//使用了lay-verify表单验证//发送恳求
  11.         $.ajax({
  12.             type:"post",
  13.             url: ctx +"/user/login",
  14.             data:{
  15.                 userName:data.field.username,
  16.                 userPwd:data.field.password
  17.             },
  18.             dataType:'json',
  19.             success:function (data){if(data.code ==200){//存储cookie/* $.cookie("userId",data.result.userId);
  20.                     $.cookie("userName",data.result.userName);
  21.                     $.cookie("trueName",data.result.trueName);*/
  22.                     $.cookie("userIdStr",data.result.userId);
  23.                     $.cookie("userName",data.result.userName);
  24.                     $.cookie("trueName",data.result.trueName);//记住密码if($("#rememberMe").prop("checked")){
  25.                         $.cookie("userIdStr", data.result.userId,{ expires:7});
  26.                         $.cookie("userName", data.result.userName,{ expires:7});
  27.                         $.cookie("trueName", data.result.trueName,{ expires:7});}//跳转到首页
  28.                     window.location.href = ctx +"/main";}else{
  29.                     layer.msg(data.msg,{icon:5});}}});returnfalse;//阻止表单跳转。假设需要表单跳转,去掉这段即可。});});
复制代码
6.3.9.修改Cookie的数据

将 Cookie 中的 userId 的值加密存储。
6.3.10.主页面显示用户名信息

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

使用测试账号执行登录操作。(用户名:admin ,密码:123)
7. 密码修改功能实现

7. 1. 核心思路分析

    确保用户是否是登录状态获取cookie中的id 非空 查询数据库
    校验老密码 非空 老密码必需要跟数据库中密码一致
    新密码 非空 新密码不能和原密码一致
    确认密码 非空 确认必需和新密码一致
    执行修改操作,返回ResultInfo
7. 2. UserService

updateUsERPassword 方法实现
  1. /** 修改密码
  2.      */publicvoiduserUpdate(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());// 执行修改操作,返回ResultInfo
  3.         user.setUserPwd(Md5Util.encode(newPassword));
  4.         user.setUpdateDate(newDate());//判断是否修改胜利AssertUtil.isTrue(userMapper.updateByPrimaryKeySelective(user)<1,"密码修改失败");}/**密码校验
  5.      *          1.确保用户是否是登录状态获取cookie中的id 非空 查询数据库
  6.      *          2.校验老密码 非空  老密码必需要跟数据库中密码一致
  7.      *          3.新密码    非空  新密码不能和原密码一致
  8.      *          4.确认密码  非空  确认必需和新密码一致
  9.      *          5.执行修改操作,返回ResultInfo
  10.      * @param oldPassword
  11.      * @param newPassword
  12.      * @param confirmPassword
  13.      * @param dbPassword
  14.      */privatevoidcheckUpdateData(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 方法实现
  1. /**
  2.      *  修改密码
  3.      */@PostMapping("update")@ResponseBodypublicResultInfoupdate(HttpServletRequest request,String oldPassword,String newPassword,String confirmPassword){ResultInfo resultInfo =newResultInfo();//int i = 1/0;//获取登录用户的idint id =LoginUserUtil.releaseUserIdFromCookie(request);//userService.userUpdate(id,oldPassword,newPassword,confirmPassword);try{
  4.             userService.userUpdate(id,oldPassword,newPassword,confirmPassword);}catch(ParamsException e){
  5.             e.printStackTrace();
  6.             resultInfo.setCode(400);
  7.             resultInfo.setMsg(e.getMsg());}catch(Exception e){
  8.             e.printStackTrace();
  9.             resultInfo.setCode(500);
  10.             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. 全局异常实现思路

控制层的方法返回的内容两种情况
    视图:视图异常
    Json:方法执行错误 返回错误json信息
9. 2. 全局异常拦截器实现

实现 HandlerExceptionResolver 接口 ,处置应用程序异常信息
  1. packagecom.xxxx.crm;importcom.alibaba.fastjson.JSON;importcom.xxxx.crm.base.ResultInfo;importcom.xxxx.crm.exceptions.NoLoginException;importcom.xxxx.crm.exceptions.ParamsException;importorg.springframework.stereotype.Component;importorg.springframework.web.bind.annotation.ResponseBody;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.HandlerExceptionResolver;importorg.springframework.web.servlet.ModelAndView;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;@ComponentpublicclassGlobalExceptionResolverimplementsHandlerExceptionResolver{/**
  2.      * 控制层的方法返回的内容两种情况
  3.      *             1. 视图:视图异常
  4.      *           2. Json:方法执行错误 返回错误json信息
  5.      * @param request
  6.      * @param response
  7.      * @param handler
  8.      * @param ex
  9.      * @return
  10.      */@OverridepublicModelAndViewresolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){ModelAndView mv =newModelAndView();if(ex instanceofNoLoginException){NoLoginException ne =(NoLoginException)ex;// mv.setViewName("index");   目前是直接去找视图//目的是跳转到登录页面   必需通过接口才干显示
  11.             mv.setViewName("redirect:index");return mv;}//设置默认的异常处置
  12.         mv.setViewName("error");
  13.         mv.addObject("code",300);
  14.         mv.addObject("msg","数据异常,请重试");//判断目的方法返回的是视图还是json数据if(handler instanceofHandlerMethod){//转换成controller方法对象HandlerMethod handlerMethod =(HandlerMethod)handler;//获取responsebody注解对象ResponseBody reponsebody = handlerMethod.getMethod().getDeclaredAnnotation(ResponseBody.class);//判断当前方法是否存在responsebody注解if(reponsebody ==null){//返回视图的接口异常处置if(ex instanceofParamsException){ParamsException pe =(ParamsException)ex;
  15.                     mv.addObject("code",pe.getCode());
  16.                     mv.addObject("msg",pe.getMsg());}return mv;}else{//返回json的接口异常处置ResultInfo resultInfo =newResultInfo();
  17.                 resultInfo.setCode(500);
  18.                 resultInfo.setMsg("系统异常请重试");//判断是否是自定义异常if(ex instanceofParamsException){ParamsException pe =(ParamsException)ex;
  19.                     resultInfo.setCode(pe.getCode());
  20.                     resultInfo.setMsg(pe.getMsg());}//将resultinfo数据传给前台的ajax回调函数//设置数据传输的类型和编码格式
  21.                 response.setContentType("application/json;charset=utf-8");PrintWriter writer =null;try{//获取输出流
  22.                     writer = response.getWriter();//将数据对象转换成json格式的,传输进来
  23.                     writer.write(JSON.toJSONString(resultInfo));
  24.                     writer.flush();}catch(IOException e){
  25.                     e.printStackTrace();}finally{if(writer !=null){
  26.                         writer.close();}}returnnull;}}return mv;}}
复制代码
9. 3. 消除 try-catch 代码

系统引入全局异常,简化控制层 try-catch 代码
  1. packagecom.xxxx.crm.controller;importcom.xxxx.crm.base.BaseController;importcom.xxxx.crm.base.ResultInfo;importcom.xxxx.crm.exceptions.ParamsException;importcom.xxxx.crm.service.UserService;importcom.xxxx.crm.utils.LoginUserUtil;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.Mapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.ResponseBody;importjavax.annotation.Resource;importjavax.servlet.http.HttpServletRequest;@Controller@RequestMapping("user")publicclassUserControllerextendsBaseController{@ResourceprivateUserService userService;/**
  2.      *  用户登录
  3.      * @param userName
  4.      * @param userPwd
  5.      */@PostMapping("login")@ResponseBodypublicResultInfologin(String userName,String userPwd){return userService.loginCheck(userName, userPwd);/* ResultInfo resultInfo = new ResultInfo();
  6.         try {
  7.             resultInfo = userService.loginCheck(userName, userPwd);
  8.         } catch (ParamsException e) {
  9.             e.printStackTrace();
  10.             resultInfo.setCode(400);
  11.             resultInfo.setMsg(e.getMsg());
  12.         } catch (Exception e) {
  13.             e.printStackTrace();
  14.             resultInfo.setCode(500);
  15.             resultInfo.setMsg("登录失败");
  16.         }
  17.         return resultInfo;*/}/**
  18.      *  修改密码
  19.      */@PostMapping("update")@ResponseBodypublicResultInfoupdate(HttpServletRequest request,String oldPassword,String newPassword,String confirmPassword){//ResultInfo resultInfo = new ResultInfo();//int i = 1/0;//获取登录用户的idint id =LoginUserUtil.releaseUserIdFromCookie(request);
  20.         userService.userUpdate(id,oldPassword,newPassword,confirmPassword);/*try {
  21.             userService.userUpdate(id,oldPassword,newPassword,confirmPassword);
  22.         } catch (ParamsException e) {
  23.             e.printStackTrace();
  24.             resultInfo.setCode(400);
  25.             resultInfo.setMsg(e.getMsg());
  26.         } catch (Exception e) {
  27.             e.printStackTrace();
  28.             resultInfo.setCode(500);
  29.             resultInfo.setMsg("修改密码失败");
  30.         }*/returnsuccess();//return resultInfo;}/**
  31.      *  修改密码
  32.      *//* @PostMapping("update")
  33.     @ResponseBody
  34.     public ResultInfo update(HttpServletRequest request, String oldPassword, String newPassword, String confirmPassword){
  35.         int i = 1/0;
  36.         //获取登录用户的id
  37.         int id = LoginUserUtil.releaseUserIdFromCookie(request);
  38.             userService.userUpdate(id,oldPassword,newPassword,confirmPassword);
  39.         return success();
  40.     }*///翻开修改密码页面@RequestMapping("toPasswordPage")publicStringtoPasswordPage(){//int i = 1/0;return"user/password";}}
复制代码
10. 非法恳求拦截

对于后端菜单资源,这里要求用户必需停止登录来维护 web 资源的安全性,此时引入非法恳求拦截功
能。
10. 1. 实现思路

判断用户是否是登录状态
获取Cookie对象,解析用户ID的值
假设用户ID不为空,且在数据库中存在对应的用户记录,表示恳求合法
否则,恳求不合法,停止拦截,重定向到登录页面
10. 2. 定义拦截器

在新建 interceptors 包,创建 NoLoginInterceptor 类,并继承 HandlerInterceptorAdapter 适配器,
实现拦截器功能。
  1. packagecom.xxxx.crm.interceptors;importcom.xxxx.crm.exceptions.NoLoginException;importcom.xxxx.crm.service.UserService;importcom.xxxx.crm.utils.LoginUserUtil;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.handler.HandlerInterceptorAdapter;importjavax.annotation.Resource;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;publicclassLoginInterceptorimplementsHandlerInterceptor{@ResourceprivateUserService userService;/**
  2.      * 在恳求到达目的接口之前,拦截
  3.      * @param request
  4.      * @param response
  5.      * @param handler
  6.      * @return
  7.      * @throws Exception
  8.      */@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throwsException{//通过 cookie中的userIdStr 判断用户是否是登录状态int id =LoginUserUtil.releaseUserIdFromCookie(request);if(id ==0||null== userService.selectByPrimaryKey(id)){thrownewNoLoginException();}returntrue;//放行 执行目的接口方法}}
复制代码
10. 3. 全局异常类配置

在全局异常处置类中引入未登录异常判断
  1. packagecom.xxxx.crm.exceptions;/**
  2. * 自定义参数异常
  3. */publicclassNoLoginExceptionextendsRuntimeException{privateInteger code=300;privateString msg="用户未登录!";publicNoLoginException(){super("用户未登录!");}publicNoLoginException(String msg){super(msg);this.msg = msg;}publicNoLoginException(Integer code){super("用户未登录!");this.code = code;}publicNoLoginException(Integer code,String msg){super(msg);this.code = code;this.msg = msg;}publicIntegergetCode(){return code;}publicvoidsetCode(Integer code){this.code = code;}publicStringgetMsg(){return msg;}publicvoidsetMsg(String msg){this.msg = msg;}}
复制代码
10. 4. 拦截器生效配置

新建 config 包,添加拦截器生效的配置类
  1. packagecom.xxxx.crm.config;importcom.xxxx.crm.interceptors.LoginInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassMvcConfigimplementsWebMvcConfigurer{@BeanpublicLoginInterceptorcreateLoginInterceptor(){returnnewLoginInterceptor();}@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){
  2.         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

在用户登录表单中添加记住密码的复选框
  1. <#--记住我--><divclass="layui-form-item"><inputtype="checkbox"name="rememberMe"id="rememberMe"value="true"lay-skin="primary"title="记住密码"></div>
复制代码
11. 2. 修改 index.js

假设用户在登录时,勾选了 “记住我” 的复选框,则在登录胜利之后,设置 cookie 的有效期
  1. //记住密码if($("#rememberMe").prop("checked")){
  2.   $.cookie("userIdStr", data.result.userId,{expires:7});
  3.   $.cookie("userName", data.result.userName,{expires:7});
  4.   $.cookie("trueName", data.result.trueName,{expires:7});}
复制代码

回复

举报 使用道具

相关帖子
全部回复
暂无回帖,快来参与回复吧
本版积分规则 高级模式
B Color Image Link Quote Code Smilies

请和xx谈恋爱
注册会员
主题 12
回复 14
粉丝 0
|网站地图
快速回复 返回顶部 返回列表