assess-admin/src/main/java/com/gkhy/assess/admin/controller/CaptchaController.java
对比新文件 @@ -0,0 +1,33 @@ package com.gkhy.assess.admin.controller; import com.gkhy.assess.common.api.CommonResult; import com.gkhy.assess.system.service.CaptchaService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 验证码操作处理 * * @author ruoyi */ @Api(tags = "验证码前端控制器") @RestController @RequestMapping("/system/captcha") public class CaptchaController { @Autowired private CaptchaService captchaService; /** * 生成验证码 */ @ApiOperation(value = "生成验证码") @GetMapping("/captchaImage") public CommonResult captchaImage() { return CommonResult.success(captchaService.captchaImage()); } } assess-common/pom.xml
@@ -107,6 +107,10 @@ <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> </dependency> </dependencies> </project> assess-common/src/main/java/com/gkhy/assess/common/config/CaptchaConfig.java
对比新文件 @@ -0,0 +1,84 @@ package com.gkhy.assess.common.config; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; import static com.google.code.kaptcha.Constants.*; /** * 验证码配置 * */ @Configuration public class CaptchaConfig { @Bean(name = "captchaProducer") public DefaultKaptcha getKaptchaBean() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } @Bean(name = "captchaProducerMath") public DefaultKaptcha getKaptchaBeanMath() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 边框颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); // 验证码文本生成器 properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.gkhy.assess.common.config.KaptchaTextCreator"); // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 验证码噪点颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); // 干扰实现类 properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } assess-common/src/main/java/com/gkhy/assess/common/config/KaptchaTextCreator.java
对比新文件 @@ -0,0 +1,68 @@ package com.gkhy.assess.common.config; import com.google.code.kaptcha.text.impl.DefaultTextCreator; import java.util.Random; /** * 验证码文本生成器 * */ public class KaptchaTextCreator extends DefaultTextCreator { private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); @Override public String getText() { Integer result = 0; Random random = new Random(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); int randomoperands = random.nextInt(3); if (randomoperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append("*"); suChinese.append(CNUMBERS[y]); } else if (randomoperands == 1) { if ((x != 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); suChinese.append("/"); suChinese.append(CNUMBERS[x]); } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } } else { if (x >= y) { result = x - y; suChinese.append(CNUMBERS[x]); suChinese.append("-"); suChinese.append(CNUMBERS[y]); } else { result = y - x; suChinese.append(CNUMBERS[y]); suChinese.append("-"); suChinese.append(CNUMBERS[x]); } } suChinese.append("=?@" + result); return suChinese.toString(); } } assess-common/src/main/java/com/gkhy/assess/common/constant/CacheConstant.java
@@ -11,6 +11,8 @@ String SYS_CONFIG_KEY = "sys_config:"; String CAPTCHA_CODE_KEY = "captcha_codes:"; assess-common/src/main/java/com/gkhy/assess/common/domain/vo/LoginBody.java
@@ -19,6 +19,6 @@ private String password; @ApiModelProperty(value = "验证码",required = false) private String code; @ApiModelProperty(value = "唯一标识",required = false) @ApiModelProperty(value = "验证码唯一标识",required = false) private String uuid; } assess-framework/src/main/java/com/gkhy/assess/framework/aop/LogAspect.java
对比新文件 @@ -0,0 +1,130 @@ package com.gkhy.assess.framework.aop; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.json.JSONObject; import com.gkhy.assess.system.utils.ShiroUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Aspect @Component @Slf4j public class LogAspect { @Pointcut("execution(public * com.gkhy.assess.*.controller..*.*(..))") public void logPointCut(){ } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @Around("logPointCut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{ long startTime = System.currentTimeMillis(); //获取当前请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); StringBuffer requestURL = request.getRequestURL(); JSONObject webLog = new JSONObject(); String urlStr = request.getRequestURL().toString(); webLog.put("basePath",StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath())); webLog.put("ip", ServletUtil.getClientIP(request,null)); webLog.put("method",request.getMethod()); Object params=getParameter(method, joinPoint.getArgs()); webLog.put("parameter",params); webLog.put("uri",request.getRequestURI()); webLog.put("url",requestURL.toString()); String userId=ShiroUtils.getUserId()!=null?String.valueOf(ShiroUtils.getUserId()):""; webLog.put("userId",userId); log.info(webLog.toString()); Object result = joinPoint.proceed(); if (result == null) { //如果切到了 没有返回类型的void方法,这里直接返回 return null; } long endTime = System.currentTimeMillis(); webLog.put("result",result); webLog.put("spendTime",endTime - startTime); log.info(webLog.toString()); return result; } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { //获取当前请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String urlStr = request.getRequestURL().toString(); log.error("@AfterThrowing异常通知:url={},出错了error_message={}", urlStr,e.getMessage()); } /** * 根据方法和传入的参数获取请求参数 */ private Object getParameter(Method method, Object[] args) { List<Object> argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //将RequestBody注解修饰的参数作为请求参数 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } //将RequestParam注解修饰的参数作为请求参数 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); if (requestParam != null) { Map<String, Object> map = new HashMap<>(); String key = parameters[i].getName(); if (StrUtil.isNotEmpty(requestParam.value())) { key = requestParam.value(); } map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } } assess-framework/src/main/java/com/gkhy/assess/framework/config/DruidConfig.java
@@ -80,6 +80,7 @@ String commonJsPattern=pattern.replaceAll("\\*","js/common.js"); final String filePath="support/http/resources/js/common.js"; Filter filter=new Filter() { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest,servletResponse); @@ -91,6 +92,7 @@ text=text.replaceAll("powered.*?shrek.wang</a>",""); servletResponse.getWriter().write(text); } }; assess-framework/src/main/java/com/gkhy/assess/framework/interceptor/LogInterceptor.java
@@ -26,7 +26,7 @@ MDC.put("requestId", requestId); MDC.put("clientIP", ip); MDC.put("url", url); String userId= String.valueOf(ShiroUtils.getUserId()); String userId= ShiroUtils.getUserId()!=null?String.valueOf(ShiroUtils.getUserId()):""; MDC.put("userId", userId); return true; } assess-framework/src/main/java/com/gkhy/assess/framework/shiro/ShiroConfig.java
@@ -75,6 +75,7 @@ chain.addPathDefinition("/system/agency/getAgencyById","anon"); chain.addPathDefinition("/system/user/agencyRegister","anon"); chain.addPathDefinition("/system/captcha/captchaImage","anon"); //除了以上的请求外,其它请求都需要登录 chain.addPathDefinition("/**", "jwtFilter,authc"); // 需登录才能访问 assess-system/src/main/java/com/gkhy/assess/system/domain/vo/CaptchaVO.java
对比新文件 @@ -0,0 +1,14 @@ package com.gkhy.assess.system.domain.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel(value = "验证码对象", description = "验证码对象") public class CaptchaVO { @ApiModelProperty("唯一标识") private String uuid; @ApiModelProperty("验证码图片内容,base64") private String image; } assess-system/src/main/java/com/gkhy/assess/system/service/CaptchaService.java
对比新文件 @@ -0,0 +1,15 @@ package com.gkhy.assess.system.service; import com.gkhy.assess.system.domain.vo.CaptchaVO; import java.awt.image.BufferedImage; public interface CaptchaService { /** * 生成验证码图片 * @return */ CaptchaVO captchaImage(); } assess-system/src/main/java/com/gkhy/assess/system/service/impl/CaptchaServiceImpl.java
对比新文件 @@ -0,0 +1,60 @@ package com.gkhy.assess.system.service.impl; import cn.hutool.core.codec.Base64; import com.gkhy.assess.common.constant.CacheConstant; import com.gkhy.assess.common.exception.ApiException; import com.gkhy.assess.common.utils.RedisUtils; import com.gkhy.assess.system.domain.vo.CaptchaVO; import com.gkhy.assess.system.service.CaptchaService; import com.google.code.kaptcha.Producer; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.FastByteArrayOutputStream; import javax.annotation.Resource; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.UUID; import java.util.concurrent.TimeUnit; @Slf4j @Service public class CaptchaServiceImpl implements CaptchaService { @Resource(name = "captchaProducer") private Producer captchaProducer; @Resource(name = "captchaProducerMath") private Producer captchaProducerMath; @Autowired private RedisUtils redisUtils; @Override public CaptchaVO captchaImage() { String uuid= UUID.randomUUID().toString(); String verifyKey= CacheConstant.CAPTCHA_CODE_KEY+uuid; String capText=captchaProducerMath.createText(); String capStr=capText.substring(0,capText.lastIndexOf("@")); String code=capText.substring(capText.lastIndexOf("@")+1); BufferedImage image=captchaProducerMath.createImage(capStr); redisUtils.set(verifyKey,code,2, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { log.error(e.getMessage()); throw new ApiException("生成验证码失败"); } CaptchaVO captchaVO=new CaptchaVO(); captchaVO.setUuid(uuid); captchaVO.setImage(Base64.encode(os.toByteArray())); return captchaVO; } } assess-system/src/main/java/com/gkhy/assess/system/service/impl/SysUserServiceImpl.java
@@ -1,6 +1,7 @@ package com.gkhy.assess.system.service.impl; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gkhy.assess.common.api.CommonPage; import com.gkhy.assess.common.constant.CacheConstant; @@ -57,6 +58,8 @@ @Override public AccountVO login(LoginBody loginBody) { // 验证码校验 validateCaptcha(loginBody.getUsername(), loginBody.getCode(), loginBody.getUuid()); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginBody.getUsername(), loginBody.getPassword(), false); Subject subject= SecurityUtils.getSubject(); String msg ; @@ -84,6 +87,32 @@ } } /** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public void validateCaptcha(String username, String code, String uuid) { if(StrUtil.isBlank(code)||StrUtil.isBlank(uuid)){ throw new ApiException("验证码或验证码标识为空"); } String verifyKey = CacheConstant.CAPTCHA_CODE_KEY +uuid; String captcha = (String) redisUtils.get(verifyKey); redisUtils.del(verifyKey); if (StrUtil.isBlank(captcha)) { throw new ApiException("验证码已失效"); } if (!code.equalsIgnoreCase(captcha)) { throw new ApiException("验证码不正确"); } } @Override public void logout() { String jwtToken = request.getHeader(JwtTokenUtil.USER_LOGIN_TOKEN); assess-system/src/main/java/com/gkhy/assess/system/utils/ShiroUtils.java
@@ -25,7 +25,7 @@ public static Long getUserId() { return getSysUser().getId().longValue(); return getSysUser()!=null?getSysUser().getId().longValue():null; } public static SysUser getSysUser() pom.xml
@@ -39,6 +39,7 @@ <java-jwt.version>3.11.0</java-jwt.version> <fastjson.version>1.2.76</fastjson.version> <caffeine.version>2.9.3</caffeine.version> <kaptcha.version>2.3.2</kaptcha.version> </properties> <dependencyManagement> <dependencies> @@ -143,6 +144,13 @@ <version>${caffeine.version}</version> </dependency> <!-- 验证码 --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>${kaptcha.version}</version> </dependency> </dependencies> </dependencyManagement>