记录使用kaptcha的过程
为前后端分离项目,前端vue框架

文章目录

  • 0.下载jar包
  • 1.添加依赖
  • 2.添加KaptchaConfig配置
  • 3.后端用于登录的封装类编写
  • 4.修改controller层代码与前端交互
    • 后端:
    • 前端:
  • 5.密码加盐和token相关的代码
  • 6.测试

0.下载jar包

在maven的网站https://mvnrepository.com/search?q=com.github.penggle
搜索下载jar

导入到工程中

1.添加依赖

<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>5.4.1</version></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.1</version></dependency><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-core</artifactId><version>2.1.14</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency>

后面的依赖是因为测试的时候有报错添加的,以防万一可以添加一下

2.添加KaptchaConfig配置

package com.louis.mango.config;import java.util.Properties;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;@Configuration
public class KaptchaConfig {@Beanpublic DefaultKaptcha producer() {Properties properties = new Properties();properties.put("kaptcha.border", "no");properties.put("kaptcha.textproducer.font.color", "black");properties.put("kaptcha.textproducer.char.space", "5");Config config = new Config(properties);DefaultKaptcha defaultKaptcha = new DefaultKaptcha();defaultKaptcha.setConfig(config);return defaultKaptcha;}
}

注意注解 @Bean,不添加这个注解的话后面注入时会报无法注入。

3.后端用于登录的封装类编写

LoginBean:

