可莉要你帮她做一个蹦蹦炸弹管理系统!(Spring Security + Vue前后端分离学习示例)...

前端面试题-Vue3常见高频面试50题及答案 Vue3相较于Vue2,引入Composition API、Proxy响应式系统、Tree-shaking优化及Teleport、Suspense等新特性,提升性能与开发体验。Composition API支持逻辑复用与更灵活的代码组织,配合ref、reactive、computed、watch等增强响应式能力。生命周期钩子改为onXxx形式,setup函数作为入口点。通过Pinia实现模块化状态管理,搭配VueRouter4支持懒加载与路由守卫。虚拟DOM优化如PatchFlag、静态提升与v-memo提 阅读详情

最近旅行者除了完成每日委托之外,还花了许多时间研究了Spring Security的使用。

今天,可莉急匆匆地跑来找到了旅行者,说自己研究了许多种类的炸弹,但是随着炸弹种类、数量的增加,可莉觉得越来越难以管理自己的“宝贝”了,想请旅行者帮帮忙。

看着可莉满是“求求你”的样子,旅行者决定答应她。

8606675912226.gif

旅行者一拍脑袋,决定用自己这几天“废寝忘食”研究的Spring Security,加上Vue,搭建一个“蹦蹦炸弹”的管理系统。

于此同时,可莉担心被琴团长发现自己具体的“宝贝”类型和数量,便让旅行者做的管理系统只能由她自己来操作,其他人不能查看或者修改。

可莉的好伙伴七七、迪奥娜和早柚也听说可莉最近研发出来了很多类型的炸弹,都非常好奇,可莉也决定让旅行者将她们也加入管理系统,但是可莉只让她们查询炸弹信息,不能修改。

废话不多说,明确了任务,旅行者拿出了笔记本电脑,坐在猫尾酒馆里开始整理思路,并打开了vscode、idea开始干了。

1,思路?整理!梳理啦!

在做这个管理系统之前,旅行者决定好好梳理一下思路。

在此之前,我们还是需要学习用户登录的一些基本原理,这样看这个文章才更好理解,如果不熟悉可以看看这个文章:链接

既然旅行者使用了Spring Security作为安全框架,我们就先来大致了解一下其原理。

Spring Security的登录验证流程核心就是过滤器链。当一个请求到达时按照过滤器链的顺序依次进行处理,通过了所有过滤器链的验证,才能访问我们的API接口。

20200413213910599.png

与此同时,Spring Security还提供了多种过滤器,以实现多种不同认证方式:

  • UsernamePasswordAuthenticationFilter 使用用户名密码的登录认证方式
  • SmsCodeAuthenticationFilter 使用短信验证码登录认证方式
  • SocialAuthenticationFilter 使用社交媒体方式登录认证方式
  • Oauth2AuthenticationProcessingFilter 使用Oauth2的认证方式

今天,旅行者决定使用UsernamePasswordAuthenticationFilter的方式来实现这个管理系统的登录认证方式。

来简单看看认证流程:

image.png

这里看不懂没关系,我们不需要完全掌握里面所有类的逻辑,重要的部分在下面会讲到。

然后,这是一个前后端分离的系统,也就是说,Spring Security不再负责各个页面的跳转,只是负责各个接口的访问管理,前端vue实现页面跳转。

默认情况下,Spring Security中登录成功、失败、退出登录、403等等,都是会跳转到一个专门的页面(默认是请求转发方式)。

但是现在由于是前后端分离的系统,Spring Security只能向前端发送一个JSON格式的响应,前端接收到之后,根据请求内容,控制页面跳转。

除此之外,登录成功后,后端还需要写一个判断用户是否登录的接口并加入拦截。这样前端每次访问这个接口判断是否登录,如果登录了后端就会返回用户数据给前端显示,没有登录就会被拦截返回403,前端跳转至登录页。

思路明确了,我们只让Spring Security控制对各个接口的访问,还有用户登录、权限等等。每一个操作,后端就会返回前端一个JSON数据,前端判断是否操作成功,然后控制跳转和显示。

2,依赖?准备!设置啦!

首先新建Spring Boot工程,在其中勾选上Security,以及MyBatis的相关依赖,并配置数据源。

也可以后续在pom.xml中加入Spring Security依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
复制代码

除此之外,我们还需要用到fastjson的json工具:

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.73</version>
</dependency>
复制代码

然后启动项目,访问你的地址,你会发现进入了个登录页面,因为默认情况下,Spring Security会拦截所有请求。

因此,里面有许多东西我们需要修改。

3,数据?模型!构建啦!

现在,我们就需要构建一下数据模型,以及数据库表。

在这个系统中,无疑要使用到RBAC(基于角色的权限管理)。

也就是说,我们要通过建立用户与角色的对应关系,使得每个用户可以拥有多个角色,每个角色可以拥有多个权限,用户根据拥有的角色进行操作与资源访问。

一个最基本的RBAC的类图如下:

image.png

用户可以有多个角色,一个角色可以被多个用户拥有。同时,一个角色可以有多个权限,一个权限也可以被多个角色拥有。

也就是说,用户和角色之间是多对多的关系,角色和权限之间也是多对多的关系。

至于为什么要在用户和权限之间加一个角色?这不是多此一举吗?其实这是有必要的,有利于系统未来扩展,以及灵活地权限控制。

根据此,我们就可以设计数据库了,对于多对多的数据库模型建立和MyBatis的级联如果不太熟悉,可以先看看这个文章:链接

在这个管理系统中,需要有 管理员(admin) 和 访问者(visitor) 两个角色,其中管理员可以 增加(addBomb) 和查询(queryBomb) 炸弹,而访问者只能 查询(queryBomb) 炸弹。

