一.需求分析
主UI:
二.服务架构
采用单服务架构,后期的博客会将该项目扩展到服务集群和分布式缓存系统架构,敬请期待。
三.数据表设计
部分E-R图:
用户表:
CREATE TABLE `sys_user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_code` varchar(32) NOT NULL COMMENT '用户账号',
`user_name` varchar(64) NOT NULL COMMENT '用户名称',
`user_password` varchar(32) NOT NULL COMMENT '用户密码',
`user_state` char(1) NOT NULL COMMENT '1:正常,0:暂停',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
客户表:
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
`lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名',
`lkm_cust_id` bigint(32) NOT NULL COMMENT '客户id',
`lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别',
`lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机',
`lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
`lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq',
`lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位',
`lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注',
PRIMARY KEY (`lkm_id`),
KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
联系人表:
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
`lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名',
`lkm_cust_id` bigint(32) NOT NULL COMMENT '客户id',
`lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别',
`lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机',
`lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
`lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq',
`lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位',
`lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注',
PRIMARY KEY (`lkm_id`),
KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
客户拜访记录表:
CREATE TABLE `sale_visit` (
`visit_id` varchar(32) NOT NULL,
`visit_cust_id` bigint(32) DEFAULT NULL COMMENT '客户id',
`visit_user_id` bigint(32) DEFAULT NULL COMMENT '负责人id',
`visit_time` date DEFAULT NULL COMMENT '拜访时间',
`visit_interviewee` varchar(32) DEFAULT NULL COMMENT '被拜访人',
`visit_addr` varchar(128) DEFAULT NULL COMMENT '拜访地点',
`visit_detail` varchar(256) DEFAULT NULL COMMENT '拜访详情',
`visit_nexttime` date DEFAULT NULL COMMENT '下次拜访时间',
PRIMARY KEY (`visit_id`),
KEY `FK_sale_visit_cust_id` (`visit_cust_id`),
KEY `FK_sale_visit_user_id` (`visit_user_id`),
CONSTRAINT `FK_sale_visit_cust_id` FOREIGN KEY (`visit_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_sale_visit_user_id` FOREIGN KEY (`visit_user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
客户信息表:
CREATE TABLE `cst_customer_detail` (
`cust_id` bigint(32) NOT NULL,
`cust_region` varchar(64) DEFAULT NULL COMMENT '客户地区',
`cust_zip` varchar(16) DEFAULT NULL COMMENT '客户邮政编码',
`cust_address` varchar(128) DEFAULT NULL COMMENT '客户联系地址',
`cust_fax` varchar(64) DEFAULT NULL COMMENT '客户传真',
`cust_website` varchar(128) DEFAULT NULL COMMENT '客户网址',
`cust_licence` varchar(64) DEFAULT NULL COMMENT '客户营业执照注册号',
`cust_corporation` varchar(64) DEFAULT NULL COMMENT '企业法人',
`cust_capital` bigint(16) DEFAULT NULL COMMENT '客户注册资金',
`cust_bank` varchar(512) DEFAULT NULL COMMENT '开户银行及账号',
`cust_pic` varchar(64) DEFAULT NULL COMMENT '客户资质图片',
`cust_memo` longtext COMMENT '客户简介',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据字典表:
CREATE TABLE `base_dict` (
`dict_id` varchar(32) NOT NULL COMMENT '数据字典id(主键)',
`dict_type_code` varchar(10) NOT NULL COMMENT '数据字典类别代码',
`dict_type_name` varchar(64) NOT NULL COMMENT '数据字典类别名称',
`dict_item_name` varchar(64) NOT NULL COMMENT '数据字典项目名称',
`dict_item_code` varchar(10) DEFAULT NULL COMMENT '数据字典项目(可为空)',
`dict_sort` int(10) DEFAULT NULL COMMENT '排序字段',
`dict_enable` char(1) NOT NULL COMMENT '1:使用 0:停用',
`dict_memo` varchar(64) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`dict_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
用户角色表:
CREATE TABLE `sys_role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(32) NOT NULL COMMENT '角色名称',
`role_memo` varchar(128) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user_role` (
`role_id` bigint(32) NOT NULL COMMENT '角色id',
`user_id` bigint(32) NOT NULL COMMENT '用户id',
PRIMARY KEY (`role_id`,`user_id`),
KEY `FK_user_role_user_id` (`user_id`),
CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
四.程序编写
4.1 项目引入Spring4, Hibernate, Struts2三大框架。
导入SSH所需的jar
在WEB-INF文件夹下新建一个web.xml文件,配置如下:
luyuan_crm
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
classpath:applicationContext.xml
OpenSessionInViewFilter
org.springframework.orm.hibernate5.support.OpenSessionInViewFilter
OpenSessionInViewFilter
*.action
struts2
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
struts2
/*
在SRC下,新建applicationContext.xml,将Hibernate和Struts2交给Spring管理。
org.hibernate.dialect.MySQLDialect
true
true
update
com/luyuan/crm/domain/User.hbm.xml
新建jdbc数据库配置文件jdbc.properties。
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///crm #要连接的数据库名称
jdbc.username=root
jdbc.password=root
引入日志记录配置文件log4j.properties,这个去拷贝apache log4j配置文件即可。
新建struts.xml配置文件。
至此,SSH的基本配置完成的差不多了,可以正式进行项目的开发。
4.2.搭建项目基本骨架
创建BaseDao.java,定义常用的几个通用接口,一般为增删改查,因为一般表都需要CRUD操作。
/**
* 通用的DAO的接口
* @author hp
*
* @param
*/
public interface BaseDao {
public void save(T t);
public void update(T t);
public void delete(T t);
public T getById(Serializable id);
// 查询所有
public List getAll();
//统计个数的方法
public Integer getCount(DetachedCriteria detachedCriteria);
//分页查询
public List getByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize);
}
BaseDaoImpl.java
由于这里需要知道表映射成的实体类型的class, 需要用反射的方式获取该泛型中的T的具体class类型。可以采用在无参构造中获取。
/**
* 通用的DAO的实现类
* @author hp
*
* @param
*/
public class BaseDaoImpl extends HibernateDaoSupport implements BaseDao{
private Class clazz;
public BaseDaoImpl() {
// 反射:第一步获得Class
Class clazz = this.getClass();// 正在被调用那个类的Class,CustomerDaoImpl或者LinkManDaoImpl。
// 查看JDK的API
Type type = clazz.getGenericSuperclass();// 参数化类型:BaseDaoImpl,BaseDaoImpl
// 得到这个type就是一个参数化的类型, 将type强转成参数化的类型:
ParameterizedType pType = (ParameterizedType) type;
// 通过参数化类型获得实际类型参数:得到一个实际类型参数的数组?Map.
Type[] types = pType.getActualTypeArguments();
// 只获得第一个实际类型参数即可。
this.clazz = (Class) types[0];// 得到Customer、LinkMan、User
}
@Override
public void save(T t) {
this.getHibernateTemplate().save(t);
}
@Override
public void update(T t) {
this.getHibernateTemplate().update(t);
}
@Override
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
@Override
public T getById(Serializable id) {
return (T) this.getHibernateTemplate().get(clazz, id);
}
@Override
public List getAll() {
return (List) this.getHibernateTemplate().find("from " + clazz.getSimpleName());
}
@Override
public Integer getCount(DetachedCriteria detachedCriteria) {
// 设置统计个数的条件:
detachedCriteria.setProjection(null); //清除条件的缓存
detachedCriteria.setProjection(Projections.rowCount());
List counts = (List) this.getHibernateTemplate().findByCriteria(detachedCriteria);
if(null != counts && counts.size() > 0) {
return counts.get(0).intValue();
}
return null;
}
@Override
public List getByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize) {
detachedCriteria.setProjection(null);
return (List) this.getHibernateTemplate().findByCriteria(detachedCriteria, begin, pageSize);
}
}
4.3 登录注册模块
注册界面:
登录界面:
创建实体和表的映射
创建实体:
public class User {
private long user_id;
private String user_code;
private String user_name;
private String user_password;
private String user_state;
public long getUser_id() {
return user_id;
}
public void setUser_id(long user_id) {
this.user_id = user_id;
}
public String getUser_code() {
return user_code;
}
public void setUser_code(String user_code) {
this.user_code = user_code;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getUser_password() {
return user_password;
}
public void setUser_password(String user_password) {
this.user_password = user_password;
}
public String getUser_state() {
return user_state;
}
public void setUser_state(String user_state) {
this.user_state = user_state;
}
}
创建映射文件User.hbm.xml:
DAO层创建UserDao.java
public interface UserDao extends BaseDao{
//注册调用父类的save()
//登录
User login(User user);
//账户是否已经被注册
boolean isRegeisted(User user);
}
实现类UserDaoImpl.java
public class UserDaoImpl extends BaseDaoImpl implements UserDao {
@Override
public User login(User user) {
List users = (List) this.getHibernateTemplate().find("from User where user_code=? "
+ "and user_password=?", user.getUser_code(),
user.getUser_password());
if(null != users && users.size() > 0) {
return users.get(0);
}
return null;
}
@Override
public boolean isRegeisted(User user) {
List users = (List) this.getHibernateTemplate().find("from User where user_code=?",
user.getUser_code());
if(null != users && users.size() > 0) {
return true;
}
return false;
}
}
业务service层创建UserService.java接口
public interface UserService {
void regeist(User user);
User login(User user);
}
实现类UserServiceImpl.java
//因为涉及保存操作,必须开启事务,不然Hibernate默认only-read
@Transactional
public class UserServiceImpl implements UserService {
//注入Dao
private UserDao userDao;
public void setUserDao(UserDao mUserDao) {
this.userDao = mUserDao;
}
@Override
public void regeist(User user) {
//先查询该账户是否已经注册
if(!userDao.isRegeisted(user)) {
user.setUser_password(MD5Utils.md5(user.getUser_password()));
user.setUser_state("1");
userDao.save(user);
}
}
@Override
public User login(User user) {
//密码是密文传输的
user.setUser_password(MD5Utils.md5(user.getUser_password()));
return userDao.login(user);
}
}
并在applicationContext.xml添加相关Dao:
public class UserAction extends ActionSupport implements ModelDriven {
//注入UserService
private UserService userService;
public void setUserService(UserService mUserService) {
this.userService = mUserService;
}
// 模型驱动使用的对象
private User user = new User();
@Override
public User getModel() {
return user;
}
//注册
public String regeist() {
userService.regeist(user);
return LOGIN;
}
//登录
public String login() {
// 调用业务层查询用户
User existUser = userService.login(user);
if(null == existUser) {
// 登录失败
//添加错误信息
this.addActionError("用户名或密码错误!");
return LOGIN;
} else {
//登录成功,将用户信息持久化
ActionContext.getContext().getSession().put("existUser", existUser);
return SUCCESS;
}
}
}
并在applicationContext.xml添加相关Service:
在struts.xml添加下列配置:
/login.jsp
/index.html
测试注册:
查询数据库,添加用户成功:
测试登录:
登录失败
登录成功,跳转到首页
4.4 客户管理模块
列表页
添加页
创建实体和表的映射
创建实体:
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_phone;
private String cust_mobile;
//设置器和访问器...
}
创建映射文件Customer.hbm.xml:
然后在applicationContext.xml引入该映射文件
com/luyuan/crm/domain/User.hbm.xml
com/luyuan/crm/domain/Customer.hbm.xml
创建CustomerDao.java
public interface CustomerDao extends BaseDao {
}
CustomerDaoImpl.java
public class CustomerDaoImpl extends BaseDaoImpl implements CustomerDao {
}
业务层Service
创建CustomerService.java
/**
* 客户管理的Service的接口
* @author hp
*
*/
public interface CustomerService {
void save(Customer customer);
PageBean getByPage(DetachedCriteria detachedCriteria, Integer currPage,Integer pageSize);
Customer getById(Long cust_id);
void delete(Customer customer);
void update(Customer customer);
List getAll();
}
创建CustomerServiceImpl.java
public class CustomerServiceImpl implements CustomerService {
// 注入客户的DAO
private CustomerDao customerDao;
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
// 业务层保存客户的方法:
@Override
public void save(Customer customer) {
customerDao.save(customer);
}
// 业务层根据ID查询客户的方法
@Override
public Customer getById(Long cust_id) {
return customerDao.getById(cust_id);
}
@Override
public void delete(Customer customer) {
customerDao.delete(customer);
}
@Override
public void update(Customer customer) {
customerDao.update(customer);
}
@Override
public List getAll() {
return customerDao.getAll();
}
// 业务层分页查询客户的方法:
@Override
public PageBean getByPage(DetachedCriteria detachedCriteria, Integer currPage, Integer pageSize) {
PageBean pageBean = new PageBean<>();
//封装当前页数
pageBean.setCurrPage(currPage);
//封装每页显示的条数
pageBean.setPageSize(pageSize);
//封装总条数
int totalCount = customerDao.getCount(detachedCriteria);
pageBean.setTotalCount(totalCount);
//封装总页数
int totalPage = (int) Math.ceil(totalCount * 1.0 / pageSize) ;
pageBean.setTotalPage(totalPage);
// 封装每页显示数据的集合
int begin = (currPage - 1) * pageSize;
List data = customerDao.getByPage(detachedCriteria, begin, pageSize);
pageBean.setList(data);
return pageBean;
}
}
创建CustomerAction.java
public class CustomerAction extends ActionSupport implements ModelDriven{
//模型驱动使用的对象
private Customer customer = new Customer();
@Override
public Customer getModel() {
return customer;
}
//注入Service
private CustomerService customerService;
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
// 接收分页数据:提供了两个参数名称:page 代表当前页数 和 rows 每页显示记录数
private Integer page = 1;
private Integer rows = 8;
public void setPage(Integer page) {
if (page == null) {
page = 1;
}
this.page = page;
}
public void setRows(Integer rows) {
if (rows == null) {
rows = 8;
}
this.rows = rows;
}
/**
* 分页查询客户的方法
* @return
* @throws IOException
*/
public String getByPage() throws IOException {
// 创建离线条件查询
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Customer.class);
//调用业务层
PageBean pageBean = customerService.getByPage(detachedCriteria, page, rows);
// 使用gson转成json
HashMap map = new HashMap();
map.put("total", pageBean.getTotalCount());
map.put("rows", pageBean.getList());
String json = new Gson().toJson(map);
System.out.println("json:" + json);
ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
ServletActionContext.getResponse().getWriter().println(json);
return NONE;
}
/**
* 编写save方法:
*
* @throws IOException
*/
public String save() throws IOException {
Map map = new HashMap();
try {
// 调用业务层:
customerService.save(customer);
map.put("msg", "保存成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "保存失败!");
}
//换一种方式转json, 使用JSONlib转成JSON:
JSONObject jsonObject = JSONObject.fromObject(map);
System.out.println(jsonObject.toString());
ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
ServletActionContext.getResponse().getWriter().println(jsonObject.toString());// {"msg":..}
return NONE;
}
public String getById() throws IOException {
customer = customerService.getById(customer.getCust_id());
ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
ServletActionContext.getResponse().getWriter().println(new Gson().toJson(customer));
return NONE;
}
public String update() throws IOException {
Map map = new HashMap();
try {
// 调用业务层:
customerService.update(customer);
map.put("msg", "保存成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "保存失败!");
}
ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
ServletActionContext.getResponse().getWriter().println(new Gson().toJson(map));
return NONE;
}
public String delete() throws IOException {
Map map = new HashMap();
try {
// 调用业务层:
customer = customerService.getById(customer.getCust_id());
customerService.delete(customer);
map.put("msg", "保存成功!");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "保存失败!");
}
ServletActionContext.getResponse().setContentType("text/html;charset=UTF-8");
ServletActionContext.getResponse().getWriter().println(new Gson().toJson(map));
return NONE;
}
}
未完待续。。。