当前位置: 首页 > news >正文

江西省水利水电建设集团招标网站专业网站开发哪里找

江西省水利水电建设集团招标网站,专业网站开发哪里找,app制作,南京定制网站建设怎么收费文章目录 开发模式和环境搭建开发模式环境搭建 1. 用户注册1.1 注册接口基本代码编写1.2 注册接口参数校验 2. 用户登录2.1 登录接口基本代码编写2.2 登录认证2.2.1 登录认证引入2.2.2 JWT 简介2.2.3 登录功能集成 JWT2.2.4 拦截器 3. 获取用户详细信息3.1 获取用户详细信息基本… 文章目录 开发模式和环境搭建开发模式环境搭建 1. 用户注册1.1 注册接口基本代码编写1.2 注册接口参数校验 2. 用户登录2.1 登录接口基本代码编写2.2 登录认证2.2.1 登录认证引入2.2.2 JWT 简介2.2.3 登录功能集成 JWT2.2.4 拦截器 3. 获取用户详细信息3.1 获取用户详细信息基本代码编写3.2 ThreadLocal 优化 4. 更新用户基本信息4.1 更新用户基本信息基本代码编写4.2 更新用户基本信息参数校验 5. 更新用户头像5.1 更新用户头像基本代码编写5.2 url 参数校验 6. 更新用户密码 开发模式和环境搭建 开发模式 前后端分离开发中前端向后端发出请求后台处理完请求后给出响应数据。此时会出现一个问题前端写代码时怎么知道后端有哪些接口后端写代码时怎么知道前端需要什么样的数据呢 这时就需要一套约束标准——接口文档。接口文档对每个接口的访问路径、请求方式、请求参数、响应数据进行了明确的说明。 前后端程序员参考同一份接口文档进行开发项目就能无缝衔接了。 环境搭建 执行 big_event.sql 脚本准备数据库表创建 springboot 工程引入对应的依赖web、mybatis、mysql 驱动在配置文件 application.yml 中引入 mybatis 的配置信息将来连接的数据库在哪用户名和密码是什么创建包结构并准备实体类 (1) 创建表 -- 创建数据库 create database big_event;-- 使用数据库 use big_event;-- 用户表 create table user (id int unsigned primary key auto_increment comment ID,username varchar(20) not null unique comment 用户名,password varchar(32) comment 密码,nickname varchar(10) default comment 昵称,email varchar(128) default comment 邮箱,user_pic varchar(128) default comment 头像,create_time datetime not null comment 创建时间,update_time datetime not null comment 修改时间 ) comment 用户表;-- 分类表 create table category(id int unsigned primary key auto_increment comment ID,category_name varchar(32) not null comment 分类名称,category_alias varchar(32) not null comment 分类别名,create_user int unsigned not null comment 创建人ID,create_time datetime not null comment 创建时间,update_time datetime not null comment 修改时间,constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束 );-- 文章表 create table article(id int unsigned primary key auto_increment comment ID,title varchar(30) not null comment 文章标题,content varchar(10000) not null comment 文章内容,cover_img varchar(128) not null comment 文章封面,state varchar(3) default 草稿 comment 文章状态: 只能是[已发布] 或者 [草稿],category_id int unsigned comment 文章分类ID,create_user int unsigned not null comment 创建人ID,create_time datetime not null comment 创建时间,update_time datetime not null comment 修改时间,constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束 )(2) 创建springboot工程这次采用手动创建的方式 创建好的 maven 工程缺少 resources 目录 于是创建 resources 目录 工程需要在 resources 目录下提供一个核心配置文件即 yml 配置文件 至此boot 工程创建完毕。下面引入所需的依赖 (3) 在配置文件 application.yml 中引入 mybatis 的配置信息 spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driver# 3306是mysql默认端口big_event是数据库名称url: jdbc:mysql://localhost:3306/big_eventusername: rootpassword: 123456(4) 创建包结构并准备实体类 ① 创建包结构controller、service(包括impl)、mapper、pojo、util ② 准备实体类 import java.time.LocalDateTime;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;//更新时间 }import java.time.LocalDateTime;public class Article {private Integer id;//主键IDprivate String title;//文章标题private String content;//文章内容private String coverImg;//封面图像private String state;//发布状态 已发布|草稿private Integer categoryId;//文章分类idprivate Integer createUser;//创建人IDprivate LocalDateTime createTime;//创建时间private LocalDateTime updateTime;//更新时间 }import java.time.LocalDateTime;public class Category {private Integer id;//主键IDprivate String categoryName;//分类名称private String categoryAlias;//分类别名private Integer createUser;//创建人IDprivate LocalDateTime createTime;//创建时间private LocalDateTime updateTime;//更新时间 }③ boot 工程启动类 SpringBoot 的启动类名称一般是工程名Application所以先来为 App 重命名一下 现在 BigEventApplication.java 内容是这样 改造后的 BigEventApplication.java 在用户模块总共有 6 个接口需要开发 注册登录获取用户详细信息更新用户基本信息更新用户头像更新用户密码 开发流程 1. 用户注册 接口文档 1.1 注册接口基本代码编写 需求输入用户名、密码点击注册。 首先来看一下数据库表字段和实体类属性两者是一一对应的。Java 的属性习惯用驼峰命名法数据库表字段习惯用下划线命名法。另外可以观察到用户头像userPic / user_pic的数据类型是字符串这是因为头像的图片会存放在三方服务器上这里的变量只是存放一个服务器上的访问地址。 可以看到实体类中并没有写 getter、setter、toString 等方法这是因为可以通过 lombok 工具在编译阶段自动生成 getter、setter、toString 等方法。 使用 lombok 需要在 pom.xml 中引入依赖并在实体类上添加注解。 !--lombok依赖-- dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId /dependency添加注解后重新编译 从 target 中找到编译过的 User 类可以发现其中已经有 getter、setter、toString 等方法。 响应数据一般由 code、message、data 三个数据组成。于是可以定义 Result 类该类含 code、message、data 三个成员变量。 当我们给浏览器响应一个 Result 对象时Spring 会将该 Result 对象转换成 json 字符串此时的响应数据格式就符合了文档要求。 //统一所有接口的响应结果格式 //lombok 提供了两个注解 NoArgsConstructor //在编译时生成无参构造方法 AllArgsConstructor //在编译时生成全参构造方法 Data public class ResultT {private Integer code;//业务状态码 0-成功 1-失败private String message;//提示信息// data成员变量的类型是泛型T也就是说data可以对应Object、String、Bean对象等等private T data;//响应数据//返回操作成功响应结果(带响应数据)public static E ResultE 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);} }在上面代码中使用了 lombok 提供的两个注解 NoArgsConstructor在编译时生成无参构造方法 AllArgsConstructor在编译时生成全参构造方法 完成了实体类下面就来分析一下三层架构 在 Controller 中要声明 register 方法完成注册功能。方法上添加了 PostMapping是因为文档中标明该请求是 post 请求。请求路径是 /register对比文档少了 /user是因为 Controller 类上会添加 /user两者拼接后就是文档中要求的路径了。返回值类型是统一的 Result。在方法内部要首先看用户名是否已被占用然后注册。当然这些过程需要调用 Service 来完成。 Service 层要提供对应的两个方法根据用户名查询用户、注册 相应地Mapper 层也需要声明两个方法分别用于执行查询和插入的 Sql 下面开始编写代码首先创建相关的类和接口 首先编写 UserController findByUserName 和 register 两个方法爆红是因为 UserService 中还未声明这两个方法。将光标放在爆红的方法上Ctrl Enter 就能在 UserService 中生成对应的方法。 在 UserController 中userService 爆红是因为还未向 IOC 容器中注入 UserService 的 bean 对象。 下面编写 UserService 的实现类 Service public class UserServiceImpl implements UserService {Autowiredprivate UserMapper userMapper;//根据用户名查询用户Overridepublic User findByUserName(String username) {User user userMapper.findByUserName(username);return user;}//注册(添加用户)Overridepublic void register(String username, String password) {//要先将密码通过MD5加密然后调用Mapper层进行注册String md5String Md5Util.getMD5String(password);userMapper.add(username, md5String);} }UserMapper Mapper public interface UserMapper {//根据用户名查询用户Select(select * from user where username #{username})User findByUserName(String username);//注册(添加用户)//now()是mysql的函数Insert(insert into user(username, password, create_time, update_time) values(#{username}, #{password}, now(), now()))void add(String username, String password); }postman 测试 数据库表已写入 现在数据库中已经有了 wangba 用户如果再用同样的用户名注册就会出现以下情况 1.2 注册接口参数校验 前面开发了注册接口但忽略了一件事。接口文档中对于 username 和 password 有明确的说明两者必须是 5~16 位的非空字符。 所以后端的接口必须能保证如果前端传递的参数不符合规则是不能完成注册的。因此后端接口需要对两个参数进行校验。我们首先想到的方式可能是通过 if-else 来判断如下 这种方式确实能实现预期功能但是代码看起来很繁琐。 因此Spring 提供了一个参数校验框架 Spring Validation使用预定义的注解来完成参数校验。 使用 Spring Validation 对接口参数的合法性进行校验的流程 引入Spring Validation 起步依赖在参数前面添加 Pattern 注解按照正则表达式的要求进行参数校验在 Controller 类上添加 Validated 注解使类中方法的参数上的注解能够被扫描到使 Pattern 生效 !--validationy依赖-- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-validation/artifactId /dependency完成以上操作后发现虽然校验起作用了但是返回的 json 数据并不符合接口文档要求的 code-message-data 格式 此时可以使用全局异常处理器来解决。 全局异常处理器参数校验失败异常处理 需要定义一个类并标注 RestControllerAdvice 注解表示该类用于处理异常。由于该注解是 Restxxx所以该类中所有方法的返回值都会被转换成 json 字符串相应给浏览器。在类中需要添加一个方法用于处理异常方法上添加 ExceptionHandlerException.class 表示处理所有异常。注意方法返回值类型是 Result如此一来即使出现异常返回的结果也是满足接口文档要求的。 RestControllerAdvice public class GlobalExceptionHandler {ExceptionHandler(Exception.class)public Result handleException(Exception e){//参数上声明一个变量一旦出现异常就接收捕获异常对象//为了方便调试将异常信息输出到控制台,否则因为异常被捕获了就不会输出了e.printStackTrace();//Exception对象一般都会封装错误提示信息使用e.getMessage()来获取//但有些异常并不会封装错误提示信息所以需要判断一下//借助Spring中String字符串的工具类StringUtils.hasLength()如果包含错误信息就返回否则返回“操作失败”return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : 操作失败);} }添加异常处理类之后返回的错误信息就符合接口文档中的 json 格式要求了 2. 用户登录 接口文档 2.1 登录接口基本代码编写 需求输入用户名、密码登录。 对于登录仍然是考虑 Controller、Service、Mapper 层。可以发现Service、Mapper 层需要做的就只是根据用户名查询而且这部分代码在注册部分已经完成了。 于是只关注 Controller 即可 postman 测试 ① 用户名不存在 ② 用户名密码都正确登录成功 ③ 密码错误 2.2 登录认证 2.2.1 登录认证引入 假如现在已经有了 ArticalController 接口该接口中有文章列表查询方法。 RestController RequestMapping(/article) public class ArticleController {GetMapping(/list)public ResultString list(){return Result.success(所有的文章数据...);} }正常情况下如果用户未登录应该是不能访问到 ArticalController 的文章列表查询方法的。所以其他接口就应该在提供服务之前对登录状态进行检查这个检查的过程称为登录认证。当前程序显然不能达到登录认证的效果。 如何实现登录验证呢需要借助令牌技术。 浏览器访问登录接口时如果登录成功就在后台生成令牌并把该令牌响应给浏览器。浏览器再访问其他接口时都需要携带该令牌。其他接口如果看到浏览器已携带令牌且该令牌合法就会正常提供服务否则不提供。这个令牌跟皇帝的令牌差不多起身份识别的作用。 令牌本质是一个字符串且满足以下要求 承载业务数据减少后续请求查询数据库的次数 比如系统中经常需要知道本次操作是由哪个用户发起的如果每次都去数据库查询该用户的信息就会降低系统性能。浏览器每次发起请求都会携带令牌若能把用户信息封装到令牌中需要用户数据时就可以从令牌中获取从而减少数据库查询次数提高系统性能。防篡改保证信息的合法性和有效性 令牌要具备防伪功能否则会有安全隐患 当前满足令牌要求的规范有很多在 web 开发中最常用的是 JWT。 2.2.2 JWT 简介 JWT 全称JSON Web Token即用于 web 领域的基于 json 格式的令牌https://jwt.io/ 定义了一种简洁的、自包含的格式用于通信双方以 json 数据格式安全的传输信息。 上面是一个 JWT 令牌字符串通过两个 . 将字符串分成了三部分每个字串对应 token 令牌中的一部分。 第一部分: Head(头)是由一段 json 字符串编码得来该 json 字符串记录两个信息alg 是加密算法防篡改type 是令牌类型。 第二部分: Payload(有效载荷)是由一段 json 字符串编码得来该 json 字符串用于存放业务数据如用户的 id 和 username。 在这两部分中json 字符串如何才能转换成 token 令牌中展示的这段字符串呢在 JWT 中会借助 Base64 这种编码方式来完成。把任意数据转换成 64 个字符可打印字符这些字符的特点就是通用在任意场景下都能被支持。 为什么要将 json 字符串转换为 64 个可打印字符呢主要是为了提高 token 的适用性。比如当 json 中包含中文或空格等字符时cookie 就不能支持因此 JWT 中就将 json 字符串通过 Base64 编码转换成 token 中展示的形式。 Base64 仅仅是一种编码方式并不是加密方式且这种编码方式是公开的任何人都能通过 Base64 进行编码和解码。所以在 token 的第二部分有效载荷一定不要存放登录密码等私密数据。 第三部分: Signature(数字签名)将第一、二部分借助密钥和加密算法经过加密得来这里的加密算法就是第一部分头中通过 alg 来指定的密钥可以在程序中单独配置。有了数字签名就可以防篡改了确保 token 是安全的。因为即使篡改了前两部分第三部分也是不能篡改的因为该部分是加密后的字符串。将来 JWT 在去解析 token 令牌时会通过解密数字签名来得到第一、二部分再拿着解密的内容与用户传递的内容进行比对如果不一样就证明篡改过数据就不允许访问。 下面在代码中实现 在用户登录成功后需要生成 JWT 令牌并响应给浏览器。前面说过浏览器在访问其他接口时都需要携带该令牌。其他接口如果看到浏览器已携带令牌且该令牌合法就会正常提供服务否则不提供。所以重点在于如何生成令牌、验证令牌。 在实际开发中生成令牌、验证令牌的代码不需要记一般直接调用现成 api。 (1) 导入坐标 !--SpringBoot整合单元测试的起步依赖-- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactId /dependency(2) 生成、验证 JWT 令牌 先通过测试类编写代码主体验证代码的功能。 public class JwtTest {//生成JWT令牌Testpublic void testGen() {MapString, Object claims new HashMap();claims.put(id, 1);claims.put(username, 张三);//生成jwt的代码String token JWT.create().withClaim(user, claims)//添加数据//添加过期时间登录后一个小时不操作令牌就失效.withExpiresAt(new Date(System.currentTimeMillis() 1000 * 60 * 60)).sign(Algorithm.HMAC256(itheima));//指定算法配置密钥为itheimaSystem.out.println(token);}//验证JWT令牌Testvoid testParse() {String token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDU2NzQzNDYsInVzZXIiOnsiaWQiOjEsInVzZXJuYW1lIjoi5byg5LiJIn19.lgWdmBGpXd6wNXSIxxUxBgx5BcGEH12f17a1iJ-2AaU;//申请JWT验证器解密时采用与加密同样的算法和密钥//build方法用于生成验证器JWTVerifier jwtVerifier JWT.require(Algorithm.HMAC256(itheima)).build();//调用验证器的方法来验证token生成解析后的JWT对象//如果解析成功就可以从中得到头部、载荷、签名DecodedJWT decodedJWT jwtVerifier.verify(token);//getClaims得到所有的载荷MapString, Claim claims decodedJWT.getClaims();//只需要得到键为user的载荷System.out.println(claims.get(user));} }如果解析令牌时抛出异常可能的原因是 篡改了头部或载荷部分的数据解密与加密的密钥不一致token 过期 2.2.3 登录功能集成 JWT 在上节我们已经能够生成和验证 JWT 令牌本节将在登录接口中集成 JWT。 登录成功时生成令牌其他接口在提供服务前验证该令牌只有令牌合法才提供服务。 当然第一步仍然是导入坐标 !--SpringBoot整合单元测试的起步依赖-- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactId /dependencyJWT 生成和验证工具类 public class JwtUtil {private static final String KEY itheima;//接收业务数据,生成token并返回public static String genToken(MapString, 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 MapString, Object parseToken(String token) {return JWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim(claims).asMap();}}添加了 JWT 令牌生成功能的 UserController //RestController整合了ResponseBody注解将后台传到前端的java对象转为json数据 RestController RequestMapping(/user) Validated public class UserController {Autowiredprivate UserService userService;PostMapping(/register)//^和$之间是正则表达式的主体\S表示非空它前面的\是转义字符{5,16}表示5~16位public Result register(Pattern(regexp ^\\S{5,16}$) String username, Pattern(regexp ^\\S{5,16}$) String password){//查询用户User user userService.findByUserName(username);if(user null){//没有占用注册userService.register(username, password);return Result.success();}else {//已占用return Result.error(用户名已被占用);}}PostMapping(/login)public ResultString login(Pattern(regexp ^\\S{5,16}$) String username, Pattern(regexp ^\\S{5,16}$) String password){//根据用户名查询用户User user userService.findByUserName(username);//判断用户是否存在if (user null){return Result.error(用户名错误);}//判断密码是否正确从数据库获取到的user对象的password是密文if (Md5Util.getMD5String(password).equals(user.getPassword())){//登录成功生成JWT令牌MapString, Object claims new HashMap();//令牌携带id和username即可claims.put(id, user.getId());claims.put(username, user.getUsername());//调用方法生成JWT令牌String token JwtUtil.genToken(claims);return Result.success(token);}//密码错误return Result.error(密码错误);} }postman 验证 在 Article 提供服务之前先验证 token 令牌这个 token 从哪来呢之前我们说浏览器访问其他接口时会携带 token这个 token 是以什么形式携带的呢是请求头还是请求体文档中给出了说明 用户登录成功后系统会自动下发 JWT 令牌然后在后续的每次请求中浏览器将 JWT 令牌包裹在请求头 header 中携带到服务端。JWT 令牌在请求头中的名称为 Authorization。 如果检测到用户未登录则 http 响应状态码为 401(未授权)。 所以应该在请求头中去获取浏览器携带的 token。从请求头获取 token 可以直接在参数中声明并在参数前面添加注解 RequestHeader(name请求头名称)这样 token 就能获取到了。获取到 token 之后再利用 JwtUtil 解析 token如果解析的代码正常执行说明 JWT 验证成功否则失败。另外在验证失败时http 响应状态码是 401。如何设置响应状态码需要一个 Response 对象Response 对象也可以在参数中声明到时候框架在调用方法时会把该对象传入。 RestController RequestMapping(/article) public class ArticleController {GetMapping(/list)public ResultString list(RequestHeader(name Authorization) String token, HttpServletResponse response){try {//如果这行代码正常执行就解析成功MapString, Object claims JwtUtil.parseToken(token);return Result.success(所有的文章数据...);} catch (Exception e) {//设置http响应状态码为401response.setStatus(401);return Result.error(未登录);}}}完成了 JWT 验证的代码下面来测试一下是否能达到预期效果 未登录时RequestHeader(name Authorization) String token 参数接收不到请求头抛出异常文章列表访问不成功这段 json 是由前面定义的异常处理器返回的 如果浏览器要携带请求头需要写前端代码如果不写前端代码的话可以用 postman 辅助验证 2.2.4 拦截器 至此就完成了登录认证。但是存在一个问题Contorller 层有 UserController、ArticleController 等很多 Controller且每个 Contorller 都提供了很多接口难道要在每个接口中都写同样的代码完成令牌的校验吗这显然是不合适的。 如果多个接口有同样的任务要完成可以用拦截器实现 要实现拦截器首先要定义一个类去实现 HandlerInterceptor 接口并重写 preHandle 方法意为在目标方法执行之前拦截下来 Component//将当前拦截器的对象注入IOC容器 public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//拦截请求拦截下来之后进行令牌验证//之前通过参数声明来拿到令牌这里借助request对象(请求对象)拿到令牌因为这个对象包含所有的请求数据String token request.getHeader(Authorization);//解析令牌try {//如果这行代码正常执行就解析成功MapString, Object claims JwtUtil.parseToken(token);//放行return true;} catch (Exception e) {//解析失败//设置http响应状态码为401response.setStatus(401);//不放行return false;}} }还要写一个 web 配置类它实现 WebMvcConfigurer 接口在该类重写的 addInterceptors 方法中将刚刚编写的拦截器注册进去就可以了。注意登录和注册接口不拦截。 Configuration public class WebConfig implements WebMvcConfigurer {Autowiredprivate LoginInterceptor loginInterceptor;Overridepublic void addInterceptors(InterceptorRegistry registry) {//登录接口和注册接口不拦截registry.addInterceptor(loginInterceptor).excludePathPatterns(/user/login, /user/register);} }现在我们把 ArticleController 中接口的代码注释掉 用 postman 测试 ① 没有登录携带令牌时访问文章列表 ② 登录携带令牌后访问文章列表 3. 获取用户详细信息 3.1 获取用户详细信息基本代码编写 需求当用户登录成功后需要跳转到首页首页顶部展示了用户昵称、头像等信息像这样的数据都需要访问后台接口来获取。在个人中心中有基本资料、更换头像、重置密码功能也都需要先查询再展示。 获取用户详细信息需要根据已登录的用户名查询用户。 但接口文档中写到该请求没有请求参数并没有携带 username。此时可以从 token 令牌中获取 username因此可以在方法上声明一个带RequestHeader(name Authorization)注解的参数从而可以获取 token进而解析 token 得到用户名。关于根据用户名查询信息在 Service 和 Mapper 层的实现前面都已经完成了此处不再赘述。 代码具体实现 在 UserController 中添加 RequestMapping(/userInfo) public ResultUser userInfo(RequestHeader(name Authorization) String token){MapString, Object map JwtUtil.parseToken(token);String username (String) map.get(username);User user userService.findByUserName(username);return Result.success(user); }postman 测试 未登录未携带 token 令牌 已登录已携带 token 令牌 我们用 postman 测试时每一个请求都要单独在请求头中设置 token 令牌比较繁琐。实际上可以为一个集合中所有的请求统一添加请求头 但是此时还有一个问题后台在响应获取用户信息的请求时把用户密码也传过去了这样是不安全的。 解决方式在 password 成员变量上添加 JsonIgnore 注解这样SpringMVC 在把当前对象转换成 json 字符串时就会忽略 password最终的 json 字符串也就没有 password 这个属性了。 postman 测试发现后台传回的数据中确实没有 password 了 另外可以发现传回的数据中 nickname、email、userPic 为空这时因为用户注册时并没有填写这些信息后续用户注册成功后才会完善这些信息。 那么createTime 和 updateTime 为什么为 null 呢 原因在于数据库表中的字段是下划线命名方式而 User 实体类中的成员变量是驼峰命名方式二者名称不一致所以 mybatis 就不知道该如何封装了。 要解决这个问题需要在配置文件application.yml中开启驼峰命名和下划线命名自动转换 mybatis:configuration:map-underscore-to-camel-case: true#开启驼峰命名和下划线命名自动转换开启之后当 mybatis 发现数据库表中是下划线命名时就会在实体类中去找对应的驼峰命名的属性然后完成数据封装。此时再次用 postman 测试可以发现后台已经能返回 createTime 和 updateTime。 3.2 ThreadLocal 优化 在获取用户信息的代码中为了获取已登录用户的用户名需要在方法的参数上声明 token并且在方法体中解析 token 得到用户名。这看似合理但是拦截器中已经解析过 token 了重复写相同的代码不是一种好的编程习惯。因此我们希望在其他地方复用拦截器中解析得到的结果。如何才能做到呢 此时需要用到 ThreadLocal 来优化代码。 ThreadLocal 的作用 提供线程的局部变量。提供 set() / get() 方法来存取数据: 使用 ThreadLocal 存储的数据是线程安全的每个线程之间互不影响。 举个例子多个线程使用 ThreadLocal 存取用户名现有蓝色、绿色两个线程它们都持有 ThreadLocal 的 tl 对象的引用。在两个线程中都可以使用 set() 方法来存储用户名蓝色线程存储用户名“萧炎”绿色线程存储用户名“药尘”。存储完毕后在蓝色线程中调用 get() 方法获取用户名时获取到的就是“萧炎”而不能获取到绿色线程存入的“药尘”绿色线程中调用 get() 方法时同理。因为 ThreadLocal 会为两个线程分别创建数据存储空间可以做到线程隔离。 代码验证 public class ThreadLocalTest {Testpublic void testThreadLocalSetAndGet(){//提供一个ThreadLocal对象ThreadLocal tl new ThreadLocal();//开启两个线程//传递两个参数线程任务和线程名字new Thread(()-{tl.set(萧炎);System.out.println(Thread.currentThread().getName(): tl.get());System.out.println(Thread.currentThread().getName(): tl.get());System.out.println(Thread.currentThread().getName(): tl.get());}, 蓝色).start();new Thread(()-{tl.set(药尘);System.out.println(Thread.currentThread().getName(): tl.get());System.out.println(Thread.currentThread().getName(): tl.get());System.out.println(Thread.currentThread().getName(): tl.get());}, 绿色).start();} }输出结果 蓝色: 萧炎 蓝色: 萧炎 绿色: 药尘 绿色: 药尘 绿色: 药尘 蓝色: 萧炎现在ThreadLocal 的作用已经明白了但是它与当前需求有什么关系呢 假设现在程序中有 ArticleController、ArticleService、ArticleDao它们都有 add() 方法且方法内部都要用到 userId那么就得在每个方法上声明 userId 完成参数传递如果其他地方需要 userId也需要通过传参来完成。 这个操作不难但是很繁琐。如果既不想在方法上声明又想使用 userId怎么办呢此时就可以用 ThreadLocal 来优化。 可以维护一个全局的 ThreadLocal 对象用来存储用户名这类数据。有了它可以在请求到达拦截器之后调用 tl.set() 方法将 userId 存储到 ThreadLocal 中。接下来当请求到达 ArticleController、ArticleService、ArticleDao 之后就可以在它们的 add() 方法中调用 tl.get() 方法从 ThreadLocal 中获取 userId 来使用。 Controller、Service、Dao 在容器中一般都是单例的获取 id 的时候怎么知道是哪个用户的 id 呢会不会发生线程安全问题呢 假如现在有两个用户来访问程序其 userId 分别是 1 和 2当请求到达 tomcat 之后服务器会为每个用户开辟一个线程用来提供服务。在为用户 1 提供服务时会把拦截器中的 preHandle() 方法加载进黑色线程执行。在执行 tl.set(userId) 时由于用户 1 携带的 userId1所以就将 1 这个 userId 设置进去。拦截器放行之后会依次加载 ArticleController、ArticleService、ArticleDao 中的 add() 方法进栈继续执行在执行这些 add() 方法时获取到的 userId 都是 1为用户 2 提供服务时同理。这样就做到了线程隔离。 因此可以借助 ThreadLocal 做到两件事 减少参数传递同一个线程执行的代码之间共享数据拦截器中的数据共享到 Controller、Service、Dao 中 明白了 ThreadLocal 的使用场景下面借助 ThreadLocal 优化一下代码 (1) 提供 ThreadLocal 工具类 SuppressWarnings(all) public class ThreadLocalUtil {//提供一个常量THREAD_LOCAL用来维护一个全局唯一的ThreadLocal对象private static final ThreadLocal THREAD_LOCAL new ThreadLocal();//根据键获取值public static T T get(){//调用ThreadLocal对象的get()方法把得到的数据返回//强制类型转换return (T) THREAD_LOCAL.get();}//存储键值对public static void set(Object value){//调用ThreadLocal的set方法把值存进去THREAD_LOCAL.set(value);}//清除ThreadLocal防止内存泄漏//ThreadLocal对象是全局唯一的生命周期特别长如果里面的数据一直不清除有可能造成内存泄漏public static void remove(){THREAD_LOCAL.remove();} }(2) 在拦截器中把 token 令牌含id、username的解析结果存入 ThreadLocal (3) UserController 中的方法获取 ThreadLocal 存储的 token 令牌解析结果 前面说过ThreadLocal 中的数据用完后要清除应该在哪清除呢 (4) 在拦截器中重写 afterCompletion() 方法来清除数据 当请求发起后在拦截器中解析 token 令牌并将解析结果存入 ThreadLocal拦截器放行之后Controller、Service、Dao 中就都能使用到 ThreadLocal 中的数据。那么 ThreadLocal 中的数据什么时候用完呢当响应完成即本次请求结束后该数据就不再使用了。所以就应该在请求结束后将数据移除。 postman 测试成功 4. 更新用户基本信息 接口文档 4.1 更新用户基本信息基本代码编写 需求当用户在个人中心点击“基本信息”时页面主区域会展示当前用户的详细信息用户可以修改“用户昵称”、“用户邮箱”最后提交修改从而访问后台接口更新当前用户信息。而且可以看到登录名称即“用户名”是不允许修改的。 浏览器在请求体中以 json 格式携带 id、username、nicknime、email 等数据在后台会用实体类对象 User 来接收这些数据为了让框架能够将请求体中的 json 数据自动转换成实体类对象需要在接收参数之前添加 RequestBody 注解方法声明完成后在方法体内调用 Service 层的方法完成更新就可以了。所以 Service 层将来也要提供更新对应的方法Mapper 层也要执行对应的 SQL UserController 中添加方法 PutMapping(/update) //RequestBody将请求体中的json数据自动转换成实体类对象传给参数user public Result update(RequestBody User user){userService.update(user);return Result.success(); }UserService 及其实现类添加 //UserService.java void update(User user);//更新用户信息//UserServiceImpl.java Override public void update(User user) {//更新用户信息//获取系统当前时间user.setUpdateTime(LocalDateTime.now());userMapper.update(user); }UserMapper 添加 //更新用户信息 //格式数据库表字段名#{实体类属性名} Update(update user set nickname#{nickname},email#{email},update_time#{updateTime} where id#{id}) void update(User user);postman测试 4.2 更新用户基本信息参数校验 在接口的方法上声明了一个实体类参数 user用它来接收前端传过来的参数 id、username、nicknime、email但刚才并没有对这些参数进行校验而接口文档中对 id、nicknime、email 这三个参数是由要求的id 必须传递不能为 nullnicknime 除了不为 null还得是 1~10 位的非空字符email 除了不为 null还要满足邮箱格式。而 username 在注册之后就不能修改了所以此时可以不用关注。 本章节采用 Validation 完成参数校验。 之前注册的时候已经使用 Validation 进行了请求参数的校验只不过是通过直接在方法参数上添加 Pattern 注解的方式。 但现在把请求数据封装在实体类对象 user 中了像这样的实体参数应该如何完成校验呢 ① 在实体类的成员变量上添加 Validation 提供的注解对指定的属性值进行参数校验 ② 在具体使用该实体参数的地方添加 Validated 注解此时实体类属性上的注解才能生效 5. 更新用户头像 接口文档 5.1 更新用户头像基本代码编写 需求当用户在个人中心点击了“更换头像”之后在页面主区域展示出当前用户的头像用户点击“选择图片”按钮选择本地的一张图片然后点击“上传头像”按钮以访问后台接口完成头像更新。 Controller 层添加更新头像的方法由于请求方式是 PATCH所以方法上添加 PatchMapping 注解方法的参数列表上声明 avatarUrl 参数来接收头像地址。方法内部调用 Service 层更新头像的方法Mapper 层编写 SQL 来更新头像update_time 也要更新一下。 UserController 中添加更新头像的方法 PatchMapping(/updateAvatar) //RequestParam: 用于接收url地址传参,标明需要从queryString中获取数据 public Result updateAvatar(RequestParam String avatarUrl){userService.updateAvatar(avatarUrl);return Result.success(); }UserService 中添加 //UserService接口 //更新用户头像 void updateAvatar(String avatarUrl);//UserService实现类 //更新用户头像 Override public void updateAvatar(String avatarUrl) {//因为mapper层需要根据id修改用户头像所以要先从ThreadLocal中把id拿过来MapString,Object map ThreadLocalUtil.get();Integer id (Integer) map.get(id);userMapper.updateAvatar(avatarUrl, id); }UserMapper 中添加 //更新用户头像 Update(update user set user_pic#{avatarUrl}, update_timenow() where id#{id}) void updateAvatar(String avatarUrl, Integer id);postman 测试 5.2 url 参数校验 在当前更新用户头像代码的基础上如果传递一个不符合 url 地址格式要求的地址仍然会操作成功且更新数据库这是不合理的。因此需要后台要对传递过来的头像地址 avatarUrl 进行参数校验。 如何校验是否为 url 地址呢 Validation 提供了一个注解 URL 来完成数据是否是 url 的校验 postman 测试 6. 更新用户密码 接口文档 需求用户点击个人中心的“重置密码”后会在主区域展示重置密码的表单表单填写完成后可以点击“修改密码”按钮从而调用后台接口完成修改密码操作。 之前接收前端 json 数据时采用的是 User 实体对象当时是因为 json 中的键名刚好与 User 实体类中的属性名相同。但是现在更新用户密码时json 中的键名可能是 oldPwd、newPwd 等与实体类的属性不一致所以需要声明一个 Map 集合来接收前端传过来的 json 数据。到时候SpringMVC 会自动将 json 数据转换成 Map 集合对象。方法声明好之后同样需要在方法内部调用 Service 层的方法完成密码更新Mapper 层也需要执行对应的 SQL除了要更新 password 字段还要更新 update_time字段且同样要通过 id 来更新。 UserController 中添加修改 / 更新密码的方法 PatchMapping(/updatePwd) //RequestBody使SpringMVC框架自动读取请求体中的json数据转换为Map集合对象 public Result updatePwd(RequestBody MapString, String params){//Validation提供的注解并不能满足需求所以要手动校验参数String oldPwd params.get(old_pwd);String newPwd params.get(new_pwd);String rePwd params.get(re_pwd);//原密码、新密码、确认密码是否都传过来了if (!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)){return Result.error(缺少必要的参数);}//原密码填写是否正确//从ThreadLocalUtil的信息中拿到username从而查询到数据库中的原密码与用户输入的原密码进行比对MapString,Object map ThreadLocalUtil.get();String username (String) map.get(username);User user userService.findByUserName(username);if (!user.getPassword().equals(Md5Util.getMD5String(oldPwd))){return Result.error(原密码填写不正确);}//新密码与确认密码是否一致if (!newPwd.equals(rePwd)){return Result.error(新密码与确认密码不一致);}//调用Service完成密码更新userService.updatePwd(newPwd);return Result.success(); }UserService 中添加 //UserService接口 //更新用户密码 void updatePwd(String newPwd);//UserService实现类 //更新用户密码 Override public void updatePwd(String newPwd) {//因为mapper层需要根据id修改用户密码所以要先从ThreadLocal中把id拿过来MapString,Object map ThreadLocalUtil.get();Integer id (Integer) map.get(id);//新密码需要先加密再更新到数据库userMapper.updatePwd(Md5Util.getMD5String(newPwd), id); }UserMapper 中添加 //更新用户密码 Update(update user set password#{password}, update_timenow() where id#{id}) void updatePwd(String password, Integer id);postman 测试
http://www.zqtcl.cn/news/463225/

