做美容行业的网站哪个好网站开发工程师ppt
- 作者: 五速梦信息网
- 时间: 2026年04月18日 10:03
当前位置: 首页 > news >正文
做美容行业的网站哪个好,网站开发工程师ppt,做网站需要ftp,比较有名的网站建设公司一、前言
ruoyi项目默认的验证码是这样的
今天来尝试增加滑块验证码#xff0c;我们用到的是tianai-captcha。 文档地址#xff1a;http://doc.captcha.tianai.cloud/ 源码地址#xff1a;https://gitee.com/tianai/tianai-captcha
下面来看具体的步骤。
二、后端
在g…一、前言
ruoyi项目默认的验证码是这样的
今天来尝试增加滑块验证码我们用到的是tianai-captcha。 文档地址http://doc.captcha.tianai.cloud/ 源码地址https://gitee.com/tianai/tianai-captcha
下面来看具体的步骤。
二、后端
在gateway中引入依赖
dependencygroupIdcloud.tianai.captcha/groupIdartifactIdtianai-captcha-springboot-starter/artifactIdversion1.4.1/version
/dependency并增加相应的配置
客户端验证码
captcha:cache:enabled: truecache-size: 20# 二次验证secondary:enabled: false# 是否初始化默认资源init-default-resource: false在gateway中新增加一个SliderCaptchaHandler处理类
import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cloud.tianai.captcha.spring.vo.CaptchaResponse;
import cloud.tianai.captcha.spring.vo.ImageCaptchaVO;
import com.iinplus.common.core.exception.CaptchaException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;import javax.annotation.Resource;/*** 验证码获取/
Component
public class SliderCaptchaHandler implements HandlerFunctionServerResponse {Resourceprivate ImageCaptchaApplication sca;Overridepublic MonoServerResponse handle(ServerRequest serverRequest) {CaptchaResponseImageCaptchaVO res;try {// 1.生成滑块验证码(该数据返回给前端用于展示验证码数据)res sca.generateCaptcha();} catch (CaptchaException e) {return Mono.error(e);}return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(res));}
}在RouterFunctionConfiguration中新增一个路由
Resource
private SliderCaptchaHandler sliderCaptchaHandler;Bean
public RouterFunction routerFunc() {return RouterFunctions.route(RequestPredicates.GET(/captcha).and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), sliderCaptchaHandler);
}新增一个filter类用来验证图形验证码。
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.spring.application.ImageCaptchaApplication;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.iinplus.common.core.exception.CaptchaException;
import com.iinplus.common.core.utils.ServletUtils;
import com.iinplus.common.core.utils.StringUtils;
import com.iinplus.gateway.config.properties.CaptchaProperties;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;import javax.annotation.Resource;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;/** 验证码过滤器/
Component
public class ValidateCaptchaFilter extends AbstractGatewayFilterFactoryObject {private final static String[] VALIDATE_URL new String[]{/system/login};Resourceprivate ImageCaptchaApplication sca;Resourceprivate CaptchaProperties captchaProperties;private static final String TRACK sliderCaptchaTrack;private static final String UUID id;Overridepublic GatewayFilter apply(Object config) {return (exchange, chain) - {ServerHttpRequest request exchange.getRequest();// 非登录/注册请求或验证码关闭不处理ListString list Arrays.asList(VALIDATE_URL);// 请求地址String url request.getURI().getPath();if (!StringUtils.matches(url, list) || !captchaProperties.getEnabled()) {return chain.filter(exchange);}try {String rspStr resolveBodyFromRequest(request);if (StringUtils.isEmpty(rspStr)) {throw new CaptchaException(验证码不能为空);}JSONObject obj JSON.parseObject(rspStr);if (!obj.containsKey(UUID) || !obj.containsKey(TRACK)) {throw new CaptchaException(验证码不能为空);}String id obj.getString(UUID);ImageCaptchaTrack sliderCaptchaTrack obj.getObject(TRACK, ImageCaptchaTrack.class);ApiResponse? match sca.matching(id, sliderCaptchaTrack);if (!match.isSuccess()) {throw new CaptchaException(match.getMsg());}} catch (Exception e) {e.printStackTrace();return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());}return chain.filter(exchange);};}private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {// 获取请求体FluxDataBuffer body serverHttpRequest.getBody();AtomicReferenceString bodyRef new AtomicReference();body.subscribe(buffer - {CharBuffer charBuffer StandardCharsets.UTF_8.decode(buffer.asByteBuffer());DataBufferUtils.release(buffer);bodyRef.set(charBuffer.toString());});return bodyRef.get();}
}注意其中/system/login为登录验证的路径需要在网关中配置并且需要加入白名单。
如果需要修改图形验证码默认的背景图
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.generator.common.constant.SliderCaptchaConstant;
import cloud.tianai.captcha.resource.common.model.dto.Resource;
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
import cloud.tianai.captcha.resource.impl.DefaultResourceStore;
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
import org.springframework.stereotype.Component;import static cloud.tianai.captcha.generator.impl.StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;/** 自定义背景图片/
Component
public class ResourceStore extends DefaultResourceStore {public ResourceStore() {// 滑块验证码 模板 (系统内置)ResourceMap template1 new ResourceMap(default, 4);template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat(/1/active.png)));template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat(/1/fixed.png)));ResourceMap template2 new ResourceMap(default, 4);template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat(/2/active.png)));template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat(/2/fixed.png)));// 1. 添加一些模板addTemplate(CaptchaTypeConstant.SLIDER, template1);addTemplate(CaptchaTypeConstant.SLIDER, template2);// 2. 添加自定义背景图片addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/1.png, default));addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/2.png, default));addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/3.png, default));addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/4.png, default));addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/5.png, default));addResource(CaptchaTypeConstant.SLIDER, new Resource(classpath, bg/6.png, default));}
}图片的路径如下
网关配置增加filters把ValidateCaptchaFilter加上才生效
spring:cloud:gateway:discovery:… …routes:# 系统模块- id: systemuri: lb://systempredicates:- Path/system/filters:# 验证码处理- ValidateCaptchaFilter- StripPrefix1system模块的登录验证
/ 系统用户登录*/
RestController
public class TokenController {Autowiredprivate SysLoginService sysLoginService;PostMapping(login)public RpcResult? login(RequestBody Validated LoginBody form) { LoginUser userInfo sysLoginService.login(form);… …return RpcResult.success(rspMap);}
}import lombok.Data;
import javax.validation.constraints.NotBlank;Data
public class LoginBody {NotBlank(message 用户名不能为空)private String username;NotBlank(message 密码不能为空)private String password;
}三、前端
在components下增加一个组件SliderCaptcha
templatediv classslider rotatediv classcontentdiv classbg-img-divimg :srccaptcha.backgroundImage idimgId alt/canvas idcanvasId refcanvas/canvas/divdiv classrotate-img-div :stylerotateImgDivimg :srccaptcha.templateImage alt//div/divdiv classslider-movediv classslider-move-track拖动滑块到正确位置/divdiv classslider-move-btn :stylesliderMoveBtn mousedowndown touchstartdown/div/divdiv classbottomdiv classclose-btn clickclose()/divdiv classrefresh-btn clickrefreshCaptcha/div/div/div
/templatescript
export default {name: Slider,props: {captcha: {type: Object},},data() {return {currentCaptchaConfig: {},sliderMoveBtn: ,rotateImgDiv: ,checkParam: {}}},mounted() {this.initCaptcha();},methods: {refreshCaptcha() {this.\(emit(init);this.initCaptcha();},initCaptcha() {this.sliderMoveBtn background-position: -5px 11.79625%; transform: translate(0, 0);this.rotateImgDiv transform: translate(0, 0);this.currentCaptchaConfig {};let bgImageWidth this.\)refs.canvas.offsetWidth;let bgImageHeight this.\(refs.canvas.offsetHeight;this.checkParam {bgImageWidth: bgImageWidth,bgImageHeight: bgImageHeight,startSlidingTime: new Date(),endSlidingTime: undefined,trackList: [],}},down(event) {let targetTouches event.originalEvent ? event.originalEvent.targetTouches : event.targetTouches;let startX event.pageX;let startY event.pageY;if (startX undefined) {startX Math.round(targetTouches[0].pageX);startY Math.round(targetTouches[0].pageY);}this.currentCaptchaConfig.startX startX;this.currentCaptchaConfig.startY startY;const pageX this.currentCaptchaConfig.startX;const pageY this.currentCaptchaConfig.startY;const startSlidingTime this.checkParam.startSlidingTime;const trackList this.checkParam.trackList;trackList.push({x: pageX - startX,y: pageY - startY,type: down,t: (new Date().getTime() - startSlidingTime.getTime())});// pcwindow.addEventListener(mousemove, this.move);window.addEventListener(mouseup, this.up);// 手机端window.addEventListener(touchmove, this.move, false);window.addEventListener(touchend, this.up, false);this.sliderMoveBtn background-position:-5px 31.0092%;},move(event) {if (event instanceof TouchEvent) {event event.touches[0];}let pageX Math.round(event.pageX);let pageY Math.round(event.pageY);const startX this.currentCaptchaConfig.startX;const startY this.currentCaptchaConfig.startY;const startSlidingTime this.checkParam.startSlidingTime;const end 305;const trackList this.checkParam.trackList;let moveX pageX - startX;const track {x: pageX - startX,y: pageY - startY,type: move,t: (new Date().getTime() - startSlidingTime.getTime())};trackList.push(track);if (moveX 0) {moveX 0;} else if (moveX end 5) {moveX end;}this.sliderMoveBtn transform:translate(\){moveX}px, 0);this.rotateImgDiv transform:translate(\({moveX}px, 0);;},up(event) {window.removeEventListener(mousemove, this.move);window.removeEventListener(mouseup, this.up);// 手机端window.removeEventListener(touchmove, this.move);window.removeEventListener(touchend, this.up);if (event instanceof TouchEvent) {event event.changedTouches[0];}let pageX Math.round(event.pageX);let pageY Math.round(event.pageY);const startX this.currentCaptchaConfig.startX;const startY this.currentCaptchaConfig.startY;const startSlidingTime this.checkParam.startSlidingTime;const trackList this.checkParam.trackList;const track {x: pageX - startX,y: pageY - startY,type: up,t: (new Date().getTime() - startSlidingTime.getTime())}trackList.push(track);this.checkParam.endSlidingTime new Date();// 校验this.checkCaptcha()},close() {this.\)emit(close);},checkCaptcha() {//this.checkParam {};this.\(emit(checkParam, this.checkParam)this.\)emit(login);}},
}
/script
style scoped
.slider {background-color: #fff;width: 380px;height: 340px;z-index: 999;box-sizing: border-box;padding: 9px;border-radius: 6px;box-shadow: 0 0 11px 0 #999999;
}.slider .content {width: 100%;height: 160px;position: relative;
}.bg-img-div {width: 100%;height: 100%;position: absolute;transform: translate(0px, 0px);
}.bg-img-div img {width: 100%;
}.bg-img-div canvas {width: 100%;position: absolute;left: 0;top: 0;
}.slider .slider-move {height: 60px;width: 100%;margin: 0;position: relative;top: 80px
}.slider .bottom {height: 25px;width: 100%;margin: 65px 10px 10px 0;
}.refresh-btn, .close-btn, .slider-move-btn {background: url(../../assets/images/sprite.1.2.4.png) no-repeat;
}.refresh-btn, .close-btn {display: inline-block;
}.slider-move .slider-move-track {line-height: 38px;font-size: 14px;text-align: center;white-space: nowrap;color: #88949d;-moz-user-select: none;-webkit-user-select: none;user-select: none;border-radius: 50px;background: #dfe1e2;width: 100%;
}.slider {user-select: none;
}.slider-move .slider-move-btn {transform: translate(0, 0);background-position: -5px 11.79625%;position: absolute;top: -12px;left: 0;width: 100%;height: 100%;
}.slider-move-btn:hover, .close-btn:hover, .refresh-btn:hover {cursor: pointer
}.bottom .close-btn {width: 25px;height: 25px;background-position: 0 44.86874%;margin: 10px 10px 10px 5px;float: left;
}.bottom .refresh-btn {width: 25px;height: 25px;background-position: 0 81.38425%;margin: 7px 10px 10px 2px;float: left;
}.rotate-img-div {height: 140%;position: absolute;transform: translate(0, 0);
}.rotate-img-div img {height: 100%;
}
/style修改登录和获取验证码的方法
// 登录方法
export function login(data) {return request({url: /system/login,headers: {isToken: false},method: post,data: data})
}
// 获取验证码
export function captcha() {return request({url: /captcha,headers: {isToken: false},method: get,timeout: 20000})
}修改login.vue页面
templatediv classloginel-form refloginForm :modelloginForm :rulesloginRules classlogin-formh3 classtitlexxxx管理系统/h3el-form-item propusernameel-inputv-modelloginForm.usernametypetextauto-completeoffplaceholder账号svg-icon slotprefix icon-classuser classel-inputicon input-icon //el-input/el-form-itemel-form-item proppasswordel-inputv-modelloginForm.passwordtypepasswordauto-completeoffplaceholder密码svg-icon slotprefix icon-classpassword classel-inputicon input-icon //el-input/el-form-item!–注释掉原来的验证码–!–el-form-item propcode v-ifcaptchaEnabled stylemargin: 10px 0el-inputv-modelloginForm.codeauto-completeoffplaceholder验证码stylewidth: 68%keyup.enter.nativehandleLoginsvg-icon slotprefix icon-classvalidCode classel-input__icon input-icon //el-inputdiv classlogin-codeimg :srccodeUrl clickgetCode classlogin-code-img//div/el-form-item–el-form-item stylewidth:100%; margin: 10px 0el-checkbox v-modelloginForm.rememberMe记住密码/el-checkbox/el-form-itemel-form-item stylewidth:100%;margin-bottom: 10pxel-button:loadingloadingsizemediumtypeprimarystylewidth:100%;click.native.preventinitCaptchaspan v-if!loading登 录/spanspan v-else登 录 中…/span/el-button/el-form-item/el-form!– 滑块验证码通过show来控制显示遮蔽层–div v-ifshow classmaskSliderCaptchav-ifcaptchaVisiblerefsliderCaptcha:captchacaptchainitinitCaptchaclosehideCaptchaloginhandleLogin//div!– 底部 –div classel-login-footerspanCopyright © xxx All Rights Reserved./span/div/div
/templatescript
import {captcha} from /api/login
import Cookies from js-cookie;
import {decrypt, encrypt} from /utils/jsencrypt
import SliderCaptcha from /components/SliderCaptchaexport default {name: Login,components: {SliderCaptcha},data() {return {// codeUrl: ,show: false,captcha: {},captchaVisible: false,loginForm: {username: undefined,password: undefined,rememberMe: false,//code: ,//uuid: ,// 增加下面两个属性ImageCaptchaTrack: {},id: },loginRules: {username: [{ required: true, trigger: blur, message: 请输入您的账号 }],password: [{ required: true, trigger: blur, message: 请输入您的密码 }],// 不再需要这个验证// code: [{ required: true, trigger: change, message: 请输入验证码 }]},loading: false,// 验证码开关captchaEnabled: true,redirect: undefined};},watch: {\(route: {handler: function(route) {this.redirect route.query route.query.redirect;},immediate: true}},created() {//this.getCode();this.getCookie();},methods: {//注释原先的获取验证码方法/*getCode() {getCodeImg().then(res {this.captchaEnabled res.captchaEnabled undefined ? true : res.captchaEnabled;if (this.captchaEnabled) {this.codeUrl data:image/gif;base64, res.img;this.loginForm.uuid res.uuid;}});},*/getCookie() {const username Cookies.get(username);const password Cookies.get(password);const rememberMe Cookies.get(rememberMe)this.loginForm {username: username undefined ? this.loginForm.username : username,password: password undefined ? this.loginForm.password : decrypt(password),rememberMe: rememberMe undefined ? false : Boolean(rememberMe)};},hideCaptcha() {this.captchaVisible falsethis.show false;},// 获取图形验证码initCaptcha() {this.\)refs.loginForm.validate(valid {if (valid) {captcha().then(res {if (res) {this.captcha res[captcha];this.loginForm.id res[id];this.captchaVisible truethis.show true;}})}})},// 登录方法handleLogin() {this.\(refs.loginForm.validate(valid {if (valid) {this.loading true;if (this.loginForm.rememberMe) {Cookies.set(username, this.loginForm.username, { expires: 30 });Cookies.set(password, encrypt(this.loginForm.password), { expires: 30 });Cookies.set(rememberMe, this.loginForm.rememberMe, { expires: 30 });} else {Cookies.remove(username);Cookies.remove(password);Cookies.remove(rememberMe);}// 从子组件获取值this.loginForm.sliderCaptchaTrack this.\)refs.sliderCaptcha.checkParamthis.\(store.dispatch(Login, this.loginForm).then(() {// console.info(this.redirect, this.redirect)this.\)router.push({ path: this.redirect || / }).catch((){});}).catch(() {this.loading false;//调用子组件的刷新图形验证码的方法this.\(refs.sliderCaptcha.refreshCaptcha()});}});}}
};
/scriptstyle relstylesheet/scss langscss
!--新增遮蔽层其他省略--
.mask {box-sizing: border-box;position: fixed;top: 0;left: 0;bottom: 0;right: 0;z-index: 1001;background: rgba(0, 0, 0, 0.3);transition: all 0.5s;display: flex;flex-direction: column;justify-content: center;align-items: center;
}
/style最后this.\)store.dispatch(Login, this.loginForm)调用的Login也需要修改一下在user.js里面。
最终效果 点击【登录】按钮
- 上一篇: 做美工的网站卡片式设计网站
- 下一篇: 做美食的视频网站卡片式wordpress模板
相关文章
-
做美工的网站卡片式设计网站
做美工的网站卡片式设计网站
- 技术栈
- 2026年04月18日
-
做贸易要看什么网站手机上page转换wordpress
做贸易要看什么网站手机上page转换wordpress
- 技术栈
- 2026年04月18日
-
做贸易的网站有哪些wordpress简洁主题
做贸易的网站有哪些wordpress简洁主题
- 技术栈
- 2026年04月18日
-
做美食的视频网站卡片式wordpress模板
做美食的视频网站卡片式wordpress模板
- 技术栈
- 2026年04月18日
-
做美食分享网站源码小程序拉新推广平台
做美食分享网站源码小程序拉新推广平台
- 技术栈
- 2026年04月18日
-
做美食没有广告的网站wap游戏纵横四海
做美食没有广告的网站wap游戏纵横四海
- 技术栈
- 2026年04月18日
