完整JavaWeb项目笔记 第十一部分-登陆页设计

一 需求

  服务端设计已经准备完毕,各类数据访问接口设计都已经实现,前端的页面模板、Js模板也已就绪,剩下最后的Html设计及Js编写工作,这部分没有太多的新鲜技术,而且我个人对前端技术不是很熟练,所以剩下部分我仅针对部分Bootstrap和服务端接口设计进行介绍。

  登录页我们需要提供两个基础功能,登陆和注册,并且通过服务端进行账户验证。这里我偷了个懒,登陆和注册用同一个页面,仅提供两个不同的按钮来做功能上的区分。

  用户注册时可以选填一个昵称,用以登陆后做显示用的用户名,登陆时不需要填写。

  注册时需要账号格式为手机号码,以后有时间我会再提供一个验证码功能,正确填写验证图片中的字符才能获取手机验证码,并且仅手机验证码通过校验才能注册成功,手机验证码通过第三方短信服务实现。这里用到手机号,各位懂得。

  注册时密码格式暂不做太多限制,只要不为空就好,各位可按自己的口味酌情处理。

  登陆时要求账户(手机号码)及密码正确,登陆成功后服务端对用户会话状态进行保存,如果是移动端APP登陆的话有效会话时间为30天,主要不主动退出则会话状态在每次登陆应用时更新,这些都是后话了。

二 页头设计

  第一部分做一个通用的页头,这里使用Bootstrap提供的一个布局组件PageHeader来实现,如下:

<!-- 页头 -->
	<div>
		<div class="container">
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
					<div class="page-header">
						<h1>
							简字 <small>简单文字,你的心声</small>
						</h1>
					</div>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</div>

  注意上述代码中的container,因为我们的整体布局是通过Bootstrap的栅格系统实现的,只有将内容置入container内,每行内容才能获得适当的缩放。

  另外,clearfix样式是为了消除在小屏幕浏览时网格错乱而设置的,读者可自行调试下不加入clearfix的缩放区别。

  最后每一列的宽占比通过col-*-*来实现不同屏幕尺寸设备的分辨率来实现不同的展示效果,这部分知识如果读者不是很清楚可以自行百度。

三 主内容区设计

  我习惯把网页除开页头页尾侧边栏等部分,剩余的主体显示区域叫做主内容区。

  主内容区是一个简单的表单,三行输入框,两个功能按钮,先上代码:

<!-- 主内容区 -->
	<div>
		<div class="container">
			<!--内容-->
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
                    <form class="form-horizontal">
                        <div class="form-group">
                            <input type="tel" class="form-control" id="inputPhone" placeholder="手机号码(必输)">
                        </div>
                        <div class="form-group">
                            <input type="password" class="form-control" id="inputPassword" placeholder="密码(必输)">
                        </div>
                        <div class="form-group">
                            <input type="text" class="form-control" id="inputNickname" placeholder="昵称(选输)">
                        </div>
                        <div class="form-group">
                            <button type="button" class="btn btn-default" onclick="LoginPageMVC.Controller.login()">登录</button>
                            <button type="button" class="btn btn-default" onclick="LoginPageMVC.Controller.register()">注册</button>
                        </div>
                    </form>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</div>

  整个表单我采用Bootstrap的一个css组件form-horizontal,这是一个水平排列的表单,使用很简单,在父元素上加入form-horizontal样式,把表单组件加入一个带有form-group样式的div中即可,所有的表单控件需要指定样式form-control。

  另外有一点小细节,每一个input的有一个属性叫placeholder,它会以灰底颜色显示提示信息,账户密码的必输提示我没有通过Js的表单验证来处理,一个是自己懒,一个是服务端做了处理,虽然消耗了服务端资源,但是对于这样一个Demo项目来说无可厚非,读者如果有兴趣可以自己尝试一下表单验证,Jquery也提供了很多这方面的插件。

  最后注意一下两个功能按钮的onclick事件,它们直接调用Login.js提供的方法,这部分实现后文介绍。

四 页尾设计

  页尾是固定在页面底部的,不会随着屏幕滚动而发生位置的变化,为了实现这样的需求,我使用了<footer class=“navbar-fixed-bottom”>标签,如下:

<!-- 页尾 -->
	<footer class="navbar-fixed-bottom">
		<div class="container">
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
					<address contenteditable="true">
						<strong>冒泡工作室, 大福楠</strong><br /> <abbr title="Phone">Email:</abbr>
						983950935@qq.com
					</address>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</footer>

  完整的效果图参考第九部分,完整的html代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>简字-登陆</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="css/your-style.css" rel="stylesheet">
