博客系统-小项目

基于spring boot,spring mvc,配置,日志,spring ioc 和di, mybatis-plus,统一功能处理的博客系统。

目录

🍁页面展示

🍁建表

🍁创建项目

🍁添加mybatis-plus依赖

🍁配置yml文件

🍁MyBatisX插件创建部分项目结构

🍁总体项目结构

🍁项目公共模块

🍀统⼀返回结果实体类

🍀统⼀返回结果

🍀定义项目异常

🍀统一异常处理

🍀与表相对的实体类

🍁实现博客列表

🍀约定前后端交互接⼝

🍀控制层

🍀客户端代码

🍁实现博客详情

🍀约定前后端交互接⼝

🍀控制层

🍀业务层

🍀直接客户端代码吧

🍁实现登录

🍀约定前后端交互接⼝

🍀控制层

🍀业务层

🍀客户端代码

🍁实现强制登陆

🍀添加拦截器

🍀配置拦截路径

🍀客户端代码

🍁实现显示用户信息

🍀约定前后端交互接口

🍀控制层

🍀业务层

🍀客户端代码

🍁实现用户退出

🍁加密/加盐

🍀加密

🍀校验


🍁页面展示

刚开始访问http:127.0.0.1:8080/blog_login.html是这样子的。

正确输入完用户名和密码就可以进入大厅页面

可以看到,这个页面把每篇博客都显示出来了,有每篇博客的标题,时间,正文,左边版块有登录用户的个人信息。查看全文,主页,写博客,注销等小按钮。

然后我们点击查看全文按钮

进来之后,很明显,这个正文显示了全部,而大厅页面的那个显示的是部分正文,这里为啥会显示编辑和删除呢,因为我们校验了登录用户和这篇文章的作者是同一个人,也就是是登录用户本人的文章。反之不是同一个人,就不显示编辑和删除。

假如我们点击删除

它弹出一个小框框,提示我们要不要删除。

如果点击编辑按钮,

里面就可以写文章了,写完之后点击更新文章,就将数据写入数据库了,然后页面会自动跳转大厅博客列表显示页面了。

这个博客系统小项目大体页面逻辑就是这样的,有登录页面,大厅博客列表显示页面,查看博客详情页面,编辑博客页面,以及个人信息小板块,删除小按钮,主页小按钮,写博客小按钮,注销小按钮。

🍁建表

创建一个叫user_info的用户表,先不说别的,里面肯定必须有delete_flag,create_time,update_time三个字段,然后也得有个主键id吧,其次就是用户名和密码,github地址.一共7个字段,,非常的通俗易懂。

然后看下博客表,记录每篇播客的相关信息。

也是有铁打不动的delete_flag,create_time,update_time以及主键id字段,其次就是得要有博客的标题,正文字段,最后这篇博客是谁写的,所以user_id关联用户表的主键id来表示这篇博客的作者。一共7个字段。

字段类型以及约束这里我没提,算了我只拿这个博客表来说啊,id为主键,就要保证它的非空和唯一性,所以加上not null和auto_increment约束,title和content的not null约束不必多说。字段类型我就不说了,看不懂大家去查去。其实也没啥可讲的这里,就到这里吧。

🍁创建项目

定好项目名字,选maven,点击next,

然后勾选Lombok和Spring Web和MySQL Driver依赖,因为我们用的是mybatis-plus,创建项目成功之后,得在xml依赖文件里手动添加对应得mybatis-plus依赖,里面就集成了mysql framework依赖,也就是mybatis-plus可以用,mybatis也可以用!

🍁添加mybatis-plus依赖

java 复制代码
<!-- 添加mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot4-starter</artifactId>
            <version>3.5.15</version>
        </dependency>

🍁配置yml文件

