CMS客户关系管理系统【上】
引言
本文介绍了最近刚做的一个cms小项目,如有不足请各位及时指正,不胜感激!
因受社区文章限制,故将此项目分为上、中、下三部分进行发表

CMS客户关系管理系统【上】
简介
CMS客户关系管理系统,是一套用于管理业务员与客户的拜访记录的一套系统。在这套系统中:
业务员:
- 查询查看客户
- 新增客户
- 业务删除客户
- 变更客户信息
管理员:
- 新增业务员
- 重置业务员密码
- 删除业务员(业务删除)
- 查看拜访记录
当前用户:
- 修改密码
- 修改自己头像

注意:
不同的角色登录,显示的菜单和功能按钮不相同
数据库设计
在开发中,需要根据需求和原型进行数据库==概要设计。设计完成后,进行会议讨论==。每个人负责的模块不尽相同,站的考虑问题的角度也不同,设计者的设计很大可能存在偏向性。需要在会议中进行思想的碰撞,完善设计。
用户表(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);
}
}
注意点:
- 在网页中,每多一个iframe就多一个window对象
- 子iframe 访问父iframe使用window.parent访问父窗口的window对象,根据window对象获取其他对象
- 父iframe访问子iframe
* 使用name直接访问: iframeName.window
* 使用iframe的id,frames\[‘id值’\]
* 使用索引iframes\[索引\]
后台
在JavaWeb开发中,基本都是基于MVC思想进行开发。Web后台框架,主要负责:M层和C层。
C层:使用Servlet技术。
M层主要分为:
- 实体类
- 数据操作层。
但是,在具体的实际开发中,发现:Controller层和M层耦合性极高,代码复用性极差。
按照之前的结构:Controller直接访问Dao层。如果类似以下场景:
用户注册场景:controller层只负责接收View层数据,将数据传递给M层中的Dao层,发现在Dao层, * 查询注册用户名是否存在 * 插入一个数据
修改用户信息场景: * 查询用户名是否已存在 * 修改数据 发现查询用户是否存在是重复操作,并且Dao层并不是单纯只是进行数据库的数据操作。也存在各种业务判断,违背了设计原则:单一职责。并且代码的复用不高,维护性也差。 所以基于这样的情况,在M层加上了service,service只进行业务判断,dao层只做数据操作。controller访问==__service__==层。相关包如下:
- pojo : 所有的实体类
- dao : 数据操作类
- service :业务处理类
- controller :控制器
- util : 工具类
- common :公共类
- 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 回复