这里的管理员、访问者就是角色,增加、查询炸弹就是权限。

这里创建一个名为dataobject的包,并在里面创建用户类(MyUser)、角色类(Role)和权限类(Permission),如下:

package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * 用户
 */
@Setter
@Getter
@NoArgsConstructor
public class MyUser implements Serializable {

   /**
    * 主键id
    */
   private int id;

   /**
    * 用户名
    */
   private String username;

   /**
    * 密码
    */
   private String password;

   /**
    * 昵称
    */
   private String nickname;

   /**
    * 头像
    */
   private String avatar;

   /**
    * 用户的角色
    */
   private Set<Role> roles;

   /**
    * 获取该用户的全部权限
    *
    * @return 用户的权限
    */
   public Set<Permission> getPermissions() {
      Set<Permission> permissions = new HashSet<>();
      for (Role role : roles) {
         for (Permission permission : role.getPermissions()) {
            permissions.add(permission);
         }
      }
      return permissions;
   }

}
复制代码
package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

/**
 * 角色类
 */
@Getter
@Setter
@NoArgsConstructor
public class Role implements Serializable {

   /**
    * 主键id
    */
   private int id;

   /**
    * 角色名
    */
   private String name;

   /**
    * 拥有该角色的用户
    */
   private List<MyUser> myUsers;

   /**
    * 该角色拥有的权限
    */
   private Set<Permission> permissions;

}
复制代码
package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.Set;

/**
 * 权限
 */
@Getter
@Setter
@NoArgsConstructor
public class Permission implements Serializable {

   /**
    * 主键id
    */
   private int id;

   /**
    * 权限名
    */
   private String name;

   /**
    * 该权限下的角色
    */
   private Set<Role> roles;

}
复制代码

然后创建相应的MyBatis的Mapper类和XML,这里只实现增加和查询两个功能,在此不贴代码了,最后会给出示例仓库的地址。

与此同时,我们最好再构建一个返回结果模型,专门用于返回结果给前端。新建model包,里面建立Result类:

package com.example.securitytest.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
@NoArgsConstructor
public class Result<T> implements Serializable {

   /**
    * 消息
    */
   private String message;

   /**
    * 是否操作成功
    */
   private boolean success;

   /**
    * 返回的数据主体(返回的内容)
    */
   private T data;

   /**
    * 设定结果为成功
    *
    * @param msg 消息
    */
   public void setResultSuccess(String msg) {
      this.message = msg;
      this.success = true;
      this.data = null;
   }

   /**
    * 设定结果为成功
    *
    * @param msg  消息
    * @param data 数据体
    */
   public void setResultSuccess(String msg, T data) {
      this.message = msg;
      this.success = true;
      this.data = data;
   }

   /**
    * 设定结果为失败
    *
    * @param msg 消息
    */
   public void setResultFailed(String msg) {
      this.message = msg;
      this.success = false;
      this.data = null;
   }

}
复制代码

可莉要管理的内容是炸弹,因此还需要创建炸弹实体类及其相应的数据库表。

好了,数据模型构建完成,DAO层也完成了,最后定义Control层编写API获取数据,这里就省略API的代码了。

接下来,我们要开始建立用户登录服务了。

这里给出sql文件:

-- 初始化用户表
drop table if exists `my_user`, `role`, `user_role`, `permission`, `role_permission`, `bomb`;
create table `my_user`
(
   `id`       int          not null,
   `username` varchar(16)  not null unique,
   `password` varchar(64)  not null,
   `nickname` varchar(16)  not null,
   `avatar`   varchar(128) not null,
   primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

create table `role`
(
   `id`   int         not null,
   `name` varchar(16) not null unique,
   primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

create table `user_role`
(
   `user_id` int not null,
   `role_id` int not null,
   primary key (`user_id`, `role_id`),
   foreign key (`user_id`) references `my_user` (`id`) on delete cascade on update cascade,
   foreign key (`role_id`) references `role` (`id`) on delete cascade on update cascade
) engine = InnoDB
  default charset = utf8mb4;

create table `permission`
(
   `id`   int         not null,
   `name` varchar(16) not null unique,
   primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

create table `role_permission`
(
   `role_id`       int not null,
   `permission_id` int not null,
   primary key (`role_id`, `permission_id`),
   foreign key (`role_id`) references `role` (`id`) on delete cascade on update cascade,
   foreign key (`permission_id`) references `permission` (`id`) on delete cascade on update cascade
) engine = InnoDB
  default charset = utf8mb4;

create table `bomb`
(
   `id`   int unsigned auto_increment,
   `name` varchar(32) not null,
   `type` varchar(32) not null,
   primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

-- 初始化测试数据
insert into `my_user`
values (0, 'klee', '$2a$10$uifIuXG6FzrI.NqhhZN0H.PziUzqn78Nwq7Dg9C2V4Fa3nXNdZj5e', '可莉', '/avatar/klee.jpg'),    -- 密码:123456
      (1, 'qiqi', '$2a$10$4QRxlo/YuR8ZdFJSW4uV3uT9HqUxZgad5oeQHuHy2nfS4en/Z8dQ2', '七七', '/avatar/qiqi.jpg'),    -- 密码:789101112
      (2, 'diona', '$2a$10$Q8QQb4s9Iy12qCDmiw0Qwe8/TvCOolKgaylPAus5kE5E5k/cp.2km', '迪奥娜', '/avatar/diona.jpg'), -- 密码:13141516
      (3, 'sayu', '$2a$10$uifIuXG6FzrI.NqhhZN0H.PziUzqn78Nwq7Dg9C2V4Fa3nXNdZj5e', '早柚', '/avatar/sayu.jpg');
-- 密码:123456

-- Spring Security中角色必须以"ROLE_"开头
insert into `role`
values (0, 'ROLE_admin'),
      (1, 'ROLE_visitor');

insert into `user_role`
values (0, 0),
      (1, 1),
      (2, 1),
      (3, 1);

insert into `permission`
values (0, 'queryBomb'),
      (1, 'addBomb');

insert into `role_permission`
values (0, 0),
      (0, 1),
      (1, 0);

insert into `bomb` (name, type)
values ('小炸弹', '诡雷'),
      ('兔兔伯爵', '嘲讽'),
      ('蹦蹦炸弹', '集束炸弹');
复制代码

4,服务?逻辑!重写啦!

Spring Security中有着自己的用户认证、登录成功/失败、退出登录等等逻辑,这些我们都需要重写,自定义为自己的,才能满足实际业务需要。

(1) 自定义用户名密码登录拦截器

由于是前后端分离系统,我们一般会发送json格式的登录数据给后端,但是默认只支持表单格式的,这个时候我们就需要重写UsernamePasswordAuthenticationFilter,重新实现其中attemptAuthentication方法,这个方法主要用于从前端请求获取用户名和密码,并提交给后续认证。

创建一个包filter,在里面创建MyAuthFilter类,实现自定义的认证过滤器,先给出我的代码:

package com.example.securitytest.filter;


import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * 自定义的认证过滤器,实现前后端分离的json格式登录请求解析
 */
public class MyAuthFilter extends UsernamePasswordAuthenticationFilter {

   /**
    * 请求体中的用户名字段名
    */
   private String usernameParameter = "username";

   /**
    * 请求体中的密码字段名
    */
   private String passwordParameter = "password";

   /**
    * 创建自定义认证过滤器
    */
   public MyAuthFilter() {

   }

   /**
    * 创建自定义认证过滤器,并自定义用户名、密码字段名
    *
    * @param usernameParameter 自定义用户名字段名
    * @param passwordParameter 自定义密码字段名
    */
   public MyAuthFilter(String usernameParameter, String passwordParameter) {
      this.usernameParameter = usernameParameter;
      this.passwordParameter = passwordParameter;
   }

   @Override
   public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
      // 判断是否为JSON类型数据
      if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
         // 获取请求体
         Map<String, String> requestBody = null;
         try {
            // 解析json为Map对象
            requestBody = new ObjectMapper().readValue(request.getInputStream(), Map.class);
         } catch (Exception e) {
            e.printStackTrace();
         }
         // 获取请求体中的用户名/密码字段值,并执行认证
         UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(requestBody.get(usernameParameter), requestBody.get(passwordParameter));
         // 返回认证结果
         return this.getAuthenticationManager().authenticate(auth);
      } else {
         // 否则,使用默认表单登录方式
         return super.attemptAuthentication(request, response);
      }
   }

}
复制代码

上述我还加入了usernameParameter和passwordParameter字段,以方便自定义前端登录认证请求中的用户名/密码字段名。

当然,现在实现了自定义的拦截器,我们是不是还需要配置进Spring Security中去呢?是的!当前现在不要急啦!这些自己实现的类,后面会来一起配置。

(2) 自定义用户数据查询逻辑

我们建立一个service的包,并在里面建立impl包,在其中创建一个类表示自己的用户数据查询逻辑,这个类需要实现UserDetailsService接口里面的loadUserByUsername方法,我这里先放上代码:

package com.example.securitytest.service.impl;

import com.example.securitytest.dao.MyUserDAO;
import com.example.securitytest.dataobject.MyUser;
import com.example.securitytest.dataobject.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * 自行实现Spring Security的登录逻辑
 */
@Component
public class MyUserDetailsServiceImpl implements UserDetailsService {

   @Autowired
   private MyUserDAO myUserDAO;

   /**
    * 取出用户
    *
    * @param username 用户名
    */
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      MyUser myUser = myUserDAO.getByUsername(username);
      if (myUser == null) {
         throw new UsernameNotFoundException("找不到用户!");
      }
      // 组装权限
      Set<GrantedAuthority> authorities = new HashSet<>();
      Set<Permission> permissions = myUser.getPermissions();
      for (Permission permission : permissions) {
         authorities.add(new SimpleGrantedAuthority(permission.getName()));
      }
      return new User(myUser.getUsername(), myUser.getPassword(), authorities);
   }

}
复制代码

首先需要说明的是,Spring Security中有一个User类,表示用户对象,但是实际情况下我们还是要建立自己的用户模型(我这里的MyUser),因为内置的User类不能满足业务需要。

这个User有个三个参数构造器,三个参数分别是:用户名、密码、权限。

不过这个loadUserByUsername到底是干什么的呢?顾名思义,这个方法肯定是用于根据用户名(凭证)从数据库查询用户信息的。

因此,这里仅仅只用从数据库查询我们自己的用户信息,然后将里面的用户名、密码和权限或者角色字段提取出来放到User类实例并返回即可。不需要在这里实现密码比对。

注意在Spring Security中,角色和权限都被视为GrantedAuthority,但是角色名一定要以ROLE_开头!这是Spring Security对角色和权限两者的判断依据,这个在前面sql文件中也注释说明了。

这样,这个方法就会返回相应的用户信息拿去和前端传来的进行比对。

还有一点要注意的是:这个类虽然也是属于Service层的,但是其作用仅仅是用于查询出用户对象传给Spring Security框架,不建议将自己的其它用户逻辑也写在里面,建议自己建立自己的用户服务接口和实现类并把自己的用户服务逻辑写在自己的实现类中。

(3) 自定义登录成功后逻辑

默认登录成功会跳转至某个页面,但是现在我们需要登录成功后发送json数据。

建立包handler,并创建类MyAuthSuccessHandler,这个类要实现AuthenticationSuccessHandler接口中的onAuthenticationSuccess方法。这里先上代码:

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 自定义登录成功处理器逻辑
 */
public class MyAuthSuccessHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
      // 设定响应状态码为200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // 设定响应内容是utf-8编码的json类型
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // 组装自己的结果对象
      Result result = new Result();
      result.setResultSuccess("登录成功!");
      // 序列化结果对象为JSON
      String resultJSON = JSON.toJSONString(result);
      // 写入响应体
      PrintWriter writer = httpServletResponse.getWriter();
      writer.write(resultJSON);
      writer.flush();
      writer.close();
   }

}
复制代码

