src/
├── main/
│ ├── java/
│ │ └── com.springmvc/
│ │ ├── action/ # 控制層
│ │ ├── dao/ # 數據訪問接口
│ │ ├── model/ # 實體類
│ │ ├── service/ # 業務接口
│ │ └── service/impl/# 業務實現
│ ├── resources/
│ │ ├── spring-mybatis/ # Spring+MyBatis 配置
│ │ │ └── spring-mybatis-config.xml
│ │ └── com/springmvc/sqlmap/ # MyBatis XML 映射
│ │ └── UserEntity.xml
│ └── webapp/
│ ├── WEB-INF/
│ │ ├── view/ # JSP 視圖
│ │ │ ├── login.jsp
│ │ │ └── index.jsp
│ │ ├── web.xml
│ │ └── dispatcher-servlet.xml
│ └── static/ # 靜態資源
│ ├── bootstrap/
│ ├── jquery/
│ └── js/
└── test/

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="https://www.springframework.org/schema/aop"
xmlns:tx="https://www.springframework.org/schema/tx"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="...省略...">
<!-- 組件掃描 -->
<context:component-scan base-package="com.springmvc.dao"/>
<context:component-scan base-package="com.springmvc.service"/>
<!-- 屬性佔位符 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<!-- 數據源 (DBCP) -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="maxActive" value="100"/>
<property name="maxIdle" value="30"/>
<property name="maxWait" value="500"/>
</bean>
<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:com/springmvc/sqlmap/*.xml"/>
</bean>
<!-- SqlSessionTemplate -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!-- 事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- AOP 事務切面 (Service 層) -->
<aop:config>
<aop:advisor pointcut="execution(* com.springmvc..service..*.*(..))" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- Mapper 接口自動代理 -->
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.springmvc.dao.UserDao"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
package com.springmvc.model;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -8772180261203847538L;
private Long id;
private String name;
private String email;
private String isEmail; // '1' 已驗證
private String password;
private String isDelete; // '0' 正常
// Getter/Setter 省略...
}
package com.springmvc.dao;
import java.util.List;
import com.springmvc.model.User;
public interface UserDao {
List<User> getUser(User user);
}
// UserService.java
package com.springmvc.service;
import java.util.List;
import com.springmvc.model.User;
public interface UserService {
List<User> getUser(User user);
}
// UserServiceImpl.java
package com.springmvc.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springmvc.dao.UserDao;
import com.springmvc.model.User;
import com.springmvc.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired private UserDao userDao;
@Override public List<User> getUser(User user) { return userDao.getUser(user); }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springmvc.dao.UserDao">
<resultMap id="rm_user" type="com.springmvc.model.User">
<result property="id" column="id" jdbcType="NUMERIC"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="isEmail" column="isEmail" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="isDelete" column="isDelete" jdbcType="VARCHAR"/>
</resultMap>
<select id="getUser" resultMap="rm_user" parameterType="com.springmvc.model.User">
select * from user
<where>
1=1
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="password != null and password != ''">
and password = #{password}
</if>
and isEmail = '1'
and isDelete = '0'
</where>
</select>
</mapper>
<web-app version="3.0" ...>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-mybatis/spring-mybatis-config.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 編碼過濾器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param>
<init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param>
</filter>
<filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
<!-- DispatcherServlet -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<welcome-file-list><welcome-file>index.html</welcome-file></welcome-file-list>
</web-app>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:mvc="https://www.springframework.org/schema/mvc"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="...省略...">
<context:annotation-config/>
<context:component-scan base-package="com.springmvc.action"/>
<!-- JSON 轉換 -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>json=application/json html=text/html</value>
</property>
</bean>
<!-- 視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
<%@ page pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<% String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/"; %>
<html>
<head>
<jsp:include page="/WEB-INF/util/bootstrap.jsp"/>
<script src="<%=basePath%>static/view/js/login.js"></script>
</head>
<body>
<div class="tab-content" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:300px;">
<ul class="nav nav-tabs">
<li class="active"><a href="#login" data-toggle="tab">登錄</a></li>
<li><a href="#register" data-toggle="tab">注冊</a></li>
</ul>
<form class="tab-pane fade in active" id="login" onsubmit="return false;">
<c:if test="${not empty error}"><div class="alert alert-danger"><c:out value="${error}"/></div></c:if>
<input id="login-email" name="email" class="form-control login-input" placeholder="郵箱">
<input type="password" name="password" id="login-password" class="form-control login-input" placeholder="密碼">
<div class="checkbox"><label><input type="checkbox" id="login-remember"> 記住我</label></div>
<button class="btn btn-lg btn-primary btn-block" onclick="loginFunction.loginSubmit();">登錄</button>
</form>
<form class="tab-pane fade" id="register" onsubmit="return false;">...</form>
</div>
</body></html>
var loginFunction = {
loginSubmit: function() {
var email = $("#login-email").val();
var password = $("#login-password").val();
if (!email) { alert("請輸入郵箱"); return; }
if (!password) { alert("請輸入密碼"); return; }
$.ajax({
url: basePath + "101/loginCheck.htm",
type: "post",
data: {email: email, password: password}, // 實際應加密傳輸
dataType: "json",
success: function(data) {
if (data.status == "success")
window.location.href = basePath + "101/index.htm";
else
alert("用戶名或密碼錯誤");
}
});
}
};
$(function() {});
package com.springmvc.action;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import com.springmvc.model.User;
import com.springmvc.service.UserService;
@Controller
@RequestMapping("/101")
public class IndexAction {
@Autowired private UserService userService;
@RequestMapping("/index.htm")
public String index() { return "index"; }
@RequestMapping("/login.htm")
public String login() { return "login"; }
@RequestMapping(value="/loginCheck.htm", method=RequestMethod.POST)
@ResponseBody
public Map<String,Object> loginCheck(User user) {
List<User> list = userService.getUser(user);
Map<String,Object> map = new HashMap<>();
if (list != null && !list.isEmpty()) {
map.put("status", "success");
map.put("message", "驗證成功");
// 實際應:生成 Token、存 Session/Redis、密碼 BCrypt 驗證
}
return map;
}
}
https://localhost:8080/項目名/101/login.htm
@Valid + BindingResult@ControllerAdvice + @ExceptionHandler<mvc:resources mapping="/static/**" location="/static/"/>