vue2 新增行为验证码
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-framework</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>yudao-spring-boot-starter-captcha</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<description>
|
||||||
|
验证码
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring 核心 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
|
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 验证码相关 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.anji-plus</groupId>
|
||||||
|
<artifactId>spring-boot-starter-captcha</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package cn.iocoder.yudao.captcha.core.service;
|
||||||
|
|
||||||
|
import com.anji.captcha.service.CaptchaCacheService;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CaptchaServiceImpl implements CaptchaCacheService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String type() {
|
||||||
|
return "redis";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String key, String value, long expiresInSeconds) {
|
||||||
|
stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(String key) {
|
||||||
|
return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(String key) {
|
||||||
|
stringRedisTemplate.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get(String key) {
|
||||||
|
return stringRedisTemplate.opsForValue().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long increment(String key, long val) {
|
||||||
|
return stringRedisTemplate.opsForValue().increment(key,val);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
cn.iocoder.yudao.captcha.core.service.CaptchaServiceImpl
|
||||||
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 39 KiB |
@ -1,3 +0,0 @@
|
|||||||
### 请求 /captcha/get-image 接口 => 成功
|
|
||||||
GET {{baseUrl}}/system/captcha/get-image
|
|
||||||
tenant-id: {{adminTenentId}}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.system.controller.admin.common;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
|
|
||||||
import cn.iocoder.yudao.module.system.service.common.CaptchaService;
|
|
||||||
import io.swagger.annotations.Api;
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import javax.annotation.security.PermitAll;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|
||||||
|
|
||||||
@Api(tags = "管理后台 - 验证码")
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/system/captcha")
|
|
||||||
public class CaptchaController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private CaptchaService captchaService;
|
|
||||||
|
|
||||||
@GetMapping("/get-image")
|
|
||||||
@PermitAll
|
|
||||||
@ApiOperation("生成图片验证码")
|
|
||||||
public CommonResult<CaptchaImageRespVO> getCaptchaImage() {
|
|
||||||
return success(captchaService.getCaptchaImage());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.system.convert.common;
|
|
||||||
|
|
||||||
import cn.hutool.captcha.AbstractCaptcha;
|
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
|
|
||||||
import org.mapstruct.Mapper;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
|
||||||
|
|
||||||
@Mapper
|
|
||||||
public interface CaptchaConvert {
|
|
||||||
|
|
||||||
CaptchaConvert INSTANCE = Mappers.getMapper(CaptchaConvert.class);
|
|
||||||
|
|
||||||
default CaptchaImageRespVO convert(String uuid, AbstractCaptcha captcha) {
|
|
||||||
return CaptchaImageRespVO.builder().uuid(uuid).img(captcha.getImageBase64()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.common;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码 Service 接口
|
|
||||||
*/
|
|
||||||
public interface CaptchaService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得验证码图片
|
|
||||||
*
|
|
||||||
* @return 验证码图片
|
|
||||||
*/
|
|
||||||
CaptchaImageRespVO getCaptchaImage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否开启图片验证码
|
|
||||||
*
|
|
||||||
* @return 是否
|
|
||||||
*/
|
|
||||||
Boolean isCaptchaEnable();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得 uuid 对应的验证码
|
|
||||||
*
|
|
||||||
* @param uuid 验证码编号
|
|
||||||
* @return 验证码
|
|
||||||
*/
|
|
||||||
String getCaptchaCode(String uuid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除 uuid 对应的验证码
|
|
||||||
*
|
|
||||||
* @param uuid 验证码编号
|
|
||||||
*/
|
|
||||||
void deleteCaptchaCode(String uuid);
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.system.service.common;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.common.vo.CaptchaImageRespVO;
|
|
||||||
import cn.iocoder.yudao.module.system.dal.redis.common.CaptchaRedisDAO;
|
|
||||||
import cn.iocoder.yudao.module.system.framework.captcha.config.CaptchaProperties;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseRedisUnitTest;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
@Import({CaptchaServiceImpl.class, CaptchaProperties.class, CaptchaRedisDAO.class})
|
|
||||||
public class CaptchaServiceTest extends BaseRedisUnitTest {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private CaptchaServiceImpl captchaService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private CaptchaRedisDAO captchaRedisDAO;
|
|
||||||
@Resource
|
|
||||||
private CaptchaProperties captchaProperties;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetCaptchaImage() {
|
|
||||||
// 调用
|
|
||||||
CaptchaImageRespVO respVO = captchaService.getCaptchaImage();
|
|
||||||
// 断言
|
|
||||||
assertNotNull(respVO.getUuid());
|
|
||||||
assertNotNull(respVO.getImg());
|
|
||||||
String captchaCode = captchaRedisDAO.get(respVO.getUuid());
|
|
||||||
assertNotNull(captchaCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetCaptchaCode() {
|
|
||||||
// 准备参数
|
|
||||||
String uuid = randomString();
|
|
||||||
String code = randomString();
|
|
||||||
// mock 数据
|
|
||||||
captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout());
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
String resultCode = captchaService.getCaptchaCode(uuid);
|
|
||||||
// 断言
|
|
||||||
assertEquals(code, resultCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDeleteCaptchaCode() {
|
|
||||||
// 准备参数
|
|
||||||
String uuid = randomString();
|
|
||||||
String code = randomString();
|
|
||||||
// mock 数据
|
|
||||||
captchaRedisDAO.set(uuid, code, captchaProperties.getTimeout());
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
captchaService.deleteCaptchaCode(uuid);
|
|
||||||
// 断言
|
|
||||||
assertNull(captchaRedisDAO.get(uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 此处可直接引用自己项目封装好的 axios 配合后端联调
|
||||||
|
*/
|
||||||
|
|
||||||
|
import request from './../utils/axios' // 组件内部封装的axios
|
||||||
|
// import request from "@/api/axios.js" //调用项目封装的axios
|
||||||
|
|
||||||
|
// 获取验证图片 以及token
|
||||||
|
export function reqGet(data) {
|
||||||
|
return request({
|
||||||
|
url: '/captcha/get',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动或者点选验证
|
||||||
|
export function reqCheck(data) {
|
||||||
|
return request({
|
||||||
|
url: '/captcha/check',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
axios.defaults.baseURL = process.env.VUE_APP_BASE_API
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
timeout: 40000,
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
service.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// response interceptor
|
||||||
|
service.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
const res = response.data
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
export default service
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
export function resetSize(vm) {
|
||||||
|
let img_width, img_height, bar_width, bar_height // 图片的宽度、高度,移动条的宽度、高度
|
||||||
|
|
||||||
|
let parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
|
||||||
|
let parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
|
||||||
|
|
||||||
|
if (vm.imgSize.width.indexOf('%') !== -1) {
|
||||||
|
img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px'
|
||||||
|
} else {
|
||||||
|
img_width = this.imgSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.imgSize.height.indexOf('%') !== -1) {
|
||||||
|
img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px'
|
||||||
|
} else {
|
||||||
|
img_height = this.imgSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.barSize.width.indexOf('%') !== -1) {
|
||||||
|
bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px'
|
||||||
|
} else {
|
||||||
|
bar_width = this.barSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.barSize.height.indexOf('%') !== -1) {
|
||||||
|
bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px'
|
||||||
|
} else {
|
||||||
|
bar_height = this.barSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||||
|
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
|
||||||
|
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import CryptoJS from 'crypto-js'
|
||||||
|
/**
|
||||||
|
* @word 要加密的内容
|
||||||
|
* @keyWord String 服务器随机返回的关键字
|
||||||
|
*/
|
||||||
|
export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
|
||||||
|
const key = CryptoJS.enc.Utf8.parse(keyWord)
|
||||||
|
const secs = CryptoJS.enc.Utf8.parse(word)
|
||||||
|
const encrypted = CryptoJS.AES.encrypt(secs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @word 要解密的内容
|
||||||
|
* @keyWord String 服务器随机返回的关键字
|
||||||
|
*/
|
||||||
|
export function aesDecrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
|
||||||
|
const key = CryptoJS.enc.Utf8.parse(keyWord)
|
||||||
|
const decrypt = CryptoJS.AES.decrypt(word, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
|
||||||
|
return CryptoJS.enc.Utf8.stringify(decrypt).toString()
|
||||||
|
}
|
||||||