SpringBoot实战项目第一天

环境搭建

后端部分需要准备:

sql数据库

创建SpringBoot工程,引入对应的依赖(web\mybatis\mysql驱动)

配置文件application.yml中引入mybatis的配置信息

创建包结构,并准备实体类

完成今日开发后项目部分内容如下图示

用户注册于登录部分相关内容

注册

谈到注册,首先就要看看数据库中用户表的构成:

然后对应的,完成User实体类的开发

@Data
//lombok  在编译阶段,为实体类自动生成setter  getter toString
// pom文件中引入依赖   在实体类上添加注解
public class User {private Integer id;//主键IDprivate String username;//用户名private String password;//密码private String nickname;//昵称private String email;//邮箱private String userPic;//用户头像地址private LocalDateTime createTime;//创建时间private LocalDateTime updateTime;//更新时间
}

此处用到了lombok技术,该技术可以在java文件编译时自动为变量生成getter、setter方法和tostring方法,后期实体类的开发也均会用到该技术。

        <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

接下来,就是要完成Mapper -> Service -> Controller这三层的对应开发。

在开发之前,我们要明确开发需求,即用户的注册。

而在用户注册时,会出现两种情况,注册成功与注册失败。此时我们粗略地将这两种情况对应为

 数据库中没有该用户名对应行 -> 该用户还尚不存在 ->允许注册 ->注册

 数据库中存在该用户名对应行 -> 该用户已经存在 -> 不允许注册 ->返回注册失败原因

理清逻辑后我们从Mapper层开始开发

        首先,我们要注意到,在用户注册时我们会首先对数据库中是否已经存在该用户进行检测,如果没有,再在数据库中录入新用户信息

        这就涉及到了sql中的两种操作,@select与@insert,所以,我们在Mapper层的接口中就要提供这两个操作

@Mapper
public interface UserMapper {@Select("select * from user where username = #{username}")User getByUserName(String username);@Insert("insert into user(username,password,create_time,update_time)"+"values (#{username},#{password},now(),now())")void add(String username, String password);
}

不要忘记使用@Mapper注册该类! 

 

再来看Service

service层开发较为简单,只需要将dao层的对应方法调用

service接口

public interface UserService {//用户名查询用户User getByUserName(String username);//新用户注册void register(String username, String password);
}

而impl文件中在实现这些方法之余,我们在service层会对用户输入的密码进行加密,这里使用到了MD5加密

@Service
public class SuerServiceImpl implements UserService {@AutowiredUserMapper userMapper;@Overridepublic User getByUserName(String username) {User u = userMapper.getByUserName(username);return u;}@Overridepublic void register(String username, String password) {//密码加密String p = Md5Util.getMD5String(password);userMapper.add(username,p);}
}

记得检查传入mapper层的密码,一定要是加密后的! 

MD5:

public class Md5Util {/*** 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合*/protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};protected static MessageDigest messagedigest = null;static {try {messagedigest = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException nsaex) {System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");nsaex.printStackTrace();}}/*** 生成字符串的md5校验值** @param s* @return*/public static String getMD5String(String s) {return getMD5String(s.getBytes());}/*** 判断字符串的md5校验码是否与一个已知的md5码相匹配** @param password  要校验的字符串* @param md5PwdStr 已知的md5校验码* @return*/public static boolean checkPassword(String password, String md5PwdStr) {String s = getMD5String(password);return s.equals(md5PwdStr);}public static String getMD5String(byte[] bytes) {messagedigest.update(bytes);return bufferToHex(messagedigest.digest());}private static String bufferToHex(byte bytes[]) {return bufferToHex(bytes, 0, bytes.length);}private static String bufferToHex(byte bytes[], int m, int n) {StringBuffer stringbuffer = new StringBuffer(2 * n);int k = m + n;for (int l = m; l < k; l++) {appendHexPair(bytes[l], stringbuffer);}return stringbuffer.toString();}private static void appendHexPair(byte bt, StringBuffer stringbuffer) {char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>// 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换stringbuffer.append(c0);stringbuffer.append(c1);}}

 

最后一步,我们进行Controller层的开发 

在controller层中,我们需要进行相应的逻辑判断来验证该用户名是否已经被占用,具体操作如下:

@RestController
@RequestMapping("/user")
public class UserController {@AutowiredUserService userService;@PostMapping("/register")public Result register(String username,String password){//查询该用户名是否已经存在User u = userService.getByUserName(username);if (u==null){//用户名没有被占用,注册userService.register(username,password);return Result.success();} else {return Result.error("用户名被占用!");}}
}

Result实体类如下:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {private Integer code;//业务状态码  0-成功  1-失败private String message;//提示信息private T data;//响应数据//快速返回操作成功响应结果(带响应数据)public static <E> Result<E> success(E data) {return new Result<>(0, "操作成功", data);}//快速返回操作成功响应结果public static Result success() {return new Result(0, "操作成功", null);}public static Result error(String message) {return new Result(1, message, null);}
}

最后的最后,我们使用测试软件对注册部分进行测试

先看新用户注册正常,注册成功情况

