1.界面
使用了FreeMarker+jQuery form+Bootstrap,完成异步提交表单登录,出错后异步提示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content= "width=device-width, initial-scale=1.0">
<meta name="description" content= "">
<meta name="author" content= "Mosaddek">
<meta name="keyword"
content= "FlatLab, Dashboard, Bootstrap, Admin, Template, Theme, Responsive, Fluid, Retina">
<title>师说CMS 后台</title>
<!-- Bootstrap core CSS -->
<link href=" ${BASE_PATH}/static/manage/css/bootstrap.min.css"
rel=" stylesheet">
<link href=" ${BASE_PATH}/static/manage/css/bootstrap-reset.css"
rel=" stylesheet">
<!--external css-->
<link
href=" ${BASE_PATH}/static/manage/assets/font-awesome/css/font-awesome.css"
rel=" stylesheet" />
<!-- Custom styles for this template -->
<link href=" ${BASE_PATH}/static/manage/css/style.css" rel="stylesheet" >
<link href=" ${BASE_PATH}/static/manage/css/style-responsive.css"
rel=" stylesheet" />
<!-- HTML5 shim and Respond.js IE8 support of HTML5 tooltipss and media queries -->
<!--[if lt IE 9]>
<script src="${BASE_PATH}/static/manage/js/html5shiv.js"></script>
<script src="${BASE_PATH}/static/manage/js/respond.min.js"></script>
<![endif]-->
<script src=" ${BASE_PATH}/static/manage/js/jquery.js" ></script>
<script src="${BASE_PATH} /static/manage/js /jquery.form.min.js"></script>
<style type="text/css" >
p.error {
color: #DE5959;
}
.form-signin input[type="text"].error, .form- signin input[type="password"].error
{
border-color: #b94a48;
color: #b94a48;
- webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
input.error:focus {
border-color: #953b39;
color: #b94a48;
- webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px
#d59392;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
}
</style>
</head>
<body class="login-body">
<div class="container" >
<form class="form-signin" id="adminForm"
action= "${BASE_PATH} /admin/login.json" autocomplete="off"
method= "post">
<h2 class="form-signin-heading" >
<img src="${TEMPLATE_BASE_PATH} /images/logo.png"
style= "height: 38px;" />
</h2>
<div class="login-wrap">
<div class="form-group">
<label for="exampleInputEmail1">用户名 </label>
<input type="text" name="name" class="form-control" placeholder="用户名" value= "" style="*width: 250px;" autofocus>
</div>
<div class="form-group">
<label for="exampleInputEmail1">密码 </label>
<input type="password" name="password" class="form-control" placeholder="密码" value= "" style="*width: 250px;">
</div>
<div class="form-group">
<input type="text" name= "captcha " class="form-control"
placeholder= "验证码" style="width: 100px; float: left;" > <img
id= "captcha "
style= "cursor: pointer; cursor: hand; margin-top: -13px;"
οnclick="this.src='${BASE_PATH} /admin/captcha.htm?'+Math.random();"
src=" ${BASE_PATH}/admin/captcha.htm" >
</div>
<div class="clearfix" ></div>
<div>
<p class="error" for= "captcha " style="display: none;"></p>
</div>
<button class="btn btn-lg btn-login btn-block" type="submit" >登录 </button>
</div>
</form>
</div>
<script type="text/ javascript">
/**
* 显示表单的错误提示
* @param id 表单ID
* @param errors 错误列表
*/
function showErrors(id, errors) {
id.find('p[class=error]').hide();
id.find('input,select').removeClass("error");
for ( var name in errors) {
var e = id.find('p[for=' + name + ']');
id.find('input[name=' + name + '],select[name=' + name + ']')
.addClass("error");
if (e.length == 0) {
id.find(
'input[name=' + name + '],select[name=' + name
+ ']').after(
' <p for="'+name+'" class="error"></p>');
e = id.find('p[for=' + name + ']');
}
if (errors[name] != "") {
e.html(errors[name]);
e.show();
}
}
}
$(function() {
$('#adminForm')
.ajaxForm(
{
dataType : 'json',
success : function(data) {
if (data.result) {
location.href = "${BASE_PATH}/manage/article/list.htm";
} else {
showErrors($('#adminForm'), data.errors);
if (data.msg == "change_captcha") {
$('# captcha'). attr(
" src",
" ${BASE_PATH}/admin/captcha.htm?"
+ Math.random());
$(
'#adminForm input[name="captcha"]')
.val('');
}
}
}
});
});
</script>
</body>
</html>
2.后台Controller
界面与后台的交互,异步处理方法
@ResponseBody
@RequestMapping(value = "/login.json", method = RequestMethod.POST)
public JsonVo<String> adminLogin( @RequestParam(value = "name") String name,
@RequestParam(value = "password") String password,
@RequestParam(value = "captcha") String captcha,
HttpServletRequest request, ModelMap modelMap) {
JsonVo<String> json = new JsonVo<String>();
try {
String kaptcha = (String) request.getSession().getAttribute(
com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY);
if (StringUtils. isBlank(password)) {
json.getErrors().put( "password", "密码不能为空" );
} else if (password.length() < 6 || password.length() > 30) {
json.getErrors().put( "password", "密码最少6个字符,最多30个字符" );
}
// 校验验证码
if (StringUtils. isNotBlank(kaptcha)
&& kaptcha.equalsIgnoreCase(captcha)) {
} else {
json.getErrors().put( "captcha", "验证码错误" );
}
json.check();
adminService.adminLogin(name, password, request);
} catch (Exception e) {
// 异常,重置验证码
request.getSession().removeAttribute(
com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY);
json.setResult( false);
json.getErrors().put( "password", "邮箱或密码错误" );
json.setMsg( "change_captcha");
}
return json;
}
/**
* 生成验证码
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "captcha.htm", method = RequestMethod.GET)
public void captcha(HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setDateHeader( "Expires", 0);
response.setHeader( "Cache-Control",
"no-store, no-cache, must-revalidate");
response.addHeader( "Cache-Control", "post-check=0, pre-check=0");
response.setHeader( "Pragma", "no-cache");
response.setContentType( "image/jpeg");
String capText = captchaProducer.createText();
request.getSession().setAttribute(
com.google.code.kaptcha.Constants. KAPTCHA_SESSION_KEY, capText);
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO. write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
}
3.后台逻辑与验证
/**
* 管理员登陆
*
* @param email
* @param password
* @param request
* @throws IOException
*/
public void adminLogin(String name, String password,
HttpServletRequest request) throws AuthException,
IOException {
AdminVo admin = adminDao.getAdminByName(name);
if (admin == null) {
throw new AuthException( "邮箱或密码错误" );
}
String loginPassword = AuthUtils.getPassword(password);
if (loginPassword.equals(admin.getPassword())) {
HttpSession session = request.getSession();
admin.setPassword( "");
if (name.equals(PropertyUtils
. getValue("shishuocms.admin"))) {
admin.setAdmin( true);
} else {
admin.setAdmin( false);
}
session.setAttribute(SystemConstant. SESSION_ADMIN,
admin);
} else {
throw new AuthException( "邮箱或密码错误" );
}
}
4.自定义的JsonVo
import java.util.HashMap;
import org.codehaus.jackson.map.annotate.JsonSerialize;
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class JsonVo<T> {
/**
* 结果
*/
private boolean result;
/**
* 成功的消息
*/
private String msg;
/**
* 具体每个输入错误的消息
*/
private HashMap<String, String> errors = new HashMap<String, String>();
/**
* 返回的数据
*/
private T t;
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public HashMap<String, String> getErrors() {
return errors;
}
public void setErrors(HashMap<String, String> errors) {
this.errors = errors;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public void check() throws ValidateException {
if (this.getErrors().size() > 0) {
this.setResult(false);
throw new ValidateException("有错误发生");
} else {
this.setResult(true);
}
}
}
5.流程总结
1.前端通过ajax方式提交表单,根据后台返回的信息,确认登录,or 显示错误提示
2.后台接收form参数后,调用Service进行处理,通过自定义类似Map的对象,将要提示的信息封装后返回给前端(包括后台验证的提示信息,与逻辑处理的提示信息)
3.Service进行逻辑处理,出错时抛出自定义异常,Controller来处理抛出的异常
4.与逻辑无关的验证码,可通过单独的Servlet来生成,然后返回给前端页面(使用Google的kaptcha.jar或自定义验证码的生成类)
自定义验证码生成
1.自定义生成验证码高级工具类 -- 可指定验证码生成的长度和生成图片的大小
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;
import javax.imageio.ImageIO;
public class VerifyCodeUtils{
//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static Random random = new Random();
/**
* 使用系统默认字符源生成验证码
* @param verifySize 验证码长度
* @return
*/
public static String generateVerifyCode(int verifySize){
return generateVerifyCode(verifySize, VERIFY_CODES);
}
/**
* 使用指定源生成验证码
* @param verifySize 验证码长度
* @param sources 验证码字符源
* @return
*/
public static String generateVerifyCode(int verifySize, String sources){
if(sources == null || sources.length() == 0){
sources = VERIFY_CODES;
}
int codesLen = sources.length();
Random rand = new Random(System.currentTimeMillis());
StringBuilder verifyCode = new StringBuilder(verifySize);
for(int i = 0; i < verifySize; i++){
verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
}
return verifyCode.toString();
}
/**
* 生成随机验证码文件,并返回验证码值
* @param w
* @param h
* @param outputFile
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, outputFile, verifyCode);
return verifyCode;
}
/**
* 输出随机验证码图片流,并返回验证码值
* @param w
* @param h
* @param os
* @param verifySize
* @return
* @throws IOException
*/
public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
String verifyCode = generateVerifyCode(verifySize);
outputImage(w, h, os, verifyCode);
return verifyCode;
}
/**
* 生成指定验证码图像文件
* @param w
* @param h
* @param outputFile
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
if(outputFile == null){
return;
}
File dir = outputFile.getParentFile();
if(!dir.exists()){
dir.mkdirs();
}
try{
outputFile.createNewFile();
FileOutputStream fos = new FileOutputStream(outputFile);
outputImage(w, h, fos, code);
fos.close();
} catch(IOException e){
throw e;
}
}
/**
* 输出指定验证码图片流
* @param w
* @param h
* @param os
* @param code
* @throws IOException
*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
int verifySize = code.length();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Random rand = new Random();
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
Color[] colors = new Color[5];
Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.YELLOW };
float[] fractions = new float[colors.length];
for(int i = 0; i < colors.length; i++){
colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
fractions[i] = rand.nextFloat();
}
Arrays.sort(fractions);
g2.setColor(Color.GRAY);// 设置边框色
g2.fillRect(0, 0, w, h);
Color c = getRandColor(200, 250);
g2.setColor(c);// 设置背景色
g2.fillRect(0, 2, w, h-4);
//绘制干扰线
Random random = new Random();
g2.setColor(getRandColor(160, 200));// 设置线条的颜色
for (int i = 0; i < 20; i++) {
int x = random.nextInt(w - 1);
int y = random.nextInt(h - 1);
int xl = random.nextInt(6) + 1;
int yl = random.nextInt(12) + 1;
g2.drawLine(x, y, x + xl + 40, y + yl + 20);
}
// 添加噪点
float yawpRate = 0.05f;// 噪声率
int area = (int) (yawpRate * w * h);
for (int i = 0; i < area; i++) {
int x = random.nextInt(w);
int y = random.nextInt(h);
int rgb = getRandomIntColor();
image.setRGB(x, y, rgb);
}
shear(g2, w, h, c);// 使图片扭曲
g2.setColor(getRandColor(100, 160));
int fontSize = h-4;
Font font = new Font("Algerian", Font.ITALIC, fontSize);
g2.setFont(font);
char[] chars = code.toCharArray();
for(int i = 0; i < verifySize; i++){
AffineTransform affine = new AffineTransform();
affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
g2.setTransform(affine);
g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
}
g2.dispose();
ImageIO.write(image, "jpg", os);
}
private static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
private static int getRandomIntColor() {
int[] rgb = getRandomRgb();
int color = 0;
for (int c : rgb) {
color = color << 8;
color = color | c;
}
return color;
}
private static int[] getRandomRgb() {
int[] rgb = new int[3];
for (int i = 0; i < 3; i++) {
rgb[i] = random.nextInt(255);
}
return rgb;
}
private static void shear(Graphics g, int w1, int h1, Color color) {
shearX(g, w1, h1, color);
shearY(g, w1, h1, color);
}
private static void shearX(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(2);
boolean borderGap = true;
int frames = 1;
int phase = random.nextInt(2);
for (int i = 0; i < h1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(0, i, w1, 1, (int) d, 0);
if (borderGap) {
g.setColor(color);
g.drawLine((int) d, i, 0, i);
g.drawLine((int) d + w1, i, w1, i);
}
}
}
private static void shearY(Graphics g, int w1, int h1, Color color) {
int period = random.nextInt(40) + 10; // 50;
boolean borderGap = true;
int frames = 20;
int phase = 7;
for (int i = 0; i < w1; i++) {
double d = (double) (period >> 1)
* Math.sin((double) i / (double) period
+ (6.2831853071795862D * (double) phase)
/ (double) frames);
g.copyArea(i, 0, 1, h1, 0, (int) d);
if (borderGap) {
g.setColor(color);
g.drawLine(i, (int) d, i, 0);
g.drawLine(i, (int) d + h1, i, h1);
}
}
}
// public static void main(String[] args) throws IOException{
// File dir = new File("F:/verifies");
// int w = 200, h = 80;
// for(int i = 0; i < 50; i++){
// String verifyCode = generateVerifyCode(4);
// File file = new File(dir, verifyCode + ".jpg");
// outputImage(w, h, file, verifyCode);
// }
// }
}
2.Controller或单独的Servlet去生成验证码
1.Controller方式
@RequestMapping(value = "getCode.htm",method=RequestMethod.GET)
public void getCode(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String code = VerifyCodeUtils.generateVerifyCode(4);
request.getSession().setAttribute("img_code", code);
VerifyCodeUtils.outputImage(96, 33, response.getOutputStream(), code);
}
2.Servlet方式,需要在web.xml中配置单独的获取url路径
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import com.alexgaoyh.admin.login.shiro.captcha.CaptchaUtil;
import com.alexgaoyh.admin.login.shiro.captcha.constant.CaptchaConstant;
/**
* 验证码servlet
* @author Administrator
*
*/
public class CaptchaServlet extends HttpServlet {
private static final Logger LOGGER = Logger.getLogger(CaptchaServlet.class);
private static final long serialVersionUID = -124247581620199710L;
//通过自定义的<span style="font-family: Arial, Helvetica, sans-serif;">VerifyCodeUtils生成并输出验证码</span>
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
<pre name="code" class="java"><pre name="code" class="java"> // 设置相应类型,告诉浏览器输出的内容为图片
resp.setContentType("image/jpeg");
// 不缓存此内容
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expire", 0);
String code = VerifyCodeUtils.generateVerifyCode(4);
req.getSession().setAttribute("img_code", code);
VerifyCodeUtils.outputImage(96, 33, resp.getOutputStream(), code);
}
//通过Google的kaptcha.jar生成验证码
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//doGet(req, resp);
<pre name="code" class="java"> // 设置相应类型,告诉浏览器输出的内容为图片
resp.setContentType("image/jpeg");
// 不缓存此内容
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expire", 0);
try {
HttpSession session = req.getSession();
CaptchaUtil tool = new CaptchaUtil();
StringBuffer code = new StringBuffer();
BufferedImage image = tool.genRandomCodeImage(code);
session.removeAttribute(CaptchaConstant.KEY_CAPTCHA);
session.setAttribute(CaptchaConstant.KEY_CAPTCHA, code.toString());
// 将内存中的图片通过流动形式输出到客户端
ImageIO.write(image, "JPEG", resp.getOutputStream());
} catch (Exception e) {
LOGGER.info("context", e);
}
}
}
3.前台获取验证码
<form id="loginForm" name="loginForm" method="post">
<div class="name">
<label>用户名</label><input type="text" class="text" id="username" placeholder="用户名" name="username" tabindex="1">
<label>密 码</label><input type="password" class="text" id="password" placeholder="密码" name="password" tabindex="2">
<label>验证码</label><input type="text" class="text" id="code" placeholder="验证码" name="code" tabindex="3">
<img alt="" src="${ctx }/getCode.htm" id="code_img">
<input type="button" class="regist" tabindex="4" value="登录" id="login_submit">
<!-- <input type="checkbox" name="rememberMe" id="remember_me">记住我 -->
<div class="check"></div>
</div>
<div class="tip"></div>
</form>
$("#code_img").click(function() {
<span style="white-space:pre"> </span>$(this).attr("src", ctx+'/getCode.htm?' + Math.random());
<span style="white-space:pre"> </span>});