版权提醒
本系列文章为原创内容。转载请注明文章来源。
前言
本文为搭建 SSSP 框架极简书店教程系列的其中一篇。本文主要内容:实现管理员登录功能。
因为作者写教程的初衷是为了写作业,而作业给出的数据库设计中没有管理员用户表,所以这里的登录功能只有一个管理员用户,使用字符串相同的方式验证。也正因如此,这个功能很适合在还没有讲 JPA 的时候写。
创建管理员登录相关 JSP 文件
在 web 目录下建立 admin 目录,在 admin 目录下创建 admin_login.jsp 和 admin_welcome.jsp 。
admin_login.jsp
这个文件是管理员登录页面。使用 required
属性使用户必须输入内容才能提交。
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<title>管理员登录</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<style type="text/css">
/*垂直居中,div上边界距离窗口上边的距离为窗口高度的50%,
实际上此时div内容整体已经偏下,再把整个身子往上移动一半即可
并针对不同浏览器进行兼容。
*/
.col-center-block {
position: absolute;
top: 50%;
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
-ms-transform: translateY(-50%);
-o-transform: translateY(-50%);
transform: translateY(-50%);
}
</style>
</head>
<body>
<!-- 在外层添加一个div,把行内容居中,添加.row .justify-content-center -->
<div class="row justify-content-center ">
<div class="col-sm-3 card border-dark card text-center col-center-block shadow-lg" style="
padding-right: 0px;
padding-left: 0px;
min-width: 20rem;">
<div class="card-header">管理员登录</div>
<div class="card-body text-dark">
<form action="toLogin.action" method="post">
<div class="form-group">
<s:fielderror fieldName="loginFall"/>
</div>
<div class="form-group">
<label for="userName" class="sr-only">Email address</label>
<input type="text" class="form-control" id="userName" name="userName" placeholder="管理员账号" required autofocus>
</div>
<div class="form-group">
<label for="password" class="sr-only">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="密码" required>
</div>
<button type="submit" class="btn btn-primary btn-block">登录</button>
</form>
</div>
</div>
</div>
</body>
</html>
admin_welcome.jsp
这个文件是管理员登录成功后的欢迎页面。因为后台所有页面的风格都是一致的,所以为了便于维护和使用,将相同的部分进行了提取,使用时通过 <%@include file=""%>
标签导入。
此文件同时演示了 jsp 读取 Session 的方法之一。使用 ${sessionScope.get("adminName")}
读取 Session 中保存的管理员账户名。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!doctype html>
<html lang="zh-cmn-Hans">
<head>
<title>后台首页</title>
<!-- 引入CSS样式和JS脚本文件等-->
<%@include file="common/admin_headContext.jsp"%>
</head>
<body>
<!-- 引入顶部导航条-->
<%@include file="common/admin_common_top.jsp"%>
<div class="container-fluid">
<div class="row">
<!-- 引入左侧导航栏-->
<%@include file="common/admin_common_left.jsp"%>
<!-- 页面主体 -->
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<h1>欢迎进入后台管理,${sessionScope.get("adminName")}!</h1>
</main>
</div>
</div>
<!-- 设置左侧导航栏为正确选中项-->
<script type="text/javascript">
$(document).ready(function(){
$("#welcome").addClass("active");
});
</script>
</body>
</html>
共用文件
在 admin 目录下建立 common 目录,建立以下文件:
admin_headContext.jsp
此文件存放除标题外所有 <head>
标签中的内容。
${pageContext.request.contextPath}
用于获取网站根目录,对应 web 目录。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<!-- Custom styles for this template -->
<link href="${pageContext.request.contextPath}/css/dashboard.css" rel="stylesheet">
<style>
.bd-placeholder-img {
font-size: 1.125rem;
text-anchor: middle;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@media (min-width: 768px) {
.bd-placeholder-img-lg {
font-size: 3.5rem;
}
}
</style>
admin_common_top.jsp
此文件是后台页面顶部导航栏。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 顶部 -->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="${pageContext.request.contextPath}/admin/index.action">Bookstore后台管理</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse" data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="${pageContext.request.contextPath}/admin/index!logout.action">退出登录</a>
</li>
</ul>
</nav>
admin_common_left.jsp
此文件对应后台页面左侧导航栏。
每个导航项都有一个 id
属性,用于被对应的主页面的 js 代码识别,然后添加高亮效果。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 左侧 -->
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<!-- 左侧导航列表 -->
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" id="welcome" href="${pageContext.request.contextPath}/admin/index.action">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-house-door" viewBox="0 0 16 16">
<path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z"/>
</svg>
欢迎界面
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="order" href="${pageContext.request.contextPath}/admin/index!toOrderManage.action">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-columns" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M0 .5A.5.5 0 0 1 .5 0h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 0 .5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2A.5.5 0 0 1 .5 2h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2A.5.5 0 0 1 .5 4h10a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2A.5.5 0 0 1 .5 6h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2A.5.5 0 0 1 .5 8h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Zm-13 2a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5Zm13 0a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5Z"/>
</svg>
订单管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="customer" href="${pageContext.request.contextPath}/admin/index!toCustomerManage.action">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16">
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
</svg>
客户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="book" href="${pageContext.request.contextPath}/admin/index!toBookManage.action">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stack" viewBox="0 0 16 16">
<path d="m14.12 10.163 1.715.858c.22.11.22.424 0 .534L8.267 15.34a.598.598 0 0 1-.534 0L.165 11.555a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.66zM7.733.063a.598.598 0 0 1 .534 0l7.568 3.784a.3.3 0 0 1 0 .535L8.267 8.165a.598.598 0 0 1-.534 0L.165 4.382a.299.299 0 0 1 0-.535L7.733.063z"/>
<path d="m14.12 6.576 1.715.858c.22.11.22.424 0 .534l-7.568 3.784a.598.598 0 0 1-.534 0L.165 7.968a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.659z"/>
</svg>
书籍管理
</a>
</li>
</ul>
</div>
</nav>
dashboard.css
为了便于管理, css 文件应放在 web/css 目录下。
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar .nav-link.active {
color: #007bff;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
创建用于管理员登录的 Action
在 Action 包中创建 AdminLogin.java 。
package com.chiyu.action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import java.util.Map;
//使用注释标志此类是一个控制器Bean,类型为prototype,原型,与单例模式相反。Bean的id默认为类首字母小写。
@Controller
@Scope("prototype")
public class AdminLogin extends ActionSupport {
//使用属性封装获取用户登录时输入的用户名和密码。
private String userName;
private String password;
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;
}
//验证用户输入的用户名与密码是否正确,不正确将返回input并返回信息:用户名或密码错误!
@Override
public void validate() {
if (!(this.userName.equals("admin") && this.password.equals("123456"))){
addFieldError("loginFall","用户名或密码错误!");
}
}
//因为有前置的数据校验,到达此方法时,用户名与密码是正确的,此方法将保存“此用户是管理员”和“管理员账户名”到Session
@Override
public String execute() throws Exception {
Map<String, Object> session = ActionContext.getContext().getSession();
session.put("isAdmin",true);
session.put("adminName",this.userName);
return SUCCESS;
}
}
创建用于后台中控的 Action
在 Action 包中创建 BackgroundDispatcher.java 。此 Action 主要功能是对应导航栏跳转到对应页面以及管理员退出登录。
package com.chiyu.action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import java.util.Map;
@Controller
@Scope("prototype")
public class BackgroundDispatcher extends ActionSupport {
//先确认当前用户的管理员身份是否有效。
@Override
public void validate() {
Map<String, Object> session = ActionContext.getContext().getSession();
if (session.get("isAdmin") == null){
addFieldError("loginFall","请先登录!");
}
}
//默认方法,到达后台首页。
@Override
public String execute() throws Exception {
return SUCCESS;
}
//退出登录方法。清空Session。
public String logout(){
Map<String, Object> session = ActionContext.getContext().getSession();
session.clear();
return LOGIN;
}
}
配置后台 Struts 配置文件
修改 background.xml 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<package name="background" namespace="/admin" extends="struts-default" strict-method-invocation="false">
<action name="index" class="backgroundDispatcher">
<result name="input">/admin/admin_login.jsp</result>
<result name="success">/admin/admin_welcome.jsp</result>
<result name="login">/admin/admin_login.jsp</result>
</action>
<action name="toLogin" class="adminLogin">
<result name="success" type="redirectAction">index.action</result>
<result name="input">/admin/admin_login.jsp</result>
</action>
</package>
</struts>
稍微解释一下此配置文件
package 中的name
属性用于唯一标识 package,namespace="/admin"
表示访问此包中的 Action 应用类似这样的地址:http://localhost:8000/admin/index.action
,strict-method-invocation="false"
,不使用严格的方法调用,为了方便的使用动态方法调用。
action 中的class
属性值是 Spring Bean ,由 Spring 扫描对应注释得到。
完成
到此,你已经完成管理员登录后进入后台的功能以及退出登录的功能。现在访问:http://localhost:8000/admin/index.action 试试吧!