总结自开源项目bootdo
图标设置
验证码
验证码生成工具类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* @author bootdo
*/
public class RandomValidateCodeUtil {
public static final String RANDOMCODEKEY = "RANDOMVALIDATECODEKEY";//放到session中的key
private String randString = "0123456789";//随机产生只有数字的字符串 private String
//private String randString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产生只有字母的字符串
//private String randString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";//随机产生数字与字母组合的字符串
private int width = 95;// 图片宽
private int height = 25;// 图片高
private int lineSize = 40;// 干扰线数量
private int stringNum = 4;// 随机产生字符数量
private static final Logger logger = LoggerFactory.getLogger(RandomValidateCodeUtil.class);
private Random random = new Random();
/**
* 获得字体
*/
private Font getFont() {
return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
}
/**
* 获得颜色
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc - 16);
int g = fc + random.nextInt(bc - fc - 14);
int b = fc + random.nextInt(bc - fc - 18);
return new Color(r, g, b);
}
/**
* 生成随机图片
*/
public void getRandcode(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics g = image.getGraphics();// 产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作
g.fillRect(0, 0, width, height);//图片大小
g.setFont(new Font("Default", Font.ROMAN_BASELINE, 18));//字体大小
g.setColor(getRandColor(110, 133));//字体颜色
// 绘制干扰线
for (int i = 0; i <= lineSize; i++) {
drowLine(g);
}
// 绘制随机字符
String randomString = "";
for (int i = 1; i <= stringNum; i++) {
randomString = drowString(g, randomString, i);
}
logger.info(randomString);
//将生成的随机字符串保存到session中
session.removeAttribute(RANDOMCODEKEY);
session.setAttribute(RANDOMCODEKEY, randomString);
g.dispose();
try {
// 将内存中的图片通过流动形式输出到客户端
ImageIO.write(image, "JPEG", response.getOutputStream());
} catch (Exception e) {
logger.error("将内存中的图片通过流动形式输出到客户端失败>>>> ", e);
}
}
/**
* 绘制字符串
*/
private String drowString(Graphics g, String randomString, int i) {
g.setFont(getFont());
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
.nextInt(121)));
String rand = String.valueOf(getRandomString(random.nextInt(randString
.length())));
randomString += rand;
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(rand, 13 * i, 16);
return randomString;
}
/**
* 绘制干扰线
*/
private void drowLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(13);
int yl = random.nextInt(15);
g.drawLine(x, y, x + xl, y + yl);
}
/**
* 获取随机的字符
*/
public String getRandomString(int num) {
return String.valueOf(randString.charAt(num));
}
}
/**
* 生成验证码
*/
@GetMapping(value = "/getVerify")
public void getVerify(HttpServletRequest request, HttpServletResponse response) {
try {
response.setContentType("image/jpeg");//设置相应类型,告诉浏览器输出的内容为图片
response.setHeader("Pragma", "No-cache");//设置响应头信息,告诉浏览器不要缓存此内容
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
RandomValidateCodeUtil randomValidateCode = new RandomValidateCodeUtil();
randomValidateCode.getRandcode(request, response);//输出验证码图片方法
} catch (Exception e) {
logger.error("获取验证码失败>>>> ", e);
}
}
<div class="col-xs-6 pull_left">
<a href="javascript:void(0);" rel="external nofollow" title="点击更换验证码">
<img style="margin-top: 12px;" id="imgVerify" src="" alt="更换验证码" height="36" width="100%" onclick="getVerify(this);">
</a>
</div>
//获取验证码
function getVerify(obj) {
obj.src = "/getVerify?" + Math.random(); //加上随机数,切换图片,避免服务器缓存,相同路径,不再发送请求
console.log(obj)
}
关于请求头的设置
图片来自菜鸟教程7.1.2 Android Http请求头与响应头的学习 | 菜鸟教程
表单验证
转自Validate表单验证_RocsLee筱站-CSDN博客_validate
validate属性,jquery的小插件
一、 validate的使用步骤
引入jquery.min.js
引入 jquery.validate.js
页面加载后对表单进行验证 $("#表单id名").validate({})
在validate中的rules中编写验证规则(格式如下)
字段的name属性:“校验器”(tisps:一个输入框只有一个校验器的时候使用)
字段的name属性:{校验器:值,校验器:值}(tips:输入框需要有多个校验器的时候使用)
在validate中的messages中编写提示信息(tips格式与rules相对应)
在validate中的submitHandler中编写验证通过执行的内容
二、实例
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript" src="scripts/common/jquery.min.js"></script>
<script type="text/javascript" src="scripts/common/jquery.validate.js"></script
<script type="text/javascript">
$("#zhuce").validate({
rules:{
regist_username:{
required:true
/* remote:{
type: "post",url: url,
data:{
userName:function () {
return $("regist_username").val();
}
}
}用ajax判断数据库中用户名是否存在*/
},
regist_password:{
required:true,
minlength:6
},
final_password:{
equalTo:"#regist_password"
}
},
messages:{
regist_username:{required:"用户名不能为空"},
regist_password:{required:"密码不能为空",minlength:"密码最小的长度为6"},
final_password:{equalTo:"两次密码不一致"}
},
submitHandler: function () {
//验证通过后进行注册
register();
}
})
</script>
</head>
<body>
<form id="zhuce">
<dl>
<dt>
<div class='header'>
<h3>注 册</h3>
</div>
</dt>
<dt></dt>
<dt>
<div class='letter'>
<label for="regist_username">用户名: </label>
<input type="text" name="regist_username" id="regist_username" tabindex='5'/>
</div>
</dt>
<dt>
<div class='letter'>
昵 称: <input type="text" name="nickname" id="nickname" tabindex='6'/>
</div>
</dt>
<dt>
<div class='letter'>
<label for="regist_password"> 密 码: </label>
<input type="password" name="regist_password" id="regist_password" tabindex='7'/>
</div>
</dt>
<dt>
<div class='password'>
<label for="final_password"> 确认密码: </label>
<input type="password" name="final_password" id="final_password" tabindex='8'/>
</div>
</dt>
<dt>
<div>
<input type="submit" name="" id="regist_button" value=' 注 册 ' tabindex='9'/>
<input type="button" name="" id="back" value=' 返 回 ' tabindex='10'/>
</div>
</dt>
</dl>
</form>
</body>
</html>
实例二
function validateRule() {
var icon = "<i class='fa fa-times-circle'></i> ";
$("#signupForm").validate({ //jquery插件验证表单内容
rules: {
username: {
required: true
},
password: {
required: true
}
},
messages: {
username: {
required: icon + "请输入您的用户名",
},
password: {
required: icon + "请输入您的密码",
}
}
})
}
详细使用jQuery validator 详解/应用_猿来是你-CSDN博客
获取model中数据
thymeleaf模板引擎为前端获取数据提供了较大的便利,在html标签可以通过th标签加${}表达式访问model中的数据。但是如果不想通过th标签而是简单的访问model对象数据,或者是在js中访问modle中的数据,则需要使用内联的方法。
比如我们可以使用[[$ {} ]]访问对象
<p>Hello, [[${session.user.name}]]!</p>
以上的代码可以取代一般的th标签写法
<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>
Expressions between [[...]]
or [(...)]
are considered inlined expressions in Thymeleaf。即使用此语法被称为“内联”。使用内联的语句的确会更加简洁一点。
若是想在javascript代码块内直接的使用model的对象值,则必须使用内联实现。
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
var max = /*[[${maxSumOfDateInYear}]]*/ 20;
alert(max);
/*]]>*/
</script>
其中第2、5行为基于XML的转义写法,第3行把内联访问的语句用注释引起来,且后面跟着一个空格20,是指若不存在此对象,则自动设置默认值为20。
注意以上的javascript内联写法是基于你需要“直接地”使用model对象值的场景。否则还有其它写法,比如简单地声明一个hidden input:
<input type="hidden" id="maxSumOfDateInYear" th:value="${maxSumOfDateInYear}"/>
<script type="text/javascript" th:inline="none">
var max = $("#maxSumOfDateInYear").val();
var data = [["2012-05-07", 6], ["2012-04-16", 4]];
alert(max);
alert(data);
</script>
以上第1行声明了一个hidden的maxSumOfDateInYear,然后第3行通过jQuery来间接访问。
注意,若在thymeleaf代码里存在第4行的二维数组字面量的写法,则必须要把javascript代码块设置为inline为none的,否则thymeleaf引擎会把此数组的[[也当成了内联语句处理,从而导致后端报错An error happened during template parsing。要么,你就还是通过hidden间接访问的方式实现。
或者,也可以通过ajax直接获取内容来实现。但这就属与thymeleaf模板引擎计算过程无关了。
thymeleaf的内联th:inline(在javascript访问model中的数据)_纱厨藤簟,玉人罗扇轻缣。-CSDN博客
登录验证
发送请求
function login() {
$.ajax({
type: "POST",
url: ctx + "login",
//serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。您可以选择一个或多个表单元素(比如 input 及/或 文本框),
// 或者 form 元素本身。序列化的值可在生成 AJAX 请求时用于 URL 查询字符串中。
data: $('#signupForm').serialize(),
success: function (r) {
if (r.code == 0) {
var index = layer.load(1, {
shade: [0.1, '#fff'] //0.1透明度的白色背景
});
// 父页面跳转 http://localhost/index
parent.location.href = '/index';
} else {
//jquery插件 layer弹窗js
layer.msg(r.msg);
}
},
});
}
接收请求
@PostMapping("/login")
@ResponseBody
R ajaxLogin(String username, String password,String verify,HttpServletRequest request) {
try {
//从session中获取验证码的值
String random = (String) request.getSession().getAttribute(RandomValidateCodeUtil.RANDOMCODEKEY);
if (StringUtils.isBlank(verify)) { //isBlank 判空
return R.error("请输入验证码");
}
if (random.equals(verify)) {
} else {
return R.error("请输入正确的验证码");
}
} catch (Exception e) {
logger.error("验证码校验失败", e);
return R.error("验证码校验失败");
}
// md5加盐算法后的密码
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return R.ok();
} catch (AuthenticationException e) {
return R.error("用户或密码错误");
}
}
R是一个标志类
import java.util.HashMap;
import java.util.Map;
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "操作成功");
}
public static R error() {
return error(1, "操作失败");
}
public static R error(String msg) {
return error(500, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
MD5加盐工具类
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class MD5Utils {
private static final String SALT = "1qazxsw2";
private static final String ALGORITH_NAME = "md5";
private static final int HASH_ITERATIONS = 2;
public static String encrypt(String pswd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex();
return newPassword;
}
public static String encrypt(String username, String pswd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(username + SALT),
HASH_ITERATIONS).toHex();
return newPassword;
}
public static void main(String[] args) {
// System.out.println(MD5Utils.encrypt("admin", "111111"));
}
}
对MD5加盐不太了解的可以看这篇文章使用Shiro的SimpleHash加密密码工具类_mr_foxsand的专栏-CSDN博客
关于ajax的页面跳转
页面跳转的几种形式
-
window.parent.location
用于更改父窗口的位置。 -
window.top.location
- 它是对象'window'的属性。
- 它返回窗口层次结构中最顶层窗口的位置。
- 如果窗口没有父窗口,则top是对自身的引用(窗口=== window.top)
window.top.location.href 和 window.location.href 的区别 - 戒不掉n_思念 - 博客园
日志记录
使用aop横切日志
package com.bootdo.common.aspect;
import com.bootdo.common.utils.HttpContextUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.net.util.IPAddressUtil;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("execution( * com.bootdo..controller.*.*(..))")//两个..代表所有子目录,最后括号里的两个..代表所有参数
public void logPointCut() {
}
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
logger.info("请求地址 : " + request.getRequestURL().toString());
logger.info("HTTP METHOD : " + request.getMethod());
// 获取真实的ip地址
//logger.info("IP : " + IPAddressUtil.getClientIpAddress(request));
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
// loggger.info("参数 : " + joinPoint.getArgs());
}
@AfterReturning(returning = "ret", pointcut = "logPointCut()")// returning的值和doAfterReturning的参数名一致
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容(返回值太复杂时,打印的是物理存储空间的地址)
logger.debug("返回值 : " + ret);
}
@Around("logPointCut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = pjp.proceed();// ob 为方法的返回值
logger.info("耗时 : " + (System.currentTimeMillis() - startTime));
return ob;
}
}