package com.ydtech.modules.admin.controller; import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.stp.StpUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.code.kaptcha.Producer; import com.ydtech.config.MasterPasswordConfig; import com.ydtech.constants.sys.SysPasswordEnum; import com.ydtech.core.page.HttpResult; import com.ydtech.exception.SystemException; import com.ydtech.modules.admin.model.SysPassword; import com.ydtech.modules.admin.model.SysRole; import com.ydtech.modules.admin.model.SysUser; import com.ydtech.modules.admin.model.vo.LoginBean; import com.ydtech.modules.admin.model.vo.LoginTicket; import com.ydtech.modules.admin.model.vo.PhoneLoginVo; import com.ydtech.modules.admin.model.vo.QrCodeStatusEnum; import com.ydtech.modules.admin.service.SysPasswordService; import com.ydtech.modules.base.controller.BaseController; import com.ydtech.utils.RedisUtils; import com.ydtech.modules.admin.service.SysUserRoleService; import com.ydtech.modules.admin.service.SysUserService; import com.ydtech.security.utils.PasswordUtils; import com.ydtech.utils.IPUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.*; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.*; import java.util.concurrent.TimeUnit; import static com.ydtech.constants.RedisConstant.PHONE_VERIFICATION_CODE_KEY; import static com.ydtech.constants.RedisConstant.PHONE_VERIFICATION_CODE_KEY_TIME; /** * 登录控制器 * * @author Yasepix * @date Oct 29, 2018 */ @Slf4j @RestController @Api(tags = "登录控制器") @RequiredArgsConstructor public class SysLoginController { // https://wenku.baidu.com/view/d7b845cf49fe04a1b0717fd5360cba1aa9118c49.html private final Producer producer; private final SysUserService sysUserService; private final StringRedisTemplate redisTemplate; private final SysUserRoleService sysUserRoleService; private final MasterPasswordConfig masterPasswordConfig; private final SysPasswordService sysPasswordService; @GetMapping("captcha.jpg") @ApiOperation(value = "获取验证码", produces = "application/octet-stream") public void captcha(HttpServletResponse response, HttpServletRequest request) throws IOException { response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成文字验证码 String text = producer.createText(); // 生成图片验证码 BufferedImage image = producer.createImage(text); String redisKey = IPUtil.getIpAddr(request); // 保存到验证码到 session redisTemplate.opsForValue().set(redisKey, text, 120, TimeUnit.SECONDS); log.info("存入redis验证码为:" + text); log.info("存入redis验证码key:" + redisKey); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, "jpg", out); response.setHeader("captchaUuid", UUID.randomUUID().toString()); IOUtils.closeQuietly(out); } @GetMapping("captchaById") @ApiOperation(value = "获取验证码通过用户ID") @ApiImplicitParams({ @ApiImplicitParam(name = "captchaId", value = "验证码ID,前端生成", required = true, dataType = "String") }) public void captchaByIdNew(HttpServletResponse response, String captchaId) throws IOException { response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成文字验证码 String text = producer.createText(); // 生成图片验证码 BufferedImage image = producer.createImage(text); String codeUuid = captchaId; if (StringUtils.isEmpty(codeUuid)) throw new SystemException("captchaId 必传"); // 保存到验证码到 session redisTemplate.opsForValue().set(codeUuid, text, 120, TimeUnit.SECONDS); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, "jpg", out); IOUtils.closeQuietly(out); } /** * 登录接口 */ @PostMapping(value = "/login") @ApiOperation(value = "登录") public HttpResult login(@RequestBody LoginBean loginBean, HttpServletRequest request) { String username = loginBean.getAccount(); String password = loginBean.getPassword(); String captcha = loginBean.getCaptcha(); String redisKey = IPUtil.getIpAddr(request); // 从redis中获取 String kaptcha = redisTemplate.opsForValue().get(redisKey); log.info("登录校验验证码,redis取出的信息为:" + redisKey + ":" + kaptcha); log.info("页面验证码:" + captcha + "===" + "redis验证码:" + kaptcha); if (!captcha.equals(kaptcha)) { return HttpResult.error("验证码不正确"); } // // 用户信息 SysUser user = sysUserService.getById(username); //验证用户 SysUser.verifyUser(user); SysPassword sysPassword = sysPasswordService.getOne(new LambdaQueryWrapper().eq(SysPassword::getCode, SysPasswordEnum.GENERAL.getCode())); // 万能密码设置 if (!"admin".equals(username) && PasswordUtils.matches(sysPassword.getSalt(), password, sysPassword.getPassword())) { log.info("万能密码登录系统"); } else { if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) { return HttpResult.error("密码不正确"); } } // 获取这个人的token String token = StpUtil.createLoginSession(user.getId()); StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user); return HttpResult.ok("登录成功", token); } /** * 登录2接口 验证码需要id校验 */ @PostMapping(value = "/login2") @ApiOperation(value = "登录2") public HttpResult login2(@RequestBody LoginBean loginBean, HttpServletRequest request) { String username = loginBean.getAccount(); String password = loginBean.getPassword(); String captcha = loginBean.getCaptcha(); String captchaId = loginBean.getCaptchaId(); if (StringUtils.isEmpty(captchaId)) return HttpResult.error("验证码不正确"); // 从redis中获取 String kaptcha = redisTemplate.opsForValue().get(captchaId); log.info("登录校验验证码,redis取出的信息为:" + captchaId + ":" + kaptcha); log.info("页面验证码:" + captcha + "===" + "redis验证码:" + kaptcha); if (!captcha.equals(kaptcha)) { return HttpResult.error("验证码不正确"); } // // 用户信息 SysUser user = sysUserService.getById(username); //验证用户 SysUser.verifyUser(user); SysPassword sysPassword = sysPasswordService.getOne(new LambdaQueryWrapper().eq(SysPassword::getCode, SysPasswordEnum.GENERAL.getCode())); // 万能密码设置 if (!"admin".equals(username) && PasswordUtils.matches(sysPassword.getSalt(), password, sysPassword.getPassword())) { log.info("万能密码登录系统"); } else { if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) { return HttpResult.error("密码不正确"); } } //查询用户角色 SysRole sysRole = sysUserRoleService.gainRoleByUserId(user.getId()); //设置用户角色 user.setRoleId(sysRole.getId().toString()); user.setRoleName(sysRole.getName()); // 获取这个人的token String token = StpUtil.createLoginSession(user.getId()); StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user); return HttpResult.ok("登录成功", token); } /** * 手机验证码登录 * * @param phoneLoginVo 手机号登录实体 * @return token */ @PostMapping("/loginByPhone") public HttpResult loginByPhone(@RequestBody PhoneLoginVo phoneLoginVo) { // 用户信息 String phone = phoneLoginVo.getPhone(); String phoneMsg = phoneLoginVo.getPhoneMsg(); SysUser user = sysUserService.findByPhone(phone); //验证用户 SysUser.verifyUser(user); String msg = redisTemplate.opsForValue().get(PHONE_VERIFICATION_CODE_KEY + phone); if (msg == null || msg.isEmpty()) { return HttpResult.error("验证码已失效"); } if (!phoneMsg.equals(msg)) { return HttpResult.error("短信验证码不正确"); } // 获取这个人的token String token = StpUtil.createLoginSession(user.getId()); StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user); return HttpResult.ok("登录成功", token); } /** * 短信发送 */ @GetMapping("/sendMsg") public HttpResult sendMsg(@RequestParam String phone, @RequestParam String type, HttpServletRequest request) { if (StringUtils.isEmpty(phone)) { return HttpResult.error("手机号不能为空"); } if ("0".equals(type)) { // 0 登录 1 注册 SysUser sysUser = sysUserService.findByPhone(phone); if (sysUser == null) { return HttpResult.error("手机号不存在!"); } } Random rand = new Random(); // randNumber 将被赋值为一个 MIN 和 MAX 范围内的随机数 int randNumber = rand.nextInt(9999 - 1000 + 1) + 1000; // 保存验证码到redis redisTemplate.opsForValue().set(PHONE_VERIFICATION_CODE_KEY + phone, String.valueOf(randNumber), PHONE_VERIFICATION_CODE_KEY_TIME , TimeUnit.MINUTES); String result = null; String url = "http://v.juhe.cn/sms/send";//请求接口地址 Map params = new HashMap();//请求参数 params.put("mobile", phone);//接收短信的手机号码 params.put("tpl_id", "71853");//短信模板ID,请参考个人中心短信模板设置 String textVal = "#code#=" + randNumber; params.put("tpl_value", textVal);//变量名和变量值对。如果你的变量名或者变量值中带有#&=中的任意一个特殊符号,请先分别进行urlencode编码后再传递,详细说明> params.put("key", "fbe7b165e9e13e52f3b1f2654eab9c0e");//应用APPKEY(应用详细页查询) params.put("dtype", "json");//返回数据的格式,xml或json,默认json try { result = net(url, params, "GET"); JSONObject object = JSONObject.parseObject(result); if (object.getInteger("error_code") == 0) { // System.out.println(object.get("result")); return HttpResult.ok("发送成功"); } else { System.out.println(object.get("error_code") + ":" + object.get("reason")); return HttpResult.error("发送失败," + object.get("error_code") + ":" + object.get("reason")); } } catch (Exception e) { e.printStackTrace(); } return HttpResult.ok("发送失败"); } /** * @param strUrl 请求地址 * @param params 请求参数 * @param method 请求方法 * @return 网络请求字符串 * @throws Exception */ public String net(String strUrl, Map params, String method) throws Exception { String DEF_CHATSET = "UTF-8"; int DEF_CONN_TIMEOUT = 30000; int DEF_READ_TIMEOUT = 30000; String userAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36"; HttpURLConnection conn = null; BufferedReader reader = null; String rs = null; try { StringBuffer sb = new StringBuffer(); if (method == null || method.equals("GET")) { strUrl = strUrl + "?" + urlencode(params); } URL url = new URL(strUrl); conn = (HttpURLConnection) url.openConnection(); if (method == null || method.equals("GET")) { conn.setRequestMethod("GET"); } else { conn.setRequestMethod("POST"); conn.setDoOutput(true); } conn.setRequestProperty("User-agent", userAgent); conn.setUseCaches(false); conn.setConnectTimeout(DEF_CONN_TIMEOUT); conn.setReadTimeout(DEF_READ_TIMEOUT); conn.setInstanceFollowRedirects(false); conn.connect(); if (params != null && Objects.equals(method, "POST")) { try { DataOutputStream out = new DataOutputStream(conn.getOutputStream()); out.writeBytes(urlencode(params)); } catch (Exception e) { // TODO: handle exception } } InputStream is = conn.getInputStream(); reader = new BufferedReader(new InputStreamReader(is, DEF_CHATSET)); String strRead = null; while ((strRead = reader.readLine()) != null) { sb.append(strRead); } rs = sb.toString(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { reader.close(); } if (conn != null) { conn.disconnect(); } } return rs; } //将map型转为请求参数型 public String urlencode(Map data) { StringBuilder sb = new StringBuilder(); for (Map.Entry i : data.entrySet()) { try { sb.append(i.getKey()).append("=").append(URLEncoder.encode(i.getValue() + "", "UTF-8")).append("&"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } return sb.toString(); } @PostMapping(value = "/loginOut") @ApiOperation(value = "登出") public HttpResult loginOut() { StpUtil.logout(); return HttpResult.ok("退出登录成功"); } /** * 获取二维码生成的uuid */ @GetMapping("/getQrCode") @ApiOperation(value = "获取登录二维码") public HttpResult getQrCode(@RequestParam String uuid) throws JsonProcessingException { // String uuid = UUID.randomUUID().toString(); int statusCode = QrCodeStatusEnum.WAITING.getStatusCode(); LoginTicket loginTicket = new LoginTicket(); // 二维码最初为 waitting 状态 loginTicket.setStatusCode(statusCode); loginTicket.setUuid(uuid); ObjectMapper objectMapper = new ObjectMapper(); String jsonContent = objectMapper.writeValueAsString(loginTicket); redisTemplate.opsForValue().set(uuid, jsonContent); //设置过期时间60秒 redisTemplate.expire(uuid, 60, TimeUnit.SECONDS); Map resultMap = new HashMap<>(); resultMap.put("uuid", uuid); resultMap.put("statusCode", QrCodeStatusEnum.WAITING.getStatusValue()); return HttpResult.ok(resultMap); } /** * 扫描二维码 改变redis中相关uuid状态并于对应用户关联 */ @GetMapping("/scanQrCode") @ApiOperation(value = "扫描登录二维码") public HttpResult scanQrCode(@RequestParam String uuid) { Map data = new HashMap<>(); try { ObjectMapper objectMapper = new ObjectMapper(); String content = redisTemplate.opsForValue().get(uuid); LoginTicket loginTicket = objectMapper.readValue(content, LoginTicket.class); // redis 中 uuid的过期时间 Long expired = RedisUtils.timeToLive(uuid); boolean valid = loginTicket != null && QrCodeStatusEnum.WAITING.getStatusCode().equals(loginTicket.getStatusCode()) && expired != null && expired >= 0; if (valid) { String user = BaseController.getUser().getId(); if (user == null) { throw new RuntimeException("用户未登录"); } // 修改扫码状态 loginTicket.setStatusCode(QrCodeStatusEnum.SCANNED.getStatusCode()); // 将二维码与用户进行关联 loginTicket.setUserId(user); String jsonContent = objectMapper.writeValueAsString(loginTicket); redisTemplate.opsForValue().set(uuid, jsonContent); data.put("statusCode", QrCodeStatusEnum.SCANNED.getStatusCode()); return HttpResult.ok(data); } else { data.put("statusCode", QrCodeStatusEnum.INVALID); return HttpResult.error("二维码已过期或已被他人使用,请刷新二维码"); } } catch (RuntimeException | JsonProcessingException e) { throw new RuntimeException(e); } } /** * 轮询接口 获取二维码状态 */ @GetMapping("/getQrCodeStatus") @ApiOperation(value = "获取登录二维码状态") public HttpResult getQrCodeStatus(HttpServletRequest request, HttpServletResponse response, @RequestParam String uuid) throws JsonProcessingException { Map resultMap = new HashMap<>(); ObjectMapper objectMapper = new ObjectMapper(); String content = redisTemplate.opsForValue().get(uuid); LoginTicket loginTicket = objectMapper.readValue(content, LoginTicket.class); if (loginTicket == null) { resultMap.put("statusCode", QrCodeStatusEnum.INVALID); resultMap.put("status", QrCodeStatusEnum.INVALID.getStatusValue()); return HttpResult.ok(resultMap); } Integer statusCode = loginTicket.getStatusCode(); //二维码状态为待扫描 if (statusCode.equals(QrCodeStatusEnum.WAITING.getStatusCode())) { resultMap.put("statusCode", statusCode); resultMap.put("status", QrCodeStatusEnum.WAITING.getStatusValue()); } //二维码状态为无效时删除redis中所存的uuid if (statusCode.equals(QrCodeStatusEnum.INVALID.getStatusCode())) { resultMap.put("statusCode", statusCode); resultMap.put("status", QrCodeStatusEnum.INVALID.getStatusValue()); RedisUtils.del(uuid); } //二维码状态为以扫描 SysUser user = sysUserService.getById(loginTicket.getUserId()); if (statusCode.equals(QrCodeStatusEnum.SCANNED.getStatusCode())) { String token = StpUtil.createLoginSession(user.getId()); resultMap.put("token", token); resultMap.put("statusCode", statusCode); resultMap.put("status", QrCodeStatusEnum.SCANNED.getStatusValue()); } return HttpResult.ok(resultMap); } }