在这个方法中,有一个HttpServletRequest类型参数表示接收的请求对象,HttpServletResponse参数表示返回相应对象,Authentication类型参数表示用户认证后的信息,里面有用户名、权限、用户登录ip等等我们可以在此获取。

与此同时,我们使用fastjson将我们的结果对象序列化为JSON字符串,并写入响应流,使得前端得到我们的JSON数据的响应。

(4) 自定义登录失败后逻辑

登录失败同样也需要返回json。

我们还是在handler中建立MyAuthFailureHandler,需要实现接口AuthenticationFailureHandler的onAuthenticationFailure方法。

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 自定义认证失败处理器
 */
public class MyAuthFailureHandler implements AuthenticationFailureHandler {

   @Override
   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
      // 设定响应状态码为200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // 设定响应内容是utf-8编码的json类型
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // 组装自己的结果对象
      Result result = new Result();
      result.setResultFailed("用户名或者密码错误!");
      // 序列化结果对象为JSON
      String resultJSON = JSON.toJSONString(result);
      // 写入响应体
      PrintWriter writer = httpServletResponse.getWriter();
      writer.write(resultJSON);
      writer.flush();
      writer.close();
   }

}
复制代码

和登录成功的方法很类似,这里不再过多赘述。

(5) 自定义403无权限访问逻辑

在handler中新建MyAccessDeniedHandler,需要实现接口AccessDeniedHandler的handle方法。

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 自定义权限不足处理器
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {

   @Override
   public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
      // 设定响应状态码为403
      httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
      // 设定响应内容是utf-8编码的json类型
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // 组装自己的结果对象
      Result result = new Result();
      result.setResultFailed("权限不足!请联系管理员!");
      // 序列化结果对象为JSON
      String resultJSON = JSON.toJSONString(result);
      // 写入响应体
      PrintWriter writer = httpServletResponse.getWriter();
      writer.write(resultJSON);
      writer.flush();
      writer.close();
   }

}
复制代码

默认Spring Security会拦截全部请求,拦截后就会触发这个无权访问的方法。在后面我们需要配置具体需要拦截的路径。

(6) 自定义退出登录成功逻辑

在handler中建立MyLogoutSuccessHandler,实现接口LogoutSuccessHandler的onLogoutSuccess方法。

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 自定义登出成功处理器
 */
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

   @Override
   public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
      // 设定响应状态码为200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // 设定响应内容是utf-8编码的json类型
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // 组装结果
      Result result = new Result();
      result.setResultSuccess("退出成功!", null);
      // 序列化结果对象为JSON
      String resultJSON = JSON.toJSONString(result);
      // 写入响应体
      PrintWriter writer = httpServletResponse.getWriter();
      writer.write(resultJSON);
      writer.flush();
      writer.close();
   }

}
复制代码

可见这几个xxxHandler类(登录成功、登录失败、无权访问、退出登录逻辑)都很相似,返回JSON的方法也是一样的,主要是组装我们自己的结果对象、序列化为JSON字符串然后写入返回的响应流中。

5,安全?策略!配置啦!

上述定义了一系列的自定义实现类,现在就需要进行配置了。除此之外,我们还要配置拦截路径、各个接口路径的访问权限。

新建config包,在里面新建SecurityConfig类,继承WebSecurityConfigurerAdapter并重写其中的configure方法。先上代码:

package com.example.securitytest.config;

import com.example.securitytest.filter.MyAuthFilter;
import com.example.securitytest.handler.MyAccessDeniedHandler;
import com.example.securitytest.handler.MyAuthFailureHandler;
import com.example.securitytest.handler.MyAuthSuccessHandler;
import com.example.securitytest.handler.MyLogoutSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Spring Security配置
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   /**
    * 配置密码加密器
    */
   @Bean
   public PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
   }

   /**
    * 配置自定义用户名密码拦截器为Bean
    */
   @Bean
   public UsernamePasswordAuthenticationFilter myAuthFilter() throws Exception {
      UsernamePasswordAuthenticationFilter myAuthFilter = new MyAuthFilter();
      // 注意,因为是自定义登录拦截器,所以登录接口地址要在此配置!
      myAuthFilter.setFilterProcessesUrl("/api/user/login");
      // 设定为自定义的登录成功/失败处理器
      myAuthFilter.setAuthenticationSuccessHandler(new MyAuthSuccessHandler());
      myAuthFilter.setAuthenticationFailureHandler(new MyAuthFailureHandler());
      myAuthFilter.setAuthenticationManager(authenticationManagerBean());
      return myAuthFilter;
   }

   /**
    * 配置安全拦截策略
    */
   @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception {
      // 设定认证拦截器
      httpSecurity.authorizeRequests()
            // 设定查询炸弹列表的权限
            .antMatchers("/api/bomb/get").hasAuthority("queryBomb")
            // 设定添加炸弹的权限
            .antMatchers("/api/bomb/add").hasAuthority("addBomb")
            // 需要登录后才能获取用户信息
            .antMatchers("/api/user/islogin").authenticated()
            // 放行头像url
            .antMatchers("/avatar/*").permitAll();
      // 自定义退出登录url和配置自定义的登出成功处理器
      httpSecurity.logout().logoutUrl("/api/user/logout").logoutSuccessHandler(new MyLogoutSuccessHandler());
      // 关闭csrf
      httpSecurity.csrf().disable();
      // 设定自己的登录认证拦截器
      httpSecurity.addFilterAt(myAuthFilter(), UsernamePasswordAuthenticationFilter.class);
      // 设定为自定义的权限不足处理器
      httpSecurity.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
   }

}
复制代码

