spring boot 之登录笔记
在测试平台的开发中,会牵涉到登录内容,页面需要登录后才能访问,所以,对于登录的开发是很有必要的。本文记录我在系统登录的一些自己的做法。
首先对登录进行设计。
如下:
1.登录密码输入错误超过次数限制
2.能够使用用户名密码登录,邮箱密码登录。
目前登录所有信息都是在数据库存储。后续优化到redis里面。
使用spring boot+ jpa+durid。数据库mysql。
数据库设计如下:
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '用户密码', `email` varchar(64) DEFAULT NULL COMMENT '用户邮箱', `status` int(11) NOT NULL DEFAULT '0' COMMENT '状态,1代表删除', `admin` int(11) NOT NULL DEFAULT '0' COMMENT '是否是管理员,1代表是管理员', `iphone` varchar(20) DEFAULT NULL COMMENT '用户手机号', `workid` int(11) NOT NULL DEFAULT '0', `token` varchar(255) DEFAULT NULL, `errornum` int(2) NOT NULL DEFAULT '0', `freeze` int(2) NOT NULL DEFAULT '0', `freezetime` datetime DEFAULT NULL, PRIMARY KEY (`id`,`username`), KEY `username` (`username`), KEY `email` (`email`) ) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8 COMMENT='用户表';
数据库名称为planone
数据库创建后,我们去插入数据。
INSERT INTO `user` VALUES ('1', 'liwanlei', 'lueSGJZetyySpUndWjMBEg==', '952943386@qq.com', '0', '1', '', '1', 'liwanlei_vlywm4en9/IyTI64bLDQEA==', '0', '1', '2019-04-29 16:14:22');
那么接下来我们去创建工程
在pom文件增加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
、 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
添加依赖后同步更新。
在application.yaml 进行配置
spring: datasource: druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/planone?serverTimeZone=UTC&characterEncoding=utf8&useSSL=true username: root password: 123456 initial-size: 10 max-active: 1000 min-idle: 10 max-wait: 60000 time-between-eviction-runs-millis: 2000 keep-alive: true filter: stat: slow-sql-millis: 10000 log-slow-sql: true max-evictable-idle-time-millis: 900000 min-evictable-idle-time-millis: 400000 thymeleaf: cache: false suffix: .html mode: HTML5 encoding: UTF-8 prefix: classpath:/templates/ server: port: 9999
配置完毕后,我们在resoures 文件新建templates 文件 和static 文件,添加静态页面用。
那么我们接下来开发后台,首先写的是dao
@Data @DynamicUpdate @Entity @JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler"}) public class User implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String password; private String email; private String iphone; private Integer status =0; private Integer admin =0; private String token; private Date freezetime; private Integer errornum=0; private Integer freeze= 0; }
接下来去开发基于JpaRepository数据访问层
1 2 3 4 5 | public interface UserRepository extends JpaRepository<User, Integer> { User findByUsername(String username); User findByEmail(String email); } |
开发server
1 2 3 4 5 | public interface UserSerice { User login(String username, String password); } |
对应的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | @Service @Component public class UserSericeImpl implements UserSerice { @Autowired private UserRepository userRepository; @Override public User login(String username, String password) { /*根据邮箱或者用户名登录*/ User user = userRepository.findByUsername(username); if (user != null ) { user = login_is(user, password, username); return user; } else { User useremail = userRepository.findByEmail(username); if (useremail != null ) { user = login_is(useremail, password, username); return user; } } throw new PanExection(ResultEmus.USER_NOT_EXIT); } public User login_is(User user, String password, String username) { if (user.getStatus().equals(UserEmus.DELETE.getCode())) { throw new PanExection(ResultEmus.USER_DELETE); } SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); try { try { if (user.getFreeze().equals(FreezeEmus.DELETE.getCode()) && ( new Date().getTime() - format.parse(user.getFreezetime().toString()).getTime() < 6 * 60 * 1000 )) { user.setErrornum( 0 ); userRepository.saveAndFlush(user); throw new PanExection(ResultEmus.USER_FREE); } } catch (ParseException e) { throw new PanExection(ResultEmus.EXCEPTIONS); } } catch (NullPointerException e) { userRepository.saveAndFlush(user); } Boolean b = null ; try { b = MD5Until.checkoutpassword(password, user.getPassword()); } catch (Exception e) { throw new PanExection(ResultEmus.EXCEPTIONS); } if (b) { user.setErrornum( 0 ); user.setFreezetime( null ); Date date = new Date(); String tokne = null ; tokne = (String) userredis(redisTemplate).opsForValue().get(user.getUsername()); if (tokne == null ) { try { tokne = MD5Until.md5(user.getUsername() + date.toString()); } catch (Exception e) { throw new PanExection(ResultEmus.EXCEPTIONS); } String token = user.getUsername() + "_" + tokne; user.setToken(token); userRepository.saveAndFlush(user); userredis(redisTemplate).opsForValue().set(username, token, 1 , TimeUnit.DAYS); } return user; } else { if (user.getErrornum() > 4 ) { user.setErrornum(user.getErrornum() + 1 ); user.setFreeze(FreezeEmus.DELETE.getCode()); user.setFreezetime( new Date()); userRepository.saveAndFlush(user); throw new PanExection(ResultEmus.USER_FREE); } else { Integer err = user.getErrornum() + 1 ; user.setErrornum(err); userRepository.saveAndFlush(user); throw new PanExection(ResultEmus.USER_ERROR_PASSWORD); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ResultEmus.java @Getter public enum ResultEmus { SUCCESS_REQUEST( 0 , "成功" ), USER_NOT_EXIT( 1 , "用户不存在" ), USER_BIND( 2 , "用户已经绑定" ), USER_DELETE( 3 , "用户已经删除" ), EXCEPTIONS( 4 , "转化异常" ), USER_ERROR_PASSWORD( 225 , "密码错误" ), USER_ERRPOR_EMAIL( 6 , "邮箱不匹配" ), PARM_ERROR( 7 , "参数错误" ), USER_EXIT( 8 , "用户已经存在" ) ; private Integer code; private String message; ResultEmus(Integer code, String message) { this .code = code; this .message = message; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | MD5Until.java public class MD5Until { public static String md5(String text) throws Exception { MessageDigest md5 = MessageDigest.getInstance( "MD5" ); BASE64Encoder base64en = new BASE64Encoder(); String newstr = base64en.encode(md5.digest(text.getBytes())); return newstr; } public static boolean checkoutpassword(String password, String oldpassword) throws Exception { return md5(password).equals(oldpassword); } } |
开发完server端,我们去写controller
@RestController @RequestMapping("/plan") public class LoginContorl { @Autowired private UserSerice userSerice; @GetMapping("/logins") public ModelAndView login(ModelAndView modelAndView) { modelAndView.setViewName("login"); return modelAndView; } @PostMapping("/logins") public ModelAndView login(ModelAndView modelAndView, @Valid UserForm userVo, BindingResult bindingResult, HttpServletRequest httpServletRequest) { HttpSession session = httpServletRequest.getSession(); if (bindingResult.hasErrors()) { modelAndView.addObject("error", bindingResult.getFieldError().getDefaultMessage()); modelAndView.setViewName("/plan/logins"); return modelAndView; } String userName = userVo.getUsername(); String password = userVo.getPassword(); try { User user = userSerice.login(userName, password); session.setMaxInactiveInterval(3600); session.setAttribute("userid", user.getId()); session.setAttribute("username", user.getUsername()); return new ModelAndView("redirect:/web/index"); } catch (PanExection e) { modelAndView.addObject("error", e.getMessage()); modelAndView.setViewName("login"); return modelAndView; } } }
那么我们需要去写对应的html
html写在templates ,login接口如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | <! DOCTYPE html> < html xmlns:th="http://www.thymeleaf.org"> < head > < meta charset="utf-8"> < meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> < title >AutoTestPlatform</ title > < link rel="stylesheet" th:href="@{/css/style.css}" href="../static/css/style.css"> < link rel="shortcut icon" th:href="@{/images/favicon.png}" href="../static/images/favicon.png" /> </ head > < body > < div class="body-wrapper"> < div class="page-wrapper"> < main class="content-wrapper auth-screen"> < div class="mdc-layout-grid"> < div class="mdc-layout-grid__inner"> < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-4"> </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-4"> < div class="mdc-card"> < section class="mdc-card__primary bg-white"> < form th:action="@{/plan/logins}" method="post"> < div > <!--/*@thymesVar id="error" type=""*/--> < span id="basic-addon0"> </ span > < span style="font-size: 12px;color: red" th:text="${error}" aria-describedby="basic-addon0"></ span > < br /> </ div > < div class="mdc-layout-grid"> < div class="mdc-layout-grid__inner"> < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-12"> < label class="mdc-text-field w-100"> 用户名< input type="username"name="username" class="mdc-text-field__input"> < div class="mdc-text-field__bottom-line"></ div > </ label > </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-12"> < label class="mdc-text-field w-100"> 密码< input type="password" name="password" class="mdc-text-field__input"> < div class="mdc-text-field__bottom-line"></ div > </ label > </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-6 d-flex align-item-center justify-content-end"> < a th:href="@{/plan/rebackpassword}">忘记密码</ a > </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-6 d-flex align-item-center justify-content-end"> < a th:href="@{/plan/reg}">注册</ a > </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-12"> < button class="mdc-button mdc-button--raised w-100" data-mdc-auto-init="MDCRipple"> 登录 </ button > </ div > </ div > </ div > </ form > </ section > </ div > </ div > < div class="mdc-layout-grid__cell stretch-card mdc-layout-grid__cell--span-4"> </ div > </ div > </ div > </ main > </ div > </ div > <!-- body wrapper --> <!-- plugins:js --> < script src="../../node_modules/material-components-web/dist/material-components-web.min.js"></ script > < script src="../../node_modules/jquery/dist/jquery.min.js"></ script > <!-- endinject --> <!-- Plugin js for this page--> <!-- End plugin js for this page--> <!-- inject:js --> < script src="../../js/misc.js"></ script > < script src="../../js/material.js"></ script > <!-- endinject --> <!-- Custom js for this page--> <!-- End custom js for this page--> </ body > </ html > |
这样我们整个界面就开发完毕,我们进行调试测试
前面插入的数据是 liwanlei 密码是:111111
登录后跳转到首页
补充首页代码
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>AutoTestPlatform</title> </head> <h1>hello</h1> <body>
至此 我们登录开发完毕
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?