crm练习
课程内容
课程目标
1. 通过SSH框架完成基本功能的开发
系统截图
登录
http://localhost:8080/ssh2/
全局异常
http://localhost:8080/ssh2/user_login.action
首页
http://localhost:8080/ssh2/index.jsp
客户列表
/ssh2/customer_findByPage.action
客户新增
/ssh2/customer_initAddUI.action
联系人列表
http://localhost:8080/ssh2/linkman_findByPage.action
客户拜访记录
http://localhost:8080/ssh2/visit_findByPage.action
客户拜访-新增
http://localhost:8080/ssh2/jsp/visit/add.jsp
客户来源统计
http://localhost:8080/ssh2/customer_findBySource.action
安全退出
/ssh2/user_exit.action
用户模块
功能一:用户注册功能
1. 可以先判断登录名是否已经存在
2. 要给密码使用MD5进行加密操作
功能二:用户登录功能
1. 登录功能要注意需要先给密码加密后,再进行查询
* 密码加密后再查询
* 用户的状态必须是1,字符串类型的
功能三:用户退出功能
1. 把用户信息从HttpSession中清除
客户模块
功能一:查询所有客户功能**
1. 数据字典表的引入
* 数据字典表的作用:规范开发中数据的写法
* 字段表与客户表是一对多的关系
* 修改客户表,添加外键(使用SQLyog进行修改)
2. 创建字典表的实体和映射的配置文件
* 编写字典表的JavaBean和映射的配置文件
* 修改Customer的JavaBean,因为是多方,需要把外键字段换成字典对象
* 修改Customer.hbm.xml的配置文件,配置多对一
3. 分页查询所有的客户功能实现
Customer.java
Customer.java
// 描述的是 一客户的来源,多是客户
private Dict source;
// 一客户的行业 多是客户
private Dict industry;
// 一客户级别 多是客户
private Dict level;
Customer.hbm2.xml
<!-- 配置的多方 name是JavaBean属性名称 class="一方类的全路径" cloumn="外键的名称" -->
<many-to-one name="source" class="com.itheima.domain.Dict" column="cust_source"/>
<many-to-one name="industry" class="com.itheima.domain.Dict" column="cust_industry"/>
<many-to-one name="level" class="com.itheima.domain.Dict" column="cust_level"/>
CustomerAction.java
/**
* 分页的查询方法
* @return
*/
public String findByPage(){
// 调用service业务层
DetachedCriteria criteria = DetachedCriteria.forClass(Customer.class);
// 拼接查询的条件
String cust_name = customer.getCust_name();
if(cust_name != null && !cust_name.trim().isEmpty()){
// 说明,客户的名称输入值了
criteria.add(Restrictions.like("cust_name", "%"+cust_name+"%"));
}
// 拼接客户的级别
Dict level = customer.getLevel();
if(level != null && !level.getDict_id().trim().isEmpty()){
// 说明,客户的级别肯定选择了一个级别
criteria.add(Restrictions.eq("level.dict_id", level.getDict_id()));
}
// 客户的来源
Dict source = customer.getSource();
if(source != null && !source.getDict_id().trim().isEmpty()){
// 说明,客户的级别肯定选择了一个级别
criteria.add(Restrictions.eq("source.dict_id", source.getDict_id()));
}
// 查询
PageBean<Customer> page = customerService.findByPage(pageCode,pageSize,criteria);
// 压栈
ValueStack vs = ActionContext.getContext().getValueStack();
// 栈顶是map<"page",page对象>
vs.set("page", page);
return "page";
}
3. 分页查询所有的客户功能实现
PageBean.java
package com.itheima.domain;
import java.util.List;
/**
* 分页的JavaBean
* @author Administrator
*/
public class PageBean<T> {
// 当前页
private int pageCode;
// 总页数
// private int totalPage;
// 总记录数
private int totalCount;
// 每页显示的记录条数
private int pageSize;
// 每页显示的数据
private List<T> beanList;
public int getPageCode() {
return pageCode;
}
public void setPageCode(int pageCode) {
this.pageCode = pageCode;
}
/**
* 调用getTotalPage() 获取到总页数
* JavaBean的属性规定:totalPage是JavaBean是属性 ${pageBean.totalPage}
* @return
*/
public int getTotalPage() {
// 计算
int totalPage = totalCount / pageSize;
// 说明整除
if(totalCount % pageSize == 0){
return totalPage;
}else{
return totalPage + 1;
}
}
/*public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}*/
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public List<T> getBeanList() {
return beanList;
}
public void setBeanList(List<T> beanList) {
this.beanList = beanList;
}
}
page.jsp 静态引入 <%@ include file="/jsp/page.jsp" %>
<%@ include file="/jsp/page.jsp" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<SPAN id=pagelink>
<DIV style="LINE-HEIGHT: 20px; HEIGHT: 20px; TEXT-ALIGN: right">
共[<B>${page.totalCount}</B>]条记录,共[<B>${page.totalPage}</B>]页 ,每页显示 <select
name="pageSize">
<option value="2" <c:if test="${page.pageSize==2 }">selected</c:if>>2</option>
<option value="3" <c:if test="${page.pageSize==3 }">selected</c:if>>3</option>
</select> 条
<c:if test="${ page.pageCode > 1 }">
[<A href="javascript:to_page(${page.pageCode-1})">前一页</A>]
</c:if>
<B>${page.pageCode}</B>
<c:if test="${ page.pageCode < page.totalPage }">
[<A href="javascript:to_page(${page.pageCode+1})">后一页</A>]
</c:if>
到 <input type="text" size="3" id="page" name="pageCode" /> 页 <input
type="button" value="Go" onclick="to_page()" />
</DIV>
</SPAN>
BaseAction.java
package com.itheima.web.action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* Action的父类
* @author Administrator
*/
public class BaseAction extends ActionSupport{
private static final long serialVersionUID = 2736308223315548464L;
// 属性驱动的方式
// 当前页,默认值就是1
private Integer pageCode = 1;
public void setPageCode(Integer pageCode) {
if(pageCode == null){
pageCode = 1;
}
this.pageCode = pageCode;
}
public Integer getPageCode() {
return pageCode;
}
// 每页显示的数据的条数
private Integer pageSize = 2;
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Integer getPageSize() {
return pageSize;
}
/**
* 调用值栈对象的set方法
*/
public void setVs(String key,Object obj){
ActionContext.getContext().getValueStack().set(key, obj);
}
/**
* 调用值栈的push方法
* @param obj
*/
public void pushVs(Object obj){
ActionContext.getContext().getValueStack().push(obj);
}
}
LinkManAction.java findByPage()
/**
* 分页查询
* @return
*/
public String findByPage(){
DetachedCriteria criteria = DetachedCriteria.forClass(Linkman.class);
// 获取到联系人的名称
String lkm_name = linkman.getLkm_name();
if(lkm_name != null && !lkm_name.trim().isEmpty()){
criteria.add(Restrictions.like("lkm_name", "%"+lkm_name+"%"));
}
// 获取客户
Customer c = linkman.getCustomer();
if(c != null && c.getCust_id() != null){
// 拼接查询的条件
criteria.add(Restrictions.eq("customer.cust_id", c.getCust_id()));
}
// 调用业务层
PageBean<Linkman> page = linkmanService.findByPage(this.getPageCode(),this.getPageSize(),criteria);
// 压栈
this.setVs("page", page);
return "page";
}
list.jsp分页时保留上次查询条件
<TABLE cellSpacing=0 cellPadding=2 border=0>
<TBODY>
<TR>
<TD>客户名称:</TD>
<TD>
<INPUT class=textbox id=sChannel2 style="WIDTH: 80px" maxLength=50 name="cust_name" value="${ model.cust_name }">
</TD>
<td>客户级别</td>
<td>
<select name="level.dict_id" id="levelId">
<option value="">--请选择--</option>
</select>
</td>
<td>客户来源</td>
<td>
<select name="source.dict_id" id="sourceId">
<option value="">--请选择--</option>
</select>
</td>
<TD>
<INPUT class=button id=sButton2 type=submit value="筛选 " name=sButton2>
</TD>
</TR>
</TBODY>
</TABLE>
list.jsp延迟加载客服来源下拉框
// 页面的加载
$(function(){
// 发送ajax的请求
var url = "${ pageContext.request.contextPath }/dict_findByCode.action";
var param = {"dict_type_code":"006"};
$.post(url,param,function(data){
// 遍历
$(data).each(function(i,n){
// alert(i+" : "+n.dict_item_name);
// alert(this.dict_item_name);
// 先获取值栈中的值,使用EL表达式
var vsId = "${model.level.dict_id}";
// 值栈中的id值和遍历的id值相同,让被选中
if(vsId == n.dict_id){
// JQ的DOM操作
$("#levelId").append("<option value='"+n.dict_id+"' selected>"+n.dict_item_name+"</option>");
}else{
$("#levelId").append("<option value='"+n.dict_id+"'>"+n.dict_item_name+"</option>");
}
});
},"json");
});
功能二:按条件查询所有的客户
1. 使用异步的方式加载客户级别和客户的来源
* 前端使用JQuery的ajax技术
* 后端使用fastjson的jar包
* 导入fastjson的开发jar包fastjson-1.2.8.jar
* String s = JSON.toJSONString(集合)
* String s = JSON.toJSONString(对象)
* 如果List集合中存入相同引用的对象
* fastjson默认的情况下是进行循环检测的,去除掉死循环调用的方式
* 可以使用JSON.toJSONString(p,SerializerFeature.DisableCircularReferenceDetect) 去除循环检测,但是就会出现死循环的效果
* 最后可以使用注解:@JSONField(serialize=false)对指定的属性不转换成json
2. 异步获取客户级别
* ajax的代码
var url = "${pageContext.request.contextPath }/dict_findByCode.action";
var param = {"dict_type_code":"006"};
$.post(url,param,function(data){
$(data).each(function(){
var id = "${model.level.dict_id}";
if(id == this.dict_id){
$("#levelId").append("<option value='"+this.dict_id+"' selected>"+this.dict_item_name+"</option>");
}else{
$("#levelId").append("<option value='"+this.dict_id+"'>"+this.dict_item_name+"</option>");
}
});
},"json");
Action的代码
public String findByCode(){
List<Dict> list = dictService.findByCode(dict.getDict_type_code());
String jsonString = FastJsonUtil.toJSONString(list);
HttpServletResponse response = ServletActionContext.getResponse();
FastJsonUtil.write_json(response, jsonString);
return NONE;
}
CustomerAction的分页查询的代码
public String findByPage(){
// 调用service业务层
DetachedCriteria criteria = DetachedCriteria.forClass(Customer.class);
// 拼接查询的条件
String name = customer.getCust_name();
if(name != null && !name.trim().isEmpty()){
criteria.add(Restrictions.like("cust_name", "%"+name+"%"));
}
// System.out.println(customer.getLevel().getDict_type_code());
Dict level = customer.getLevel();
if(level != null && !level.getDict_id().trim().isEmpty()){
criteria.add(Restrictions.eq("level.dict_id", level.getDict_id()));
}
Dict source = customer.getSource();
if(source != null && !source.getDict_id().trim().isEmpty()){
criteria.add(Restrictions.eq("source.dict_id", source.getDict_id()));
}
// 查询
PageBean<Customer> page = customerService.findByPage(pageCode,pageSize,criteria);
// 压栈
ValueStack vs = ActionContext.getContext().getValueStack();
// 栈顶是map<"page",page对象>
vs.set("page", page);
vs.set("cust_name", name);
return "page";
}
FastJsonUtil.java
package com.itheima.utils;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastJsonUtil {
/**
* 将对象转成json串
* @param object
* @return
*/
public static String toJSONString(Object object){
//DisableCircularReferenceDetect来禁止循环引用检测
return JSON.toJSONString(object,SerializerFeature.DisableCircularReferenceDetect);
}
//输出json
public static void write_json(HttpServletResponse response,String jsonString){
response.setContentType("application/json;utf-8");
response.setCharacterEncoding("UTF-8");
try {
response.getWriter().print(jsonString);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* ajax提交后回调的json字符串
* @return
*/
public static String ajaxResult(boolean success,String message)
{
Map map=new HashMap();
map.put("success", success);//是否成功
map.put("message", message);//文本消息
String json= JSON.toJSONString(map);
return json;
}
/**
* JSON串自动加前缀
* @param json 原json字符串
* @param prefix 前缀
* @return 加前缀后的字符串
*/
public static String JsonFormatterAddPrefix(String json,String prefix,Map<String,Object> newmap)
{
if(newmap == null){
newmap = new HashMap();
}
Map<String,Object> map = (Map) JSON.parse(json);
for(String key:map.keySet())
{
Object object=map.get(key);
if(isEntity(object)){
String jsonString = JSON.toJSONString(object);
JsonFormatterAddPrefix(jsonString,prefix+key+".",newmap);
}else{
newmap.put(prefix+key, object);
}
}
return JSON.toJSONString(newmap);
}
/**
* 判断某对象是不是实体
* @param object
* @return
*/
private static boolean isEntity(Object object)
{
if(object instanceof String )
{
return false;
}
if(object instanceof Integer )
{
return false;
}
if(object instanceof Long )
{
return false;
}
if(object instanceof java.math.BigDecimal )
{
return false;
}
if(object instanceof Date )
{
return false;
}
if(object instanceof java.util.Collection )
{
return false;
}
return true;
}
}
功能三:添加客户功能(含有文件上传功能)
流程
1. 跳转到客户的添加页面,需要通过ajax来显示客户的级别,客户的来源和客户的行业。
2. 添加文件上传的选择项
3. 客户端三个注意事项
* method="post"
* enctype="multipart/form-data"
* <input type="file" name="upload">
4. Struts2框架的使用拦截器完成了文件上传,并且底层使用也是FileUpload开源的组件。
* 提供 FileUpload 拦截器,用于解析 multipart/form-data 编码格式请求,解析上传文件的内容
* fileUpload拦截器 默认在 defaultStack 栈中, 默认会执行的
* 在Action中编写文件上传,需要定义三个属性
> 文件类型File ,属性名与表单中file的name属性名一致.
> 字符串类型String , 属性名:前段是name属性名一致 + ContentType;
> 字符串类型String , 属性名:前段是name属性名一致+FileName;
> 最后需要为上述的三个属性提供set方法。
> 可以通过FileUtils提供 copyFile 进行文件复制,将上传文件 保存到服务器端
文件上传中存在的问题
1 先配置input逻辑视图
2 在页面中显示错误信息
3 文件上传的总大小默认值是2M,如果超过了2M,程序会报出异常。可以使用<s:actionError>来查看具体信息!
> 解决总大小的设置,找到常量:
* struts.multipart.parser=jakarta -- 默认文件上传解析器,就是FileUpload组件
* struts.multipart.saveDir= -- 文件上传的临时文件存储目录
* struts.multipart.maxSize=2097152 -- 文件上传的最大值(总大小),默认是2M
> 可以在struts.xml中设置常量,修改文件上传的默认总大小!!!
* <constant name="struts.multipart.maxSize" value="5000000"></constant>
4 还可以通过配置拦截器来设置文件上传的一些属性
* 先在<action>标签中引入文件上传的拦截器
<interceptor-ref name="defaultStack">
<!-- 设置单个上传文件的大小 -->
<param name="fileUpload.maximumSize">2097152</param>
<!-- 设置扩展名 -->
<param name="fileUpload.allowedExtensions">.jpg,.txt</param>
</interceptor-ref>
功能三:修改客户的功能**
1. 先通过客户的主键查询出客户的详细信息,显示到修改的页面上
* 要把客户的主键和上传文件的路径使用隐藏域保存起来
* 在edit.jsp中,把客户的网络地址等信息删除掉,没有用这些字段。
2. 修改客户的信息
* 修改表单的enctype属性(enctype="multipart/form-data")
* 给edit.jsp页面添加文件上传项()
* 如果用户新上传了文件,删除旧的文件,上传新的文件。
* 如果用户没有上传新文件,正常更新。
3. 如果要客户和联系人配置了一对多
* 再修改客户的时候,由于Customer对象中linkmans的set中没有值,所以在默认修改Customer的时候,会把set集合中的Linkman的外键设置成null
* 创建linkman的SQL语句中,要求外键是不能为null的
* <set name="linkmans" inverse="true">
add.jsp
<FORM id=form1 name=form1 action="${pageContext.request.contextPath }/customer_save.action" method="post" enctype="multipart/form-data">
<TR>
<td>客户传真 :</td>
<td>
<INPUT class=textbox id=sChannel2
style="WIDTH: 180px" maxLength=50 name="custFax">
</td>
<td>上传资质:</td>
<td>
<input type="file" name="upload" />
</td>
</TR>
structs.xml
<!-- 配置客户的Action,如果Action由Spring框架来管理,class标签上只需要编写ID值就OK -->
<action name="customer_*" class="customerAction" method="{1}">
<result name="page">/jsp/customer/list.jsp</result>
<result name="initAddUI">/jsp/customer/add.jsp</result>
<result name="save" type="redirectAction">customer_findByPage.action</result>
<result name="input">/jsp/error.jsp</result>
<result name="delete" type="redirectAction">customer_findByPage.action</result>
<result name="initUpdate">/jsp/customer/edit.jsp</result>
<result name="update" type="redirectAction">customer_findByPage.action</result>
<result name="findBySource">/jsp/totals/sources.jsp</result>
<!-- 引入默认的拦截器 -->
<interceptor-ref name="userInterceptor"/>
<interceptor-ref name="defaultStack">
<!-- 决定上传文件的类型 -->
<param name="fileUpload.allowedExtensions">.jpg,.txt</param>
</interceptor-ref>
</action>
CustomerAction.java save() delete()
/**
* 文件的上传,需要在CustomerAction类中定义成员的属性,命名是有规则的!!
* private File upload; // 表示要上传的文件
* private String uploadFileName; 表示是上传文件的名称(没有中文乱码)
* private String uploadContentType; 表示上传文件的MIME类型
* 提供set方法,拦截器就注入值了
*/
// 要上传的文件
private File upload;
// 文件的名称
private String uploadFileName;
// 文件的MIME的类型
private String uploadContentType;
public void setUpload(File upload) {
this.upload = upload;
}
public void setUploadFileName(String uploadFileName) {
this.uploadFileName = uploadFileName;
}
public void setUploadContentType(String uploadContentType) {
this.uploadContentType = uploadContentType;
}
/**
* 保存客户的方法
* @return
* @throws IOException
*/
public String save() throws IOException{
// 做文件的上传,说明用户选择了上传的文件了
if(uploadFileName != null){
// 打印
System.out.println("文件类型:"+uploadContentType);
// 把文件的名称处理一下
String uuidname = UploadUtils.getUUIDName(uploadFileName);
// 把文件上传到D:\\apache-tomcat-7.0.52\\webapps\\upload
String path = "D:\\apache-tomcat-7.0.52\\webapps\\upload\\";
// 创建file对象
File file = new File(path+uuidname);
// 简单方式
FileUtils.copyFile(upload, file);
// 把上传的文件的路径,保存到客户表中
customer.setFilepath(path+uuidname);
}
// 保存客户成功了
customerService.save(customer);
return "save";
}
/**
* 删除客户
* @return
*/
public String delete(){
// 删除客户,获取客户的信息获取到,上传文件的路径
customer = customerService.findById(customer.getCust_id());
// 获取上传文件的路径
String filepath = customer.getFilepath();
// 删除客户
customerService.delete(customer);
// 再删除文件
File file = new File(filepath);
if(file.exists()){
file.delete();
}
return "delete";
}
功能四:删除客户的功能
1. 删除上传的文件后,再删除客户信息。
抽取通用的BaseDao功能
1. 通过上面编写的一些功能,DAO层的代码相对比较固定,所以可以想办法来抽取出通用的方法!!
2. 代码如下
private Class clazz;
public BaseDaoImpl(){
Class c = this.getClass();
Type type = c.getGenericSuperclass();
// 判断
if(type instanceof ParameterizedType){
ParameterizedType ptype = (ParameterizedType) type;
// 获取实际类型参数
Type[] types = ptype.getActualTypeArguments();
// 获取0位置的值
clazz = (Class) types[0];
}
}
BaseDao.java
package com.itheima.dao;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
import com.itheima.domain.PageBean;
/**
* 以后所有的DAO的接口都需要继承BaseDao接口
* 自定义泛型接口
* @author Administrator
*/
public interface BaseDao<T> {
public void save(T t);
public void delete(T t);
public void update(T t);
public T findById(Long id);
public List<T> findAll();
public PageBean<T> findByPage(Integer pageCode, Integer pageSize, DetachedCriteria criteria);
}
BaseDaoImpl.java
package com.itheima.dao;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import com.itheima.domain.Customer;
import com.itheima.domain.PageBean;
/**
* 以后所有的Dao成的实现类,都可以继承BaseDaoImpl,增删改查分页方法不用再编写了
* @author Administrator
* @param <T>
*/
@SuppressWarnings("all")
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T>{
// 定义成员的属性
private Class clazz;
public BaseDaoImpl(){
// this表示的子类,c表示就是CustomerDaoImpl的Class对象
Class c = this.getClass();
// CustomerDaoImpl extends BaseDaoImpl<Customer> map<k,v>
// 第2步:获取到是BaseDaoImpl<Customer>
Type type = c.getGenericSuperclass();
// 目的:把type接口转换成子接口
if(type instanceof ParameterizedType){
ParameterizedType ptype = (ParameterizedType) type;
// 获取到 Customer
Type[] types = ptype.getActualTypeArguments();
this.clazz = (Class) types[0];
}
}
/**
* 添加
*/
public void save(T t) {
this.getHibernateTemplate().save(t);
}
/**
* 删除
*/
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
/**
* 修改
*/
public void update(T t) {
this.getHibernateTemplate().update(t);
}
/**
* 通过主键查询
*/
public T findById(Long id) {
return (T) this.getHibernateTemplate().get(clazz, id);
}
/**
* 查询所有的数据
*/
public List<T> findAll() {
return (List<T>) this.getHibernateTemplate().find("from "+clazz.getSimpleName());
}
/**
* 分页查询
*/
public PageBean<T> findByPage(Integer pageCode, Integer pageSize, DetachedCriteria criteria) {
// 创建分页的对象
PageBean<T> page = new PageBean<T>();
// 一个一个设置
page.setPageCode(pageCode);
page.setPageSize(pageSize);
// 设置查询聚合函数:SQL已经变成了 select count(*) from
criteria.setProjection(Projections.rowCount());
List<Number> list = (List<Number>) this.getHibernateTemplate().findByCriteria(criteria);
if(list != null && list.size() > 0){
int totalCount = list.get(0).intValue();
// 总记录数
page.setTotalCount(totalCount);
}
// 清除SQL select * from xxx
criteria.setProjection(null);
List<T> beanList = (List<T>) this.getHibernateTemplate().findByCriteria(criteria, (pageCode-1)*pageSize, pageSize);
// 每页显示的数据
page.setBeanList(beanList);
return page;
}
}
LinkmanDao.java
package com.itheima.dao;
import com.itheima.domain.Linkman;
public interface LinkmanDao extends BaseDao<Linkman>{
}
LinkManDaoImpl.java
package com.itheima.dao;
import com.itheima.domain.Linkman;
public class LinkmanDaoImpl extends BaseDaoImpl<Linkman> implements LinkmanDao {
}
抽取BaseAction的功能
1. Action需要完成分页的代码,需要接收pageCode和pageSize的请求参数,可以编写BaseAction用来接收分页的请求参数
BaseAction.java
联系人模块
功能一:查询联系人功能
1. 分页显示所有的联系人的数据
功能二:添加联系人功能
1. 自己完成
功能三:修改联系人功能
1. 自己完成
功能四:删除联系人功能
1. 自己完成
问题 客户和联系人的死循环
FastJson遍历死循环 Customer.java
// 和联系人配置一对多
// 默认不把set集合进行json的转换
@JSONField(serialize=false)
private Set<Linkman> linkmans = new HashSet<Linkman>();
问题2 修改客户时,导致联系人所属客户信息被清空
让客户放弃外键的维护的权力 Customer.hbm2.xml
<!-- 让Customer放弃外键的维护的权力 -->
<set name="linkmans" inverse="true">
<key column="lkm_cust_id"/>
<one-to-many class="com.itheima.domain.Linkman"/>
</set>
客户拜访模块
客户拜访,用户 和客户(联系人)是多对多关系
功能一:搭建客户拜访表的开发环境(导入资料中的拜访客户的SQL语句)
1. 客户关系拜访表是该系统的用户和客户之间的关系建立表
* 用户可以拜访多个客户
* 客户也可以被多个用户所拜访
* 所以:用户和客户之间应该是多对多的关系,那么客户拜访表就是用户和客户的中间表。
* 正常的情况下,在用户和客户中添加set集合,在映射的配置文件中配置<set>标签即可。
* 但是现在客户拜访中间表中存在其他的字段,默认的情况下,中间表只能维护外键。而不能维护其他的字段。所以需要把一对多拆开成两个一对多。
2. 用户与客户拜访表是一对多的关系
3. 客户与客户拜访表是一对多的关系
4. 创建客户拜访表的实体类和映射配置文件
5. 编写客户拜访的Action等类和完成配置
* 先开启注解的扫描
* <context:component-scan base-package="com.itheima"/>
* Action编写(@RestController(value="visitAction") @Scope(value="prototype"))
* Service编写(@Service(value="visitService") @Transactional)
* Dao编写(@Repository(value="visitDao"))
* 重点是dao中注入SessionFactory对象
@Resource(name="sessionFactory")
public void sSessionFactory(SessionFactory sessionFactory){
// 重点的代码
super.setSessionFactory(sessionFactory);
}
功能二:客户拜访列表查询功能*
1. 先导入客户拜访的页面
* 在资料中的(visit文件夹和jquery文件夹)
* visit文件夹复制到jsp的目录下
* jquery文件夹复制到WebContent目录下
2. 查询我的客户拜访记录
* 登录的用户,点击客户拜访列表,查询该用户下的所有的拜访记录
* 通过用户的主键查询该用户下的所有的拜访记录
Visit.java
/**
* 客户拜访的JavaBean
* @author Administrator
*/
public class Visit {
// 主键
private String visit_id;
// 外键,和客户
private Customer customer;
// 外键,和用户
private User user;
Visit.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.itheima.domain.Visit" table="sale_visit">
<id name="visit_id" column="visit_id">
<!-- 主键是字符串的类型 -->
<generator class="uuid"/>
</id>
<property name="visit_time" column="visit_time"/>
<property name="visit_interviewee" column="visit_interviewee"/>
<property name="visit_addr" column="visit_addr"/>
<property name="visit_detail" column="visit_detail"/>
<property name="visit_nexttime" column="visit_nexttime"/>
<many-to-one name="customer" class="com.itheima.domain.Customer" column="visit_cust_id"/>
<many-to-one name="user" class="com.itheima.domain.User" column="visit_user_id"/>
</class>
</hibernate-mapping>
VisitAction.java
/**
* 客户拜访的控制器
* @author Administrator
* Controller(value="visitAction") = <bean id="visitAction" class="..." scope="">
*/
@Controller(value="visitAction")
@Scope(value="prototype")
public class VisitAction extends BaseAction implements ModelDriven<Visit>{
private static final long serialVersionUID = -139853886677968072L;
private Visit visit = new Visit();
public Visit getModel() {
return visit;
}
@Resource(name="visitService")
private VisitService visitService;
private String beginDate;
private String endDate;
public void setBeginDate(String beginDate) {
this.beginDate = beginDate;
}
public void setEndDate(String endDate) {
this.endDate = endDate;
}
/**
* 分页的查询
* 查询客户的拜访记录,根据用户的主键查询
* select * from sale_visit where visit_user_id = ?
* @return
*/
public String findByPage(){
// 先获取当前登录的用户
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("existUser");
// 判断
if(user == null){
// 配置全局结果跳转
return LOGIN;
}
// 查询该用户下所有的拜访记录
DetachedCriteria criteria = DetachedCriteria.forClass(Visit.class);
// 拼接查询的条件
if(beginDate != null && !beginDate.trim().isEmpty()){
criteria.add(Restrictions.ge("visit_time", beginDate));
}
// select * from xxx where visit_time >= ? and visit_time <= ?
if(endDate != null && !endDate.trim().isEmpty()){
criteria.add(Restrictions.le("visit_time", endDate));
}
// 添加查询的条件
criteria.add(Restrictions.eq("user.user_id",user.getUser_id()));
// 分页查询
PageBean<Visit> page = visitService.findByPage(this.getPageCode(),this.getPageSize(),criteria);
this.setVs("page", page);
return "page";
}
/**
* 保存拜访记录
* @return
*/
public String save(){
// 把用户获取到,设置到当前的拜访记录中,再保存
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("existUser");
// 判断
if(user == null){
// 配置全局结果跳转
return LOGIN;
}
// 设置
visit.setUser(user);
// 保存数据
visitService.save(visit);
return "save";
}
}
VisitDaoImpl.java sessionfactory 注解方式注入问题
/**
* 客户拜访的持久层
* @author Administrator
*/
@Repository(value="visitDao")
public class VisitDaoImpl extends BaseDaoImpl<Visit> implements VisitDao {
@Resource(name="sessionFactory")
public void set2SessionFactory(SessionFactory sessionFactory){
// 关键,调用父类的方法
super.setSessionFactory(sessionFactory);
}
}
VisitService.java
/**
* 客户拜访业务层
* @author Administrator
*/
@Service(value="visitService")
@Transactional
public class VisitServiceImpl implements VisitService {
@Resource(name="visitDao")
private VisitDao visitDao;
/**
* 分页查询
*/
public PageBean<Visit> findByPage(Integer pageCode, Integer pageSize, DetachedCriteria criteria) {
return visitDao.findByPage(pageCode, pageSize, criteria);
}
public void save(Visit visit) {
visitDao.save(visit);
}
}
visit list.jsp 日期控件
<TD>选择日期:</TD>
<TD>
<INPUT class=textbox id="beginDate" style="WIDTH: 80px" maxLength=50 name="beginDate">
至
<INPUT class=textbox id="endDate" style="WIDTH: 80px" maxLength=50 name="endDate">
</TD>
<!-- 日期插件,使用jquery -->
<script type="text/javascript" src="${pageContext.request.contextPath }/jquery/jquery-1.4.2.js"></script>
<link rel="stylesheet" href="${pageContext.request.contextPath }/jquery/jquery.datepick.css" type="text/css">
<script type="text/javascript" src="${pageContext.request.contextPath }/jquery/jquery.datepick.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/jquery/jquery.datepick-zh-CN.js"></script>
<SCRIPT language=javascript>
function to_page(page){
if(page){
$("#page").val(page);
}
document.customerForm.submit();
}
$(function(){
// 给beginDate和endDate绑定日期控件的方法
$('#beginDate').datepick({dateFormat: 'yy-mm-dd'});
$('#endDate').datepick({dateFormat: 'yy-mm-dd'});
});
</SCRIPT>
功能三:新增客户拜访记录功能
1. 点击新增客户拜访功能菜单,跳转到新增页面,输入信息,保存数据
* 从HttpSession中获取到用户的信息,设置到拜访记录中,保存到数据库中
功能四:按条件查询客户信息列表功能
1. 修改list.jsp的页面,添加开始和结束日期的选项
<TD>拜访时间:</TD>
<TD>
<INPUT class=textbox id="beginDate" style="WIDTH: 80px" maxLength=50 name="beginDate">
至
<INPUT class=textbox id="endDate" style="WIDTH: 80px" maxLength=50 name="endDate">
</TD>
统计分析模块
功能一:客户来源统计
1. 想要统计客户的来源,即该来源下有多少个客户
* SQL语句:SELECT d.dict_item_name,COUNT(*) FROM base_dict d,cst_customer c WHERE d.dict_id = c.cust_source GROUP BY d.dict_id;
* HQL语句:String hql = "select c.source.dict_item_name,COUNT(*) from Customer c inner join c.source GROUP BY c.source";
CustomerAction.java findBySource()
/**
* 统计来源客户的数量
* @return
*/
public String findBySource(){
List<Object[]> list = customerService.findBySource();
// 压栈
ValueStack vs = ActionContext.getContext().getValueStack();
// 栈顶是map<"page",page对象>
vs.set("list", list);
return "findBySource";
}
CustomerDaoImpl.java findBySource()
sources.jsp
/**
* 统计客户的来源
*/
public List<Object[]> findBySource() {
// 定义HQL
// SELECT * FROM cst_customer c,base_dict d WHERE d.dict_id = c.cust_source
// 分组查询 SELECT * FROM cst_customer c,base_dict d WHERE d.dict_id = c.cust_source group by d.dict_id
// 查询内容:SELECT d.dict_item_name,count(*) FROM cst_customer c,base_dict d
// WHERE d.dict_id = c.cust_source group by d.dict_id
String hql = "select c.source.dict_item_name,count(*) from Customer c inner join c.source group by c.source";
// 查询
return (List<Object[]>) this.getHibernateTemplate().find(hql);
}
public List<Object[]> findBySource2() {
String hql = "SELECT d.dict_item_name,count(*) FROM Customer c,Dict d WHERE d.dict_id = c.source.dict_id group by d.dict_id";
// 查询
return (List<Object[]>) this.getHibernateTemplate().find(hql);
}
HQL*SQL
HQL*SQL2
用户登录的拦截器
用户登录的拦截器功能实现
1. 功能:如果用户没有登录,是不能操作后台的功能的!!
2. 代码如下
public class UserInterceptor extends MethodFilterInterceptor{
private static final long serialVersionUID = 335018670739692955L;
/**
* 进行拦截的方法
*/
protected String doIntercept(ActionInvocation invocation) throws Exception {
// 获取session对象
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("existUser");
if(user == null){
// 说明,没有登录,后面就不会执行了
return "login";
}
return invocation.invoke();
}
}
3. 配置如下
<interceptors>
<interceptor name="UserInterceptor" class="com.itheima.web.interceptor.UserInterceptor"/>
</interceptors>
<interceptor-ref name="UserInterceptor">
<!-- login方法不拦截 -->
<param name="excludeMethods">login</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
用户登录
UserAction().java
package com.itheima.web.action;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.ServletActionContext;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
/**
* 用户的控制器
* @author Administrator
*/
public class UserAction extends ActionSupport implements ModelDriven<User>{
private static final long serialVersionUID = -3413092622818913571L;
private User user = new User();
public User getModel() {
return user;
}
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
/**
* 注册功能
* @return
*/
public String regist(){
// 接收请求参数
userService.save(user);
return LOGIN;
}
/**
* 通过登录名,判断,登录名是否已经存在
* @return
*/
public String checkCode(){
// 调用业务层,查询
User u = userService.checkCode(user.getUser_code());
// 获取response对象
HttpServletResponse response = ServletActionContext.getResponse();
response.setContentType("text/html;charset=UTF-8");
try {
// 获取输出流
PrintWriter writer = response.getWriter();
// 进行判断
if(u != null){
// 说明:登录名查询到用户了,说明登录已经存在了,不能注册
writer.print("no");
}else{
// 说明,不存在登录名,可以注册
writer.print("yes");
}
} catch (IOException e) {
e.printStackTrace();
}
return NONE;
}
/**
* 登录功能
* @return
*/
public String login(){
User existUser = userService.login(user);
// 判断,登录名或者密码错误了
if(existUser == null){
return LOGIN;
}else{
ServletActionContext.getRequest().getSession().setAttribute("existUser", existUser);
// 登录成功
return "loginOK";
}
}
/**
* 退出功能
* @return
*/
public String exit(){
ServletActionContext.getRequest().getSession().removeAttribute("existUser");
return LOGIN;
}
}
UserDaoImpl.java
/**
* 持久层:都可以继承HibernateDaoSupport类
*
* @author Administrator
*/
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
/**
* 通过登录名进行验证
*/
public User checkCode(String user_code) {
List<User> list = (List<User>) this.getHibernateTemplate().find("from User where user_code = ?", user_code);
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
/**
* 保存用户
*/
public void save(User user) {
this.getHibernateTemplate().save(user);
}
/**
* 登录功能
* 通过用户名和密码和用户的状态
*/
public User login(User user) {
// QBC的查询,按条件进行查询
DetachedCriteria criteria = DetachedCriteria.forClass(User.class);
// 拼接查询的条件
criteria.add(Restrictions.eq("user_code", user.getUser_code()));
criteria.add(Restrictions.eq("user_password", user.getUser_password()));
criteria.add(Restrictions.eq("user_state", "1"));
// 查询
List<User> list = (List<User>) this.getHibernateTemplate().findByCriteria(criteria);
if(list != null && list.size() > 0){
return list.get(0);
}
return null;
}
}
regist.html
<script type="text/javascript">
// 验证登录名
function checkCode(){
// 获取用户输入的登录名
var code = $("#user_code").val();
// 进行判断,说明没有输入登录名
if(code.trim() == ""){
// 给提示
$("#codeId").addClass("error");
$("#codeId").html("登录名不能为空");
}else{
// 登录名不为空,ajax请求,验证
var url = "${pageContext.request.contextPath}/user_checkCode.action";
var param = {"user_code":code};
$.post(url,param,function(data){
// 操作data,进行判断
if(data && data == "no"){
// 提示
$("#codeId").addClass("error");
$("#codeId").html("登录名已经存在");
}else{
$("#codeId").removeClass("error");
$("#codeId").html("可以注册");
}
});
}
}
// 可以阻止表单的提交
function checkForm(){
// 先让校验名称的方法先执行以下
checkCode();
// 获取error的数量,如果数量 > 0,说明存在错误,不能提交表单
if($(".error").size() > 0){
return false;
}
}
</script>