SpringBoot实战2
SpringBoot实战2–注意所有的请求参数如果是通过JSON格式的数据请求需要加上@RequestBody
ThreadLocal—》使用ThreadLocal存储的数据,线程安全
ThreadLocal 类提供了一种方式,使得每个线程可以独立地持有自己的变量副本,而不是共享变量。这可以避免线程间的同步问题,因为每个线程只能访问自己的ThreadLocal变量
使用ThreadLocal时,通常需要实现以下步骤:
• 初始化:创建ThreadLocal变量。
private static ThreadLocal````threadLocal = new ThreadLocal <>();
• 设置值:使用set(T value)方法为当前线程设置值。
threadLocal.set(value);
• 获取值:使用get()方法获取当前线程的值。
T value = threadLocal.get();
• 移除值:使用remove()方法在线程结束时清除ThreadLocal变量,以避免内存泄漏。
threadLocal.remove();
/**
* ThreadLocal 工具类
*/
@SuppressWarnings("all")
public class ThreadLocalUtil {
//提供ThreadLocal对象,
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
//根据键获取值
public static <T> T get(){
return (T) THREAD_LOCAL.get();
}
//存储键值对
public static void set(Object value){
THREAD_LOCAL.set(value);
}
//清除ThreadLocal 防止内存泄漏
public static void remove(){
THREAD_LOCAL.remove();
}
}
1、登录拦截器中实现ThreadLocal传递参数,拦截器调用结束之后清空ThreadLocal中的数据
2、在解析过token的验证信息之后将业务数据存储到ThreadLocal中,方便后续调用
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在这个拦截器中验证Token---》通过请求头中携带的的token
String token = request.getHeader("Authorization");
//解析Token
try {
Map<String, Object> claims = JwtUtil.parseToken(token);
//TODO:将相关的业务数据存到ThreadLocal中-->方便后续业务逻辑中获取
ThreadLocalUtil.set(claims);
//TODO:如果解析成功,则放行
return true;
} catch (Exception e) {
response.setStatus(401);
//TODO:如果解析失败,则返回错误信息,并且拦截
return false;
}
}
//TODO:拦截器执行完毕后,清除ThreadLocal中的数据
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocal中的数据
ThreadLocalUtil.remove();
}
}
一、更新用户信息(对于更新部分实体信息通过注解@PatchMapping,更新全部信息的话使用@PutMapping)
(每一次的业务操作都需要重新获取对应的token信息,通过Jwt令牌验证是否登录)
1、定义请求接口
// TODO: 修改用户信息
@PutMapping("/update")
public Result update(@RequestBody User user)//@RequestBody将对应的User对象要修改的JSON格式的信息转换到user对象
{
userService.update(user);
return Result.success();
}
2、通过业务层UserServiceImpl中实现对应的调用
注意:在对于update_time字段在业务层进行修改更新,同时通过对象的set方法将update信息进行更新
@Override
public void update(User user) {
user.setUpdateTime(LocalDateTime.now());
userMapper.update(user);
}
3、通过数据访问层UserMapper实现数据库更新处理
@Update("update user set nickname=#{nickname},email=#{email},update_time=#{updateTime} where id=#{id}")
void update(User user);
实体对象的参数校验
参考原有的普通参数的校验操作通过@Validated——Validation
同时在参数前指定参数的约束正则表达式
@Pattern(regexp="^\\S{5,16}$")
public Result<User> register(@Pattern(regexp = "^\\S{5,16}$") String username,
@Pattern(regexp = "^\\S{5,16}$") String password){
// TODO: 查询用户是否存在
User user=userService.findByUserName(username);
if (user!=null){
return Result.error("用户已存在");
}else {
// TODO: 用户不存在的话就开始注册用户
userService.register(username,password);
return Result.success();
}
}
实体对象的参数校验—》写在实体对象参数的引入处@Validated
通过实体参数校验的注解实现写在实体对象的定义处
@NotEmpty:值不能为空(null)并且内容不为空
@NotNull:值不能为空(null)
@Pattern(regexp=“^\\S{5,10}$”)
public class User {
@NotNull
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore//springmvc在将次对象转换为json字符串时忽略此字段
private String password;//密码
@NotEmpty
@Pattern(regexp = "^\\S{5,10}$")
private String nickname;//昵称
@NotEmpty
@Email
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
// TODO: 修改用户信息
@PutMapping("/update")
public Result update(@RequestBody @Validated User user)//@RequestBody将请求体中json数据封装到user对象中
{
userService.update(user);
return Result.success();
}
二、更新用户头像
在参数上指定请求的参数为avatarUrl,通过@URL注解指定这个参数必须是一个URL形式的
// TODO: 更新用户头像
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl)//@RequestParam将请求参数中name=avatarUrl的值封装到avatarUrl中
{
userService.updateAvatar(avatarUrl);
return Result.success();
}
在进行数据操作层的调用之前将token中携带的id信息传递进去
// TODO: 通过token 获取用户id
Map<String, Object> map = ThreadLocalUtil.get();
// 获取用户id通过map中key的值
Integer id = (Integer) map.get("id");
@Override
public void updateAvatar(String avatarUrl) {
// TODO: 通过token 获取用户id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvatar(avatarUrl,id);
}
通过更新操作数据库–@Param指定参数名—》#{}中的对应指定的名字
@Update("update user set user_pic=#{avatarUrl},update_time=now() where id=#{id}")
void updateAvatar(@Param("avatarUrl") String avatarUrl, Integer id);
三、用户更新密码
在Controller层进行编写更新密码的操作
@PatchMapping指定请求路径
// TODO: 更新用户的密码
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String,String> params){
//1、校验参数
String oldPwd = params.get("old_pwd");
String newPwd = params.get("new_pwd");
String rePwd = params.get("re_pwd");
//判断只要有一个为空就返回错误信息
if(oldPwd==null||newPwd==null||rePwd==null){
return Result.error("参数不能为空");
}
//判断原密码是否正确---》调用Service根据用户名获取原密码,和oldPwd进行比较
//用户名通过ThreadLocalUtil存储获取
Map<String,Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = userService.findByUserName(username);
String pwd = user.getPassword();//原有的密码是进行了加密的,所以需要先对用户输入的密码进行加密再进行比较
if(!Md5Util.getMD5String(oldPwd).equals(pwd)){
return Result.error("原密码错误");
}
//判断新密码和确认密码是否一致
if(!newPwd.equals(rePwd)){
return Result.error("新密码和确认密码不一致");
}
//2、调用Service完成更新密码
userService.updatePwd(newPwd);
return Result.success();
}
Service层中将新密码先进行加密再传递给Mapper层
@Override
public void updatePwd(String newPwd) {
//将新密码进行加密再传递给mapper层
newPwd = Md5Util.getMD5String(newPwd);
//通过LocalThread获取用户id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updatePwd(newPwd,id);
}
Mapper层中进行修改密码
@Update("update user set password=#{newPwd} where id=#{id}")
void updatePwd(String newPwd, Integer id);
四、新增文章分类
根据接口文档定义对应的请求路径设定Mapping映射的路径,对于相关的实体对象参数进行校验
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
//TODO:新增文章分类
@PostMapping
public Result add(@RequestBody @Validated Category category){
categoryService.add(category);
return Result.success();
}
}
书写对应的业务层代码,对于对象中为实现的参数进行赋值定义,在传递给Mapper层
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public void add(Category category) {
//由于传递的参数只有categoryName和categoryAlias,所以需要手动设置createUser和createTime,updateTime
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
//通过ThreadLocal获取当前登录用户id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
category.setCreateUser(id);
categoryMapper.add(category);
}
在Mapper层实现数据访问操作
@Mapper
public interface CategoryMapper {
@Insert("insert into category(category_name,category_alias,create_user,create_time,update_time)" +
" values (#{categoryName},#{categoryAlias},#{createUser},#{createTime},#{updateTime})")
void add(Category category);
}
五、文章分类列表
Controller层
//TODO:获取文章分类列表
@GetMapping
public Result<List<Category>> list(){
List<Category> list=categoryService.list();
return Result.success(list);
}
Service层传递此用户的ID到Mapper
@Override
public List<Category> list() {
// TODO:查询的当前用户的文章分类列表
//通过ThreadLocalUtil.get()获取当前登录用户id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
return categoryMapper.findCategoryById(id);
}
Mapper执行查询全部同一ID
@Select("select * from category where create_user=#{id}")
List<Category> findCategoryById(Integer id);
注意对于查询时间这一类的信息的时候可以通过在实体类的定义的时候进行注解@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")指定日期格式
//通过@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解来指定日期格式
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间
六、获取文章详细信息
Controller层
//TODO:获取文章分类详情
@GetMapping("/detail")
public Result<Category> detail(@RequestParam Integer id){
Category category=categoryService.detail(id);
return Result.success(category);
}
Service层
@Override
public Category detail(Integer id) {
return categoryMapper.detail(id);
}
Mapper层
@Select("select * from category where id=#{id}")
Category detail(Integer id);
七、更新文章分类
Controller层–》对于实体对象的参数的校验@Validated
//TODO:更新文章分类信息
@PutMapping
public Result update(@RequestBody @Validated Category category){
categoryService.update(category);
return Result.success();
}
Service层
@Override
public void update(Category category) {
category.setUpdateTime(LocalDateTime.now());
categoryMapper.update(category);
}
Mapper层
@Update("update category set category_name=#{categoryName},category_alias=#{categoryAlias},update_time=#{updateTime} where id=#{id}")
void update(Category category);
@Validated注解的分组参数校验
对于在进行添加,修改操作的时候,方法中的参数校验不是一样的,那么就需要用到分组校验的形式进行校验参数
@NotNull(groups = Update.class)//表示此字段不能为空
private Integer id;//主键ID
@NotEmpty(groups = {Add.class, Update.class})//指定这个参数校验属于两个分组
private String categoryName;//分类名称
@NotEmpty(groups = {Add.class, Update.class})
private String categoryAlias;//分类别名
更新操作需要设置id为@NotNull,添加操作不需要
//TODO:更新文章分类信息
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){
categoryService.update(category);
return Result.success();
}
//TODO:新增文章分类
@PostMapping
public Result add(@RequestBody @Validated(Category.Add.class) Category category){
categoryService.add(category);
return Result.success();
}
同一个实体参数的校验项多个组别中都需要用到
@NotEmpty(groups = {Add.class, Update.class}) private String categoryAlias;//分类别名
如果在如果说对于某些校验的参数没有进行分组,那么默认就是属于默认分组Default
八、删除文章分类
Controller层:
//TODO:删除文章分类
@DeleteMapping
public Result delete(@RequestParam Integer id){
categoryService.delete(id);
return Result.success();
}
Service层:
@Override
public void delete(Integer id) {
categoryMapper.delete(id);
}
Mapper层:
@Delete("delete from category where id=#{id}")
void delete(Integer id);
九、新增文章
Controller层:
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;
//TODO:新增文章
@PostMapping
public Result add(@RequestBody @Validated Article article){
articleService.add(article);
return Result.success();
}
}
实体对象的参数校验:
原有方式:通过@Validated自带的对应参数校验的注解进行校验
public class Article {
private Integer id;//主键ID
@NotEmpty
//1~10个非空字符
@Pattern(regexp = "^\\S{1,10}$")
private String title;//文章标题
@NotEmpty
private String content;//文章内容
@NotEmpty
@URL
private String coverImg;//封面图像
@NotEmpty
//状态只能为已发布或者草稿
@Pattern(regexp = "^(已发布|草稿)$")
private String state;//发布状态 已发布|草稿
@NotNull
private Integer categoryId;//文章分类id
private Integer createUser;//创建人ID
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
通过自定义参数校验的注解
对于State状态的校验为已发布或者草稿—》只能是这两种方式
1、自定义一个实体变量参数校验注解(用于关联校验规则类)
注解需要实现三个方法–》
message():用于指定校验失败的错误信息
groups():用于指定该校验注解的分组
payload():指定该校验注解的负载
通过@Constraint关联用于编写校验规则的类
@Constraint( validatedBy = {StateValidation.class} )
@Documented//元注解
//TODO:元注解,表示该注解可以用在哪些地方
@Target({FIELD})
//TODO:元注解,表示该注解在运行时生效
@Retention(RetentionPolicy.RUNTIME)
//TODO:指定那个类来提供校验规则
@Constraint(
validatedBy = {StateValidation.class}
)
public @interface State {
//TODO:校验失败时,提示信息---》只能为已发布或者草稿
String message() default "状态只能为已发布或者草稿";
//TODO:指定分组校验
Class<?>[] groups() default {};
//TODO:指定负载,获取State注解的附加信息 (一般用不上)
Class<? extends Payload>[] payload() default {};
}
2、实现对应的校验规则类–》实现ConstraintValidator接口<指定给哪个注解提供校验规则,校验的数据类型>
重写isValid方法,提供校验规则
//TODO:状态校验器类
// TODO:实现ConstraintValidator接口<指定给哪个注解提供校验规则,校验的数据类型>
// 接口的泛型为@State注解和String类型(已发布|草稿----》为String类型)
// TODO:重写isValid方法
public class StateValidation implements ConstraintValidator<State, String> {
/**
* 重写isValid方法,提供校验规则
* @param value 将要进行校验的数据
* @param context 上下文对象,可以获取到校验规则
* @return 校验结果为true表示校验通过,false表示校验失败
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//TODO:提供校验规则
if(value==null){
return false;
}
if(value.equals("已发布")||value.equals("草稿")){
return true;
}
return false;
}
}
3、在对应的实体对象中的State成员变脸上添加@State注解
@NotEmpty
//状态只能为已发布或者草稿
//@Pattern(regexp = "^(已发布|草稿)$")
@State
private String state;//发布状态 已发布|草稿
Service层:
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Override
public void add(Article article) {
//指定创建文章时间,更新文章时间
article.setCreateTime(LocalDateTime.now());
article.setUpdateTime(LocalDateTime.now());
//从LocalThread中获取当前登录用户id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id= (Integer) map.get("id");
article.setCreateUser(id);
articleMapper.add(article);
}
}
Mapper层:
@Mapper
public interface ArticleMapper {
@Insert("insert into article(title,content,cover_img,state,category_id,create_user,create_time,update_time) " +
"values (#{title},#{content},#{coverImg},#{state},#{categoryId},#{createUser},#{createTime},#{updateTime})")
void add(Article article);
}