<!-- 以下两个插件用于在IE8以及以下版本浏览器支持HTML5元素和媒体查询,如果不需要用可以移除 -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
	<!-- 页头 -->
	<div>
		<div class="container">
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
					<div class="page-header">
						<h1>
							简字 <small>简单文字,你的心声</small>
						</h1>
					</div>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</div>
	<!-- 主内容区 -->
	<div>
		<div class="container">
			<!--内容-->
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
                    <form class="form-horizontal">
                        <div class="form-group">
                            <input type="tel" class="form-control" id="inputPhone" placeholder="手机号码(必输)">
                        </div>
                        <div class="form-group">
                            <input type="password" class="form-control" id="inputPassword" placeholder="密码(必输)">
                        </div>
                        <div class="form-group">
                            <input type="text" class="form-control" id="inputNickname" placeholder="昵称(选输)">
                        </div>
                        <div class="form-group">
                            <button type="button" class="btn btn-default" onclick="LoginPageMVC.Controller.login()">登录</button>
                            <button type="button" class="btn btn-default" onclick="LoginPageMVC.Controller.register()">注册</button>
                        </div>
                    </form>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</div>
	<!-- 页尾 -->
	<footer class="navbar-fixed-bottom">
		<div class="container">
			<div class="row clearfix">
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
				<div class="col-sm-12 col-md-10 col-lg-8">
					<address contenteditable="true">
						<strong>冒泡工作室, 大福楠</strong><br /> <abbr title="Phone">Email:</abbr>
						983950935@qq.com
					</address>
				</div>
				<div class="col-sm-0 col-md-1 col-lg-2"></div>
			</div>
		</div>
	</footer>
	<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
	<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
	<script src="js/login.js"></script>
</body>
</html>

五 Js文件引用

  我在整个html页得最后引入需要使用Bootstrap的js文件,以及登录页功能实现的js,那么为什么在页面的最后引入,这里涉及到一个看似很常见但是没多少人能说的清楚的问题——性能分析。

  如果读者有兴趣了解前端性能知识,我贴一个链接大家自己看看,放这里细说不合适:

https://blog.csdn.net/ywb201314/article/details/53170298

六 Login.js的实现

  第十部分我贴了自己常写的Js模式,模板以登录页的Js为例,第十部分还没有实现任何逻辑,这部分逐步的将其补充完整。

  其实Js并没有什么很特殊的地方,更多的是Ajax的使用,因为页面没有数据是不完整的,而数据处理都是通过Ajax来和服务端进行交互实现的,所以这部分我主要将请求及应答数据的处理进行一个很简略的介绍。

  以用户注册功能举例:

$(function () {
    LoginPage.initial();
});

var LoginPage = {
    initial: function () {
        LoginPageMVC.View.initial();
    },
    URLs: {
        base: 'http://localhost:8080/JianZi',
        login: {
            // TODO
        },
        register: {
            url: LoginPage.URLs.base + '/user',
            method: 'POST',
            params: {
                'action': 'register',
                'phone': '',
                'password': '',
                'nickname': ''
            }
        }
    }
};

var LoginPageMVC = {
    Model: {
        user: {
            'phone': '',
            'password': '',
            'nickname': '',
            'token': ''
        }
    },
    View: {
        initial: function () {
            $("#inputPhone").val(LoginPageMVC.Model.user.phone);
            $("#inputPassword").val(LoginPageMVC.Model.user.password);
            $("#inputNickname").val(LoginPageMVC.Model.user.nickname);
        },
        refresh: function () {
			// TODO
        }
    },
    Controller: {
        login: function () {
            // TODO
        },
        register: function () {
            LoginPageMVC.Model.user.phone = $("#inputPhone").val();
            LoginPageMVC.Model.user.password = $("#inputPassword").val();
            LoginPageMVC.Model.user.nickname = $("#inputNickname").val();

            LoginPage.URLs.register.params.phone = LoginPageMVC.Model.user.phone;
            LoginPage.URLs.register.params.password = LoginPageMVC.Model.user.password;
            LoginPage.URLs.register.params.nickname = LoginPageMVC.Model.user.nickname;

            $.ajax({
                async: false,
                type: LoginPage.URLs.register.method,
                url: LoginPage.URLs.register.url,
                data: LoginPage.URLs.register.params,
                dataType: 'jsonp',
                success: function (result) {
                    if (result.code != '000000') {
                        alert(result.message);
                        LoginPageMVC.View.initial();
                    } else {
                        alert("注册成功");
                    }
                },
                error: function () {
                    alert("注册请求发送失败");
                }
            });
        }
    }
};

  初始化页面的时候,将model绑定在表单上,而后就是注册方法LoginPage.Controller.register()的实现,一个很简单的Ajax,以Jsonp方式请求,应答则是前半部分的服务端设计中提到的IResponse对象的JSON序列化格式,如果code值为000000则说明请求成功,否则请求失败,如果因网络问题或者其他情况导致的请求发送失败,则通过绑定的error回调函数弹出提示框,提示用户请求发送失败。

七 服务端对请求的处理

  服务端接收到请求后,首先根据请求Url的Servlet映射值确定具体的请求处理Servlet实例,然后通过参数action的值来确定处理该请求的方法:

<servlet>
	<servlet-name>user</servlet-name>
	<servlet-class>com.bubbling.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>user</servlet-name>
	<url-pattern>/user</url-pattern>
</servlet-mapping>
public class UserServlet extends IServlet {

	private static final long serialVersionUID = 1L;
	private static UserService service = UserService.getService();

	@Override
	protected Map<String, String> getMethodMap() {
		return new HashMap<String, String>() {

			private static final long serialVersionUID = 1L;

			{
				put("register", "register");
				put("login", "login");
			}
		};
	}

	/**
	 * 用户注册请求
	 * <p>
	 * action:register
	 * <p>
	 * 参数:phone(必填)、password(必填)、nickname(选填)
	 */
	public void register() {
		try {
			if (service.register(getParam("phone"), getParam("password"),
					getParam("nickname"))) {
				setWebResponse(Constant.STR_ERROR_CODE_SUCCESS);
			} else {
				setWebResponse(Constant.STR_ERROR_CODE_REGISTER_FAILUER);
			}
		} catch (ServiceException e) {
			setWebResponse(e.getCode());
		}
	}
	……
}

  UserServlet.register()方法通过UserService来处理注册相关的业务逻辑,其中getParam()方法被封装在IServlet中,作为所有IServlet派生类公用的方法,这部分可以查阅之前服务端设计部分。

  这里我对所有请求处理的结果做了一个code码整理,其静态值保存在Constant类中,这部分设计在前面的章节也有提到。并且所有业务层的设计都是通过抛出ServiceException来作为业务处理结果的,其设计如下:

package com.bubbling.common;

/**
 * 公用服务异常,仅供业务层处理使用 <br>
 * 当业务逻辑处理失败,包括参数校验失败、数据访问失败、文件操作失败等各种场景下抛出 <br>
 * 该异常在应答前结束作用域,对应答拼装提供错误码及错误信息
 * 
 * @author 胡楠
 *
 */
public class ServiceException extends Exception
{

	private static final long serialVersionUID = 1L;
	private String code;
	private String info;

	public ServiceException(String code)
	{
		this(code, null);
	}

	public ServiceException(String code, String info)
	{
		this.code = code;
		this.info = info;
	}

	public String getCode()
	{
		return code;
	}

	public void setCode(String code)
	{
		this.code = code;
	}

	public String getInfo()
	{
		return info;
	}

	public void setInfo(String info)
	{
		this.info = info;
	}

	@Override
	public String getMessage()
	{
		return toString();
	}

	@Override
	public String toString()
	{
		return "ServiceException [code=" + code + ", info=" + info + "]";
	}

}

  如果Service处理失败,则抛出ServiceException,异常中包括了错误码code值,和错误信息info值,这样Servlet在得到异常后会根据code值在Constant类中取得相应的应答数据,然后拼装IReponse对象,并响应客户端请求:

/**
 * @author 胡楠
 * 
 *         所有Servlet均需要自该类派生,并且需实现处理请求映射的获取方法,派生类的所有方法访问类型按规范必须声明为public,
 *         且返回值为void
 *
 */