public class LoginBean {private String account;private String password;private String captcha;public String getAccount() {return account;}public void setAccount(String account) {this.account = account;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getCaptcha() {return captcha;}public void setCaptcha(String captcha) {this.captcha = captcha;}}

4.修改controller层代码与前端交互

后端:

@RestController
public class SysLoginController {@Autowiredprivate Producer producer;@Autowiredprivate SysUserService sysUserService;@Autowiredprivate AuthenticationManager authenticationManager;@GetMapping("captcha.jpg")public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException {response.setHeader("Cache-Control", "no-store, no-cache");response.setContentType("image/jpeg");// 生成文字验证码String text = producer.createText();// 生成图片验证码BufferedImage image = producer.createImage(text);// 保存到验证码到 sessionrequest.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, text);ServletOutputStream out = response.getOutputStream();ImageIO.write(image, "jpg", out);  IOUtils.closeQuietly(out);}/*** 登录接口*/@PostMapping(value = "/login")//public HttpResultpublic String login(@RequestBody LoginBean loginBean, HttpServletRequest request) throws IOException {String username = loginBean.getAccount();String password = loginBean.getPassword();String captcha = loginBean.getCaptcha();// 从session中获取之前保存的验证码跟前台传来的验证码进行匹配Object kaptcha = request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);if (kaptcha == null) {//return HttpResult.error("验证码已失效");return "已经失效";}if (!captcha.equals(kaptcha)) {return "验证码不正确";//return HttpResult.error("验证码不正确");}// 用户信息SysUser user = sysUserService.findByName(username);System.out.println(user);// 账号不存在、密码错误if (user == null) {//return HttpResult.error("账号不存在");return "账号不存在";}if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {return "密码不正确";}// 账号锁定if (user.getStatus() == 0) {return "账号已被锁定,请联系管理员";}// 系统登录认证JwtAuthenticatioToken token = SecurityUtils.login(request, username, password, authenticationManager);System.out.println("yes!");System.out.println(token);
//
//      return HttpResult.ok(token);
//  }return "ok";}}

前端:

template:

<template><el-form :model="loginForm" :rules="fieldRules" ref="loginForm" label-position="left" label-width="0px" class="demo-ruleForm login-container"><span class="tool-bar"></span><h2 class="title" style="padding-left:22px;" >系统登录</h2><el-form-item prop="account"><el-input type="text" v-model="loginForm.account" auto-complete="off" placeholder="账号"></el-input></el-form-item><el-form-item prop="password"><el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码"></el-input></el-form-item><el-form-item ><el-col :span="12"><el-form-item prop="captcha"><el-input type="test" v-model="loginForm.captcha" auto-complete="off" placeholder="验证码, 单击图片刷新"style="width: 100%;"></el-input></el-form-item></el-col><el-col class="line" :span="1">&nbsp;</el-col><el-col :span="11"><el-form-item><img style="width: 100%;" class="pointer" :src="loginForm.src" @click="refreshCaptcha"></el-form-item></el-col></el-form-item><el-form-item style="width:100%;"><el-button type="primary" style="width:48%;" @click.native.prevent="reset">重 置</el-button><el-button type="primary" style="width:48%;" @click.native.prevent="login" :loading="loading">登 录</el-button></el-form-item></el-form>
</template>

script:

export default {data() {return {loading: false,loginForm: {account: 'admin',password: 'admin',captcha:'',src: ''},fieldRules: {account: [{ required: true, message: '请输入账号', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' }]},checked: true}},methods: {login() {this.loading = truelet userInfo = { account:this.loginForm.account, password:this.loginForm.password,captcha:this.loginForm.captcha }this.$api.login.login(userInfo).then((res) => {if (res == "ok" ) {window.sessionStorage.setItem('flag','ok'); // session 放置-->this.$message.success("登陆成功!!!");this.$router.push({ path: "/home"});}this.loading = false}).catch((res) => {this.$message({ message: res.message, type: 'error' })})},refreshCaptcha: function(){this.loginForm.src = "http://localhost:8001/captcha.jpg?t=" + new Date().getTime();},reset() {this.$refs.loginForm.resetFields()}mounted() {this.refreshCaptcha()}
}

关于其中api:

目录:
(src下)

index.js:

import api from './api'const install = Vue => {if (install.installed)return;install.installed = true;Object.defineProperties(Vue.prototype, {// 注意,此处挂载在 Vue 原型的 $api 对象上$api: {get() {return api}}})
}export default install

login.js:

import axios from '../axios'/* * 系统登录模块*/// 登录
export const login = data => {return axios({url: 'login',method: 'post',data})
}// 登出
export const logout = () => {return axios({url: 'logout',method: 'get'})
}

axios.js:

import axios from 'axios';
import config from './config';
import Cookies from "js-cookie";
import router from '@/router'export default function $axios(options) {return new Promise((resolve, reject) => {const instance = axios.create({baseURL: config.baseUrl,headers: config.headers,timeout: config.timeout,withCredentials: config.withCredentials})// request 请求拦截器instance.interceptors.request.use(config => {let token = Cookies.get('token')// 发送请求时携带tokenif (token) {config.headers.token = token} else {// 重定向到登录页面router.push('/login')}return config},error => {// 请求发生错误时console.log('request:', error)// 判断请求超时if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {console.log('timeout请求超时')}// 需要重定向到错误页面const errorInfo = error.responseconsole.log(errorInfo)if (errorInfo) {error = errorInfo.data  // 页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.rejectconst errorStatus = errorInfo.status; // 404 403 500 ...router.push({path: `/error/${errorStatus}`})}return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息})// response 响应拦截器instance.interceptors.response.use(response => {return response.data},err => {if (err && err.response) {switch (err.response.status) {case 400:err.message = '请求错误'breakcase 401:err.message = '未授权,请登录'breakcase 403:err.message = '拒绝访问'breakcase 404:err.message = `请求地址出错: ${err.response.config.url}`breakcase 408:err.message = '请求超时'breakcase 500:err.message = '服务器内部错误'breakcase 501:err.message = '服务未实现'breakcase 502:err.message = '网关错误'breakcase 503:err.message = '服务不可用'breakcase 504:err.message = '网关超时'breakcase 505:err.message = 'HTTP版本不受支持'breakdefault:}}console.error(err)return Promise.reject(err) // 返回接口返回的错误信息})// 请求处理instance(options).then(res => {resolve(res)return false}).catch(error => {reject(error)})})
}

mock目录下的index.js:

import Mock from 'mockjs'import * as login from './modules/login'
import * as user from './modules/user'
import * as role from './modules/role'
import * as dept from './modules/dept'
import * as menu from './modules/menu'
import * as dict from './modules/dict'
import * as config from './modules/config'
import * as log from './modules/log'
import * as loginlog from './modules/loginlog'// 1. 开启/关闭[所有模块]拦截, 通过调[openMock参数]设置.
// 2. 开启/关闭[业务模块]拦截, 通过调用fnCreate方法[isOpen参数]设置.
// 3. 开启/关闭[业务模块中某个请求]拦截, 通过函数返回对象中的[isOpen属性]设置.
let openMock = true
//let openMock = false
fnCreate(user, openMock)
fnCreate(role, openMock)
fnCreate(dept, openMock)
fnCreate(menu, openMock)
fnCreate(dict, openMock)
fnCreate(config, openMock)
fnCreate(log, openMock)
fnCreate(loginlog, openMock)
fnCreate(login, openMock)/*** 创建mock模拟数据* @param {*} mod 模块* @param {*} isOpen 是否开启?*/
function fnCreate (mod, isOpen = true) {if (isOpen) {for (var key in mod) {((res) => {if (res.isOpen !== false) {let url = "http://localhost:8001/"if(!url.endsWith("/")) {url = url + "/"}url = url + res.urlMock.mock(new RegExp(url), res.type, (opts) => {opts['data'] = opts.body ? JSON.parse(opts.body) : nulldelete opts.bodyconsole.log('\n')console.log('%cmock拦截, 请求: ', 'color:blue', opts)console.log('%cmock拦截, 响应: ', 'color:blue', res.data)return res.data})}})(mod[key]() || {})}}
}

mock目录下的login.js:

/* * 系统登录模块*/// 登录接口
export function login() {const loginData = {"code": 200,"msg": null,"data": {"authorities": [],"details": {"remoteAddress": "0:0:0:0:0:0:0:1","sessionId": "E9E774A8EB4405B25692D84B4521CB45"},"authenticated": false,"principal": "admin","credentials": "admin","token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU0NzkzMTYxOCwiY3JlYXRlZCI6MTU0Nzg4ODQxODgyMiwiYXV0aG9yaXRpZXMiOlt7ImF1dGhvcml0eSI6InN5czptZW51OmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6ZGVsZXRlIn0seyJhdXRob3JpdHkiOiJzeXM6Y29uZmlnOmFkZCJ9LHsiYXV0aG9yaXR5Ijoic3lzOm1lbnU6YWRkIn0seyJhdXRob3JpdHkiOiJzeXM6dXNlcjphZGQifSx7ImF1dGhvcml0eSI6InN5czpkZXB0OmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOnJvbGU6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOnJvbGU6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRpY3Q6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6ZGVsZXRlIn0seyJhdXRob3JpdHkiOiJzeXM6ZGVwdDp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6bWVudTp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6ZGljdDphZGQifSx7ImF1dGhvcml0eSI6InN5czpyb2xlOmFkZCJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRlcHQ6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmNvbmZpZzplZGl0In0seyJhdXRob3JpdHkiOiJzeXM6bG9naW5sb2c6dmlldyJ9LHsiYXV0aG9yaXR5Ijoic3lzOnVzZXI6ZWRpdCJ9LHsiYXV0aG9yaXR5Ijoic3lzOmNvbmZpZzp2aWV3In0seyJhdXRob3JpdHkiOiJzeXM6Y29uZmlnOmRlbGV0ZSJ9LHsiYXV0aG9yaXR5Ijoic3lzOmRlcHQ6YWRkIn0seyJhdXRob3JpdHkiOiJzeXM6cm9sZTpkZWxldGUifSx7ImF1dGhvcml0eSI6InN5czptZW51OmVkaXQifV19.Lw2qb2BJHwmiVMHS_vbaLf7vnTT6frr7vTS2-nJ1Lo0uOduqK6nPtBtgEka-fH7ow-s5n7OH1WZkUvH0PN6oyA","name": "admin"}}return {url: 'login',type: 'post',data: loginData}
}
// 登出接口
export function logout() {const logoutData = {"code": 200,"msg": null,"data": {}}return {url: 'logout',type: 'get',data: logoutData}
}

5.密码加盐和token相关的代码

目录:

PasswordUtils :

public class PasswordUtils {/*** 匹配密码* @param salt 盐* @param rawPass 明文 * @param encPass 密文* @return*/public static boolean matches(String salt, String rawPass, String encPass) {return new PasswordEncoder(salt).matches(encPass, rawPass);}/*** 明文密码加密* @param rawPass 明文* @param salt* @return*/public static String encode(String rawPass, String salt) {return new PasswordEncoder(salt).encode(rawPass);}/*** 获取加密盐* @return*/public static String getSalt() {return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);}
}
  • 密码加密 PasswordEncoder
public class PasswordEncoder {private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d","e", "f" };private final static String MD5 = "MD5";private final static String SHA = "SHA";private Object salt;private String algorithm;public PasswordEncoder(Object salt) {this(salt, MD5);}public PasswordEncoder(Object salt, String algorithm) {this.salt = salt;this.algorithm = algorithm;}/*** 密码加密* @param rawPass* @return*/public String encode(String rawPass) {String result = null;try {MessageDigest md = MessageDigest.getInstance(algorithm);// 加密后的字符串result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));} catch (Exception ex) {}return result;}/*** 密码匹配验证* @param encPass 密文* @param rawPass 明文* @return*/public boolean matches(String encPass, String rawPass) {String pass1 = "" + encPass;String pass2 = encode(rawPass);return pass1.equals(pass2);}private String mergePasswordAndSalt(String password) {if (password == null) {password = "";}if ((salt == null) || "".equals(salt)) {return password;} else {return password + "{" + salt.toString() + "}";}}/*** 转换字节数组为16进制字串* * @param b*            字节数组* @return 16进制字串*/private String byteArrayToHexString(byte[] b) {StringBuffer resultSb = new StringBuffer();for (int i = 0; i < b.length; i++) {resultSb.append(byteToHexString(b[i]));}return resultSb.toString();}/*** 将字节转换为16进制* @param b* @return*/private static String byteToHexString(byte b) {int n = b;if (n < 0)n = 256 + n;int d1 = n / 16;int d2 = n % 16;return hexDigits[d1] + hexDigits[d2];}
  • Security相关操作
public class SecurityUtils {/*** 系统登录认证* @param request* @param username* @param password* @param authenticationManager* @return*/public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 执行登录认证过程Authentication authentication = authenticationManager.authenticate(token);// 认证成功存储认证信息到上下文SecurityContextHolder.getContext().setAuthentication(authentication);// 生成令牌并返回给客户端token.setToken(JwtTokenUtils.generateToken(authentication));return token;}/*** 获取令牌进行认证* @param request*/public static void checkAuthentication(HttpServletRequest request) {// 获取令牌并根据令牌获取登录认证信息Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request);// 设置登录认证信息到上下文SecurityContextHolder.getContext().setAuthentication(authentication);}/*** 获取当前用户名* @return*/public static String getUsername() {String username = null;Authentication authentication = getAuthentication();if(authentication != null) {Object principal = authentication.getPrincipal();if(principal != null && principal instanceof UserDetails) {username = ((UserDetails) principal).getUsername();}}return username;}/*** 获取用户名* @return*/public static String getUsername(Authentication authentication) {String username = null;if(authentication != null) {Object principal = authentication.getPrincipal();if(principal != null && principal instanceof UserDetails) {username = ((UserDetails) principal).getUsername();}}return username;}/*** 获取当前登录信息* @return*/public static Authentication getAuthentication() {if(SecurityContextHolder.getContext() == null) {return null;}Authentication authentication = SecurityContextHolder.getContext().getAuthentication();return authentication;}}
  • JWT工具类
public class JwtTokenUtils implements Serializable {private static final long serialVersionUID = 1L;/*** 用户名称*/private static final String USERNAME = Claims.SUBJECT;/*** 创建时间*/private static final String CREATED = "created";/*** 权限列表*/private static final String AUTHORITIES = "authorities";/*** 密钥*/private static final String SECRET = "abcdefgh";/*** 有效期12小时*/private static final long EXPIRE_TIME = 12 * 60 * 60 * 1000;/*** 生成令牌** @return 令牌*/public static String generateToken(Authentication authentication) {Map<String, Object> claims = new HashMap<>(3);claims.put(USERNAME, SecurityUtils.getUsername(authentication));claims.put(CREATED, new Date());claims.put(AUTHORITIES, authentication.getAuthorities());return generateToken(claims);}/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private static String generateToken(Map<String, Object> claims) {Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();}/*** 从令牌中获取用户名** @param token 令牌* @return 用户名*/public static String getUsernameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username = claims.getSubject();} catch (Exception e) {username = null;}return username;}/*** 根据请求令牌获取登录认证信息* @return 用户名*/public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {Authentication authentication = null;// 获取请求携带的令牌String token = JwtTokenUtils.getToken(request);if(token != null) {// 请求令牌不能为空if(SecurityUtils.getAuthentication() == null) {// 上下文中Authentication为空Claims claims = getClaimsFromToken(token);if(claims == null) {return null;}String username = claims.getSubject();if(username == null) {return null;}if(isTokenExpired(token)) {return null;}Object authors = claims.get(AUTHORITIES);List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();if (authors != null && authors instanceof List) {for (Object object : (List) authors) {authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority")));}}authentication = new JwtAuthenticatioToken(username, null, authorities, token);} else {if(validateToken(token, SecurityUtils.getUsername())) {// 如果上下文中Authentication非空,且请求令牌合法,直接返回当前登录认证信息authentication = SecurityUtils.getAuthentication();}}}return authentication;}/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/private static Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;}/*** 验证令牌* @param token* @param username* @return*/public static Boolean validateToken(String token, String username) {String userName = getUsernameFromToken(token);return (userName.equals(username) && !isTokenExpired(token));}/*** 刷新令牌* @param token* @return*/public static String refreshToken(String token) {String refreshedToken;try {Claims claims = getClaimsFromToken(token);claims.put(CREATED, new Date());refreshedToken = generateToken(claims);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}/*** 判断令牌是否过期** @param token 令牌* @return 是否过期*/public static Boolean isTokenExpired(String token) {try {Claims claims = getClaimsFromToken(token);Date expiration = claims.getExpiration();return expiration.before(new Date());} catch (Exception e) {return false;}}/*** 获取请求token* @param request* @return*/public static String getToken(HttpServletRequest request) {String token = request.getHeader("Authorization");String tokenHead = "Bearer ";if(token == null) {token = request.getHeader("token");} else if(token.contains(tokenHead)){token = token.substring(tokenHead.length());} if("".equals(token)) {token = null;}return token;}}

6.测试



后端打印的内容:

【项目】springboot中使用kaptcha生成验证码,登录时密码加盐处理相关推荐

  1. Javaweb中利用kaptcha生成验证码

    引入kaptcha-2.3-jdk15.jar包 在web.xml中进行配置 <servlet> <servlet-name>Kaptcha</servlet-name& ...

  2. 手把手带你在集成SpringSecurity的SpringBoot应用中添加短信验证码登录认证功能

    本文目录 前言 1 自定义AuthenticationToken类 2 自定义AuthenticationProvider类 3 自定义MobilePhoneAuthenticationFilter ...

  3. 使用kaptcha生成验证码

    2019独角兽企业重金招聘Python工程师标准>>> kaptcha是一个简单好用的验证码生成工具,通过配置,可以自己定义验证码大小.颜色.显示的字符等等.下面就来讲一下如何使用k ...

  4. Java Web学习总结(22)——使用kaptcha生成验证码

    kaptcha是一个简单好用的验证码生成工具,通过配置,可以自己定义验证码大小.颜色.显示的字符等等.下面就来讲一下如何使用kaptcha生成验证码以及在服务器端取出验证码进行校验. 一.搭建测试环境 ...

  5. Abp Core 添加短信验证码登录(动态密码登录)

    交流QQ群:555913397 有什么问题可以加群大家一起交流 Abp Core 添加短信验证码登录(动态密码登录) 现目前我国网站的已经很少使用电子邮箱了,基本上都是手机号作为账号,有时候粗心的用户 ...

  6. D-Link登录时密码错误输入三次而无法登录问题的处理

    登录路由器配置界面须在浏览器地址栏输入192.168.0.1,在下面的对话框中填入用户名和密码.默认状态下用户名为admin,密码为空.   但是路由器登录时密码如果填写三次均错误时,会出现如下界面: ...

  7. 网页登录时密码如何传输?

    今天突发奇想想看下一般网站登录时密码是如何传输的. 首先是QQMail,gmail,各大网上银行等对于我非常重要的登录网站:      https      https的安全性自然是很高. 其次是通常 ...

  8. SpringMvc项目中使用GoogleKaptcha 生成验证码

    前言:google captcha 是google生成验证码的一个工具类,其原理是将随机生成字符串保存到session中,同时以图片的形式返回给页面,之后前台页面提交到后台进行对比. 1.jar包准备 ...

  9. java验证码画布类型,【Java工具类】使用Kaptcha生成验证码写回页面中

    1. 导入依赖 导入kaptcha依赖: com.github.penggle kaptcha 2.3.2 2. 编写配置类: @Configuration public class KaptchaC ...

最新文章

  1. mysql json类型数组索引_MySQL JSON 类型数据操作
  2. JavaScript操作select控件
  3. vs2017c语言程序添加图标,笔试编程必备技巧——Visual Studio 2017添加自定义代码片段...
  4. sql中“delete from 表名”表示_SQL查询语句知识点总结
  5. 南加州大学等开源元学习研究库learn2learn
  6. Cards BZOJ 1004
  7. 线程安全的三大不安全案例以及解决方法
  8. cocos2dx中的动作
  9. 下载安装谷歌浏览器插件
  10. “未安装任何音频输出设备”解决办法
  11. 学习制作FlappyBird时遇到的问题
  12. uniapp canvas 刮刮乐
  13. 给定3个数字,求出这3个数字中的最大数,并输出最大数
  14. 人脸识别 —— insightface
  15. Halcon的常见错误
  16. python基础教程:易忽视知识点小结
  17. 官方文档翻译《The Libra Blockchain》之执行交易(二)
  18. Oracle Study之--resmgr:cpu quantum等待事件
  19. 计算机视觉领域稍微容易中的期刊系列
  20. python 天天向上续以七天为周期_《天天向上》主持人高天鹤回应考试作弊后续:以后要靠自己去拼搏...

热门文章

  1. c语言判断字符是否等于e,C语言如何实现删除字符串中的字符
  2. 命令回复mysql数据库_用命令行恢复MySQL数据库
  3. 代码讲解java_主要代码的讲解
  4. 抖音怎么上传无损画质_抖音怎么上传高清视频?干货,从此告别模糊
  5. r2游戏服务器网站,云服务器win2008r2玩游戏
  6. dnf无限重连服务器,dnf无限连接服务器失败解决方法
  7. java编程语言大全_JAVA编程语言的基础知识(一)
  8. mysql online ddl和pt_online ddl与pt-osc详解
  9. 获取oracle 表字段描述,几种获取oracle用户表字段信息的方法
  10. html如何让条数按序号输出,JS 怎么控制页面序号较智能的生成?