首先,定义了上述的passwordEncoder方法注入为Bean,配置密码加密器,一般就如上配置BCryptPasswordEncoder即可。

后续在写添加用户服务时,需要对用户密码进行加密才能存入数据库,那么直接在相应服务类中自动注入(@Autowired)一个PasswordEncoder对象,并调用其encode方法即可加密。我们上述已经配置了创建一个BCryptPasswordEncoder实例注册为Bean,那么就可以在别的地方使用自动注入。

然后,上述方法myAuthFilter就是我们用于配置自己的用户名密码拦截器方法,将其注入为Bean。在其中还给它配置了登录请求的路径(登录信息请求发送给这个路径)、我们自定义的登录成功/失败拦截器。

重点在于下面的configure方法,利用其中的参数HttpSecurity完成各个配置。

可以看见它的方法是链式调用的,其中:

  • authorizeRequests 配置要认证的路径以及其访问权限,然后:
    • antMatchers 匹配路径,接着设定该路径的访问权限:
      • hasAuthority 拥有指定权限才能访问该路径
      • hasAnyAuthority 指定多个权限,只要拥有其中一个,就可以访问该路径
      • hasRole 拥有指定角色才能访问该路径
      • hasAnyRole 指定多个角色,只要拥有其中一个,就可以访问该路径
      • hasIpAddress 指定的ip才能访问该路径
      • authenticated 只要登录了就可以访问该路径
      • permitAll 不拦截该路径
  • logout 配置退出登录,然后:
    • logoutUrl 配置退出登录API的请求路径(退出登录请求发送给这个路径)
    • logoutSuccessHandler 设定退出登录成功的处理器,这里就填入了我们上述自定义的退出登录成功逻辑实现类的实例
  • addFilterAt 自定义拦截器,上述定义了我们自定义的用户名密码登录拦截器,在这里设定即可。第一个参数是我们拦截器的实例,上面已经定义了方法myAuthFilter,第二个参数表示要设定拦截器的类型,我们使用的是UsernamePasswordAuthenticationFilter
  • exceptionHandling 配置异常处理,然后:
    • accessDeniedHandler 设定自定义403逻辑

需要注意的是,旅行者在网上发现很多教程都是在这个configure方法、利用其中的参数HttpSecurity设定的登录路径、登录成功、失败方法逻辑,但是为什么这里不一样呢?因为我们自定义了用户名密码登录拦截器,就需要在自定义的拦截器实例里面设定,然后再像上面把自定义拦截器实例配置到HttpSecurity中即可。

以及我们上述antMatchers的路径匹配规则在这里提一下:

  • ? 表示匹配任何单字符,例如/api/??将会匹配/api/aa、/api/ab等等,但是不会匹配/api/a或者/api/aaa
  • * 表示匹配0或者任意数量的字符,例如/api/*会匹配/api/a、/api/aaa等等
  • ** 表示匹配0或者多级目录,例如/resource/**会匹配/resource/aa、/resource/aa/bb等等

上述的antMatchers其实也可以换成regexMatchers,即使用正则表达式进行匹配,其子方法和上面相同,这里不再赘述。

以及前面提了角色名必须以ROLE_开头,但是这里如果要用到hasRole或者hasAnyRole的话,里面的参数就不能以ROLE_开头了,否则会报错。例如只允许ROLE_admin访问,那么应该写hasRole("admin")。

与此同时,在这里我还是强调一下判断用户登录的API,我先放出用户API类(Controller层)代码:

package com.example.securitytest.api;

import com.example.securitytest.dao.MyUserDAO;
import com.example.securitytest.dataobject.MyUser;
import com.example.securitytest.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
public class UserAPI {

   @Autowired
   private MyUserDAO myUserDAO;

   /**
    * 判断用户登录,这个api会被加入Spring Security拦截器,若用户没有登录会返回无权访问403
    *
    * @return 用户信息
    */
   @GetMapping("/islogin")
   public Result<MyUser> isLogin(Authentication authentication) { // 在Controller类方法中加上Spring Security的Authentication类型参数,可以获取当前登录的用户信息例如用户名等等
      Result<MyUser> result = new Result<>();
      // 判断是否登录,如果未登录通常这个authentication为null
      if (authentication == null || !authentication.isAuthenticated()) {
         result.setResultFailed("用户未登录!");
         return result;
      }
      // 获取当前登录的用户信息
      UserDetails userDetails = (UserDetails) authentication.getPrincipal();
      // 获取用户名并查询用户
      MyUser myUser = myUserDAO.getByUsername(userDetails.getUsername());
      if (myUser == null) {
         result.setResultFailed("用户无效!");
         return result;
      }
      result.setResultSuccess("用户已经登录!", myUser);
      return result;
   }

}
复制代码