java 复制代码
spring:
  application:
    name: spring-blog-demo #项目名
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?
      characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration: 
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 配置打印MyBatis日志
    map-underscore-to-camel-case: true #驼峰自动转化
  mapper-locations: classpath:/mapper/**.xml #告诉mybatis我们的xml文件路径在哪

logging:
  file:
    name: logger/springboot.log #配置打印日志的存放地方

🍁MyBatisX插件创建部分项目结构

先得启用这个插件,然后操作按照下面这个顺序来

点完mysql之后,他会弹出一个下面的这个框

填好mysql数据库的账号和密码,然后Database就是你要连接的mysql里的哪个数据库名,点完ok之后,就可以显示你连接的具体数据库里的相关信息了。

点击MybatisX-Generator之后,会弹出下面这个东东

然后

最后点击Finish,就成功的自动生成service层和mapper层的相关代码了。

生成的效果如下,红框框的东东都是插件自动生成的。

其中mapper包----mybatis-plus的相关使用

service包----service层的,这里分了,接口和实现类。

然后resource底下的mapper包----也支持mybatis基础的xml写法,看你需要使用。

然后这也只是插件生成的部分东东,大部分需要我们自己写,只不过操作数据库相关的插件已经帮我们写好了绝大一部分。

🍁总体项目结构

然后我们启动下spring项目

启动成功,我们就把一些相关的配置搞好了,可以开始上手写业务代码了。

哦,忘了,在写业务代码之前,我们得把项目所要用到的公共模块给写好一部分。

🍁项目公共模块

🍀统⼀返回结果实体类

先给大家看看postman的数据响应结果

我们这里约定,后端统一返回的结果都得是code----data----errMsg这三个东东,code就是状态码的意思,这不是http响应状态码的意思,这个是我们通过代码处理了相关问题,是因为我们不想让后端给前端返回一个400什么的,前端他就只知道报了400错误码,他不知道具体原因,不方便前端进行处理,所以,我们可以设code为200表示成功啥的。。这个后面再讲。

data就不用多说了,返回前端所需要的数据。

errMsg就是有的时候前端给我们传的参数不合法,就是无法进行逻辑处理,这时候,data我们就不写,再设个业务处理失败的code状态码,errMsg就需要告诉前端出问题了。

我们先在enums包底下,创建一个枚举类,

java 复制代码
@AllArgsConstructor
public enum ResultCodeEnum {
    SUCCESS(200),
    FAIL(-2),
    UNLOGIN(-1),
    ;

    @Getter
    private int code;

}

枚举类的构造方法我们就不自己手动写了,直接加个@AllArgsConstructor全参的构造方法。

然后get方法我们也不写了,加个@Getter注解即可。

然后我们统一返回的都是code--data--errMsg这样的格式,所以我们完全可以创建个对象来这样表示,在pojo包底下写,而code--data--errMsg这样的格式,我们用Result类来表示,而又是响应,所以如下

Result类

java 复制代码
@Data
public class Result<T> {
    private int code;//业务码,不是http状态码
    private String errMsg;
    private T data;

    public static <T> Result<T> ok(T data){
        Result result=new Result();
        result.setCode(ResultCodeEnum.SUCCESS.getCode());
        result.setData(data);
        return result;
    }

    public static <T> Result<T> fail(String errMsg){
        Result result=new Result();
        result.setCode(ResultCodeEnum.FAIL.getCode());
        result.setErrMsg(errMsg);
        return result;
    }

    public static <T> Result<T> unlogin(){
        Result result=new Result();
        result.setCode(ResultCodeEnum.UNLOGIN.getCode());
        result.setErrMsg("用户未登录");
        return result;
    }

}

🍀统⼀返回结果

这个就是对于不同的返回结果进行处理,把它处理成code--data--errMsg这样的格式给前端。

所以自然要在advice统一结果返回包底下写。

复制代码
ResponseAdvice类实现ResponseBodyAdvice接口不用多少

好像这个没啥可讲的,要是真不懂,可以去看我写的SpringBoot统一功能处理那篇博客里的统一数据返回格式那块。

🍀定义项目异常

在exception包底下

java 复制代码
@Getter
public class BlogException extends RuntimeException {
    private Integer code;
    private String message;

    public BlogException(String message) {
        this.code = ResultCodeEnum.FAIL.getCode();
        this.message = message;
    }

    public BlogException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

继承RuntimeException不用多说,这是每个自定义异常类都需要继承的东东,这里重点讲下为啥要加@Getter注解,

由于我们在这个异常的构造方法里没加super(message),所以当下面这个逻辑执行

getmssage每错,我们已经没加没加super(message)了,如果再不加@Getter注解,那就getter不到message这个东东了,get不到,那就打印不到,就会报异常,虽然我有捕获Exception异常兜底,但这明显事与愿违了,大忌啊,我们既然写了这个代码,就要负责,做个专一的man。

🍀统一异常处理

这个统一异常处理返回结果是接近最后的返回数据格式,其返回结果不走统一返回结果那边,因为它加了@RestControllerAdvice===@ControllerAdvice+@ResponseBody,

这个@ControllerAdvice不用多说,统一返回结果也是用的这个注解,那为啥统一返回结果那里不加@ResponseBody注解,而这个统一异常处理要加呢?

我也搞不懂其实,但是站在我们使用者的视角下,可以这样理解,统一异常处理比较特殊,如果不加@ResponseBody注解,默认返回的就是视图。

java 复制代码
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(exception = Exception.class)
    public Result handlerException(Exception e){
        log.error("发生异常,e{}",e.getMessage());
        return Result.fail(e.getMessage());
    }

    @ExceptionHandler
    public Result handlerBlogException(BlogException e){
        log.error("发生异常,e{}",e.getMessage());
        return Result.fail(e.getMessage());
    }

    /*
    校验参数,也就是传递的不是json对象,可以用这个
     */
    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ExceptionHandler(exception = {MethodArgumentNotValidException.class,HandlerMethodValidationException.class})
    public Result handler(Exception e){
        log.error("发生异常,e{}",e.getMessage());
        return Result.fail("参数校验失败");
    }

}

