java后端+vue实现滑动验证 完整的

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>

完整的步骤都在这里 应该能看懂

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于Django后端+Vue前端+阿里云数据库实现完整的书店系统源码+详细部署说明.zip 项目简介: 实现一个提供网上购书功能的网站。 网站支持书商在上面开商店,购买者可以通过网站购买。 买家和卖家都可以注册自己的账号。 一个卖家可以开一个或多个网上商店 买家可以为自已的账户充值,在任意商店购买图书。 支持下单->付款->发货->收货 流程。 支持将喜欢的书本加入购物车 支持修改个人信息、修改密码、修改头像 支持修改店铺信息、删除店铺 支持添加书本、下架书本、上架书本、修改书本价格 支持买家查看所有订单,店铺查看所有店铺订单 支持买家取消订单,订单15分钟内未付款自动失效 支持全局搜索书本,搜索书店 支持店铺内搜索书本 支持首页推荐(最热店铺、最热书本、最近上新书本) 支持店内推荐(本店最热书本、最近上新书本) 项目采用Python Django框架编写后端所有代码(包含测试用例),总共实现38个接口,140+测试用例(均通过),项目代码总覆盖率90%+(其中接口覆盖率70%+),并包含Vue前端实现,前后端均纯原创!是一个完全可以上线使用的项目! 需求分析: (详情见需求分析.docx) API定义: (详情见API_doc文件夹) 前端设计: 1、 前端框架 ·框架:Vue ·脚手架:Vue-cli 2、 编程语言 ·javascript ·HTML ·css 3、 第三方插件 ·jquery ·bootstrap 4、 实现Vue组件(components) ·App.vue ·register.vue:用户注册 ·login.vue:用户登录 ·addstore.vue:新建店铺 ·balance.vue:查看余额/余额充值 ·book.vue:书本详情页 ·bookresult.vue:书本查询结果 ·buystore.vue:店铺详情页 ·information.vue:个人信息 ·mycart.vue:我的购物车 ·myorder.vue:我的订单 ·mystore.vue:我的店铺 ·order.vue:生成订单 ·orderinfo.vue:订单详情页 ·password.vue:修改密码 ·store.vue:店铺主页 ·storeorderinfo.vue:店铺订单详情页 ·storeorders.vue:店铺订单 ·storeresult.vue:店铺查询结果 ·TagsInput.vue:标签组件(book.vue的子组件) ·tmp.vue:中间跳转页 ·unregister.vue:注销用户 数据库设计: 项目需要有买家和卖家的区别,但是实际上他们可以由同一个表存储,所以开始时设计数据库就有两种结构,一为(User to User 设计),另一为(Seller to Buyer 设计),为方便前端设计以及后端架构,采用User to User设计。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值