SysLoginController.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. package com.ydtech.modules.admin.controller;
  2. import cn.dev33.satoken.session.SaSession;
  3. import cn.dev33.satoken.stp.StpUtil;
  4. import com.alibaba.fastjson.JSONObject;
  5. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  6. import com.fasterxml.jackson.core.JsonProcessingException;
  7. import com.fasterxml.jackson.databind.ObjectMapper;
  8. import com.google.code.kaptcha.Producer;
  9. import com.ydtech.config.MasterPasswordConfig;
  10. import com.ydtech.constants.sys.SysPasswordEnum;
  11. import com.ydtech.core.page.HttpResult;
  12. import com.ydtech.exception.SystemException;
  13. import com.ydtech.modules.admin.model.SysPassword;
  14. import com.ydtech.modules.admin.model.SysRole;
  15. import com.ydtech.modules.admin.model.SysUser;
  16. import com.ydtech.modules.admin.model.vo.LoginBean;
  17. import com.ydtech.modules.admin.model.vo.LoginTicket;
  18. import com.ydtech.modules.admin.model.vo.PhoneLoginVo;
  19. import com.ydtech.modules.admin.model.vo.QrCodeStatusEnum;
  20. import com.ydtech.modules.admin.service.SysPasswordService;
  21. import com.ydtech.modules.base.controller.BaseController;
  22. import com.ydtech.utils.RedisUtils;
  23. import com.ydtech.modules.admin.service.SysUserRoleService;
  24. import com.ydtech.modules.admin.service.SysUserService;
  25. import com.ydtech.security.utils.PasswordUtils;
  26. import com.ydtech.utils.IPUtil;
  27. import io.swagger.annotations.Api;
  28. import io.swagger.annotations.ApiImplicitParam;
  29. import io.swagger.annotations.ApiImplicitParams;
  30. import io.swagger.annotations.ApiOperation;
  31. import lombok.RequiredArgsConstructor;
  32. import lombok.extern.slf4j.Slf4j;
  33. import org.apache.commons.lang3.StringUtils;
  34. import org.apache.tomcat.util.http.fileupload.IOUtils;
  35. import org.springframework.data.redis.core.StringRedisTemplate;
  36. import org.springframework.web.bind.annotation.*;
  37. import javax.imageio.ImageIO;
  38. import javax.servlet.ServletOutputStream;
  39. import javax.servlet.http.HttpServletRequest;
  40. import javax.servlet.http.HttpServletResponse;
  41. import java.awt.image.BufferedImage;
  42. import java.io.*;
  43. import java.net.HttpURLConnection;
  44. import java.net.URL;
  45. import java.net.URLEncoder;
  46. import java.util.*;
  47. import java.util.concurrent.TimeUnit;
  48. import static com.ydtech.constants.RedisConstant.PHONE_VERIFICATION_CODE_KEY;
  49. import static com.ydtech.constants.RedisConstant.PHONE_VERIFICATION_CODE_KEY_TIME;
  50. /**
  51. * 登录控制器
  52. *
  53. * @author Yasepix
  54. * @date Oct 29, 2018
  55. */
  56. @Slf4j
  57. @RestController
  58. @Api(tags = "登录控制器")
  59. @RequiredArgsConstructor
  60. public class SysLoginController {
  61. // https://wenku.baidu.com/view/d7b845cf49fe04a1b0717fd5360cba1aa9118c49.html
  62. private final Producer producer;
  63. private final SysUserService sysUserService;
  64. private final StringRedisTemplate redisTemplate;
  65. private final SysUserRoleService sysUserRoleService;
  66. private final MasterPasswordConfig masterPasswordConfig;
  67. private final SysPasswordService sysPasswordService;
  68. @GetMapping("captcha.jpg")
  69. @ApiOperation(value = "获取验证码", produces = "application/octet-stream")
  70. public void captcha(HttpServletResponse response, HttpServletRequest request) throws IOException {
  71. response.setHeader("Cache-Control", "no-store, no-cache");
  72. response.setContentType("image/jpeg");
  73. // 生成文字验证码
  74. String text = producer.createText();
  75. // 生成图片验证码
  76. BufferedImage image = producer.createImage(text);
  77. String redisKey = IPUtil.getIpAddr(request);
  78. // 保存到验证码到 session
  79. redisTemplate.opsForValue().set(redisKey, text, 120, TimeUnit.SECONDS);
  80. log.info("存入redis验证码为:" + text);
  81. log.info("存入redis验证码key:" + redisKey);
  82. ServletOutputStream out = response.getOutputStream();
  83. ImageIO.write(image, "jpg", out);
  84. response.setHeader("captchaUuid", UUID.randomUUID().toString());
  85. IOUtils.closeQuietly(out);
  86. }
  87. @GetMapping("captchaById")
  88. @ApiOperation(value = "获取验证码通过用户ID")
  89. @ApiImplicitParams({
  90. @ApiImplicitParam(name = "captchaId", value = "验证码ID,前端生成", required = true, dataType = "String")
  91. })
  92. public void captchaByIdNew(HttpServletResponse response, String captchaId) throws IOException {
  93. response.setHeader("Cache-Control", "no-store, no-cache");
  94. response.setContentType("image/jpeg");
  95. // 生成文字验证码
  96. String text = producer.createText();
  97. // 生成图片验证码
  98. BufferedImage image = producer.createImage(text);
  99. String codeUuid = captchaId;
  100. if (StringUtils.isEmpty(codeUuid)) throw new SystemException("captchaId 必传");
  101. // 保存到验证码到 session
  102. redisTemplate.opsForValue().set(codeUuid, text, 120, TimeUnit.SECONDS);
  103. ServletOutputStream out = response.getOutputStream();
  104. ImageIO.write(image, "jpg", out);
  105. IOUtils.closeQuietly(out);
  106. }
  107. /**
  108. * 登录接口
  109. */
  110. @PostMapping(value = "/login")
  111. @ApiOperation(value = "登录")
  112. public HttpResult login(@RequestBody LoginBean loginBean, HttpServletRequest request) {
  113. String username = loginBean.getAccount();
  114. String password = loginBean.getPassword();
  115. String captcha = loginBean.getCaptcha();
  116. String redisKey = IPUtil.getIpAddr(request);
  117. // 从redis中获取
  118. String kaptcha = redisTemplate.opsForValue().get(redisKey);
  119. log.info("登录校验验证码,redis取出的信息为:" + redisKey + ":" + kaptcha);
  120. log.info("页面验证码:" + captcha + "===" + "redis验证码:" + kaptcha);
  121. if (!captcha.equals(kaptcha)) {
  122. return HttpResult.error("验证码不正确");
  123. }
  124. //
  125. // 用户信息
  126. SysUser user = sysUserService.getById(username);
  127. //验证用户
  128. SysUser.verifyUser(user);
  129. SysPassword sysPassword = sysPasswordService.getOne(new LambdaQueryWrapper<SysPassword>().eq(SysPassword::getCode, SysPasswordEnum.GENERAL.getCode()));
  130. // 万能密码设置
  131. if (!"admin".equals(username) && PasswordUtils.matches(sysPassword.getSalt(), password,
  132. sysPassword.getPassword())) {
  133. log.info("万能密码登录系统");
  134. } else {
  135. if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {
  136. return HttpResult.error("密码不正确");
  137. }
  138. }
  139. // 获取这个人的token
  140. String token = StpUtil.createLoginSession(user.getId());
  141. StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user);
  142. return HttpResult.ok("登录成功", token);
  143. }
  144. /**
  145. * 登录2接口 验证码需要id校验
  146. */
  147. @PostMapping(value = "/login2")
  148. @ApiOperation(value = "登录2")
  149. public HttpResult login2(@RequestBody LoginBean loginBean, HttpServletRequest request) {
  150. String username = loginBean.getAccount();
  151. String password = loginBean.getPassword();
  152. String captcha = loginBean.getCaptcha();
  153. String captchaId = loginBean.getCaptchaId();
  154. if (StringUtils.isEmpty(captchaId)) return HttpResult.error("验证码不正确");
  155. // 从redis中获取
  156. String kaptcha = redisTemplate.opsForValue().get(captchaId);
  157. log.info("登录校验验证码,redis取出的信息为:" + captchaId + ":" + kaptcha);
  158. log.info("页面验证码:" + captcha + "===" + "redis验证码:" + kaptcha);
  159. if (!captcha.equals(kaptcha)) {
  160. return HttpResult.error("验证码不正确");
  161. }
  162. //
  163. // 用户信息
  164. SysUser user = sysUserService.getById(username);
  165. //验证用户
  166. SysUser.verifyUser(user);
  167. SysPassword sysPassword = sysPasswordService.getOne(new LambdaQueryWrapper<SysPassword>().eq(SysPassword::getCode, SysPasswordEnum.GENERAL.getCode()));
  168. // 万能密码设置
  169. if (!"admin".equals(username) && PasswordUtils.matches(sysPassword.getSalt(), password,
  170. sysPassword.getPassword())) {
  171. log.info("万能密码登录系统");
  172. } else {
  173. if (!PasswordUtils.matches(user.getSalt(), password, user.getPassword())) {
  174. return HttpResult.error("密码不正确");
  175. }
  176. }
  177. //查询用户角色
  178. SysRole sysRole = sysUserRoleService.gainRoleByUserId(user.getId());
  179. //设置用户角色
  180. user.setRoleId(sysRole.getId().toString());
  181. user.setRoleName(sysRole.getName());
  182. // 获取这个人的token
  183. String token = StpUtil.createLoginSession(user.getId());
  184. StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user);
  185. return HttpResult.ok("登录成功", token);
  186. }
  187. /**
  188. * 手机验证码登录
  189. *
  190. * @param phoneLoginVo 手机号登录实体
  191. * @return token
  192. */
  193. @PostMapping("/loginByPhone")
  194. public HttpResult<String> loginByPhone(@RequestBody PhoneLoginVo phoneLoginVo) {
  195. // 用户信息
  196. String phone = phoneLoginVo.getPhone();
  197. String phoneMsg = phoneLoginVo.getPhoneMsg();
  198. SysUser user = sysUserService.findByPhone(phone);
  199. //验证用户
  200. SysUser.verifyUser(user);
  201. String msg = redisTemplate.opsForValue().get(PHONE_VERIFICATION_CODE_KEY + phone);
  202. if (msg == null || msg.isEmpty()) {
  203. return HttpResult.error("验证码已失效");
  204. }
  205. if (!phoneMsg.equals(msg)) {
  206. return HttpResult.error("短信验证码不正确");
  207. }
  208. // 获取这个人的token
  209. String token = StpUtil.createLoginSession(user.getId());
  210. StpUtil.getSessionByLoginId(user.getId()).set(SaSession.USER, user);
  211. return HttpResult.ok("登录成功", token);
  212. }
  213. /**
  214. * 短信发送
  215. */
  216. @GetMapping("/sendMsg")
  217. public HttpResult sendMsg(@RequestParam String phone, @RequestParam String type, HttpServletRequest request) {
  218. if (StringUtils.isEmpty(phone)) {
  219. return HttpResult.error("手机号不能为空");
  220. }
  221. if ("0".equals(type)) { // 0 登录 1 注册
  222. SysUser sysUser = sysUserService.findByPhone(phone);
  223. if (sysUser == null) {
  224. return HttpResult.error("手机号不存在!");
  225. }
  226. }
  227. Random rand = new Random();
  228. // randNumber 将被赋值为一个 MIN 和 MAX 范围内的随机数
  229. int randNumber = rand.nextInt(9999 - 1000 + 1) + 1000;
  230. // 保存验证码到redis
  231. redisTemplate.opsForValue().set(PHONE_VERIFICATION_CODE_KEY + phone, String.valueOf(randNumber), PHONE_VERIFICATION_CODE_KEY_TIME
  232. , TimeUnit.MINUTES);
  233. String result = null;
  234. String url = "http://v.juhe.cn/sms/send";//请求接口地址
  235. Map params = new HashMap();//请求参数
  236. params.put("mobile", phone);//接收短信的手机号码
  237. params.put("tpl_id", "71853");//短信模板ID,请参考个人中心短信模板设置
  238. String textVal = "#code#=" + randNumber;
  239. params.put("tpl_value", textVal);//变量名和变量值对。如果你的变量名或者变量值中带有#&=中的任意一个特殊符号,请先分别进行urlencode编码后再传递,<a href="http://www.juhe.cn/news/index/id/50"
  240. // target="_blank">详细说明></a>
  241. params.put("key", "fbe7b165e9e13e52f3b1f2654eab9c0e");//应用APPKEY(应用详细页查询)
  242. params.put("dtype", "json");//返回数据的格式,xml或json,默认json
  243. try {
  244. result = net(url, params, "GET");
  245. JSONObject object = JSONObject.parseObject(result);
  246. if (object.getInteger("error_code") == 0) {
  247. // System.out.println(object.get("result"));
  248. return HttpResult.ok("发送成功");
  249. } else {
  250. System.out.println(object.get("error_code") + ":" + object.get("reason"));
  251. return HttpResult.error("发送失败," + object.get("error_code") + ":" + object.get("reason"));
  252. }
  253. } catch (Exception e) {
  254. e.printStackTrace();
  255. }
  256. return HttpResult.ok("发送失败");
  257. }
  258. /**
  259. * @param strUrl 请求地址
  260. * @param params 请求参数
  261. * @param method 请求方法
  262. * @return 网络请求字符串
  263. * @throws Exception
  264. */
  265. public String net(String strUrl, Map params, String method) throws Exception {
  266. String DEF_CHATSET = "UTF-8";
  267. int DEF_CONN_TIMEOUT = 30000;
  268. int DEF_READ_TIMEOUT = 30000;
  269. String userAgent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36";
  270. HttpURLConnection conn = null;
  271. BufferedReader reader = null;
  272. String rs = null;
  273. try {
  274. StringBuffer sb = new StringBuffer();
  275. if (method == null || method.equals("GET")) {
  276. strUrl = strUrl + "?" + urlencode(params);
  277. }
  278. URL url = new URL(strUrl);
  279. conn = (HttpURLConnection) url.openConnection();
  280. if (method == null || method.equals("GET")) {
  281. conn.setRequestMethod("GET");
  282. } else {
  283. conn.setRequestMethod("POST");
  284. conn.setDoOutput(true);
  285. }
  286. conn.setRequestProperty("User-agent", userAgent);
  287. conn.setUseCaches(false);
  288. conn.setConnectTimeout(DEF_CONN_TIMEOUT);
  289. conn.setReadTimeout(DEF_READ_TIMEOUT);
  290. conn.setInstanceFollowRedirects(false);
  291. conn.connect();
  292. if (params != null && Objects.equals(method, "POST")) {
  293. try {
  294. DataOutputStream out = new DataOutputStream(conn.getOutputStream());
  295. out.writeBytes(urlencode(params));
  296. } catch (Exception e) {
  297. // TODO: handle exception
  298. }
  299. }
  300. InputStream is = conn.getInputStream();
  301. reader = new BufferedReader(new InputStreamReader(is, DEF_CHATSET));
  302. String strRead = null;
  303. while ((strRead = reader.readLine()) != null) {
  304. sb.append(strRead);
  305. }
  306. rs = sb.toString();
  307. } catch (IOException e) {
  308. e.printStackTrace();
  309. } finally {
  310. if (reader != null) {
  311. reader.close();
  312. }
  313. if (conn != null) {
  314. conn.disconnect();
  315. }
  316. }
  317. return rs;
  318. }
  319. //将map型转为请求参数型
  320. public String urlencode(Map<String, Object> data) {
  321. StringBuilder sb = new StringBuilder();
  322. for (Map.Entry i : data.entrySet()) {
  323. try {
  324. sb.append(i.getKey()).append("=").append(URLEncoder.encode(i.getValue() + "", "UTF-8")).append("&");
  325. } catch (UnsupportedEncodingException e) {
  326. e.printStackTrace();
  327. }
  328. }
  329. return sb.toString();
  330. }
  331. @PostMapping(value = "/loginOut")
  332. @ApiOperation(value = "登出")
  333. public HttpResult loginOut() {
  334. StpUtil.logout();
  335. return HttpResult.ok("退出登录成功");
  336. }
  337. /**
  338. * 获取二维码生成的uuid
  339. */
  340. @GetMapping("/getQrCode")
  341. @ApiOperation(value = "获取登录二维码")
  342. public HttpResult getQrCode(@RequestParam String uuid) throws JsonProcessingException {
  343. // String uuid = UUID.randomUUID().toString();
  344. int statusCode = QrCodeStatusEnum.WAITING.getStatusCode();
  345. LoginTicket loginTicket = new LoginTicket();
  346. // 二维码最初为 waitting 状态
  347. loginTicket.setStatusCode(statusCode);
  348. loginTicket.setUuid(uuid);
  349. ObjectMapper objectMapper = new ObjectMapper();
  350. String jsonContent = objectMapper.writeValueAsString(loginTicket);
  351. redisTemplate.opsForValue().set(uuid, jsonContent);
  352. //设置过期时间60秒
  353. redisTemplate.expire(uuid, 60, TimeUnit.SECONDS);
  354. Map<String, Object> resultMap = new HashMap<>();
  355. resultMap.put("uuid", uuid);
  356. resultMap.put("statusCode", QrCodeStatusEnum.WAITING.getStatusValue());
  357. return HttpResult.ok(resultMap);
  358. }
  359. /**
  360. * 扫描二维码 改变redis中相关uuid状态并于对应用户关联
  361. */
  362. @GetMapping("/scanQrCode")
  363. @ApiOperation(value = "扫描登录二维码")
  364. public HttpResult scanQrCode(@RequestParam String uuid) {
  365. Map<String, Object> data = new HashMap<>();
  366. try {
  367. ObjectMapper objectMapper = new ObjectMapper();
  368. String content = redisTemplate.opsForValue().get(uuid);
  369. LoginTicket loginTicket = objectMapper.readValue(content, LoginTicket.class);
  370. // redis 中 uuid的过期时间
  371. Long expired = RedisUtils.timeToLive(uuid);
  372. boolean valid = loginTicket != null &&
  373. QrCodeStatusEnum.WAITING.getStatusCode().equals(loginTicket.getStatusCode()) &&
  374. expired != null &&
  375. expired >= 0;
  376. if (valid) {
  377. String user = BaseController.getUser().getId();
  378. if (user == null) {
  379. throw new RuntimeException("用户未登录");
  380. }
  381. // 修改扫码状态
  382. loginTicket.setStatusCode(QrCodeStatusEnum.SCANNED.getStatusCode());
  383. // 将二维码与用户进行关联
  384. loginTicket.setUserId(user);
  385. String jsonContent = objectMapper.writeValueAsString(loginTicket);
  386. redisTemplate.opsForValue().set(uuid, jsonContent);
  387. data.put("statusCode", QrCodeStatusEnum.SCANNED.getStatusCode());
  388. return HttpResult.ok(data);
  389. } else {
  390. data.put("statusCode", QrCodeStatusEnum.INVALID);
  391. return HttpResult.error("二维码已过期或已被他人使用,请刷新二维码");
  392. }
  393. } catch (RuntimeException | JsonProcessingException e) {
  394. throw new RuntimeException(e);
  395. }
  396. }
  397. /**
  398. * 轮询接口 获取二维码状态
  399. */
  400. @GetMapping("/getQrCodeStatus")
  401. @ApiOperation(value = "获取登录二维码状态")
  402. public HttpResult getQrCodeStatus(HttpServletRequest request, HttpServletResponse response,
  403. @RequestParam String uuid) throws JsonProcessingException {
  404. Map<Object, Object> resultMap = new HashMap<>();
  405. ObjectMapper objectMapper = new ObjectMapper();
  406. String content = redisTemplate.opsForValue().get(uuid);
  407. LoginTicket loginTicket = objectMapper.readValue(content, LoginTicket.class);
  408. if (loginTicket == null) {
  409. resultMap.put("statusCode", QrCodeStatusEnum.INVALID);
  410. resultMap.put("status", QrCodeStatusEnum.INVALID.getStatusValue());
  411. return HttpResult.ok(resultMap);
  412. }
  413. Integer statusCode = loginTicket.getStatusCode();
  414. //二维码状态为待扫描
  415. if (statusCode.equals(QrCodeStatusEnum.WAITING.getStatusCode())) {
  416. resultMap.put("statusCode", statusCode);
  417. resultMap.put("status", QrCodeStatusEnum.WAITING.getStatusValue());
  418. }
  419. //二维码状态为无效时删除redis中所存的uuid
  420. if (statusCode.equals(QrCodeStatusEnum.INVALID.getStatusCode())) {
  421. resultMap.put("statusCode", statusCode);
  422. resultMap.put("status", QrCodeStatusEnum.INVALID.getStatusValue());
  423. RedisUtils.del(uuid);
  424. }
  425. //二维码状态为以扫描
  426. SysUser user = sysUserService.getById(loginTicket.getUserId());
  427. if (statusCode.equals(QrCodeStatusEnum.SCANNED.getStatusCode())) {
  428. String token = StpUtil.createLoginSession(user.getId());
  429. resultMap.put("token", token);
  430. resultMap.put("statusCode", statusCode);
  431. resultMap.put("status", QrCodeStatusEnum.SCANNED.getStatusValue());
  432. }
  433. return HttpResult.ok(resultMap);
  434. }
  435. }