CMS客户关系管理系统【上】

发布于 5 年前作者 fang064957 次浏览最后编辑 5 年前来自 share

引言
本文介绍了最近刚做的一个cms小项目,如有不足请各位及时指正,不胜感激!

因受社区文章限制,故将此项目分为上、中、下三部分进行发表

CMS客户关系管理系统【上】

简介

CMS客户关系管理系统,是一套用于管理业务员与客户的拜访记录的一套系统。在这套系统中:

业务员:

  1. 查询查看客户
  2. 新增客户
  3. 业务删除客户
  4. 变更客户信息

管理员:

  1. 新增业务员
  2. 重置业务员密码
  3. 删除业务员(业务删除)
  4. 查看拜访记录

当前用户:

  1. 修改密码
  2. 修改自己头像

注意:

不同的角色登录,显示的菜单和功能按钮不相同

数据库设计

在开发中,需要根据需求和原型进行数据库==概要设计。设计完成后,进行会议讨论==。每个人负责的模块不尽相同,站的考虑问题的角度也不同,设计者的设计很大可能存在偏向性。需要在会议中进行思想的碰撞,完善设计。

用户表(user):分角色

类型 说明
id int
username varchar(20)
password varchar(20)
realname varchar(5)
role int(1) 1:管理员 2:业务员
delete int(1) 1:有效 2:无效
img varchar(50) 用户头像
create_time varchar(19) 创建时间:yyyy-MM-dd HH:mm:ss
modify_time varchar(19) 修改时间:yyyy-MM-dd HH:mm:ss
delete_time varchar(19) 删除时间:yyyy-MM-dd HH:mm:ss

客户表(customer)

类型 说明
id int 客户ID
name varchar(20) 客户名称
sex int(1) 1 : 男 2 : 女
phone varchar(11) 手机号
salary int(10) 工资
addresss varchar(50) 地址
birth varchar(10) 生日:yyyy-MM-dd
delete int(1) 1:有效 2:无效
user_id int 业务员ID
create_time varchar(19) 创建时间:yyyy-MM-dd HH:mm:ss
modify_time varchar(19) 修改时间:yyyy-MM-dd HH:mm:ss
delete_time varchar(19) 删除时间:yyyy-MM-dd HH:mm:ss

拜访记录表(visit_log)

类型 说明
cust_id int 客户ID
cust_name varchar(20) 客户名称
user_id int 业务员ID
user_name varchar(20) 业务员真实姓名
visit_time varchar(19) 拜访时间:yyyy-MM-dd HH:mm
create_time varchar(19) 创建时间:yyyy-MM-dd HH:mm:ss