这里只有一个方法isLogin,可见其中我们可以加上Spring Security的Authentication类型参数,这个参数在我们上面自定义登录成功处理器的时候也见到过,其实就是一个东西,表示当前登录的用户信息。我们可以用这个参数,判断用户是否登录,以及获取当前登录的用户信息,我们这里就用它获取了当前登录用户的用户名并拿去查询数据库得到完整用户信息给前端。

我们还可以用这个Authentication类型参数判断当前操作和被操作的用户名是否一致。

为什么这里可以获取已经登录的用户呢?其实明白用户登录的原理就知道了,Spring Security也是用session实现用户登录的,只要某个设备在这个网站登录了,那么后面这个设备任何发给这个后端的请求都会带着cookie,里面存着sessionId,这样后端就知道这个设备的用户登录了,并且能够查到登录的用户信息,直到用户退出或者cookie失效,如果不熟悉还是建议看看我的一个讲解开发用户登录的博客,加深理解。

这样,从自定义一些处理逻辑、到配置,就完成了!

6,网页?前端!开干啦!

旅行者终于完成了后端的构建,现在开始构建前端了。

因为这篇文章主要是讲解Spring Security的使用,这里不讲解前端怎么写,只是提提思路。

这里是Vue的前后端分离模式,前端通过axios发送请求,得到了结果,来控制页面跳转、显示等等。

每一个页面加载时都会向后端的判断用户是否登录接口发送请求判断是否登录,也可见上面配置安全策略中拦截了这个判断是否登录的接口(/api/user/islogin),这个接口实质就是查询信息,这样前端判断如果返回的是403就说明没登录,返回200就说明登录了。

当然这是我的思路,大家也可以自己实现。

这里用到了Vue多页应用构建,对Vue多页应用不熟悉可以看看这个:链接

这里来看看部分效果吧!

image.png

登录界面

image.png

可莉可以增加和查看炸弹

image.png

早柚只能查看炸弹,但是不能添加炸弹

7,完成?放工!总结啦!

至此,旅行者给可莉的第一版“炸弹管理系统”就完成了!这里简单起见就没有做数量管理、用户注册等等功能。

实际开发中,我们会有数据模型(dataobject)、DAO层(操作数据库)、Service层(服务逻辑)和Controller层(API),这里简单起见省略了Service层。

不过,这里在前后端分离的模式下,我们Spring Security的一些逻辑就需要自行设定。

最核心的思想就是:

  • 后端不再控制页面跳转,只控制接口和数据的访问、用户登录等资源访问
  • 每一个操作后端都给前端返回JSON数据而非执行请求转发、跳转
  • 让前端读取结果判断操作成功还是失败,实现跳转显示等等
  • 后端开发一个判断用户是否登录的接口,前端每次加载(mounted时)就可以发送请求判断是否登录,如果登录了就返回用户信息前端以显示

示例仓库地址

来源:https://juejin.cn/post/7007349443288645662#heading-8
Hadoop生态系统-新手快速入门(含HDFS、HBase系统架构) Hadoop简介 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。 Hadoop实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS。HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件上;而且它提供高吞吐量... 阅读详情

相关推荐

surfer10与Arcgis10.3 完美生成等值线和等值面方法

surfer10 与Arcgis10.3生成等值线和等值面的步骤如下: 1.准备surfer用的Book1.dat文件,格式如下: "x" "y" "z" 116.471992 40.809975 232 116.335556 40.700542 1235 116.541626 40.713333 214 116.73349 40.798607 566 2.在surfer中,选择Gr

哈里·雪利博客 1万+

springboot+vue3健身房管理系统实战

需要有springboot、Vue2、Vue3组合api基础知识,TypeScript基础知识的小伙伴;没有vue2/vue3、spring boot基础不建议学习;

lcy198910的博客 1631

AI+Xmind自动生成测试用例(思维导图格式)

最后:我是Walker,一个热爱分享编程知识的程序员,希望能够帮助到你!

Think_and_work的博客 2335

青锋开源架构-springboot2.6.x+vue3-antdesign-vite之springsecurity实现访问权限控制

框架开源地址: 青锋开源架构-springboot2.6.x+vue3-antdesign-vite: 青锋-springboot2.6.x+vue3-antdesign-vite开源架构,实现了系统管理模块、权限控制模块(菜单权限、功能按钮权限、数据权限)、代码生成器(单表、树表)、quartz动态定时器等功能。 权限访问控制讲解 GrantedAuthority接口 Spring Security中有一些概念和实现,例如GrantedAuthority用于获得授权来授权/控制访问权限的接口。

weixin_38027960的博客 3076

反激电源反馈环路设计:从TL431+PC817原理到动态补偿实战

本文深入解析了反激电源中由TL431和PC817构成的经典反馈环路设计。从静态工作点计算到动态补偿实战,详细阐述了如何通过Type II补偿网络优化环路稳定性与动态响应,并分享了应对光耦CTR离散性、布局噪声等常见调试难题的实用技巧,帮助工程师设计出高性能、高可靠的开关电源。

moon9的博客 706

使用Python开发TreeATE的测试脚本常见问题

ImportError: No module named py_compile 找不到 DevLangPython3.dll 以上2个问题都是没有安装正确的Python,当前TreeATE依赖32位Python 2.7版本,暂时不支持Python 3以上版本

蓝白云的专栏 1033

Vue 3 模板语

