实例概述
实例名:小春论坛
实例功能:
登录界面给出一个有用户名/密码的输入表单,用户填写并提交表单后,服务器端程序检查是否有匹配的用户名/密码。若有,则记录用户的成功登录日志,更新用户最后登录时间和IP,并给该用户增加5个积分,然后重定向到欢迎页面。
环境准备
Maven:apache-maven-3.3.9(本人自己的版本,与书中不一致)
MySql::mysql 8.0.12(本人自己的版本,与书中不一致)
IDE:IDEA2018.1 64位
建表语句:
DROP DATABASE IF EXISTS sampledb;
CREATE DATABASE sampledb DEFAULT CHARACTER SET utf8;
USE sampledb;
##创建用户表
CREATE TABLE t_user (
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(30),
password VARCHAR(32),
credits INT,
last_visit datetime,
last_ip VARCHAR(23)
)ENGINE=InnoDB;
##创建用户登录日志表
CREATE TABLE t_login_log (
login_log_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
ip VARCHAR(23),
login_datetime datetime
)ENGINE=InnoDB;
##插入初始化数据
INSERT INTO t_user (user_name,password)
VALUES('admin','123456');
COMMIT;
其中t_user为用户信息表,t_login_log为用户登录日志表。ENGINE=InnoDB; 指定表的引擎为InnoDB类型,该类型的表支持事务。并且插入了一条用户数据:admin/123456,方便之后进行登录操作。
建立工程
IDEA编码设置为UTF-8编码:
File-->Setting-->打开如下页面设置
File-->Other Setting-->Default Setting-->打开如下设置(为IDE设置默认属性)
创建Maven工程步骤:
File-->New-->Project-->
点击结束完成Maven项目创建(注意,在这之前要安装好Maven环境,并且在IDEA中配置你的Maven,具体方法自行百度)
初始工程文件结构:
完整的项目目录结构:
建议读者先按完整项目目录结构建好文件,然后跟着我后面贴的代码及注释完成项目
新建文件时注意webapp的创建过程:
选择文件结构选项卡:
添加本项目模块:
添加web原型:
修改成下面的路径:
骨架搭好了接下来就要剩下把内容填充上了
源码部分
配置文件
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.smart</groupId>
<artifactId>chapter2</artifactId>
<version>1.0</version>
<name>Spring4.x第二章实例</name>
<description>Spring4.x第二章实例</description>
<packaging>war</packaging>
<!-- 全局属性-->
<properties>
<file.encoding>UTF-8</file.encoding>
<spring.version>4.2.1.RELEASE</spring.version>
<mysql.version>8.0.12</mysql.version>
<servlet.version>2.5</servlet.version>
</properties>
<dependencies>
<!--依赖的Spring模块类库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--连接池依赖-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!--依赖的Web类库-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--TestNG-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.9.9</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!--jetty插件-->
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.25</version>
<configuration><!--配置说明-->
<connectors>
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
<port>8000</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<contextPath>/bbs</contextPath>
<!--默认为0,表示禁用热部署检查。任何一个大于0的数字都将表示启用。-->
<scanIntervalSeconds>0</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom中配置了jetty插件,所以不必再手动部署配置tomcat服务器,项目完成编写后直接点击maven projects选项卡的jetty插件下的jetty:run运行即可。注意,mysql版本要设置成你自己的版本
smart-context.xml 将数据库用户名密码改成你自己的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<beans>
<!--扫描类包,将标注Spring注解的类自动转化成Bean,同时完成Bean的注入-->
<context:component-scan base-package="com.smart.dao"/>
<context:component-scan base-package="com.smart.service"/>
<!--定义一个使用DBCP实现的数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/sampledb?serverTimezone=GMT%2B8"
p:username="root"
p:password="XXXX"
/>
<!--定义JDBC模板Bean-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/>
<!--增强代码-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--通过AOP配置提供事务增强,让service包下所有Bean的方法拥有事务-->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod" expression="(execution(* com.smart.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
</beans>
</beans>
log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.logger.org.springframework=debug
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n
smart-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.smart.web"/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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_2_5.xsd"
version="2.5">
<!--指明Spring配置文件位置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:smart-context.xml</param-value>
</context-param>
<!--负责启动Spring容器的监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--Spring MVC的主控Servlet(WEB-INF下的smart-servlet.xml符合命名规则会被自动加载)-->
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!--Spring MVC处理的URL-->
<servlet-mapping>
<servlet-name>smart</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
java代码
dao层
UserDao.java
package com.smart.dao;
import com.smart.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
@Repository
public class UserDao {
private JdbcTemplate jdbcTemplate;
private final static String MATCH_COUNT_SQL=" SELECT COUNT(*) FROM t_user "+
" WHERE user_name=? AND password=? ";
private final static String UPDATE_LOGIN_INFO_SQL=" UPDATE t_user SET "
+" last_visit=?,last_ip=?,credits=? WHERE user_id=? ";
private final static String FIND_USER_BY_NAME_SQL=" SELECT * from t_user where user_name=? ";
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
public int getMatchCount(String userName,String password){
return jdbcTemplate.queryForObject(MATCH_COUNT_SQL,new Object[]{userName,password},Integer.class);
}
public User findUserByUserName(final String userName){
final User user=new User();
jdbcTemplate.query(FIND_USER_BY_NAME_SQL, new RowCallbackHandler() {//回调接口
public void processRow(ResultSet resultSet) throws SQLException {
user.setUserId(resultSet.getInt("user_id"));
user.setUserName(resultSet.getString("user_name"));
user.setCredits(resultSet.getInt("credits"));
user.setLastVisit(resultSet.getTimestamp("last_visit"));
user.setLastIp(resultSet.getString("last_ip"));
}
},userName);
return user;
}
public void updateLoginInfo(User user){
jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL,new Object[]{user.getLastVisit(), user.getLastIp(),user.getCredits(),user.getUserId()});
}
}
LoginLogDao.java
package com.smart.dao;
import com.smart.domain.LoginLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class LoginLogDao {
private JdbcTemplate jdbcTemplate;
//保存登陆日志SQL
private final static String INSERT_LOGIN_LOG_SQL=" INSERT INTO t_login_log(user_id,ip,login_datetime) values(?,?,?) ";
public void insertLoginLog(LoginLog loginLog){
Object [] args={loginLog.getUserId(),loginLog.getIp(),loginLog.getLoginDate()};
jdbcTemplate.update(INSERT_LOGIN_LOG_SQL,args);
}
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate=jdbcTemplate;
}
}
pojo类
User.java
package com.smart.domain;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
public class User implements Serializable {
private int userId;
private String userName;
private String password;
private int credits;
private String lastIp;
private Timestamp lastVisit;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getCredits() {
return credits;
}
public void setCredits(int credits) {
this.credits = credits;
}
public String getLastIp() {
return lastIp;
}
public void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
public Timestamp getLastVisit() {
return lastVisit;
}
public void setLastVisit(Timestamp lastVisit) {
this.lastVisit = lastVisit;
}
}
LoginLog.java
package com.smart.domain;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
public class LoginLog implements Serializable {
private int loginLogId;
private int userId;
private String ip;
private Timestamp loginDate;
public int getLoginLogId() {
return loginLogId;
}
public void setLoginLogId(int loginLogId) {
this.loginLogId = loginLogId;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Timestamp getLoginDate() {
return loginDate;
}
public void setLoginDate(Timestamp loginDate) {
this.loginDate = loginDate;
}
}
service层
UserService.java
package com.smart.service;
import com.smart.dao.LoginLogDao;
import com.smart.dao.UserDao;
import com.smart.domain.LoginLog;
import com.smart.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private UserDao userDao;
private LoginLogDao loginLogDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Autowired
public void setLoginLogDao(LoginLogDao loginLogDao) {
this.loginLogDao = loginLogDao;
}
public boolean hasMatchUser(String userName,String password){
int matchCount=userDao.getMatchCount(userName,password);
return matchCount>0;
}
public User findUserByUserName(String userName){
return userDao.findUserByUserName(userName);
}
@Transactional
public void loginSuccess(User user){
user.setCredits(5+user.getCredits());//每登录一次赚5积分
LoginLog loginLog=new LoginLog();
loginLog.setUserId(user.getUserId());
loginLog.setIp(user.getLastIp());
loginLog.setLoginDate(user.getLastVisit());
userDao.updateLoginInfo(user);
loginLogDao.insertLoginLog(loginLog);
}
}
web层
LoginController.java
package com.smart.web;
import com.smart.domain.User;
import com.smart.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.Date;
@Controller
public class LoginController {
@Autowired
private UserService userService;
//负责处理/index.html的请求
@RequestMapping("/index.html")
public String loginPage(){
return "login";
}
//负责处理/loginCheck.html的请求
@RequestMapping(value = "/loginCheck.html")
public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
boolean isValidUser=userService.hasMatchUser(loginCommand.getUserName(),loginCommand.getPassword());
if(!isValidUser){
return new ModelAndView("login","error","用户名或密码错误。");
}else {
User user=userService.findUserByUserName(loginCommand.getUserName());
user.setLastIp(request.getLocalAddr());
user.setLastVisit(new Timestamp(new Date().getTime()));
userService.loginSuccess(user);
request.getSession().setAttribute("user",user);
return new ModelAndView("main");
}
}
}
LoginCommand.java
package com.smart.web;
public class LoginCommand {
private String userName;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
testNG测试代码(可有可无)
package com.smart.service;
import com.smart.dao.LoginLogDao;
import com.smart.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.testng.annotations.Test;
import java.sql.Timestamp;
import java.util.Date;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@ContextConfiguration("classpath*:/smart-context.xml")//启动Spring容器
public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests {
private UserService userService;
private LoginLogDao loginLogDao;
@Autowired//注入Spring中的Bean
public void setUserService(UserService userService){
this.userService=userService;
}
@Test
public void hasMatchUser(){
boolean b1=userService.hasMatchUser("admin","123456");
boolean b2=userService.hasMatchUser("admin","1111");
User user=new User();
user.setUserName("admin");
user.setPassword("123456");
assertTrue(b1);
assertTrue(!b2);
}
@Test
public void findUserByUserName(){
User user=userService.findUserByUserName("admin");
assertEquals(user.getUserName(),"admin");
}
@Test
public void testAddLoginLog() {
User user = userService.findUserByUserName("admin");
user.setUserId(1);
user.setUserName("admin");
user.setLastIp("192.168.12.7");
user.setLastVisit(new Timestamp(new Date().getTime()));
userService.loginSuccess(user);
System.out.println("盐勺水fklajfakfklanjf");
}
public static void main(String[] args) {
System.out.println("盐勺水fklajfakfklanjf");
}
}
页面
login.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: yan
Date: 2018/10/31
Time: 22:08
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>小春论坛登录</title>
</head>
<body>
<c:if test="${!empty error}"><%--判空--%>
<font color="red"><c:out value="${error}"/></font>
</c:if>
<form action="<c:url value="/loginCheck.html"/>" method="post">
用户名:
<input type="text" name="userName"/>
<br/>
密码:
<input type="password" name="password">
<br/>
<input type="submit" value="登录"/>
<input type="reset" value="重置"/>
</form>
</body>
</html>
main.jsp
<%--
Created by IntelliJ IDEA.
User: yan
Date: 2018/10/31
Time: 22:19
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" >
<title>小春论坛</title>
</head>
<body>
${user.userName},欢迎进入小春论坛,当前积分为${user.credits};
</body>
</html>
代码的逻辑关系
首先用户访问login.jsp,返回登录页面
用户输入用户名密码,Spring根据配置调用LoginController控制器接受请求并交给后台处理
LoginController调用UserService的hasMatchUser(),其再调用UserDao的getMatchCount()方法查询数据库中是否存在该用户/密码对
如果不存在则重定向回login.jsp并用ModelAndView封装错误参数键值对给login.jsp以提示
如果存在则LoginController调用UserService的findUserByUserName()方法,取得登录用户信息,并将用户信息封装到session中
LoginController调用 UserService的loginSuccess()方法,进行前面所述的登录成功业务处理
后台处理完毕,重定向到欢迎页面main.jsp
运行展示
根据下面的配置信息得到项目访问路径:
maven中jetty配置:
Spring MVC中LoginController配置
所以正确的访问路径为:
http://localhost:8000/bbs/index.html
访问效果: