Struts2练习项目人员管理
1案例名称:
人员管理系统:user manager system (ums)
2案例功能:
*用户登录 -- xml校验、登录拦截器
*查询所有用户(条件查询)
*添加(上传简历)-- token
*编辑(下载) -- 标签回显
*查询
*删除(略)
3技术分析
4环境搭建
4.1jar包
*struts
*dbutils
*c3p0
*mysql
4.2配置文件
4.2.1struts.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- 常量 --> <constant name="struts.devMode" value="true"></constant> <constant name="struts.ui.theme" value="simple"></constant> <package name="strutsDemo" namespace="/" extends="struts-default"> </package> </struts>
4.2.2web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
4.2.3c3p0-config.xml
位置:src
<c3p0-config> <!-- 命名的配置 --> <named-config name="strutsExerciseDBXml"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/strutsExerciseDB</property> <property name="user">root</property> <property name="password">1234</property> <!-- 如果池中数据连接不够时一次增长多少个 --> <property name="acquireIncrement">5</property> <property name="initialPoolSize">20</property> <property name="minPoolSize">10</property> <property name="maxPoolSize">40</property> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
4.2.4log4j.properties ,
日志配置文件,存在src下
### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file mylog.log ### log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=d:\mylog.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### log4j.rootLogger=info, stdout
4.3包结构
4.4工具类
public class JdbcUtils { //创建一个连接池--c3p0 ,名称来自 c3p0-config.xml配置 private static ComboPooledDataSource dataSource = new ComboPooledDataSource("strutsExerciseDBXml"); /** * 获得数据源(连接池) * @return */ public static DataSource getDataSource(){ return dataSource; } }
4.5数据库和表
create database strutsExerciseDB character set utf8; use strutsExerciseDB; CREATE TABLE S_User( userID INT NOT NULL AUTO_INCREMENT, #主键ID userName VARCHAR(50) NULL, #用户姓名 loginName VARCHAR(50) NULL, #登录名 loginPwd VARCHAR(50) NULL, #密码# sex VARCHAR(10) NULL, #性别(例如:男,女) birthday VARCHAR(50) NULL, #出生日期 education VARCHAR(20) NULL, #学历(例如:研究生、本科、专科、高中) telephone VARCHAR(50) NULL, #电话 interest VARCHAR(20) NULL, #兴趣爱好(例如:体育、旅游、逛街) path VARCHAR(500) NULL, #上传路径(path路径) filename VARCHAR(100) NULL, #上传文件名称(文件名) remark VARCHAR(500) NULL, #备注 PRIMARY KEY (userID) ) ; insert into s_user(userName,loginName,loginPwd,sex) values('龙哥','jack','1234','男'); insert into s_user(userName,loginName,loginPwd,sex,filename) values('凤姐','rose','1234','女','简历.doc');
5编写javabean
private int userID; private String userName; private String loginName; private String loginPwd; private String sex; private String birthday; private String education; private String telephone; private String interest; private String path; private String filename; private String remark; private String isUpload; //用于判断文件是否上传。上传文件名是否存在 filename
6用户登录
6.1步骤
*jsp登录,提供表单,将html表单修改s标签
*编写action类,同时配置struts.xml
*action类调用service进行登录处理
*service调用dao进行查询
*数据校验
*拦截器
6.2修改login.jsp
*将html表单修改成struts标签
6.3编写action类
*继承ActionSupport
*实现ModelDriven
//#1 封装数据:查询条件、添加普通字段、编辑id、 private User user = new User();
*提供login处理方法, 和 登录首页,需要使用service
/** * 登录 * @return */ @InputConfig(resultName="loginInput") //登录校验返回位置 public String login(){ User loginUser = userService.login(user); if(loginUser != null){ //登录成功 -- session ActionContext.getContext().getSession().put("loginUser", loginUser); return "loginSuccess"; } else { //账号密码不正确 this.addFieldError("loginName", "用户名和密码不匹配"); return "login"; } } /** * 登录成功首页 * @return */ public String home(){ return "home"; }
6.4struts.xml
<package name="strutsDemo" namespace="/" extends="struts-default"> <!-- 用户action --> <action name="userAction_*" class="cn.itcast.ums.web.action.UserAction" method="{1}"> <!-- 1 登录成功 ,重定向--> <result name="loginSuccess" type="redirectAction"> <param name="namespace">/</param> <param name="actionName">userAction_home</param> </result> <!-- 2 登录首页 --> <result name="home">/login/home.jsp</result> <!-- 3 需要登录 --> <result name="login">/login/login.jsp</result> </action> </package>
6.5service 编写
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); @Override public User login(User user) { return userDao.find(user.getLoginName(),user.getLoginPwd()); } }
6.6dao编写
public class UserDaoImpl implements UserDao { // 1 提供DBUtils 核心类 -- 必须提供数据源,DBUtils底层自己维护Connection private QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); @Override public User find(String loginName, String loginPwd) { try { // 2 准备sql语句 String sql = "select * from s_user where loginName = ? and loginPwd = ?"; // 3 准备实际参数 Object[] params = {loginName,loginPwd}; // 4 执行 return runner.query(sql, new BeanHandler<User>(User.class), params); } catch (Exception e) { throw new RuntimeException(e); } } }
6.7xml数据校验
*文件名称:
单个方法:action类名-action名称-validation.xml 【】
所有方法:action类名-validation.xml
6.7.1xml文件
<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.3//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd"> <validators> <!-- 1 用户名 --> <field name="loginName"> <!-- 必填 --> <field-validator type="requiredstring"> <message><![CDATA[用户名不能为空]]></message> </field-validator> <!-- 长度 --> <field-validator type="stringlength"> <param name="minLength">2</param> <param name="maxLength">16</param> <message><![CDATA[用户名长度必须${minLength}-${maxLength}字符之间]]></message> </field-validator> </field> <!-- 2 密码 --> <field name="loginPwd"> <field-validator type="requiredstring"> <!-- key从国际化资源文件获得提示信息 --> <message key="loginPwd.message"></message> </field-validator> </field> </validators>
6.7.2properties文件
loginPwd.message = \u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
6.8拦截器
*登录拦截器目的:除了登录功能之外,其他的功能都需要先登录,在访问。
*接口:Interceptor
*实现类:MethodFilterInterceptor 指定不需要拦截方法名,也可以指定必须进行拦截方法。
*默认情况自定义拦截器无效,必须注册、然后使用。
6.8.1实现类
public class LoginInterceptor extends MethodFilterInterceptor { @Override public String doIntercept(ActionInvocation ai) throws Exception { //确定用户必须登录,如果是登录方法进行放行(配置文件明确) Object loginUser = ActionContext.getContext().getSession().get("loginUser"); if(loginUser == null){ /** 友好提示信息start*/ // 1 获得当前action实例 Object actionObject = ai.getAction(); // 2 判断当前action类是否继承ActionSupport if(actionObject instanceof ActionSupport){ ActionSupport actionSupport = (ActionSupport) actionObject; actionSupport.addFieldError("loginName", "请登录"); } /** 友好提示信息end*/ // 没有登录--必须登录 return "login"; } //放行 return ai.invoke(); } }
6.8.2配置struts.xml
<!-- 1 配置拦截器 --> <interceptors> <!-- 1.1 注册 --> <interceptor name="loginInterceptor" class="cn.itcast.ums.web.interceptor.LoginInterceptor"></interceptor> <!-- 1.2 声明自定义拦截器栈(多个拦截器的组合) --> <interceptor-stack name="loginStack"> <!-- 1.2.1 默认栈 --> <interceptor-ref name="defaultStack"></interceptor-ref> <!-- 1.2.2 其他拦截器 --> <!-- 1.2.3 自定义拦截器 --> <interceptor-ref name="loginInterceptor"> <!-- * 设置哪些方法不需要拦截,多个方法之间使用逗号分隔 --> <param name="excludeMethods">login</param> </interceptor-ref> </interceptor-stack> </interceptors> <!-- 2 将自定义栈,声明默认栈 --> <default-interceptor-ref name="loginStack"></default-interceptor-ref>
7查询所有
7.1修改left.jsp
<!-- 查询所有,文件显示位置:/user/list.jsp --> <s:a namespace="/" action="userAction_findAll" target="mainFrame">用户管理</s:a>
7.2编写action
/** * 查询所有 * @return */ public String findAll(){ //通知service 查询 List<User> allUser = userService.findAll(user); // 将查询结果存放值栈 , jsp通过key直接获得 ActionContext.getContext().getValueStack().set("allUser", allUser); return "findAll"; }
7.3struts.xml
<!-- 3.5 查询所有 --> <result name="findAll">/user/list.jsp</result>
7.4list.jsp展示
8条件查询
8.1修改list.jsp
*提供表单
<%--条件查询 start --%> <s:form namespace="/" action="userAction_findAll" id="Form1" name="Form1" > <table cellpadding="0" cellspacing="0" border="0" width="100%"> <tr> <td height="22" align="center" bgColor="#f5fafe" class="ta_01"> 用户姓名 </td> <td class="ta_01" bgColor="#ffffff"> <s:textfield name="userName" cssClass="bg" size="15"></s:textfield> </td> <td height="22" align="center" bgColor="#f5fafe" class="ta_01"> 性别: </td> <td class="ta_01" bgColor="#ffffff"> <s:select list="{'男','女'}" headerKey="" headerValue="--选择性别--" name="sex" id="sex"></s:select> </td> </tr> <tr> <td height="22" align="center" bgColor="#f5fafe" class="ta_01"> 学历: </td> <td class="ta_01" bgColor="#ffffff"> <s:select list="{'博士','硕士','本科','专科','高中'}" headerKey="" headerValue="--选择学历--" name="education" id="education"></s:select> </td> <td height="22" align="center" bgColor="#f5fafe" class="ta_01"> 是否上传简历 </td> <td class="ta_01" bgColor="#ffffff"> <s:select list="#{'1':'有','2':'无'}" headerKey="" headerValue="--请选择--" name="isUpload" id="isUpload"></s:select> </td> </tr> <tr> <td width="100" height="22" align="center" bgColor="#f5fafe" class="ta_01"> </td> <td class="ta_01" bgColor="#ffffff"> <font face="宋体" color="red"> </font> </td> <td align="right" bgColor="#ffffff" class="ta_01"><br><br></td> <td align="right" bgColor="#ffffff" class="ta_01"> <s:submit id="search" value="查询" cssClass="button_view"></s:submit> <s:reset id="reset" value="重置" cssClass="button_view"></s:reset> </td> </tr> </table> </s:form> <%--条件查询 end --%>
8.2编写dao拼写sql
@Override public List<User> findAll(User user) { try { //#1 条件, 拼写sql,拼写实际参数 // #1.1 拼写sql StringBuffer sqlBuffer = new StringBuffer("select * from s_user where 1=1 "); // #1.2 拼写实际参数 (顺序,重复) List<Object> paramsList = new ArrayList<Object>(); // #1.3 拼写条件 // * 用户名 //if(user.getUserName() != null && !"".equals(user.getUserName())){ if(StringUtils.isNotBlank(user.getUserName())){ sqlBuffer.append(" and userName like ? "); paramsList.add("%"+user.getUserName()+"%"); } // * 性别 if(StringUtils.isNotBlank(user.getSex())){ sqlBuffer.append(" and sex = ? "); paramsList.add(user.getSex()); } // * 学历 if(StringUtils.isNotBlank(user.getEducation())){ sqlBuffer.append(" and education = ? "); paramsList.add(user.getEducation()); } // * 是否上传 -- 依赖:文件名是否为空 if("1".equals(user.getIsUpload())){ //上传--不为空 sqlBuffer.append(" and filename is not null"); }else if("2".equals(user.getIsUpload())){ //没有上传 -- 为空 sqlBuffer.append(" and filename is null"); } //条件end //2 准备sql String sql = sqlBuffer.toString(); //3准备实际参数 Object [] params = paramsList.toArray(); //4执行 return runner.query(sql, new BeanListHandler<User>(User.class), params); } catch (Exception e) { throw new RuntimeException(e); } }
9用户添加
*文件上传
9.1分析
9.2修改add.jsp
*位置:/strutsExercise/WebRoot/user/add.jsp
9.3action类提供add方法
*提供addUI,显示jsp页面
*提供add进行添加处理
/** * 添加jsp页面显示 * @return */ public String addUI(){ return "addUI"; } /** * 添加功能 * @return */ public String add(){ try { // 1 处理上传文件,将数据封装到javabean -- 允许没有上传内容 if (upload != null) { // * 有上传文件--简历 。 需要给javabean的路径path和文件名filename赋值 // 1.1 确定路径 , 例如: /WEB-INF/upload/uuid (保持只是内容) String path = "/WEB-INF/upload/" + UUID.randomUUID().toString(); // 1.2 确定在tomcat下实际路径,并获得文件 File file = new File(ServletActionContext.getServletContext() .getRealPath(path)); // 1.3 保存文件--执行拷贝 FileUtils.copyFile(upload, file); // 1.4 赋值 user.setPath(path); user.setFilename(uploadFileName); } } catch (Exception e) { e.printStackTrace(); } //保持功能 this.userService.addUser(user); return "add"; }
9.4service
@Override public void addUser(User user) { userDao.save(user); }
9.5dao
@Override public void save(User user) { try { // 2 sql String sql = "insert into s_user(userName,loginName, loginPwd,sex,birthday, education,telephone,interest, path,filename,remark) values(?,?, ?,?,?, ?,?,?, ?,?,?)"; // 3 参数 Object [] params = {user.getUserName(),user.getLoginName(), user.getLoginPwd(),user.getSex(),user.getBirthday(), user.getEducation(),user.getTelephone(),user.getInterest(), user.getPath(),user.getFilename(),user.getRemark()}; // 4 执行 runner.update(sql, params); } catch (Exception e) { throw new RuntimeException(e); } }
9.6struts.xml
*需要配置jsp显示,添加成功之后处理
<!-- 3.6 添加jsp页面显示 --> <result name="addUI">/user/add.jsp</result> <!-- 3.7 添加成功,重定向到查询所有 --> <result name="add" type="redirectAction">userAction_findAll</result>
10编辑用户
*标签回显
*文件下载
10.1分析
10.2action类提供 preEdit方法
*通过id查询
/** * 编辑前操作 , 通过id查询用户 * @return */ public String preEdit(){ // 通过id查询 User tempUser = this.userService.findById(user.getUserID()); // 存放到值栈root ActionContext.getContext().getValueStack().push(tempUser); return "preEdit"; }
10.3修改edit.jsp
*位置:/strutsExercise/WebRoot/user/edit.jsp
*提供隐藏字段给服务器传递数据
<%--编辑表单start --%> <s:form name="Form1" namespace="/" action="userAction_edit" enctype="multipart/form-data"> <table cellSpacing="1" cellPadding="5" width="100%" align="center" bgColor="#eeeeee" style="border: 1px solid #8ba7e3" border="0"> <tr> <td class="ta_01" align="center" bgColor="#afd1f3" colSpan="4" height="26"> <strong><STRONG>编辑用户</STRONG> <%--提供隐藏字段,用户id %{必须解析} %{'字符串'}--%> <s:hidden name="userID" value="%{userID}"></s:hidden> <%--上传路径和名称 --%> <s:hidden name="path" value="%{path}"></s:hidden> <s:hidden name="filename" value="%{filename}"></s:hidden> </strong> </td> </tr> <tr> <td width="18%" align="center" bgColor="#f5fafe" class="ta_01"> 登录名:............
10.4编写action类 edit方法
*按照一定的逻辑处理文件上传
public String edit() throws IOException{ /* * 处理文件上传:逻辑处理--自己规定 * 之前有文件: * 编辑没有上传,使用之前的 * 编辑上传,将之前覆盖的 * 之前没有 * 编辑有 */ // 编辑上传 if(upload != null){ //上一次是否上传 if(StringUtils.isNotBlank(user.getPath()) && StringUtils.isNotBlank(user.getFilename())){ //覆盖 // 1.1 文件内容覆盖 File file = new File(ServletActionContext.getServletContext() .getRealPath(user.getPath())); // 1.3 保存文件--执行拷贝 FileUtils.copyFile(upload, file); // 1.2 文件名称覆盖 user.setFilename(uploadFileName); } else { //新上传 // 1.1 确定路径 , 例如: /WEB-INF/upload/uuid (保持只是内容) String path = "/WEB-INF/upload/" + UUID.randomUUID().toString(); // 1.2 确定在tomcat下实际路径,并获得文件 File file = new File(ServletActionContext.getServletContext() .getRealPath(path)); // 1.3 保存文件--执行拷贝 FileUtils.copyFile(upload, file); // 1.4 赋值 user.setPath(path); user.setFilename(uploadFileName); } } //TODO 编辑操作完成 return "edit"; }
10.5struts.xml
<!-- 3.8 编辑前操作,查询用户,在jsp页面显示 --> <result name="preEdit">/user/edit.jsp</result> <!-- 3.9 编辑成功,重定向首页 --> <result name="edit" type="redirectAction">userAction_findAll</result>
11下载
11.1action类
// * 下载的资源,target是struts.xml配置的 private InputStream target; public InputStream getTarget() { return target; } // * 确定下载的文件名称:处理中文乱码问题 private String downloadFileName; public String getDownloadFileName() throws UnsupportedEncodingException { if(downloadFileName != null){ return new String(downloadFileName.getBytes(),"ISO-8859-1"); } // 给<s:debug> 提供 return null; } //下载 public String download(){ // 给下载资源赋值 // 1 通过id查询 用户名 和 路径 User tempUser = this.userService.findById(user.getUserID()); if(tempUser == null){ // 有人改路径,用户存在 this.addFieldError("download", "下载资源不存在"); return "error"; } // 2 获得下载资源 this.target = ServletActionContext.getServletContext().getResourceAsStream(tempUser.getPath()); if(target == null){ // 下载资源不存在 this.addFieldError("download", "下载资源已经被删除"); return "error"; } // 3 赋值文件名称 this.downloadFileName = tempUser.getFilename(); return "download"; }
11.2struts.xml
<!-- 3.10 下载配置 * action提供给struts下载内容 * struts设置下载头,确定下载名称文件 --> <result name="download" type="stream"> <!-- 3.10.1 确定文件内容,使用action类的指定的getter方法属性:target --> <param name="inputName">target</param> <!-- 3.10.2 确定下载响应头 --> <param name="contentDisposition">attachment;filename=${downloadFileName}</param> </result>
12用户名是否存在(ajax)
*ajax
12.1使用struts json
*打入jar包
*使用父包
<package name="strutsDemo" namespace="/" extends="json-default">
PS:因为父包也继承了默认struts-default
*结果使用 json类型,让struts生成json数据
<result name="checkName" type="json"> <!-- 3.11.1 设置从值栈获得那个数据 * root 确定值栈的数据名称,如果没有设置,默认从栈顶获取。 --> <param name="root">msgMapKey</param> </result>
12.2jsp发送ajax
<%--导入工具包,自定义jQuery --%> <script type="text/javascript" src="${pageContext.request.contextPath}/js/jQuery.js"></script> <script type="text/javascript"> function checkLoginNameFn(obj){ // 请求路径 var url = "${pageContext.request.contextPath}/userAction_checkLoginName"; // 请求参数 var params = {"loginName":obj.value}; //发送ajax请求 jQuery.post(url,params,function(data){ //参考案例:/day23/WebRoot/e_ajax_json.jsp if(data.isHasUser){ //可用 } else { //不可用 } //将提示显示登录名称之后 document.getElementById("loginNameSpan").innerHTML = data.msg; }); } </script>
12.3action编写方法
*将提示信息存放Map,方法struts生成json数据
public String checkLoginName(){ //1 通过登录名称查询用户是否存在 User tempUser = this.userService.findByName(user.getLoginName()); //2 拼写提示信息 Map<String,String> msgMap = new HashMap<String, String>(); if(tempUser != null){ //存在 msgMap.put("msg", "用户名已经存在"); msgMap.put("isHasUser", "false"); //是否可以使用:不能使用 } else { //不存在 msgMap.put("msg", "用户名可以使用"); msgMap.put("isHasUser", "true"); //是否可以使用:能使用 } //将生成数据压入栈顶 ActionContext.getContext().getValueStack().set("msgMapKey", msgMap); return "checkName"; }
*如果使用set方法,在struts.xml 必须配置 结果集类型json的root属性
*如果使用push方法,压入到栈顶,直接使用。struts json 默认从栈顶获得数据。
12.4struts.xml配置
<!-- 3.11 ajax --> <result name="checkName" type="json"> <!-- 3.11.1 设置从值栈获得那个数据 * root 确定值栈的数据名称,如果没有设置,默认从栈顶获取。 --> <param name="root">msgMapKey</param> </result>
12.5jsp 响应之后处理
//发送ajax请求 jQuery.post(url,params,function(data){ //参考案例:/day23/WebRoot/e_ajax_json.jsp if(data.isHasUser){ //可用 } else { //不可用 } //将提示显示登录名称之后 document.getElementById("loginNameSpan").innerHTML = data.msg; });
13js时间
13.1导入js类库
<%--导入时间类库 --%> <script type="text/javascript" src="${pageContext.request.contextPath}/js/Calendar.js"></script>
13.2使用
<%-- struts标签:readonly="true" html标签:readonly="readonly" --%> <s:textfield readonly="true" onclick="c.showMoreDay=true;c.show(this);">