🍀与表相对的实体类

用户实体类

@TableName注解与数据库表名相匹配,加了@TableId注解----也就是不使用mybatis-plus给我们生成id雪花算法的值,而是使用数据库的id主键的AUTO_INCREMENT。

博客实体类也类似,不多讲了。


基本上我们的部分前置的东东都写好了,也就是可以开始写业务代码了。

🍁实现博客列表

我们来想想,前端传啥,后端传啥,登录完后,跳转到博客页面,也就是前端没传值,后端需要返回数据,那后端返回啥呢,首先肯定是code--data--errMsg这样的统一返回格式。data里博客标题得有吧,博客时间也得有吧,正文也得有吧,博客id也要有,因为后面点击这个查看全文,它要去数据库搜文章,而userId就无法搜文章,因为同一个用户写的文章可能不止一个。

🍀约定前后端交互接⼝

既然返回的是5个固定的属性,那么我们完全可以写个类嘛,

pojo实体类-----》response响应--------也就是实体类的响应。

至于为啥要加@Data注解,@JsonFormat注解,getContent方法,后面我们看业务层代码的时候我再讲理由,

🍀控制层

注入的是blogInfoService?不对,这是一个接口,并且也没交给spring进行管理,

那注入的是啥呢,其实是blogInfoService接口的实现类了。

回到控制层那里,调用的下面这个方法。

业务层

无疑就是从一个对象转化成另一个对象了,其中我标五角星的那个我们一起去看看。

其中BeanUtils.copyProperties(blogInfo,blogInfoResponse);方法,拿到bloginfo对象的值赋给bloginfoResponse对象,咋赋值呢,自然是通过@Data注解里的setter方法赋值,

至于我们为啥要写getContent方法,那是因为我们这个那是因为json序列化返回的时候,它要获取对象里的值,就需要get方法获取,而我们博客列表页面只显示部分正文了。