public abstract class IServlet extends HttpServlet
{
	……
	/**
	 * @return kEY值为请求参数action值,VALUE值为处理该请求的方法名
	 */
	protected abstract Map<String, String> getMethodMap();
	……
	private void process() throws IOException
	{
		……
		try
		{
			processAction();
		}
		catch (Throwable t)
		{
			result.setCode(Constant.STR_ERROR_CODE_SYSTEM_ERROR);
			result.setMessage(Constant.MAP_ERROR.get(Constant.STR_ERROR_CODE_SYSTEM_ERROR));
		}
		……
		try
		{
			PrintWriter out = response.getWriter();
			……
			out.flush();
			out.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	……

  而Service层不仅需要处理请求参数的校验,还需要通过Dao层与数据库进行交互,如果用户注册成功,需要向用户表插入一条记录,并且向用户会话表插入一条记录,用户会话记录则随着每次用户的登陆、登出等操作进行更新操作:

public boolean register(String phone, String password, String nickname) throws ServiceException {
	verifyPhone(phone);
	verifyPassword(password);
	if (StringUtil.isEmpty(nickname)) {
		nickname = StringUtil.getUUID().substring(0, 6);
	}
	User user = new User();
	user.setPhone(phone);
	user.setPassword(password);
	user.setNickname(nickname);
	return dao.add(user);
}

  这里的verifyPhone()方法不仅需要校验手机号是否为空,还需要对号码格式进行验证,通过简单的正则判断可以实现,这些公用的校验方法我们额外进行封装,以便于重用:

private void verifyPhone(String phone) throws ServiceException {
	if (StringUtil.isEmpty(phone)) {
		throw new ServiceException(Constant.STR_ERROR_CODE_PHONE_EMPTY);
	}
	if (!RegexUtil.IsCellphone(phone)) {
		throw new ServiceException(Constant.STR_ERROR_CODE_PHONE_FORMAT);
	}
}

public static boolean isEmpty(String value) {
	return value == null ? true : "".equals(value.trim());
}

/**
 * 验证手机号码
 * 
 * @param str
 * @return
 */
public static boolean IsCellphone(String str) {
	String regex = "^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(166)|(17[0,1,3,5,6,7,8])|(18[0-9])|(19[8|9]))\\d{8}$";
	return match(regex, str);
}

  最后UserService通过UserDao来进行数据库交互,UserDao本身是一个接口定义,在UserService中被UserDaoMysqlImpl实例化,UserDaoMysqlImpl中实现了UserDao的所定义的数据交互方法,这里不细细列举了,因为都是简单的JDBC操作,个人认为将前后端的逻辑串联起来是最重要的,其实现细节毫无意义。

八 结语

  到此为止,我从服务端一路高歌猛进到前端设计,将整个设计流程通过登录页这个案例串联了起来,再往后我觉得已经没有进行介绍的意义了,因为都是重复性的东西。

  这里做一个设计的总结:

  1. 数据模型设计
    数据模型设计是一个项目的根基,所有程序设计都应该是围绕数据模型而展开,没有数据模型则没有产品概念,所有的产品设计最终都是为了那些存于文件、存于数据库中的记录。数据模型设计可通过ER模型图或者其他工具设计,表结构设计时需要对字段类型、存储方式进行预先优化设计,尤其是热点表(经常被访问的数据)、热点数据,如何设置其索引、唯一键等很关键,这对服务端的性能而言极其重要。

  2. 服务端结构设计
    在进行服务设计之初需要大量的基础设施设计,包括共有处理部分(各工具类),或是使用第三方Web框架,或是想我一样自己编写一个简单的Web框架,核心都在于如何对设计进行层次化处理,良好的层次结构对于代码的维护、延展至关重要。服务端设计主要考虑实现如何拆分请求处理、业务处理以及数据交互,所有的实现尽量以接口的方式设计,这是模式化设计的基础。

  3. 前端设计
    前端设计其实是最复杂的,因为这是产品的门面,整体风格、样式这些偏产品设计的理念不细讲,站在程序员的角度来说,仅仅关心如何将请求发送至服务端,如何根据服务端的应答数据来更新页面。

  从前端到服务端的一个完整处理思路进行一个整理:

  1. 客户端初始化Html页
  2. 通过Ajax向服务端发起请求
  3. 服务端通过Servlet处理请求,并通过业务层进行请求的业务逻辑处理
  4. 业务层对请求进行业务相关处理,包括参数校验、业务逻辑实现,并通过数据访问层与数据库进行交互
  5. 数据访问层与DB、文件进行交互,并返回交互结果
  6. 业务层根据处理结果向Servlet汇报应答数据
  7. Servlet将应答数据进行拼装,并响应给客户端
  8. 客户端获取Ajax应答数据将其绑定到页面元素中,进行页面的绘制更新

  整个Demo从设计到实现我花了将近两周,因为平时工作的原因,只能周末找点时间做,勉强及格,虽然只有很简单的一些功能,但是对于一个常规Web项目来说,应有的设计也没差很多,当然达到上线产品级是远远不够的。

  一共十一部分从服务端到前端,实现的代码我并没有贴上很多,因为确实没有什么技术含量。

  后面如果有时间我会慢慢完善它,争取能让它早日部署在阿里的云服务器上。大吼一声:完结!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬睡客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值