java
private final Map<String, Integer> sliderPositions = new HashMap<>();
@GetMapping("/captcha")
public ResponseEntity<byte[]> getCaptcha() {
try {
// Width of the slider
int sliderWidth = 50;
// Total captcha width
// int imageWidth = 200;
int imageWidth = 300;
// Total captcha height
int imageHeight = 100;
int sliderPosition = ThreadLocalRandom.current().nextInt(10, imageWidth - sliderWidth - 10);
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
// Background
graphics.setColor(Color.LIGHT_GRAY);
graphics.fillRect(0, 0, imageWidth, imageHeight);
// Slider area
graphics.setColor(Color.RED);
graphics.fillRect(sliderPosition, 0, sliderWidth, imageHeight);
graphics.dispose();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "png", baos);
// Store the slider position for validation
String sessionId = "user-session-id"; // This should be unique per user/session
sliderPositions.put(sessionId, sliderPosition);
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(baos.toByteArray());
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PostMapping("/verify")
public ResponseEntity<String> verifyCaptcha(@RequestBody VerifyRequest request) {
String sessionId = "user-session-id"; // This should match the session used in getCaptcha
if (!sliderPositions.containsKey(sessionId)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Session not found");
}
int correctPosition = sliderPositions.get(sessionId);
if (Math.abs(request.getSlideDistance() - correctPosition) <= 5) { // Allow some margin
return ResponseEntity.ok("Verified");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Verification failed");
}
}
static class VerifyRequest {
private int slideDistance;
public int getSlideDistance() {
return slideDistance;
}
public void setSlideDistance(int slideDistance) {
this.slideDistance = slideDistance;
}
}
uniapp
/components/SliderCaptcha.vue
<template>
<view class="container">
<!-- widthFix -->
<image :src="captchaSrc" class="captcha" mode="heightFix" @click="fetchCaptcha"/>
<view class="slider" @touchstart="startMove" @touchmove="onMove" @touchend="endMove" :style="{ left: sliderPosition + 'px' }">|||</view>
</view>
</template>
<script>
export default {
data() {
return {
startX: 0,
currentX: 0,
sliderPosition: 0,
captchaSrc: '',
};
},
mounted() {
this.fetchCaptcha();
},
methods: {
fetchCaptcha() {
// 注意替换成你的实际后端地址
this.captchaSrc = 'http://localhost:8080/fapi/home/captcha?' + new Date().getTime(); // 防止图片缓存
this.sliderPosition = 0; // 重置滑块位置
},
startMove(event) {
this.startX = event.touches[0].clientX;
this.currentX = this.sliderPosition;
},
onMove(event) {
const moveX = event.touches[0].clientX - this.startX;
let newX = this.currentX + moveX;
// 限制滑块移动范围
newX = Math.max(0, Math.min(300 - 50, newX)); // 假设背景图宽200px,滑块宽50px
this.sliderPosition = newX;
},
endMove() {
// 向后端发送滑块位置进行验证
uni.request({
url: 'http://localhost:8080/fapi/home/verify', // 注意替换成你的实际后端地址
method: 'POST',
data: {
slideDistance: this.sliderPosition,
},
success: (res) => {
if (res.statusCode === 200) {
uni.showToast({ title: '验证成功', icon: 'success' });
} else {
uni.showToast({ title: '验证失败', icon: 'none' });
}
this.fetchCaptcha(); // 重新加载验证码
},
});
},
},
};
</script>
<style lang="scss" scoped>
/* .container {
position: relative;
width: 200px;
height: 100px;
margin: 0 auto;
}
.captcha {
width: 100%;
}
.slider {
position: absolute;
top: 0;
width: 50px;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
text-align: center;
line-height: 100px;
} */
.container {
position: relative;
width: 300px;
height: 100px;
/* margin: 0 auto; */
}
.captcha {
height: 100px;
}
.slider {
position: absolute;
top: 0;
width: 50px;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
text-align: center;
line-height: 100px;
}
</style>
CaptchaPopup.vue
<template>
<view class="container">
<!-- <button @click="showPopup = true">显示滑动验证码</button> -->
<!-- v-model="showPopup" -->
<!-- <slider-captcha /> -->
<!-- <u-popup></u-popup> -->
<!-- <u-popup :v-model="true">
出淤泥而不染,濯清涟而不妖
</u-popup> -->
<u-popup :show="show" mode="center">
<view style="margin-left: 0rpx;">
<slider-captcha />
</view>
</u-popup>
<u-button @click="show = true">打开</u-button>
</view>
</template>
<script>
import SliderCaptcha from '@/components/SliderCaptcha.vue';
// import Popup from '@/components/uni-popup/uni-popup.vue'; // 确保路径正确
export default {
components: {
SliderCaptcha
// Popup
},
data() {
return {
show: false,
};
},
};
</script>