最后@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")可以理解为我们指定了json我显示的数据格式是这样子的,你就不要乱搞默认啥的。

由于我们这里用的是mybatis-plus,service层就直接调用查询了数据库了,就不写数据层了。

🍀客户端代码

java 复制代码
$.ajax({
                type:"get",
                url:"/blog/getList",
                success: function(result){
                    if(result.code==200&&result.data!=null){
                        let finalHtml="";
                        for(let blog of result.data){
                            finalHtml+='<div class="blog">';
                            finalHtml+='<div class="title">'+blog.title+'</div>';
                            finalHtml+='<div class="date">'+blog.updateTime+'</div>';
                            finalHtml+='<div class="desc">'+blog.content+'</div>';
                            finalHtml+='<a class="detail" href="blog_detail.html?blogId='+blog.id+'">查看全文&gt;&gt;</a>';
                            finalHtml+='</div>';
                        }
                        $(".container .right").html(finalHtml);
                    }
                }
            });

我给大家顺下逻辑哈,后端返回给前端数据,前端先校验是否success,也就是200状态码,然后再看里面我们自己手动设置的code是200不,data数据空不空啊,然后拼接html页面,然后不是有个点击查看全文按钮嘛,说明是个超链接,需要博客的id来定位查询那篇博客的正文的相关信息啊,最后html片段赋值在恰当位置。

所以第一个接口写完了,难不,不能这么讲,细节很多,简单不,也不简单,因为我们只是会用一点点而已,底层的东西,源码分析啥的,json序列化啥的,说实现,我一知半解的,所以说,只是站在巨人的肩膀上,同志仍需努力。


🍁实现博客详情

🍀约定前后端交互接⼝

和博客列表的接口文档一样,我们这里偷了个懒,这里userId要用到,id其实不要返回都行,

为啥需要返回userId呢,我们登录完之后,前端就知道我们的userId了,而博客详情呢,又是有编辑和删除按钮,它必须是登录人和这篇博客的作者是同一个人才能显示的,而我们如何确定两者是同一个人呢,只能是userId唯一标识才能确定。

🍀控制层

java 复制代码
@GetMapping("/getBlogDetail")
    public BlogInfoResponse2 getBlogDetail(@NotNull(message="blogId 不能为空") Integer blogId){
        log.info("获取博客详情,blogId:{}",blogId);
        return blogInfoService.getBlogDetail(blogId);
    }

@NotNull注解检验单参是否为null,为null就报MethodArgumentNotValidException异常,不过我们在统一异常处理那块给处理好了。

🍀业务层

不多说,和博客列表的那个逻辑很像

🍀直接客户端代码吧

java 复制代码
$.ajax({
                type:"get",
                url:"/blog/getBlogDetail"+location.search,
                success:function(result){
                    //TODO 校验不全面
                    if(result.code==200&&result.data!=null){
                        let blogInfo=result.data;
                        $(".right .content .title").text(blogInfo.title);
                        $(".right .content .date").text(blogInfo.updateTime);
                        //$(".right .content .detail").text(blogInfo.content);
                        editormd.markdownToHTML("detail",{
                            markdown:blogInfo.content
                        });
                        let loginUserid=localStorage.getItem("login_userid");
                        let html ='';
                        if(blogInfo.userId==loginUserid){
  
                            html +='<div class="operating">';
                            html +='<button onclick="window.location.href=\'blog_update.html?blogId='+location.search+'\'">编辑</button>';
                            html +='<button onclick="deleteBlog()">删除</button>';
                            html +='</div>';
                            html +='';
                        }
                        $(".right .content").append(html);
                    }
                },

            });
复制代码
$(".right .content .title").text(blogInfo.title);
$(".right .content .date").text(blogInfo.updateTime);

这两行代码是标题和时间的显示,