相关SQL

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '业务员ID',
  `username` varchar(20) NOT NULL COMMENT '用户名',
  `password` varchar(20) NOT NULL COMMENT '用户密码',
  `realname` varchar(5) NOT NULL COMMENT '真名',
  `role` int(1) NOT NULL COMMENT '角色:1.管理员 2.业务员',
  `delete` int(1) NOT NULL COMMENT '业务删除:1.有效 2.无效',
  `img` varchar(50) DEFAULT NULL COMMENT '图片路径',
  `create_time` varchar(19) NOT NULL COMMENT '创建时间:yyyy-MM-dd HH:mm:ss',
  `modify_time` varchar(19) NOT NULL COMMENT '修改时间:yyyy-MM-dd HH:mm:ss',
  `delete_time` varchar(19) DEFAULT NULL COMMENT '删除时间:yyyy-MM-dd HH:mm:ss',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username_uni_key` (`username`) COMMENT '用户名唯一不能重复'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `customer` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '客户ID',
  `name` varchar(20) NOT NULL COMMENT '客户名称',
  `sex` int(1) NOT NULL COMMENT '性别:1.男 2.女',
  `phone` varchar(11) NOT NULL COMMENT '手机号',
  `salary` int(10) NOT NULL COMMENT '工资',
  `address` varchar(50) NOT NULL COMMENT '地址',
  `birth` varchar(10) NOT NULL COMMENT '生日:yyyy-MM-dd',
  `delete` int(1) NOT NULL COMMENT '业务删除:1.有效 2.无效',
  `user_id` int(11) NOT NULL COMMENT '业务员ID',
  `create_time` varchar(19) NOT NULL COMMENT '创建时间:yyyy-MM-dd HH:mm:ss',
  `modify_time` varchar(19) NOT NULL COMMENT '修改时间:yyyy-MM-dd HH:mm:ss',
  `delete_time` varchar(19) DEFAULT NULL COMMENT '删除时间:yyyy-MM-dd HH:mm:ss',
  PRIMARY KEY (`id`),
  UNIQUE KEY `phone_uni_key` (`phone`) COMMENT '手机号不能重复'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `visit_log` (
  `cust_id` int(11) NOT NULL COMMENT '客户ID',
  `cust_name` varchar(20) NOT NULL COMMENT '客户名称',
  `user_id` int(11) NOT NULL COMMENT '业务员ID',
  `user_name` varchar(20) NOT NULL COMMENT '业务员真实姓名',
  `visit_time` varchar(19) NOT NULL COMMENT '拜访时间:yyyy-MM-dd HH:mm',
  `create_time` varchar(19) NOT NULL COMMENT '创建时间:yyyy-MM-dd HH:mm:ss'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

项目架构

前端

在JavaWeb中静态资源文件是直接放在web目录下面,非静态文件是不允许客户端直接访问(jsp文件),基于安全的考虑,需要放在WEB-INF目录下。

静态资源文件,要放在web目录下面,考虑所有的静态资源文件不能被拦截。此时需要设置统一访问路径。一般使用一个统一的文件夹,将所有的静态资源文件装起来,然后判断请求路径前缀是否是这个统一的文件夹名称即可。<mark>__resources__</mark>
所有非静态资源(jsp)页面要放在WEB-INF,基于这样情况,WEB-INF文件相对较乱较杂,创建文件夹进行分类管理。view文件,表示下面是页面,然后基于每个模块,创建相应的文件夹。例如==:==



<mark>__用户模块:view/user/list.jsp__</mark>

main.jsp

main.jsp是管理界面的框架,由于需要从session获取当前用户所以使用jsp。

注意:

在web项目中,存在资源路径出现多重路径,使用相对路径会因为URL变化,导致URL请求发生404。所以在使用时使用绝对路径。在URL前面拼接:${pageContext.request.contextPath}

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" language="java" %>
<html>
<head>
    <title>Title</title>
    <link rel="stylesheet" href="${pageContext.request.contextPath}/resources/layui/css/layui.css">
</head>
<body class="layui-layout-body">
<div class="layui-layout layui-layout-admin">
    <div class="layui-header">
        <div class="layui-logo">CMS管理系统</div>
        <!-- 头部区域(可配合layui已有的水平导航) -->
        <ul class="layui-nav layui-layout-left">
        </ul>
        <ul class="layui-nav layui-layout-right">
            <li class="layui-nav-item">
                <a href="javascript:;">
                    <img src="http://t.cn/RCzsdCq" class="layui-nav-img">
                    贤心
                </a>
                <dl class="layui-nav-child">
                    <dd><a href="">基本资料</a></dd>
                    <dd><a href="">安全设置</a></dd>
                </dl>
            </li>
            <li class="layui-nav-item"><a href="">退了</a></li>
        </ul>
    </div>

    <div class="layui-side layui-bg-black">
        <div class="layui-side-scroll">
            <!-- 左侧导航区域(可配合layui已有的垂直导航) -->
            <ul class="layui-nav layui-nav-tree"  lay-filter="test">
                <li class="layui-nav-item layui-nav-itemed">
                    <a class="" href="javascript:;">基础信息管理</a>
                    <dl class="layui-nav-child">
                        <dd><a href="page.do?service=userList" target="content">员工管理</a></dd>
                        <dd><a href="index2.html" target="content">客户管理</a></dd>
                    </dl>
                </li>
                <li class="layui-nav-item">
                    <a href="javascript:;">系统管理</a>
                    <dl class="layui-nav-child">
                        <dd><a href="javascript:;">密码管理</a></dd>
                        <dd><a href="javascript:;">头像管理</a></dd>
                    </dl>
                </li>
                <li class="layui-nav-item"><a href="javascript:;">数据分析</a>
                    <dl class="layui-nav-child">
                        <dd><a href="javascript:;">拜访占比</a></dd>
                    </dl>

                </li>
            </ul>
        </div>
    </div>

    <div class="layui-body">
        <!-- 内容主体区域 -->
        <div style="padding: 15px;">
            <iframe name="content" style="width: 100%;height: 100%;border: 0px"></iframe>
        </div>
    </div>

    <div class="layui-footer">
        <!-- 底部固定区域 -->
        © layui.com - 底部固定区域
    </div>
</div>
<script src="${pageContext.request.contextPath}/resources/layui/layui.js"></script>
<script>
    //JavaScript代码区域
    layui.use('element', function(){
        var element = layui.element;

    });
</script>
</body>
</html>

控制页面跳转的Servlet

package com.sxt.controller;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @Description: 专门用于进行页面跳转
 * @author: Mr.T
 * @date 2020-09-15 10:48
 */
@WebServlet("/page.do")
public class PageController extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String service = req.getParameter("service");
        try {
            Method method = this.getClass().getDeclaredMethod(service, HttpServletRequest.class, HttpServletResponse.class);
            method.invoke(this,req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void main(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/WEB-INF/view/main.jsp").forward(req,resp);
    }

}

注意点:

  1. 在网页中,每多一个iframe就多一个window对象
  2. 子iframe 访问父iframe使用window.parent访问父窗口的window对象,根据window对象获取其他对象
  3. 父iframe访问子iframe
*   使用name直接访问: iframeName.window
*   使用iframe的id,frames\[‘id值’\]
*   使用索引iframes\[索引\]

后台

在JavaWeb开发中,基本都是基于MVC思想进行开发。Web后台框架,主要负责:M层和C层。

C层:使用Servlet技术。

M层主要分为:

  1. 实体类
  2. 数据操作层。

但是,在具体的实际开发中,发现:Controller层和M层耦合性极高,代码复用性极差。

按照之前的结构:Controller直接访问Dao层。如果类似以下场景:

用户注册场景:controller层只负责接收View层数据,将数据传递给M层中的Dao层,发现在Dao层,



*   
    
    查询注册用户名是否存在
    
    
*   
    
    插入一个数据
修改用户信息场景:



*   
    
    查询用户名是否已存在
    
    
*   
    
    修改数据
    
    




发现查询用户是否存在是重复操作,并且Dao层并不是单纯只是进行数据库的数据操作。也存在各种业务判断,违背了设计原则:单一职责。并且代码的复用不高,维护性也差。



所以基于这样的情况,在M层加上了service,service只进行业务判断,dao层只做数据操作。controller访问==__service__==层。

相关包如下:

  1. pojo : 所有的实体类
  2. dao : 数据操作类
  3. service :业务处理类
  4. controller :控制器
  5. util : 工具类
  6. common :公共类
  7. filter : web拦截器

相关jar包

fastjson-1.1.22.jar
hutool-all-5.4.1.jar
jstl-1.2.jar
mysql-connector-java-5.1.47.jar
standard-1.1.2.jar

相关类:

工具类
数据库连接配置文件
#jdbc连接配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/cms?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
数据库连接工具类
package com.sxt.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @Description:Jdbc连接工具类
 * @author: Mr.T
 * @date 2020-09-02 15:55
 */
public abstract class JdbcUtil {

    private  static final String DRIVER;

    private static final String URL;

    private static final  String USERNAME;

    private static final String PASSWORD;
    //静态块 加载 配置文件
    static {
        /**
         * 类加载器
         *  三种类加载器 :     Bootstrap  加载 JDK中核心类库
         *      扩展类加载器   加载扩展类库
         *      应用类加载器   加载应用程序自定义的类
         *  双亲委派加载
         *      1. 首先使用 Bootstrap 类加载器加载类 只加载JDK 核心类库
         *      2. 扩展类加载器 加载ext文件夹下的类
         *      3. 使用应用类加载器加载类 默认加载src源码目录下类
         *  为什么Java使用双亲委派加载机制 类一旦被加载 其他类加载器将不会加载这个类
         *  类的区分方式,包名+类名
         *  如果用户自定义一个类,且类包名,类名与系统类一致。虽然一致,但是由于类加载器不同,所以依然使用系统类。
         *  如果不是使用双亲委派机制,如果使用同一个类加载器,可能出现类覆盖。自定义的类覆盖系统类。
         *  改写了JDK源代码。容易出现安全问题。
         */
        InputStream in = JdbcUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
        //读入到properties对象中
        Properties prop = new Properties();
        if (in == null){
            throw new RuntimeException("配置文件没有找到");
        }
        try {
            prop.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER = prop.getProperty("jdbc.driver");
        URL = prop.getProperty("jdbc.url");
        USERNAME = prop.getProperty("jdbc.username");
        PASSWORD = prop.getProperty("jdbc.password");
        try {
            //加载驱动
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     * @return
     */
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }

    /**
     * 关闭连接
     * @param conn
     * @param prep
     */
    public static void close(Connection conn, PreparedStatement prep){
        if (prep != null){
            try {
                prep.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    /**
     * 关闭连接
     * @param conn
     * @param prep
     * @param rs
     */
    public static void close(Connection conn, PreparedStatement prep, ResultSet rs){
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        close(conn,prep);
    }

}
JSON结果数据工具类
package com.sxt.util;

import com.alibaba.fastjson.JSON;
import com.sxt.common.Result;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @Description: 响应数据工具类
 *              将返回的数据转化为JSON字符串 返回给客户端
 * @author: Mr.T
 * @date 2020-09-14 15:19
 */
public abstract class RespUtil {
    /**
     * 将返回的数据转化为JSON字符串返回给客户端
     * @param rs  需要返回的数据
     * @param resp  响应对象
     * @throws IOException
     */
    public static void writer(Result rs, HttpServletResponse resp) throws IOException {
        resp.setCharacterEncoding("UTF-8");
        //设置返回数据的类型
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        //将需要返回的数据转化为JSON字符串
        String s = JSON.toJSONString(rs);
        writer.write(s);
        writer.flush();
        writer.close();
    }
}
公共类
通用数据操作类
package com.sxt.common;

import com.sxt.common.PageInfo;
import com.sxt.util.JdbcUtil;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: 数据通用的操作类
 * @author: Mr.T
 * @date 2020-09-08 09:17
 */
public class BaseDao {

    /**
     * 数据更新操作
     * @param sql
     * @param params
     * @return
     */
    public boolean update(String sql,Object... params){
        //获取连接
        Connection conn = JdbcUtil.getConnection();
        //获取数据操作对象
        PreparedStatement prep = null;
        try {
            prep = conn.prepareStatement(sql);
            //校验是否有传输参数
            if (params != null && params.length > 0){
                //根据for循环设置sql中的参数
                for (int i = 1; i <= params.length; i++) {
                    prep.setObject(i,params[i-1]);
                }
            }
            int count = prep.executeUpdate();
            return  count == 1 ?true : false;
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtil.close(conn,prep);
        }
        return  false;
    }

    /**
     * 根据条件查询数据列表
     * @param sql
     * @param cls
     * @param params
     * @param <T>
     * @return
     */
    public <T> List<T> selectList(String sql,Class<T> cls,Object... params){
        //获取连接
        Connection conn = JdbcUtil.getConnection();
        //获取数据操作对象
        PreparedStatement prep = null;
        //数据结果集
        ResultSet rs = null;
        List<T> data = new ArrayList<>();
        try {
            prep = conn.prepareStatement(sql);
            int length = params.length;
            //循环处理sql参数
            if (params != null && length > 0){
                //根据for循环设置sql中的参数
                for (int i = 1; i <= length; i++) {
                    prep.setObject(i,params[i-1]);
                }
            }
            //查询  获取结果集
            rs = prep.executeQuery();
            //获取数据元信息
            ResultSetMetaData metaData = rs.getMetaData();
            //获取列数
            int count = metaData.getColumnCount();
            while (rs.next()){
                //创建对象
                T obj = cls.newInstance();
                //对象属性封装
                for (int i = 1; i <= count ; i++) {
                    //根据序列获取列别名
                    String label = metaData.getColumnLabel(i);
                    //根据列别名获取数据
                    Object value = rs.getObject(label);
                    //进行对象属性设置
                    //获取属性
                    try {
                        Field field = cls.getDeclaredField(label);
                        //设置属性的访问标识
                        field.setAccessible(true);
                        //设置属性值
                        field.set(obj,value);
                    }catch (Exception exception){
                        exception.printStackTrace();
                        //如果属性不存在 则跳过这个属性的设置
                        continue;
                    }
                }
                data.add(obj);
            }
        } catch (Exception throwables) {
            throwables.printStackTrace();
        }finally {
            JdbcUtil.close(conn,prep,rs);
        }
        return  data;
    }

    /**
     * 查询一个
     * @param sql
     * @param cls
     * @param params
     * @param <T>
     * @return
     */
    public <T> T selectOne(String sql,Class<T> cls,Object... params){
        List<T> data = this.selectList(sql, cls, params);
        if (!data.isEmpty() && data.size() > 1){
            throw new RuntimeException("查询结果不是一个!!!");
        }
        if (data.size() == 1){
            return data.get(0);
        }
        return  null;
    }

    /**
     *  分页查询
     * @param sql 查询的基础sql
     * @param cls 封装的类
     * @param page 页码
     * @param limit 每页数据条数
     * @param params 查询的参数
     * @param <T>
     * @return
     */
    public <T> PageInfo<T> selectPage(String sql,Class<T> cls,Integer page,Integer limit,Object... params){
        //查询符合条件的总数据条数
        int total = selectCount(sql,params);
        //根据总数据条数获取总页数
        //总数据条数 除以每页条数  如果能除断 说明刚好 则直接 除以
        //如果有余数 则使用新一页装剩余的数据  所以加1
        int totalPage = total%limit == 0? total/limit : total/limit + 1;
        //页码不能大于总页数
        //页码的最大值是总页数
        if (page > totalPage ){
            page = totalPage;
        }
        //页码的最小值是  1
        if (page < 1){
            page  = 1;

...

0 回复
暂无回复