 数据库存储情况

可以看到密码的加密工作也顺利完成

再来看看注册失败的情况,这里我们直接使用刚才的账密注册

注册失败,至此,我们的注册功能已完成最基本的开发与测试。 

当然,我们在日常生活中会发现,账户与密码会有一个基本的校验,即a-b位的非空字符,显然,我们还需要对账密进行进行长度检验。

账户密码长度参数校验

这里我预设的账户长度为4-16位

密码长度位11-16位

校验我们在controller层完成

@RestController
@RequestMapping("/user")
public class UserController {@AutowiredUserService userService;@PostMapping("/register")public Result register(String username,String password){if(username != null && username.length()>=4 && username.length() <= 16 &&password != null && password.length() >= 11 && password.length() <=16){//查询该用户名是否已经存在User u = userService.getByUserName(username);if (u==null){//用户名没有被占用,注册userService.register(username,password);return Result.success();} else {return Result.error("用户名被占用!");}}else{return Result.error("用户名或密码输入不合法!");}}
}

当然,我们会发现,仅仅校验账户与密码这两个参数就会导致我们的逻辑判断代码如此繁琐,Spring当然也为我们提供了简化方法——Spring Validation框架 

Spring Validation框架 :Spring提供的一个参数校验框架,使用预定义的注解完成参数校验 

Spring Validation框架使用流程

第一步、引入Spring Validation框架起步依赖
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
第二步、在参数前面添加@Pattren注解
 public Result register(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){
第三步、在Controller类上添加@Validated注解
@RestController
@RequestMapping("/user")
@Validated
public class UserController {@AutowiredUserService userService;@PostMapping("/register")public Result register(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){//查询该用户名是否已经存在User u = userService.getByUserName(username);if (u==null){//用户名没有被占用,注册userService.register(username,password);return Result.success();} else {return Result.error("用户名被占用!");}}
}

当我们输入不合法的账密时,我们会发现,这里的报错不如我们之前手动返回的,所以我们要对其进行参数校验失败异常处理

 

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public Result handleExxception(Exception e){e.printStackTrace();return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败,");}
}

再次使用非法账密测试

 

 

可以看到异常处理功能正常运行 

 登录

在开发之前,我们要明确开发需求,即用户的登录(成功登录后需求返回一个 jwt token 令牌)。

而在用户登录时,会出现三种情况,账密正确,成功登录、账号错误,登陆失败与密码错误,登陆失败。此时我们粗略地将这三种情况对应为

 数据库中没有该用户名对应行 -> 该用户还尚不存在 ->登陆失败 ->返回登陆失败原因

 数据库中存在该用户名对应行,但密码不对应 -> 密码输入错误 -> 登陆失败 -> 返回登录失败原因

 数据库中存在该用户名对应行,且密码对应 -> 账户密码输入正确 -> 登陆成功

分析完需求,我们会发现我们在书写登录模块时需要对用户的帐户密码进行相应的逻辑判断,用到的sql语句为@select与@insert,这两个方法我们在注册模块已经完成了书写,所以我们直接在Controller层编写登录模块即可!

Controller层技术开发

@PostMapping("/login")public Result<String> login(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){//查询用户是否存在User lUser = userService.getByUserName(username);if(lUser == null){return  Result.error("用户不存在!");}//查询密码是否正确(密码需要加密后再与数据库中密码比较)if (Md5Util.getMD5String(password).equals(lUser.getPassword())){return Result.success("jwt token 令牌");}return Result.error("密码错误!");}

可以看到,对于账密合法性判断我们采取了与注册时一样的操作, 逻辑判断部分也是简单的匹配,下面我们来对其进行测试

这是一组正确的账户密码测试,可以看到,此时我们暂时还没有编写jwt令牌的返回,暂时使用一个字符串代替

下面再进行一组错误账户名的测试

 

可以看到,错误信息如期。

最后在进行一组错误密码的测试

测试完毕,我们编辑的最最基础的登录部份功能正常运行!

 

登录认证

       当我们需要将某些界面设置为登陆后可见时,我们就需要对其加入登录认证的功能,接下来就进行该功能的开发

我们先建立一个chesscontroller,供下例使用:

@RestController
@RequestMapping("/article")
public class ChessController {@PostMapping("/chess")public Result<String> chess(){return Result.success("读取所有的棋子数据!");}
}

登陆验证需要一个令牌,该令牌就是一段字符串,需要满足下列要求

        承载业务数据,减少后续请求查询数据库次数

        防篡改,保证信息的合法性和有效性

在web开发中最常用的令牌便是JWT令牌

JWT

        全称:JSON Web Token

        定义了一种简洁的、自包含的格式,用于通信双方以json数据格式安全的传输信息

组成:

        第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}

        第二部分:PayLoad(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","us"1"}

        第三部分:Signature(签名),防止Token被篡改、切薄安全性。将header、payload加入指定密钥,通过指定签名算法计算而来

JWT-生成

首先导入对应依赖坐标
       <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
 接下来编写对应的程序
public class JwtTest {@Testpublic void testGen(){Map<String, Object> claims = new HashMap<>();claims.put("id",1);claims.put("username","zmx");//生成jwt代码String token = JWT.create().withClaim("user",claims)//添加载荷.withExpiresAt(new Date(System.currentTimeMillis()+1000*60*12))//添加过期时间.sign(Algorithm.HMAC256("cacb"));//指定算法,配置密钥System.out.println(token);}
}

如上,我输入了一组简单的数据来测试

运行结果:

 

JWT-验证

    @Testpublic void testParse(){//定义字符串,模拟用户传递的tokenString token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6InpteCJ9LCJleHAiOjE3MDY5NTUzNTN9.pZ0fYD5rRMPHXzAq_sLC6RKprPGwIiIHoimAuChTzdk";JWTVerifier jwtVerifier =  JWT.require(Algorithm.HMAC256("cacb")).build();DecodedJWT decodedJWT =  jwtVerifier.verify(token);//验证token,生成一个解析后的JWT对象Map<String, Claim> claims = decodedJWT.getClaims();System.out.println(claims.get("user"));}

使用上例生成的JWT进行验证

结果如下:

 

JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的

如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法

登录认证书写

由于JWT生成与验证的代码过于繁琐,所以我们选择使用一个工具类来承载JWT生成与验证的方法

public class JwtUtil {private static final String KEY = "cacb";//接收业务数据,生成token并返回public static String genToken(Map<String, Object> claims) {return JWT.create().withClaim("claims", claims).withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12)).sign(Algorithm.HMAC256(KEY));}//接收token,验证token,并返回业务数据public static Map<String, Object> parseToken(String token) {return JWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim("claims").asMap();}}
第一步、在登录界时生成token
    @PostMapping("/login")public Result<String> login(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){//查询用户是否存在User lUser = userService.getByUserName(username);if(lUser == null){return  Result.error("用户不存在!");}//查询密码是否正确(密码需要加密后再与数据库中密码比较)if (Md5Util.getMD5String(password).equals(lUser.getPassword())){Map<String,Object> claims = new HashMap<>();claims.put("id",lUser.getId());claims.put("usernname",lUser.getUsername());String token = JwtUtil.genToken(claims);return Result.success(token);}return Result.error("密码错误!");}
第二步、在需要验证token界面对应的接口验证token(复杂) 

还是以list为例(token从请求头中的Authorization读取)

    @GetMapping("/list")public Result<String> chess(@RequestHeader(name = "Authorization")String token , HttpServletResponse response){//验证tokentry {Map<String,Object> claims =  JwtUtil.parseToken(token);return Result.success("读取所有的棋子数据!");} catch (Exception e){//设置HTTP响应状态码为401response.setStatus(401);return Result.error("请登录!");}}

测试结果如下:

 

验证token高效方法、使用拦截器统一验证token令牌
第一步、将验证token转移到拦截器中
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//令牌验证String token =  request.getHeader("Authorization");//验证tokentry {Map<String,Object> claims =  JwtUtil.parseToken(token);//放行return true;} catch (Exception e){//设置HTTP响应状态码为401response.setStatus(401);//不放行return false;}}
}
第二步、书写配置类,注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册拦截器,登录接口和注册接口不拦截registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");}
}

注意:如例中的login与register接口不需要拦截,可以直接放行

第三步、修改简化被拦截的接口内容
    @GetMapping("/list")public Result<String> chess(@RequestHeader(name = "Authorization") String token, HttpServletResponse response) {return Result.success("读取所有的棋子数据!");}

测试结果如下:

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/665256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[BUUCTF]-PWN:mrctf2020_easy_equation解析

查看保护 再看ida 很明了&#xff0c;题目就是让我们用格式化字符串漏洞修改judge的值&#xff08;可以用python脚本进行计算&#xff0c;最终算出来得2&#xff09;使等式成立&#xff0c;然后getshell。 虽然操作比较简单&#xff0c;但我还是列出了几种方法 解法一&#x…

uni-app移动端图片预览组件 movable-area 、movable-view (支持缩放,拖动效果、替换部分代码图片可直接使用)

UniApp图片预览组件 利用uni-app官方<movable-area>、<movable-view>内置视图组件 配合 uView 组件的u-popup 弹框组件共同实现封装的图片预览组件&#xff0c;支持手指缩放、拖动效果&#xff0c;替换代码中部分图片后 可以直接使用。 效果图&#xff1a; 组件代码…

【数据结构与算法】——单链表的原理及C语言实现

数据结构与算法——链表原理及C语言实现 链表的原理链表的基本属性设计创建一个空链表链表的遍历&#xff08;显示数据&#xff09;释放链表内存空间 链表的基本操作设计&#xff08;增删改查&#xff09;链表插入节点链表删除节点链表查找节点增删改查测试程序 链表的复杂操作…

Vulnhub billu b0x

0x01 环境搭建 1. 从官方下载靶机环境&#xff0c;解压到本地&#xff0c;双击OVF文件直接导入到vmware虚拟机里面。2. 将虚拟机的网络适配器调成NAT模式&#xff0c;然后开机即可进行操作了。 0x02 主机发现 nmap -sn 192.168.2.0/24 成功获取靶机IP为192.168.2.129。 0x0…

本次安装Visual Studio 所用的安装程序不完整。请重新运行VisualStudio安装程序以解决此问题

今天点开VS的时候遇到了这个问题 因为昨天升级到一半电脑关机了&#xff0c;今天打开软件遇到如下错误&#xff0c; 解决办法很简单&#xff0c;找到安装目录进入Installer文件夹 我的目录在C:\Program Files (x86)\Microsoft Visual Studio\Installer 找到vs_installer.exe…

【python】python爱心代码

一、实现效果&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 二、准备工作 &#xff08;1)、导入必要的模块&#xff1a; 代码首先导入了需要使用的模块&#xff1a;requests、lxml和csv。 import requests from lxml import etree import csv 如果出现…

C#写个小工具,把多个word文档进行合并成一个word文档

先要安装包 帮助类WordDocumentMerger&#xff0c;用于处理word合并功能 using System; using System.Collections.Generic; using System.Text; using Microsoft.Office.Interop.Word; using System.Reflection; using System.IO; using System.Diagnostics;namespace WordH…

分别用JavaScript,Java,PHP,C++实现桶排序的算法(附带源码)

桶排序是计数排序的升级版。它利用了函数的映射关系&#xff0c;高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效&#xff0c;我们需要做到这两点&#xff1a; 在额外空间充足的情况下&#xff0c;尽量增大桶的数量使用的映射函数能够将输入的 N 个数据均匀的分…

【C语言】字符串函数介绍

目录 前言&#xff1a; 1. strlen 函数 函数介绍 strlen 函数的使用 strlen 函数的模拟实现 2. strcpy 函数 函数介绍 strcpy 函数的使用 strcpy 函数的模拟实现 3. strcat 函数 函数介绍 strcat 函数的使用 strcat 函数的模拟实现 4. strcmp 函数 函数介绍 st…

4K Video Downloader forMac/win:畅享高清视频下载的终极利器!

在如今的数字时代&#xff0c;高清视频已经成为人们生活中不可或缺的一部分。无论是观看精彩的电影、音乐视频&#xff0c;还是学习教育类的在线课程&#xff0c;我们都希望能够以最清晰流畅的方式来欣赏。而为了满足这一需求&#xff0c;我们需要一款功能强大的高清视频下载软…

工业平板电脑定制_三防平板电脑安卓主板厂家

工业平板电脑具有IP68级三防品质&#xff0c;采用高强度工业材质制造&#xff0c;结构稳固坚韧&#xff0c;具备较高的抗冲击和防震能力。隔空减震技术进一步加强了产品的抗冲击和防震动功能。广泛应用于工控、医疗、电信、电力、工业自动化设备、汽车检测、制造业等多个领域&a…

Flink实时数仓同步:快照表实战详解

一、背景 在大数据领域&#xff0c;初始阶段业务数据通常被存储于关系型数据库&#xff0c;如MySQL。然而&#xff0c;为满足日常分析和报表等需求&#xff0c;大数据平台采用多种同步方式&#xff0c;以适应这些业务数据的不同存储需求。这些同步存储方式包括离线仓库和实时仓…

MySQL数据库入门

MySQL数据库概述 1&#xff0c;为什么要使用数据库2&#xff0c;数据库的相关概念3&#xff0c;常见的数据库管理系统4&#xff0c;MySQL介绍5&#xff0c;关系型数据库和非关系型数据库6&#xff0c;关系型数据库的设计规则7&#xff0c;表的关联关系7.1&#xff0c;一对一7.2…

短剧小程序开发:打造高效、便捷的娱乐体验

随着移动互联网的普及和用户需求的多样化&#xff0c;短剧小程序作为一种新型的应用形态&#xff0c;逐渐受到了广大用户的青睐。短剧小程序开发旨在为用户提供一种高效、便捷的娱乐体验&#xff0c;让用户在忙碌的生活中轻松享受到精彩的短剧内容。本文将探讨短剧小程序开发的…

0203-2-输入输出系统

第六章&#xff1a;输入输出系统 I/O系统的功能&#xff0c;模型和接口 I/O系统管理的对象是I/O设备和相应的设备控制器。 I/O系统的基本功能 隐藏物理设备的细节与设备的无关性提高处理机和I/O设备的利用率对I/O设备进行控制确保对设备的正确共享错误处理 I/O软件的层次结…

Vite与Webpack打包内存溢出问题优雅处理方式

Vite与Webpack打包内存溢出问题处理 文章目录 Vite与Webpack打包内存溢出问题处理1. Vite1. 打包错误提示2. 命令行方式解决3. 配置环境变量方式解决1. 设置变量2. 配置系统的环境变量 2. Webpack1. 打包错误提示2. 命令行方式解决3. 配置环境变量方式解决1. 设置变量2. 配置系…

【DDD】学习笔记-什么是模型

从领域驱动的战略设计进入战术设计&#xff0c;简单说来&#xff0c;就是跨过系统视角的限界上下文边界进入它的内部&#xff0c;从分层架构的逻辑分层进入到每一层的内部。在思考内部的设计细节时&#xff0c;首先需要思考的问题就是&#xff1a;什么是模型&#xff08;Model&…

NETX90-多协议通讯芯片

随着作为信息物理系统核心技术的工业物联网的发展&#xff0c;Hilscher 基于 netX 51/52成功开发了新一代网络控制器netX90&#xff0c;其安全性是产品的核心价值。可实现更高性能的集成&#xff0c;并提高功率效率等级&#xff0c;凭借其较小的外形尺寸能够满足规格尺寸更小的…

css新手教程

css新手教程 课程&#xff1a;14、盒子模型及边框使用_哔哩哔哩_bilibili 一.什么是CSS 1.什么是CSS Cascading Style Sheet 层叠样式表。 CSS&#xff1a;表现&#xff08;美化网页&#xff09; 字体&#xff0c;颜色&#xff0c;边距&#xff0c;高度&#xff0c;宽度&am…