复制代码
editormd.markdownToHTML("detail",{
    markdown:blogInfo.content
});
这个就是正文的显示了,至于为啥会这样显示,这是前端markdown格式显示的问题哈哈,大家感兴趣可以去查查看。
下面的判断if(blogInfo.userId==loginUserid)来决定要不要显示编辑和删除按钮。

🍁实现登录

登录这里我们使用JWT令牌。

不过,需要先引入些以下依赖。

XML 复制代码
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

生成令牌

java 复制代码
     /*
      生成key
     */
    private static final String secretString="e4roQ6HFcMUHGAcBS5Ps6kuq1Ntnau/VP7bzGw8vJts=";//这个也叫签名
    private  static final Key key= Keys.hmacShaKeyFor(secretString.getBytes());

    private  static final long Expiration=7*24*60*60*1000;//7天

    /*
    生成令牌
     */
    public static String genJwt(Map<String,Object> claim){

        /*
        固定写法,生成token
         */
        return Jwts.builder().setClaims(claim)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()+Expiration))
                .signWith(key)//签名算法
                .compact();

    }

我给大家过下逻辑,先得要个key,起始时间,截至时间,数据载荷claim,这4个最终生成token。也就是令牌就生成了。

校验令牌

java 复制代码
     /*
    校验令牌
     */
    public  static Claims parseJwt(String jwt){
        if(!StringUtils.hasText(jwt)){
            return null;
        }
        //校验token是否合法
        //创建解析器,设置签名,密钥
        JwtParser jwtParser=Jwts.parserBuilder().setSigningKey(key).build();
        //解析token
        Claims claims=null;
        try{
            claims=jwtParser.parseClaimsJws(jwt).getBody();
        }catch(Exception e){
            log.warn("解析令牌失败,jwt:{}",jwt);

        }
        return claims;
    }

用对应的签名去创建对应的解析器,然后去校验token(jwt),如果报异常了,就是不合法的token令牌,没报异常就是合法的令牌。

🍀约定前后端交互接⼝

返回的data里有userId和token两个东东,userId用户唯一身份id,token则交与前端保管,配合拦截器校验,token,检查是否登录,当然这个拦截器的逻辑是强制登陆接口的事哈哈。

🍀控制层

由于前端传递过来的是userName和password,所以我们也可以用一个对象来接收

java 复制代码
@Data
public class UserLoginRequest {
    @NotBlank(message = "用户名不能为空")
    @Length(max=20,message="用户名长度不能超过20")
    private String userName;

    @NotBlank(message = "密码不能为空")
    @Length(max=20,message="密码长度不能超过20")
    private String password;
}

@Validated注解表示要按照对象里写的的校验规则来,@RequestBody注解不用多讲。

🍀业务层

java 复制代码
 @Override
    public UserLoginResponse login(UserLoginRequest request) {
        /*
        1.先验证密码是否正确
        2.如果正确,生成token,并返回
        3.如果不正确,返回错误信息
         */
        UserInfo userInfo=selectUserInfoByName(request.getUserName());
        if(userInfo==null||userInfo.getId()==null){
            throw new BlogException("用户名不存在");
        }

//        if(!request.getPassword().equals(userInfo.getPassword())){
//            throw new BlogException("密码不存在");
//        }

        if(!SecurityUtils.verify(request.getPassword(),userInfo.getPassword())){
            throw new BlogException("密码不正确");
        }
        //账号密码正确,claim载体
        Map<String,Object> claim=new HashMap<>();
        claim.put("id",userInfo.getId());
        claim.put("name",userInfo.getUserName());

        //生成token
        String token= JwtUtils.genJwt(claim);
        return new UserLoginResponse(userInfo.getId(),token);

    }

这个接口只是生成了token,并没有校验,

🍀客户端代码