相关文章:

  • 做兼职的翻译网站吗教育直播网站开发
  • pxhere素材网站电子商务的网站开发的工作内容
  • 邮件网站怎么做wordpress如何代码高亮
  • 电脑做视频的网站吗中小学 网站建设 通知
  • 给企业做网站赚钱吗吉 360 网站建设
  • 网站建设多少价格东莞网站推广团队
  • 做课件的软件下载带有蓝色的网站html网页制作代码实例
  • 建设银行鄂州分行官方网站健身网站开发方式
  • 大连免费建站模板花坛设计平面图
  • 建设网站对企业有什么好处wordpress教程视频下载
  • 郑州网站提升排名上海 企业 网站建设
  • 南昌好的做网站的公司营销型网站 案例
  • 南宁经典网站建设网络运维工程师是干什么的
  • 网站开发算法建网站难不难
  • 茂名模板建站定制网站开发 ide
  • 做网站现在用什么语言网站估价
  • wap开头的网站外贸网站建设官网
  • 做网站说什么5.0啥意思wordpress教程视频 下载
  • 业务型网站做seo郑州网站推广优化
  • 400网站建设南昌网站建设方案详细版
  • 网站评论回复如何做中国住建部和城乡建设官网
  • 怎么建设网站南京做南京华美整容网站
  • 有哪些可以做1元夺宝的网站推广网站哪家做的好
  • 网站备案 域名不是自己的成都电子商务网站
  • 网站内容管理系统建设2021年建站赚钱
  • 网站建设交流发言稿找做网站的上什么app
  • 企业如何应用网站的wordpress lensnews
  • 可信的邢台做网站学电商运营需要多少钱
  • 网站中文名称做微商进哪个网站安全
  • 网站前端建设需要学会什么意思wordpress 快递查询 插件