java后端
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.7</version>
</dependency>
<dependency>
<groupId>com.jhlabs</groupId>
<artifactId>filters</artifactId>
<version>2.0.235-1</version>
</dependency>
工具类:
import cn.hutool.core.img.ImgUtil;
import com.jhlabs.image.ImageUtils;
import lombok.Data;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Data
public class VerifyUtil {
private static final Integer QUALITY = 16;
private static final Integer SIZE = 30;
private static final Integer W = 300;
private static final Integer H = 150;
private static final Integer RATIO = 2;
private Image image;
private Map slide;
private Map background;
private Integer x;
private Integer y = SIZE;
public VerifyUtil(String url) {
try {
InputStream is = new DataInputStream(new URL(url).openStream());
image = ImgUtil.read(is).getScaledInstance(W* RATIO, H * RATIO, QUALITY);
} catch (Exception e) {}
slide = new HashMap();
slide.put("x", SIZE / 6 * 7);
slide.put("y", H);
background = new HashMap();
background.put("x", W);
background.put("y", H);
x = (int) Math.floor(W / 6 * 4 * Math.random()) + W / 6;
f();
}
private void f() {
GeneralPath path = path();
BufferedImage slide = new BufferedImage(W * RATIO, H * RATIO, 2);
Graphics2D gs = slide.createGraphics();
gs.setClip(path);
gs.drawImage(image, null, null);
gs.setColor(new Color(255, 0, 0, 255));
gs.setStroke(new BasicStroke(SIZE / 6 / 2.0f));
gs.draw(path);
gs.dispose();
this.slide.put("url", url(ImageUtils.getSubimage(slide, x * RATIO, 0, SIZE / 6 * 7 * RATIO, H * RATIO).getScaledInstance(SIZE / 6 * 7, H, QUALITY)));
BufferedImage background = new BufferedImage(W * RATIO, H * RATIO, 2);
Graphics2D gb = background.createGraphics();
gb.drawImage(image, null, null);
gb.setClip(path);
gb.setColor(new Color(255, 255, 255, 255));
gb.fill(path);
gb.dispose();
this.background.put("url", url(background.getScaledInstance(W, H, QUALITY)));
}
private GeneralPath path() {
double x = this.x * RATIO;
double y = this.y * RATIO;
double d = SIZE / 3.0 * RATIO;
GeneralPath path = new GeneralPath();
path.moveTo(x, y);
path.lineTo(x + d, y);
path.append(new Arc2D.Double(x + d, y - d / 2, d, d, 180, -180, 0), true);
path.lineTo(x + d * 3, y);
path.lineTo(x + d * 3, y + d);
path.append(new Arc2D.Double(x + d * 2 + d / 2, y + d, d, d, 90, -180, 0), true);
path.lineTo(x + d * 3, y + d * 3);
path.lineTo(x, y + d * 3);
path.lineTo(x, y + d * 2);
path.append(new Arc2D.Double(x - d / 2, y + d, d, d, -90, 180, 0), true);
path.lineTo(x, y);
path.closePath();
return path;
}
private String url(Image image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(ImageUtils.convertImageToARGB(image), "png", baos);
} catch (Exception e) {}
return String.format("data:image/png;base64,%s", Base64.getEncoder().encodeToString(baos.toByteArray()));
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class RedisUtil {
@Autowired
public StringRedisTemplate string_redis_template;
public ValueOperations<String, String> vo;
public ListOperations<String, String> lo;
@PostConstruct
private void initialize() {
vo = string_redis_template.opsForValue();
lo = string_redis_template.opsForList();
}
}
Service
import java.util.Map;
public interface VerifyService {
Map get();
Boolean verify(String id, double x);
}
@Service
public class VerifyServiceImplement implements VerifyService {
@Autowired
RedisUtil redis_util;
@Override
public Map get() {
Map map = new HashMap<>();
map.put("id", "...");
VerifyUtil verify_util = new VerifyUtil("http://oss-d.xiaoshizi.com/verify/default.jpg");
redis_util.vo.set("verify::" + map.get("id"), String.valueOf(verify_util.getX()), 5 * 60, TimeUnit.SECONDS);
map.put("background", verify_util.getBackground());
map.put("slide", verify_util.getSlide());
return map;
}
@Override
public Boolean verify(String id, double x) {
String string = redis_util.vo.get("verify::" + id);
if (string != null && Math.abs(Double.valueOf(string) - x) < 3) {
return true;
}
return false;
}
}
Control
/**
* 滑动验证
*/
@RestController
public class VerifyControl {
@Autowired
VerifyService service;
@RequestMapping(value = "/get", method = RequestMethod.GET)
public Result get() {
System.out.println("我是滑动验证的方法,我执行了");
return Result.succ(service.get());
}
@RequestMapping(value = "/verify", method = RequestMethod.POST)
public Result verify(@RequestBody Map<String, String> map) {
System.out.println("我是滑动后验证的方法:" + map);
return Result.succ(service.verify(map.get("id"), Double.valueOf(map.get("x"))));
}
}
登录接口
@Slf4j
@RestController
public class LoginController {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private RedisUtil redisUtil;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 获取验证码
*
* @return
*/
@ApiOperation("使用手机号登录")
@PostMapping("/sendMessage")
public Result sendMessage(@RequestBody Map map, HttpServletResponse response, HttpServletRequest request) {
//获取手机号
String phone = (String) map.get("phone");
System.out.println(phone);
//获取验证码
Integer vCode = MyRandom.getRandom();
//获取ip
String ip = IpUtil.getIpAddr(request);
//使用StringBuilder拼接 字符串生成redis需要的key
StringBuilder redisKey = new StringBuilder("phone:").append(phone);
//判断redis是否过期
if (redisUtil.get(redisKey.toString()) != null) {
return Result.fail("该验证未失效,无法重新获取");
}
//根据手机号拼接一个
redisKey = new StringBuilder("phone:").append(phone);
redisUtil.set(redisKey.toString(), vCode, 60);
//使用StringBuilder拼接字符串存入redis里面来保存次数
StringBuilder key = new StringBuilder("phone:").append(phone);
//使用了redis缓存访问次数,并且设置自增1
long count = redisTemplate.opsForValue().increment(String.valueOf(key), 1);
if (count == 5) {
log.info("用户IP[" + ip + "]访问地址[" + phone + "]超过了限定的次数[" + count + "]");
return Result.fail("该手机号已被限制!无法访问");
} else {
redisTemplate.expire(String.valueOf(key), 1000 * 30, TimeUnit.MILLISECONDS);
return Result.succ("访问成功", MapUtil.builder()
.put("vCode", vCode)
.map());
}
}
}
统一返回结果
import lombok.Data;
import java.io.Serializable;
/**
* 统一返回结果
*
* @author tjw
* @date 2021年05月24日 4:11 下午
*/
@Data
public class Result implements Serializable {
/**
* 200是正常,非200 表示异常
*/
private Integer code;
private String msg;
private Object data;
/**
* 根据任意的数据 返回信息
*
* @param data 放入的数据
* @return
*/
public static Result succ(Object data) {
return succ(200, "操作成功", data);
}
/**
* 根据任意数据 并获取token查看它
*
* @param data 任意数据
* @param Token Token
* @return
*/
public static Result succ(Object data, String Token) {
System.out.println("保存的Token-我是在统一返回结果--->Authorization:" + Token);
return succ(200, "操作成功", data);
}
/**
* 根据消息+任意数据返回信息
*
* @param msg 消息
* @param data 任意类型数据
* @return
*/
public static Result succ(String msg, Object data) {
return succ(200, msg, data);
}
/**
* 返回成功
*
* @param code 200是正常,非200 表示异常
* @param msg 消息
* @param data 放入的数据
* @return
*/
public static Result succ(Integer code, String msg, Object data) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 返回失败的原因
*
* @param msg 放入消息
* @return
*/
public static Result fail(String msg) {
return fail(400, msg, null);
}
/**
* @param msg
* @param data
* @return
*/
public static Result fail(String msg, Object data) {
return fail(400, msg, data);
}
/**
* 返回失败
*
* @param code
* @param msg
* @param data
* @return
*/
public static Result fail(Integer code, String msg, Object data) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
vue前端登录
event.js
import Vue from "vue"
export default new Vue()
组件
<template>
<div>
<el-dialog :visible.sync="flag" :close-on-click-modal="false" width="340px">
<div style="position: relative;" :style="{width: `${o.background.x}px`, height: `${o.background.y}px`}">
<div style="position: absolute;background-size: 100% 100%;"
:style="{width: `${o.background.x}px`, height: `${o.background.y}px`, background: `url(${o.background.url})`}"></div>
<div style="z-index: 2;position: absolute;background-size: 100% 100%;"
:style="{left: `${x}px`, width: `${o.slide.x}px`, height: `${o.slide.y}px`, background: `url(${o.slide.url})`}"
@mousedown="mousedown" @mousemove="mousemove" @mouseup="mouseup" @mouseout="mouseup"></div>
</div>
</el-dialog>
</div>
</template>
<script>
import event from "../utils/event"
//这个是我自定义的axios 可以用默认
import axios from "axios";
export default {
name: "Verify",
data() {
return {
flag: false,
o: {background: {}, slide: {}},
x: 0,
offset: 0,
down: false
}
},
mounted() {
event.$on("VERIFY", () => {
this.flag = true
axios({
url: "/get",
data: {}
}).then((response) => {
console.log(response.data)
this.o = response.data.data
console.log("???", this.o)
})
this.x = 0
this.offset = 0
this.down = false
})
},
methods: {
mousedown(e) {
this.down = true
this.offset = e.x - this.x
},
mousemove(e) {
if (this.down) {
this.x = Math.min(Math.max(0, e.x - this.offset), this.o.background.x - this.o.slide.x)
}
},
mouseup(e) {
if (this.down) {
this.down = false
axios.post("/verify", {
id: this.o.id,
x: this.x
}).then(res => {
if (res.data.data) {
this.flag = false
this.$message({
message: '验证成功',
type: 'success'
});
event.$emit("VERIFY_SUCCESS", this.o.id, this.x)
} else {
this.$message.error('验证失败');
this.x = 0
}
})
}
}
}
}
</script>
登录
<template>
<div>
<el-main>
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="手机号" prop="phone">
<el-input v-model="ruleForm.phone"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="code">
<el-row :span="24">
<el-col :span="12">
<el-input v-model="ruleForm.code" auto-complete="off" placeholder="请输入验证码" size=""
@keyup.enter.native="submitForm('ruleForm')"></el-input>
</el-col>
<el-col :span="12">
<div class="login-code">
<!--验证码组件-->
<el-button @click="getCode()" :class="{'disabled-style':getCodeBtnDisable}"
:disabled="getCodeBtnDisable">{{ codeBtnWord }}
</el-button>
</div>
</el-col>
</el-row>
</el-form-item>
<!--滑动验证组件-->
<Verify></Verify>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
</el-form-item>
</el-form>
</el-main>
</div>
</template>
<script>
import Verify from "@/components/Verify";
import event from "../utils/event"
let params;
let par;
export default {
name: "LoginIphone",
components: {Verify},
data() {
return {
stepMap: 0,
ruleForm: {
phone: 'xxxxxx',
code: '',
yz: '',
// params: {}
},
codeBtnWord: '获取验证码', // 获取验证码按钮文字
waitTime: 61, // 获取验证码按钮失效时间
// 校验
rules: {
phone: [
{required: true, message: '请输入手机号', trigger: 'blur'}
],
code: [
{required: true, message: '请输入验证密码', trigger: 'blur'}
]
}
};
},
computed: {
// 控制获取验证码按钮是否可点击
getCodeBtnDisable: {
get() {
if (this.waitTime === 61) {
if (this.ruleForm.phone) {
return false
}
return true
}
return true
}
}, set() {
}
}, methods: {
getCode() {
const _this = this
params = {}
params.phone = this.ruleForm.phone
// 调用获取短信验证码接口
_this.$axios.post('/sendMessage', params).then(res => {
console.log("--------查看后台响应的值-----", res)
//把所有的数据存在
par = res
console.log("--=--", par)
const mydata = res.data.data
console.log("我在短信接口这利-->", mydata)
//保存验证码
params.yz = mydata.vCode
console.log("我是查看验证码-------" + mydata.vCode)
if (res.data.code === 200) {
this.$message({
message: '验证码已发送,请稍候...',
type: 'success',
center: true
})
}
//调用滑块验证的组件
event.$emit("VERIFY")
}).catch(e => {//错误信息
//调用滑块验证的组件 【一般根据后端返回的次数调出滑块验证】
// event.$emit("VERIFY")
})
// 因为下面用到了定时器,需要保存this指向
let that = this
that.waitTime--
that.getCodeBtnDisable = true
this.codeBtnWord = `${this.waitTime}s 后重新获取`
let timer = setInterval(function () {
if (that.waitTime > 1) {
that.waitTime--
that.codeBtnWord = `${that.waitTime}s 后重新获取`
} else {
clearInterval(timer)
that.codeBtnWord = '获取验证码'
that.getCodeBtnDisable = false
that.waitTime = 61
}
}, 1000)
},
submitForm(formName) {
const _this = this
//判断输入的验证码是否相等
if (params.yz == this.ruleForm.code) {
this.$refs[formName].validate((valid) => {
if (valid) {
console.log("我是提交里面的:", par)
const jwt = par.headers['authorization']
console.log("我是token->", jwt)
const userInfo = par.data.data
console.log("查看用户信息=", userInfo)
// 把数据共享出去
_this.$store.commit("SET_TOKEN", jwt)
_this.$store.commit("SET_USERINFO", userInfo)
// 获取
console.log("我是获取的_this.$store.getters.getUser")
console.log(_this.$store.getters.getUser)
_this.$router.push("/blogs")
} else {
console.log('error submit!!');
return false;
}
});
} else {
this.$message({
showClose: true,
message: '验证输入错误',
type: 'error'
});
}
}
}
}
</script>
<style scoped>
.el-button.disabled-style {
background-color: #EEEEEE;
color: #CCCCCC;
}
.demo-ruleForm {
max-width: 500px;
margin: 0 auto;
}
</style>
完整的步骤都在这里 应该能看懂