java 复制代码
           $.ajax({
                type:"post",
                url:"/user/login",
                contentType:"application/json",
                data: JSON.stringify({
                    userName:$("#username").val(),
                    password:$("#password").val()
                }),
                success:function(result){
                    //TODO 只写了核心条件判断
                    if(result!=null&&result.code==200&&result.data!=null){
                        let response=result.data;
                        localStorage.setItem("user_token",response.token);
                        localStorage.setItem("login_userid",response.userId);
                        location.href="blog_list.html";
                    }else{
                        alert(result.errMsg);
                    }
                }
            })

JSON.stringify-----json格式化,这里的逻辑就是赋值嘛,然后

localStorage.setItem("user_token",response.token);
localStorage.setItem("login_userid",response.userId);

这两行代码,是存在浏览器里面,如下


🍁实现强制登陆

顾名思义,需要用到拦截器,令牌校验。

我们每个前端代码都引了common.js,里面的上面的这个代码表示每次向后端发起请求的时候,都会设置header请求头,携带user_token数值。

🍀添加拦截器

java 复制代码
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        response.setContentType("application/json;charset=utf-8");//要提前设置返回类型
        //从header中获取token
        String jwtToken=request.getHeader("user_token");
        log.info("从header中获取token:{}",jwtToken);

        //校验token
        Claims claims= JwtUtils.parseJwt(jwtToken);
        if(claims==null){
            log.error("令牌校验失败,token:{}",jwtToken);
            response.setStatus(HttpStatus.UNAUTHORIZED.value());//401状态码
            response.getOutputStream().write(objectMapper.writeValueAsString(Result.fail("用户未登录")).getBytes());
            return false;
        }
        log.info("令牌校验通过");
        return true;
    }
}

实现HandlerInterceptor接口,重写preHandle方法不用多说。

至于校验逻辑,无非就是校验header里的user_token能否正确校验出来。

🍀配置拦截路径

java 复制代码
@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    List<String> excludePath=List.of(
            "/user/login",
            "/**.html",
            "/blog-editormd/**",
            "/css/**",
            "/js/**",
            "/pic/**",
            "favicon.ico"
    );

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludePath);
    }
}

实现WebMvcConfigurer接口,重写addInterceptors方法不用多说,我们只针对请求接口进行拦截,那些请求静态html页面不拦截,确保前端显示正常。

🍀客户端代码

java 复制代码
$(document).ajaxError(function(event,xhr,options,exc){
    if(xhr.status==400){
        alert("参数校验失败");
    }
    if(xhr.status==401){
        alert("您未登录,请返回到登录页面进行登录");
        location.href="blog_login.html";
    }
});

不通过就直接跳转到登录页面。为啥是401未登录,是因为上面我的拦截器设置了状态码为401.


🍁实现显示用户信息

如下,博客列表页面有,博客详情页面也有!

🍀约定前后端交互接口

两个接口文档

1.

2.

这个响应也完全可以写成一个对象

🍀控制层

java 复制代码
@GetMapping("/getUserInfo")
    public UserInfoResponse getUserInfo(@NotNull Integer userId){
        return userInfoService.getUserInfo(userId);
    }

    @GetMapping("/getAuthorInfo")
    public UserInfoResponse getAuthorInfo(@NotNull Integer blogId){
        return  userInfoService.getAuthorInfo(blogId);
    }

@NotNull注解不必多说,报异常--统一异常处理也写好了。

java 复制代码
@ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ExceptionHandler(exception = {MethodArgumentNotValidException.class,HandlerMethodValidationException.class})
    public Result handler(Exception e){
        log.error("发生异常,e{}",e.getMessage());
        return Result.fail("参数校验失败");
    }

其中MethodArgumentNotValidException捕获属性异常例如:@NotNull所报的异常

而HandlerMethodValidationException捕获对象异常例如:@Validated所报的异常

🍀业务层

🍀客户端代码

博客列表的

java 复制代码
let url="/user/getUserInfo?userId="+localStorage.getItem("login_userid");

