(十三)springboot实战——springboot前后端分离方式项目集成spring securtity安全框架

前言

Spring Security 是一款强大且高度可定制的认证和访问控制框架,它是为了保护基于Spring的应用程序提供安全性支持。Spring Security提供了全面的安全服务,主要针对企业级应用程序的需求。其核心组件主要包含:Authentication(认证)、Authorization(授权)、Principal(主体)、Granted Authority(授予的权限)、Security Context(安全上下文),可提供方法级别的权限认证。其底层主要通过Filter拦截器实现,关于其实现原理,我们会在后面的章节内容中介绍。

本节内容主要是关于前后端分离的项目如何集成spring securtity安全框架,完成用户的认证与授权。也会详细介绍各个配置项及配置组件的使用。

正文

①创建一个springboot web项目,引入spring security的依赖,同时引入mysql和mybatis-plus依赖,用于实现一个数据库版本的spring security安全框架


<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>2.0.22</version>
</dependency>

<dependency>
	<groupId>com.baomidou</groupId>
	<artifactId>mybatis-plus-boot-starter</artifactId>
	<version>3.5.4</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
	<groupId>com.mysql</groupId>
	<artifactId>mysql-connector-j</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

 ②在application.yml中添加数据库配置,用于连接数据库

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.110.88:3306/ht-atp?characterEncoding=utf-8&serverTimezone=GMT%2B8&useAffectedRows=true&nullCatalogMeansCurrent=true
    username: root
    password: root
server:
  port: 8080

③ 创建一张用户数据库表auth_user,用于存储用户信息