本文深入解析Vue3模板语法的底层逻辑,从声明式编程理念出发,系统讲解插值与指令语法的核心用法。重点剖析文本插值、v-html、v-once等插值特性及安全风险;详述v-bind、v-model、v-if/v-show、v-for、v-on等指令的使用场景与常见误区,强调合理使用key、避免指令冲突、复杂逻辑抽离至script等最佳实践,帮助开发者写出高效、可维护的Vue模板代码。

cccloveforever的博客 284

深度学习实战107-基于Qwen3+GraphRAG+Agent的智能文档管理系统:精准问答与决策支持

在当今信息爆炸的时代,企业面临海量文档的管理与利用挑战。传统文档管理系统虽然提供了存储和检索功能,但缺乏对文档内容的深度理解与分析能力,更难以实现基于知识的决策支持。基于多模态大模型Qwen3、GraphRAG技术与Agent框架构建的智能文档管理系统,能够突破传统限制,实现文档内容的精准解析、知识图谱的自动构建、复杂关系的推理分析以及决策建议的生成,为企业知识管理带来革命性变革。

微学AI的博客 866

前端登录页面的书写与验证(后端)

如果有该用户就登录成功跳转至主页,如果没有用户和密码错误就登录失败不跳转。(3)写后端代码,接受前端请求并返回数据(新建目录houduan,在houduan/index.js写入)(2)前端配置路由地址,方便登录成功后页面的跳转(在routers.js写入)输入admin和123456,就可以实现登录并跳转至主页(内容暂时没有写)了。(4)后端操作数据库拿数据(在houduan/login_bs.js写入)(1)前端代码(在login2.vue文件写入)先启动后端:在terminal终端输入以下命令。

weixin_46452278的博客 227

28.Anaconda安装jupyter notebook、jupyterlab及体验

手贱,不小心将Anaconda自带得jupyter给卸载了,没法子了查了好多资料都比较麻烦,所以自己摸索着重新安装,记录一下心得。说明:Anaconda没有加入环境变量,Anaconda官方建议不要将Anaconda加入环境变量,这就意味着他和python是可以共存的。

迷逝 5031

996引擎 - 读写变量性能测试

单轮100万次读写,取3轮平均耗时(毫秒),有效读取率均为100%,数值越小性能越优。

灵台方寸山斜月三星洞 246

Vue3——第 17 篇:模板引用(ref)与组件实例——操作 DOM 的正确姿势

本文系统讲解 Vue 中模板引用的用法,涵盖 ref 基础用法、v-for 与条件渲染中的应用,重点介绍 Vue 3.5+ 的 useTemplateRef 新特性——实现类型安全、智能提示的模板引用。结合 nextTick 处理 DOM 更新时机,掌握在声明式框架中安全操作 DOM 与组件实例的核心技巧,提升开发效率与代码可靠性。

810cheng 263

DDR5中的On-Die Termination(ODT)详解:电阻值支持及其背后的秘密

DDR5支持多档ODT电阻值,范围从40Ω到480Ω,均基于240Ω标准参考电阻RZQR_{ZQ}RZQ​。不同电阻档位对应不同的信号负载和功耗需求。校准电压点关键,常用0.8×VDD进行精准调校。CA信号间ODT匹配必须严格控制,保证命令总线稳定。选择合适的ODT值是高速内存设计中不可忽视的细节。

weixin_43199439的博客 2103

Java在线租房系统源码解析:Spring Boot+Vue 3+UniApp房源管理与预约看房方案

本文介绍一套基于Java、Spring Boot、Vue 3与UniApp的在线租房平台源码,涵盖租客端、房东端及管理后台三大模块,实现房源展示、在线咨询、预约看房、合同管理与审核治理等核心功能。技术架构清晰,支持多端部署,适用于区域租房平台、公寓运营及定制化开发项目。强调业务流程闭环与数据一致性,提供二次开发指导与验收标准,助力企业快速搭建自有品牌租房系统。

weixin_42648890的博客 223

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain&#39;s apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath(&#39;DeepLearnToolbox&#39;));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand(&#39;state&#39;,0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W&#39;); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand(&#39;state&#39;,0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = &#39;sigm&#39;;%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, &#39;Too big error&#39;);```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand(&#39;state&#39;,0)sae = saesetup([784 100]);sae.ae{1}.activation_function = &#39;sigm&#39;;sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)&#39;)% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;;nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, &#39;Too big error&#39;);```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x&#39;,28,28,60000))/255;test_x = double(reshape(test_x&#39;,28,28,10000))/255;train_y = double(train_y&#39;);test_y = double(test_y&#39;);%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you&#39;ll get around 1.2% errorrand(&#39;state&#39;,0)cnn.layers = { struct(&#39;type&#39;, &#39;i&#39;) %input layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 6, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %sub sampling layer struct(&#39;type&#39;, &#39;c&#39;, &#39;outputmaps&#39;, 12, &#39;kernelsize&#39;, 5) %convolution layer struct(&#39;type&#39;, &#39;s&#39;, &#39;scale&#39;, 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, &#39;Too big error&#39;);```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, &#39;Too big error&#39;);%% ex2 neural net with L2 weight decayrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex3 neural net with dropoutrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex4 neural net with sigmoid activation functionrand(&#39;state&#39;,0)nn = nnsetup([784 100 10]);nn.activation_function = &#39;sigm&#39;; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex5 plotting functionalityrand(&#39;state&#39;,0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = &#39;softmax&#39;; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand(&#39;state&#39;,0)nn = nnsetup([784 20 10]); nn.output = &#39;softmax&#39;; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, &#39;Too big error&#39;);```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Vue Router

介绍vue-router的基本使用以及核心概念

k_5412528的博客 223

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Vue 前端开发 AI 对话页面:流式消息渲染、文件上传

