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 如果出现…

查找单词-算法(深度优先)

题目 给定一个二维数组与一个单词&#xff0c;数组中每个元素为大写字母&#xff0c;判断单词是否出现在数组中。 如二维数组&#xff1a; char[][] map {{A, B, C, E}, {S, F, C, S}, {A, D, E, E}}; 目标单词&#xff1a; ABCCEE 解题 深度优先&#xff0c;并且走过的…

03哈希表:242、有效的字母异位词

242、有效的字母异位词 文章目录 242、有效的字母异位词方法一&#xff1a;暴力破解法方法二、 哈希法 重点&#xff1a;哈希可以用数组代替表示&#xff0c;下标用与a的位置绝对值 暴力破解法&#xff1a;两层for循环&#xff0c;同时记录字母次数哈希法&#xff1a;第一个字符…

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 个数据均匀的分…

关闭windows系统的自动更新的6种方法 详细介绍

关闭Windows系统的自动更新可以通过多种方法实现&#xff0c;以下将详细介绍六种不同的方法。请注意&#xff0c;关闭自动更新可能会使您的系统面临安全风险&#xff0c;因为您将不会及时接收到最新的安全补丁和系统更新。在执行以下任何操作之前&#xff0c;请确保您了解潜在的…

多线程读写文件问题

多线程读写同个文件会不会有访问冲突或者异常&#xff1f;我们写个程序来测试一下 /*** Created by fangruibin* 测试多线程读写文件*/#include <iostream> #include <pthread.h> #include <unistd.h> #include <string.h>const char* fileName &quo…

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

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

前端工程化之:webpack1-12(常用扩展)

目录 前言 一、CleanWebpackPlugin 二、HtmlWebpackPlugin 三、CopyPlugin 四、webpack-dev-server 五 、file-loader 六、url-loader 七、路径问题 前言 由于 webpack 、 webpack-cli 、 webpack-dev-server 会存在版本不兼容问题&#xff0c;所以这里使用的版本如下&…

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

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

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

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

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

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

MySQL语句 |条件语句 IFNULL 和 COALESCE 的区别

在MySQL中&#xff0c;IFNULL和COALESCE都是用来处理NULL值的函数&#xff0c;但它们之间存在一些重要的差异。 函数定义 IFNULL(expr1, expr2): 如果expr1为NULL&#xff0c;则返回expr2&#xff0c;否则返回expr1。COALESCE(value1, value2, ..., valueN): 返回参数列表中的…

机器人中的数值优化进阶|【三】三次样条曲线推导(下)

机器人中的数值优化进阶|【三】三次样条曲线推导&#xff08;下&#xff09; 接之前的内容&#xff0c;现在开始考虑势场函数 P ( η 1 , . . . , η n − 1 ) 1000 ∑ i 1 n − 1 ∑ j 0 m max ⁡ ( r j − ∣ ∣ η i − o j ∣ ∣ , 0 ) P(\eta_1,...,\eta_{n-1}) 100…

C语言入门到精通之练习35:打印出杨辉三角形(要求打印出10行)

题目&#xff1a;打印出杨辉三角形&#xff08;要求打印出10行&#xff09;。 程序分析&#xff1a; 结构如下图所示&#xff1a; 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1代码如下&#xff1a; // Created by www.erdangjiade.com 15/11/9. //#incl…