CREATE TABLE `auth_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `password` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `enabled` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

④创建用户实体AuthUser 

@Data
public class AuthUser {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private Boolean enabled;

}

 ⑤创建用户持久层Mapper

package com.yundi.atp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yundi.atp.entity.AuthUser;
import org.apache.ibatis.annotations.Mapper;


@Mapper
public interface AuthUserMapper extends BaseMapper<AuthUser> {

}

⑥统一响应请求返回定义

package com.yundi.atp.commom;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;


@Data
@Builder
public class ApiResponse<T> {
    @Schema(name = "code", description = "状态码")
    private Integer code;

    @Schema(name = "msg", description = "消息结果")
    private String msg;

    @Schema(name = "data", description = "数据")
    private T data;

    /**
     * 成功统一响应格式
     *
     * @param
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> success() {
        ApiResponse<T> apiResponse = new ApiResponse<>(200, "成功", null);
        return apiResponse;
    }


    /**
     * 成功统一响应格式
     *
     * @param
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> success(String msg) {
        ApiResponse<T> apiResponse = new ApiResponse<>(200, msg, null);
        return apiResponse;
    }

    /**
     * 成功统一响应格式
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> apiResponse = new ApiResponse<>(200, "成功", data);
        return apiResponse;
    }

    /**
     * 成功统一响应格式
     *
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> success(String msg, T data) {
        ApiResponse<T> apiResponse = new ApiResponse<>(200, msg, data);
        return apiResponse;
    }

    /**
     * 失败统一响应格式
     *
     * @param code
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> fail(Integer code, String message) {
        return new ApiResponse<>(code, message, null);
    }

    /**
     * 失败统一响应格式
     *
     * @param errorCode
     * @param <T>
     * @return
     */
    public static <T> ApiResponse<T> fail(ErrorCode errorCode) {
        return new ApiResponse<>(errorCode.getCode(), errorCode.getMsg(), null);
    }
}

⑦ 统一错误码定义

package com.yundi.atp.commom;

public enum ErrorCode {
    SYSTEM_ERROR(10000, "系统错误!"),
    UN_AUTH(10001, "用户未认证,请先登录!"),
    AUTH_FAILURE(10002, "认证失败,用户名或密码错误!"),
    UN_ACCESS(10003, "该用户没有此操作权限!"),
    METHOD_ARGS_VALID(10004, "方法参数验证失败!"),
    TOKEN_VALID(10005, "token鉴权失败!"),
    TOKEN_NOT_EXIST(10006, "token不存在!"),
    ;


    private Integer code;
    private String msg;

    ErrorCode(Integer code, String message) {
        this.code = code;
        this.msg = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

⑧创建DbUserDetailsManager类实现UserDetailsManager和UserDetailsPasswordService接口方法,用于实现数据库版本的认证权限管理,该类主要是实现用户数据和权限数据的获取,用于认证和授权使用

package com.yundi.atp.auth;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yundi.atp.entity.AuthUser;
import com.yundi.atp.mapper.AuthUserMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;


@Service
public class DbUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
    @Resource
    private AuthUserMapper authUserMapper;

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails userDetails) {
        AuthUser authUser = new AuthUser();
        authUser.setUsername(userDetails.getUsername());
        authUser.setPassword(userDetails.getPassword());
        authUser.setEnabled(true);
        authUserMapper.insert(authUser);
    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户数据
        QueryWrapper<AuthUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        AuthUser authUser = authUserMapper.selectOne(queryWrapper);
        if (authUser == null) {
            throw new UsernameNotFoundException(username);
        } else {
            //从数据库获取用户权限列表
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(() -> "USER_LIST");
            authorities.add(() -> "USER_ADD");
            return new User(
                    //用户账号
                    authUser.getUsername(),
                    //用户密码
                    authUser.getPassword(),
                    //是否启用
                    authUser.getEnabled(),
                    //用户账号是否过期
                    true,
                    //用户凭证是否过期
                    true,
                    //用户是否未被锁定
                    true,
                    //权限列表
                    authorities);
        }
    }
}

⑨创建MyAuthenticationEntryPoint类实现AuthenticationEntryPoint接口,用于未认证的处理

package com.yundi.atp.auth;

import com.alibaba.fastjson.JSON;
import com.yundi.atp.commom.ApiResponse;
import com.yundi.atp.commom.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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


@Slf4j
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        log.info("未认证:" + authException);
        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(JSON.toJSONString(ApiResponse.fail(ErrorCode.UN_AUTH)));
    }
}

 ⑩创建MyAuthenticationSuccessHandler类实现AuthenticationSuccessHandler接口,用于认证成功的处理

package com.yundi.atp.auth;

import com.alibaba.fastjson.JSON;
import com.yundi.atp.commom.ApiResponse;
import com.yundi.atp.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

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


@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(JSON.toJSONString(ApiResponse.success("认证成功!")));
    }
}

⑪创建MyAuthenticationFailureHandler类实现AuthenticationFailureHandler接口,用于认证失败的处理

package com.yundi.atp.auth;

import com.alibaba.fastjson.JSON;
import com.yundi.atp.commom.ApiResponse;
import com.yundi.atp.commom.ErrorCode;
import lombok.extern.slf4j.Slf4j;
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;


@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
      response.getWriter().println(JSON.toJSONString(ApiResponse.fail(ErrorCode.AUTH_FAILURE)));
    }
}

⑫创建MyLogoutSuccessHandler类实现LogoutSuccessHandler接口,用于退出登录的处理

package com.yundi.atp.auth;

import com.alibaba.fastjson.JSON;
import com.yundi.atp.commom.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

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


@Slf4j
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(JSON.toJSONString(ApiResponse.success("注销成功!")));
    }
}

⑬创建MyAccessDeniedHandler类实现AccessDeniedHandler接口,用于授权不通过的处理

package com.yundi.atp.auth;

import com.alibaba.fastjson.JSON;
import com.yundi.atp.commom.ApiResponse;
import com.yundi.atp.commom.ErrorCode;
import lombok.extern.slf4j.Slf4j;
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;


@Slf4j
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(JSON.toJSONString(ApiResponse.fail(ErrorCode.UN_ACCESS)));
    }
}

⑭创建spring security的配置类,开启spring security认证以及方法级别的授权

package com.yundi.atp.config;

import com.yundi.atp.auth.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.web.SecurityFilterChain;


@EnableMethodSecurity
@Configuration
public class SpringSecurityConfig {
    @Autowired
    private DbUserDetailsManager dbUserDetailsManager;

    @Bean
    public AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(dbUserDetailsManager);
        authenticationProvider.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
        return new ProviderManager(authenticationProvider);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //authorizeRequests():开启授权保护
        //anyRequest():对所有请求开启授权保护
        //authenticated():已认证请求会自动被授权
        http.authorizeRequests(authorize -> {
            //放行路径
            authorize.antMatchers("/webjars/**", "/v3/**", "/doc.html", "/favicon.ico", "/swagger-ui/**").permitAll();
            //其它请求需要认证
            authorize.anyRequest().authenticated();
        });

        //自定义登录
        http.formLogin(formLogin -> {
            formLogin.loginPage("/login").permitAll() //登录页面无需授权即可访问
                    .usernameParameter("username") //自定义表单用户名参数,默认是username
                    .passwordParameter("password");//自定义表单密码参数,默认是password
            //登录认证成功
            formLogin.successHandler(new MyAuthenticationSuccessHandler());
            //登录认证失败
            formLogin.failureHandler(new MyAuthenticationFailureHandler());
        });

        // 基本授权方式
        http.httpBasic(Customizer.withDefaults());

        //自定义注销
        http.logout(logout -> {
            logout.logoutUrl("/logout");
            //删除授权信息
            logout.clearAuthentication(true);
            //删除cookie
            logout.deleteCookies("JSESSIONID");
            //设置session失效
            logout.invalidateHttpSession(true);
            //退出的处理
            logout.addLogoutHandler(new MyLogoutHandler());
            //退出成功的响应
            logout.logoutSuccessHandler(new MyLogoutSuccessHandler());
        });


        //错误处理
        http.exceptionHandling(exception -> {
            //请求未认证的接口
            exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());
            //未授权的接口异常处理:可能会不生效,被全局异常处理器拦截
            exception.accessDeniedHandler(new MyAccessDeniedHandler());
        });

        // 跨域
        http.cors(Customizer.withDefaults());


        //关闭csrf攻击防御
        http.csrf(csrf -> {
            csrf.disable();
        });
        return http.build();
    }

}

⑮spring security的配置说明

1.开启方法级别的权限验证

2.注入自定义的数据库版本的授权管理器

3.放行不需要认证的路径

4.配置登录的认证及认证处理

5.配置基本的授权方式

6.配置用户注销的处理

7.异常的处理

8.跨域的处理

9.关闭csrf攻击防御

⑯使用PasswordEncoderFactories工厂生成一个spring security的密码,创建一个用户用户测试

⑰访问未认证的请求

⑱使用正确的用户名和密码访问登录接口/login,注意这里必须是post请求

⑲使用错误的用户名和密码访问登录接口/login,注意这里必须是post请求

⑳测试接口授权,目前的权限包含USER_LIST、USER_ADD,在DbUserDetailsManager中查询的

㉑访问/logout注销接口,查看结果

结语

关于springboot前后端分离方式项目集成spring securtity安全框架的内容到这里就结束了,我们下期见。。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/375578.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux的打包压缩与解压缩---tar、xz、zip、unzip

最近突然用到了许久不用的压缩解压缩命令&#xff0c;真的陌生&#xff0c; 哈哈&#xff0c;记录一下&#xff0c;后续就不用搜索了。 tar的打包 tar -cvf 压缩有的文件名称 需要压缩的文件或文件夹tar -cvf virtualbox.tar virtualbox/ tar -zcvf virtualbox.tar virtualbo…

宏景eHR fieldsettree SQL注入漏洞复现

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合,满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR fieldsettree 接口处存在SQL注入漏洞,未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令,从而窃取数据库敏感…

备战蓝桥杯---搜索(进阶3)

看一道比较难又有趣的题&#xff1a; 下面是分析&#xff1a; 我们不妨把属性值看成点&#xff0c;一个装备可以看成一条边&#xff08;只能选一个端点&#xff09;不存在有装备属性值的当成一个点&#xff0c;于是我们便形成了树或图&#xff0c;如果是树的话&#xff0c;有一…

大模型工作方法论

这是去年探索大模型留下的一些有效工作方法论&#xff0c;给大家分享出来。看懂着&#xff0c;一点就通&#xff1b;看不懂着&#xff0c;会老追问这到底是什么呀。 &#xff08;1&#xff09; 1、成功&#xff1a;成功才是成功之母&#xff0c;失败不是成功之母。老研究失败没…

微信小程序(三十六)事件传参

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.传参步骤 2.传参接收解构步骤 源码&#xff1a; index.wxml <button type"primary" bind:tap"onclick" mark:index"{{0}}" mark:remb"{{1}}" class"But&quo…

OpenCV-31 获得形态学卷积核

OpenCV提供了获取卷积核的API&#xff0c;不需要我们手动创建卷积核。 通过下面API---getStructuringElement(shape&#xff0c;ksize&#xff0c;[, anchor]) shape是指卷积核的型状&#xff0c;注意不是指长宽&#xff0c;是指卷积核中1形成的形状。MORPH_RECT 卷积核中的1…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Video媒体组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Video媒体组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Video媒体组件 用于播放视频文件并控制其播放状态的组件。 子组件 无 接口…

我的QQ编程学习群

欢迎大家加入我的QQ编程学习群。 群号:950365002 群里面有许多的大学生大佬&#xff0c;有编程上的疑惑可以随时问&#xff0c;也可以聊一些休闲的东西。 热烈欢迎大家加入&#xff01;&#xff01; 上限:150人。

黑马Java——集合进阶(List、Set、泛型、树)

一、集合的体系结构 1、单列集合&#xff08;Collection&#xff09; 二、Collection集合 1、Collection常见方法 1.1代码实现&#xff1a; import java.util.ArrayList; import java.util.Collection;public class A01_CollectionDemo1 {public static void main(String[] a…

NAT——网络地址转换、NAPT

网络地址转换 NAT (Network Address Translation) 1994 年提出。 需要在专用网连接到互联网的路由器上安装 NAT 软件。 装有 NAT 软件的路由器叫做 NAT路由器&#xff0c;它至少有一个有效的外部全球 IP 地址。 所有使用本地地址的主机在和外界通信时&#xff0c;都要在 NA…

【大模型上下文长度扩展】线性偏差注意力 ALiBi

线性偏差注意力 ALiBi 核心问题&#xff1a;如何使Transformer模型在推理时有效处理长于训练时序列的输入&#xff0c;同时提高训练效率并减少资源需求&#xff1f;具体问题&#xff1a;当前位置编码方法不支持高效的序列长度外推。总结 论文&#xff1a;https://arxiv.org/pdf…

GeoServer 2.11.1升级解决Eclipse Jetty 的一系列安全漏洞问题

Eclipse Jetty 资源管理错误漏洞(CVE-2021-28165) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7656) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7657) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7658) Jetty 信息泄露漏洞(CVE-2017-9735) Eclipse Jetty 安全漏洞(CVE-2022-20…

ACK One Argo工作流:实现动态 Fan-out/Fan-in 任务编排

作者&#xff1a;庄宇 什么是 Fan-out Fan-in 在工作流编排过程中&#xff0c;为了加快大任务处理的效率&#xff0c;可以使用 Fan-out Fan-in 任务编排&#xff0c;将大任务分解成小任务&#xff0c;然后并行运行小任务&#xff0c;最后聚合结果。 由上图&#xff0c;可以使…

MySQL组复制的介绍

前言 本文介绍关于MySQL组复制的背景信息和基本原理。包括&#xff0c;介绍MySQL传统复制方法的原理和隐患、介绍组复制的原理&#xff0c;单主模式和多主模式等等。通过结合原理图学习这些概念&#xff0c;可以很好的帮助我们理解组复制技术这一MySQL高可用方案&#xff0c;有…

面向对象的三大特征之一封装

封装 概念 封装就是通过 权限修饰符&#xff08;private&#xff09;将成员变量隐藏起来 本质&#xff1a;就是将数据私有化&#xff0c;其他类使用必须通过设置的 get 和 set 方法来获取和设置例子&#xff1a;假设你有一本书&#xff0c;你将其藏起来&#xff0c;别人想要看…

Java基于微信小程序的医院核酸检测服务系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

CSS伸缩盒模型

CSS伸缩盒模型 伸缩盒模型是CSS中的一种布局手段&#xff0c;可以使元素具有弹性&#xff0c;让元素可以跟随页面大小的改变而改变。 1. 伸缩容器 给元素设置display:flex 或 display:inline-flex &#xff0c;就是伸缩容器。 2. 主轴与侧轴 主轴&#xff1a; 伸缩项目沿着…

2024年:用OKR管理你的生活

在科技高速发展的时代&#xff0c;越来越多的企业和团队开始采用OKR&#xff08;Objectives and Key Results&#xff09;管理方法来设定目标并跟踪进度。你是否想过&#xff0c;将OKR理念引入个人生活&#xff0c;以更有效地实现人生目标&#xff1f;本文将探讨如何在2024年运…

句子嵌入: 交叉编码和重排序

这个系列目的是揭开嵌入的神秘面纱&#xff0c;并展示如何在你的项目中使用它们。第一篇博客介绍了如何使用和扩展开源嵌入模型&#xff0c;选择现有的模型&#xff0c;当前的评价方法&#xff0c;以及生态系统的发展状态。第二篇博客将会更一步深入嵌入并解释双向编码和交叉编…

Java基于微信小程序的医院挂号系统

文章目录 1 简介2 技术栈3 系统目标3.2 系统功能需求分析3.2.1 功能需求分析 4 系统模块设计4.1 数据库模块设计 5 系统的实现5.1 微信小程序个人中心5.2 科**室内容查看的实现**5.3 预约挂号的实现5.4 后台管理界面实现5.5 医生预约管理5.6 医生信息管理 参考文献7 推荐阅读8 …