关键点是不能手动设置 Content-Type 为 multipart/form-data,必须由浏览器自动生成带有 boundary 的请求头,否则后端无法解析。思路:将对象序列化为 JSON 字符串,作为 FormData 的一个普通字段(如 formData.append(‘payload’, JSON.stringify(data))),后端接收该字段后再反序列化。若上传大文件(>10MB),需实现独立的分片上传接口,在提交对话前先获取文件 id 或 url,再随文本发送引用。移除输入框的禁用态;

2402_85729970的博客 398

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Vue 3 实现简单购物车

页面分三块:顶部是添加商品的表单(名称 + 价格 + 添加按钮 + 删除选中按钮),中间是商品表格,底部是总数量和总价统计。数据层是这样组织的:javascript{ id: 1, name: '信条', price: 380, num: 1, checked: false },{ id: 2, name: '坠星者', price: 430, num: 1, checked: false },

2603_95923710的博客 222

Vue.js自动化测试平台实现方案对比:5种常见方式的优缺

Playwright 是一个现代化的 E2E 测试框架,支持多种浏览器引擎,并具备强大的 API 能力。

wuchangjian的专栏 155

成为全栈·Vue 管理后台篇·Vue Router如何守住多角色后台边界

本文以 Vue 管理后台的会员、编辑、管理员角色为例,讲解 Vue Router 如何通过 meta 声明登录、控制台和能力要求,守卫怎样区分登录页、No Access、403 与 404,菜单与直接 URL 如何共享纯权限函数。文章同时明确前端路由守卫只负责导航体验,API 的真实读写权限必须由后端独立验证。

FungLeo's Blog 252

vue 树组件 vxe-tree 使用节点连接线

vxe-tree 通过 show-line 展示节点连接线,清晰呈现层级关系;结合 transform 可将平铺数据自动转为树形结构。支持自定义 icon-open 与 icon-close 图标,展开/收起状态动态切换。二者可共存,且兼容多选、复选框等能力,提升复杂树形结构的可读性与交互体验。

honkun6的博客 160

Vue3——第 15 篇:内置组件全家桶——Transition、Teleport、Suspense、KeepAlive

本文系统讲解 Vue 四大核心内置组件:Transition(过渡动画)、Teleport(DOM 传送)、Suspense(异步依赖处理)和 KeepAlive(组件缓存)。重点介绍 Transition 的 CSS/JS 钩子、类名机制、常见动画效果及与 Animate.css、GSAP 集成;TransitionGroup 实现列表元素的动态过渡;结合实战案例,帮助开发者实现流畅的视觉交互与性能优化。

810cheng 519

开源实战:基于 Spring Boot 3 + Vue 3 + Redis 的高校选修课管理系统(志愿填报 + 统一抽签)

基于 Spring Boot 3 + Vue 3 + Redis 的高校选修课管理系统,选课采用「志愿填报 + 统一抽签」:填报期无锁提交,截止后一次性批算名额,公开 seed 可独立复算,逐志愿留有中签原因与投档顺位,R1 可退选、R2 锁定最终结果。附完整 GIF 与按角色分类的截图。

欢迎点赞 + 关注,开源项目见下方栏目 275

后端够硬就行?错!用 React/Vue/Next.js 补齐全栈拼图

后端再强,前端体验崩了也白搭。本文以真实项目为镜,揭示后端开发者补全前端能力的五大核心痛点:状态调度误判、并发渲染竞态、加载体验缺失、响应式陷阱与组件边界混淆。通过 React 的函数式更新、并发渲染清理机制,Vue 的 nextTick 与 ref 安全传递,以及 Next.js 服务端组件的架构级优化,提供可直接落地的工程方案。强调“状态生命周期”与“渲染时机”的感知,才是全栈拼图中不可或缺的一块。

2302_80138126的博客 501

vue 表格组件 vxe-grid 自定义插槽模板的用法详解

vxe-grid 支持通过模板插槽(#xxx_default)与 JSX 插槽自定义单元格内容,实现图片、按钮、组合元素等灵活渲染。两者均接收 row、column 等参数,命名建议使用 {field}_default 提升可维护性。模板插槽适用于非 JSX 项目,JSX 插槽适合动态配置或逻辑内聚场景。性能上,插槽渲染较直接字段略低,但灵活性更高,可根据项目需求选择使用。

chocolate2s的博客 214

成为全栈·Vue 管理后台篇·登录页从表单变成产品入口

本文从 Vue 管理后台 LoginPage 说明如何把简单登录表单设计成产品入口:桌面双栏品牌介绍、站点设置动态品牌、窄屏单列、键盘和 autocomplete 支持、加载与错误反馈、开发环境账号预填以及按角色登录分流。重点明确视觉升级不修改账号密码契约,站点设置失败不应阻断登录,真实权限仍由路由和后端执行。

FungLeo's Blog 271

Vue-组件

核心概念:组件是「带 props 输入、emit 输出、插槽内容分发」的独立视图单元。SFC(Single-File Component)把<template><script>(或)、<style>三段放在同一个.vue文件里,是组织方式,不是运行时对象。原理解析:SFC 由先拆成相互独立的块,再分别交给模板编译器、JS/TS 编译器与 PostCSS 处理。等是编译器宏,在编译阶段被消解,因此不需要import,也不能在运行时当作普通函数使用。官方表述:;

只要肯攀登,终有登顶时 136
上一篇: Springboot使用CrosXssFilter防止sql注入xss攻击cros跨域等
下一篇: 3种 Springboot 全局时间格式化方式,别再写重复代码了
码趣资源网
博客等级 码龄4年 269粉丝 518原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值