function getUserInfo(url){
            $.ajax({
                type:"get",
                url:url,
                success:function(result){
                    if(result!=null&&result.code==200&&result.data!=null){
                        let userInfo=result.data;
                        $(".left .card h3").text(userInfo.userName);
                        $(".left .card a").atter("herf",userInfo.githubUrl)
                    }else{
                        alert(result.errMsg);
                    }
                }
            });
}

这个是获取前端自己存的

博客详情的

java 复制代码
let url="/user/getAuthorInfo"+location.search;

function getUserInfo(url){
            $.ajax({
                type:"get",
                url:url,
                success:function(result){
                    if(result!=null&&result.code==200&&result.data!=null){
                        let userInfo=result.data;
                        $(".left .card h3").text(userInfo.userName);
                        $(".left .card a").atter("herf",userInfo.githubUrl)
                    }else{
                        alert(result.errMsg);
                    }
                }
            });
}

这个是获取url里的blogid的


🍁实现用户退出

这个较为简单,就是用户点击注销按钮之后,就跳转到登录页面,并且删除存储的user_token和login_userid。

java 复制代码
function logout(){
    localStorage.removeItem("user_token");
    localStorage.removeItem("login_userid");
    location.href="blog_login.html";
}

我编辑和删除和发布博客的3个接口就不写了,套路是一样的,我最后讲个加密/加盐吧,主要有点坐不住了哈哈。

🍁加密/加盐

🍀加密

java 复制代码
/*
    加密
    password 用户输入的密码
    return 盐值+密文
     */
    public static String encrpt(String password){

        //盐值--随机生成的--32位长度的
        String salt= UUID.randomUUID().toString().replace("-","");

        //数据库种存储的值
        //md5(盐值+明文)->密文
        //数据库中存储:密文+盐值

        //这个是密文==也就是(盐值+明文)经过md5算法后得到的值--密文也是为32位长度的
        String finalPassword=DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));

        //数据库中存储:盐值+密文
        return salt+finalPassword;
    }

先随机生成盐值,然后盐值+明文---》md5算法生成一个密文,最后数据库存储这个密文

🍀校验

java 复制代码
/*
    解密--密码的验证
    sqlPassword--数据库存储的 盐值+密文
     */
    public static boolean verify(String inputPassword,String sqlPassword) {
        if(!StringUtils.hasText(inputPassword)||!StringUtils.hasText(sqlPassword)){
            return false;
        }
        if(sqlPassword.length()!=64){
            return false;
        }

        String salt=sqlPassword.substring(0,32);

        String finalPassword=DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes(StandardCharsets.UTF_8));

        //判断是否相等
        return sqlPassword.equals(salt+finalPassword);
    }

通过比对密码和数据库存储的密文,来判断是否密码正确,逻辑是这样的,先取到盐值,然后盐值+输入的密码---》经过md5算法得到密文,最后和数据库的比对即可。


完结

相关推荐
梦想的旅途215 小时前
实现企微外部群主动发送接口:从 0 到 1 实现主动给客户发送的业务实战
java·开发语言·企业微信
he___H15 小时前
leetcode100-合并区间
java·数据结构·算法
nbsaas-boot15 小时前
Drools 规则引擎实战:原理、规则语法、数据库动态规则与企业级玩法
java·数据库·python
承渊政道16 小时前
【MySQL数据库学习】(MySQL数据库基础)
数据库·学习·mysql·ubuntu·bash·数据库架构·数据库系统
0pen116 小时前
android-sqlite3:从官方 SQLite 源码自动构建 Android 可用的 sqlite3
android·数据库·sqlite
六月雨滴16 小时前
Oracle RMAN 恢复场景全解
数据库·oracle·dba
韩小兔修媛史16 小时前
SpringBoot面试八股文(持续更新)
spring boot·后端·面试
Hwang25216 小时前
Spring 框架 -01 -单例池的一些理解
java
AI人工智能+电脑小能手16 小时前
【大白话说Java面试题 第74题】【Mysql篇】第4题:InnoDB 和 MyISAM 的数据文件存储区别?
java·开发语言·mysql·面试