【苍穹外卖】学习日志-day1

news2024/11/16 19:06:44

目录

nginx 反向代理介绍

nginx 的优势

提高访问速度

负载均衡

保证后端服务安全

高并发静态资源

Swagger 生成 API 文档

Swagger 的使用方式

导入knife4j的maven坐标

在配置类中加入knife4j相关配置

 设置静态资源映射

通过注解控制生成的接口文档

项目技术点

Token 模式

 MD5 加密

新增员工开发

DTO 设计模式

隔离线程 ThreadLocal

全局异常处理器

为什么需要全局异常处理器

代码测试

员工分页查询

代码测试


本章代码地址:苍穹外卖

 

nginx 反向代理介绍

        在没学习 nginx 之前,我们的项目都是前端直接发请求 tomcat 服务器的。如下图

tomcat 的作用:Tomcat 可以处理 HTTP 请求并将其传递给 Java 应用程序进行处理。

        学习了 nginx 后,我们更希望将用户的所有请求交给 nginx 反向代理,再转发给 tomcat 处理:

nginx 的优势

提高访问速度

        nginx 可以做缓存,如果我们请求的是同一个接口地址,则无需请求后端服务,直接在 nginx 把缓存数据响应给前端。

负载均衡

        nginx 可以把大量的请求按照指定的方式均衡的分配给集群中的每台服务器


举个例子:

        以百度为例,百度的后台肯定是不止一台服务器的,但我们在访问百度的时候,只需要输入百度的地址,就会被分配到一个服务器上去,以获得服务。而我们访问的是哪个服务器我们并不知道,我们只管访问 www.baidu.com,后面的事都会有相应的机制帮我们实现。

        要实现此类效果,即无论应用有多少实例,我们只需要访问一个地址就可以得到服务。就需要在客户端与服务端之间加上一层服务器 nginx 。

        这样客户端只管访问 nginx ,再由 nginx 服务器将请求代理到真正部署有实例的服务器上去即可。

保证后端服务安全

        我们真实的服务器不应该直接暴露到公网上去,否则更加容易泄露服务器的信息,也更加容易受到攻击。而使用 nginx 可以接收来自客户端的请求并将其转发到后端服务器。这样做的好处是可以隐藏服务器的真实 IP 地址,提供额外的安全层

高并发静态资源

        nginx 专注于处理静态资源,具有出色的性能和高并发处理能力。将 nginx 作为静态资源服务器可以提高系统的响应速度,并减轻 tomcat 的负担。

         而苍穹外卖也是通过 nginx 反向代理来访问我们后台的,nginx 文件:nginx反向代理

Swagger 生成 API 文档

        使用 Swagger 你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。这样我们就不用去 Postman 配置路径再来测试了。

Knife4j 是为 Java MVC 框架集成 Swagger 生成 API 文档的增强解决方案~

Swagger 的使用方式

导入knife4j的maven坐标

            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>3.0.2</version>
            </dependency>

在配置类中加入knife4j相关配置

WebMvcConfiguration

  • Swagger 实例Bean是Docket,所以通过配置Docket实例来配置Swaggger
  • Docket 实例关联上 apiInfo
    /**
     * 通过knife4j生成接口文档
     */
    @Bean
    public Docket docket() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")  // 生成标题
                .version("2.0")              // 版本
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo)
                // 通过select()方法,去配置扫描接
                .select()
                // RequestHandlerSelectors 配置如何扫描接口
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

 设置静态资源映射

    /**
     * 设置静态资源映射
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始设置静态资源映射...");
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

通过注解控制生成的接口文档

比如

    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO) {
    // ...
    }
注解说明
@Api用在类上,例如Controller,表示对类的说明
@ApiModel用在类上,例如 entity、DTO、VO
@ApiModelProperty用在属性上,描述属性信息
@ApiOperation用在方法上,例如Controller的方法,说明方法的用途、作用 

当程序运行时,我们访问 http://localhost:8080/doc.html 便可访问我们生成的 Swagger 接口文档。

 

项目技术点


Token 模式

        JWT 是 JSON Web Token 的缩写,即 JSON Web 令牌,JWT 是通过对JSON进行加密签名来实现授权验证的方案,就是登陆成功后将相关信息组成 json 对象,然后对这个对象进行某中方式的加密,返回给客户端,客户端在下次请求时带上这个 token,服务端再收到请求时校验 token 合法性,其实也是在校验请求的合法性,只有通过校验成功才能访问后台。 

        所以 JWT 常用于完成客户端的登入系统,以及拦截器;只有当用户输入的账号与密码与数据库中的匹配,系统就会生成 JWT 令牌。而只有这个令牌,后台的接口的拦截器才会放行。

 JWT 依赖:

    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>0.9.1</version>
    </dependency>

  JWT 令牌生成工具类:

public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

拦截器

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            BaseContext.setCurrentId(empId);
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }

可以通过 yml 配置文件来定义 JWT 令牌的一些属性

sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 7200000
    # 设置前端传递过来的令牌名称
    admin-token-name: token

 

 MD5 加密

        当我们需要保存某些密码信息以用于身份确认时,如果直接将密码信息以明码方式保存在数据库中,不使用任何保密措施,系统管理员就很容易能得到原来的密码信息,这些信息一旦泄露, 密码也很容易被破译。

        为了增加安全性,有必要对数据库中需要保密的信息进行加密,MD5 算法可以很好地解决这个问题,因为它可以将任意长度的输入串经过计算得到固定长度的输出,而且只有在明文相同的情况下,才能等到相同的密文,并且这个算法是不可逆,即便得到了加密以后的密文,也不可能通过解密算法反算出明文。

 spring 提供了一个工具类 DigestUtils,我们可以利用该工具类来对数据进行加密

依赖坐标

<dependency>
   <groupId>commons-codec</groupId>
   <artifactId>commons-codec</artifactId>
   <version>1.15</version>
 </dependency>

 员工登入代码

    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();
        //1、根据用户名查询数据库中的数据
        Employee employee = employeeMapper.getByUsername(username);
        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }
        //密码比对
        // TODO 后期需要进行md5加密,然后再进行比对
        // 对输入密码进行加密
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        log.info(password);
        // 输入密码与数据库密码做比较
        if (!password.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }
        //3、返回实体对象
        return employee;
    }

        以上是将用户输入的密码通过 DigestUtils 工具类转化成密文再与数据中的密码进行匹配,注意数据库中的密码存储的是密文。这样即使是数据库的管理者,也不能知道用户的真实密码。

password = DigestUtils.md5DigestAsHex(password.getBytes());

新增员工开发


DTO 设计模式

        数据传输对象 DTO 是一种设计模式,用于封装和传输应用程序不同层之间的数据。

        DTO 是轻量级对象,通常只包含必要的字段,不包含任何业务逻辑。DTO作用于应用程序中不同的业务之间的数据传输,例如在前端和后端之间或在分布式系统中不同的微服务之间。
        在 Spring Boot 应用程序中,DTO 特别有用,因为需要在控制器层、服务层和持久层之间传输数据。通过使用 DTO 就可以将内部数据模型与外部表示
解耦,从而更好地控制数据传输。

         如上图,前端传入这么一组数据,如果有那么一两个数据不是这个实体类 Employee 的,这个时候我们就不能直接用 EmployeeDTO 来接收;而是创建一个 DTO 类 EmployeeDTO 并在数据的 Service 层对 EmployeeDTO 与 Employee 中的共同属性进行赋值操作。这样前端传入的不同属性就不会对 Employee 产生影响,起到一定的解耦效果。

        DTO 允许将暴露给外部的数据与内部的模型隔离。这可以防止暴露敏感和不必要的数据,并为数据交换提供清晰的字段,也在一定程度上保证了内部数据的安全性。


Controller

    /*
     * @description:新增员工
     **/
    @PostMapping
    @ApiOperation("新增员工")
    public Result save(@RequestBody EmployeeDTO employeeDTO) {
        log.info("新增员工:{}",employeeDTO);
        employeeService.save(employeeDTO);
        return Result.success();
    }

Service

        由于 EmployeeDTO 的属于与 Employee 的属性都是共同属性,所以可以直接使用 copyProperties 将数据拷贝给 Employee。然后再设置 EmployeeDTO 没有的属性即可。

(1) copyProperties 将一个类的属性值拷贝到另一个类上,但是一定要满足这个属性是两个类共有的。

(2)使用 MD5 给输入的密码加密,保证账号的安全性。

@Override
    public void save(EmployeeDTO employeeDTO) {
        // 参数类型向员工类型转化
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);
        // 设置账号状态
        employee.setStatus(StatusConstant.ENABLE);
        // 更新创建与修改信息时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        // 设置密文密码
        employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
        // 隔离线程中获取数据
        Long  userId = BaseContext.getCurrentId();
        // 设置创建人和修改人
        employee.setCreateUser(userId);
        employee.setUpdateUser(userId);
        employeeMapper.insert(employee);
    }

隔离线程 ThreadLocal

        在新增员工列表中需要设置创建人与修改人,毫无疑问就是获取当前用户的信息,那么该怎么获取呢?我们可以使用隔离线程 -- ThreadLocal。

        ThreadLocal,也称为线程局部变量,是一种特殊的变量。它的特点是,每个线程都有该变量的一个副本,线程之间互不影响,实现了线程间的数据隔离。

        简单来讲就是客户端为每位用户都提供了单独的线程,而每个线程在 ThreadLocal 设置存取的值都是相互独立的。

这里可以简单的验证一下: 

        我们在令牌验证、controller、service 的地方加上一下代码,判断当前线程是否相同:

我们发现是相同的,那么就可以利用这一条特性,获取当前用户的信息:

        我们可以在获取令牌这里将解析的用户id存入 ThreadLocal 线程中,并在设置创建人或者修改人的时候取出,这样就可以获取当前的用户信息了。

Long  userId = BaseContext.getCurrentId();

Mapper

    @Insert("insert into employee(name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " +
            "VALUES (#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    void insert(Employee Employee);

全局异常处理器

         因为我们数据库表的设计中,用户名是唯一属性,那么再次添加这个用户名的时候就会抛出500异常:

 这个异常是一个 SQL 异常是因为 username 冲突造成的 "Duplicate entry..."

        那么我们肯定不能直接将这个异常返回给用户,必须对它进行一定的处理,这个时候就需要用到全局异常处理器:

为什么需要全局异常处理器

不用强制写 try-catch,由全局异常处理器统一捕获处理。
自定义异常,只能用全局异常来捕获。不能直接返回给客户端,客户端是看不懂的,需要接入全局异常处理器。

处理 SQL 异常 

    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        // Duplicate entry '123' for key 'idx_username'
        String msg = ex.getMessage();
        if(msg.contains("Duplicate entry")){
            String[] split = msg.split(" ");
            String key = split[2];
            return Result.error(key+ MessageConstant.ALREADY_EXISTS);
        }
        return Result.error(MessageConstant.UNKNOWN_ERROR);
    }

        利用 ex.getMessage() 来获取报错信息,如果是 Duplicate entry 开头的异常信息那么铁定是 username 冲突问题,那么我们就需要提取 username 信息。并返回我们自定义的报错信息。如果开头不是 Duplicate entry 那么久返回未知异常。

        Result.error() 返回的错误信息不要写死,要同一进行管理,否则以后项目写大了需要更改需求就十分难找。

        我们可以采用静态常量的属性类来解决这个问题。

        这样我们的控制台就不会再抛出异常,前端也会接收到 username 已存在的信息。

代码测试

 

员工分页查询


 分页查询接口设计

 分析需求:

<1>员工信息分页查询后端返回的对象类型为:Result<PageResult>

<2>其实任何分页查询的底层都是加入了 limit 关键字分页

<3>为了内部数据模型与外部参数解耦,我们依旧采用 DTO 的设计模式

mybatis 提供的分页查询框架 pagehelper 依赖:

            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>1.3.0</version>
            </dependency>

 Controller

    @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> pageQuery(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询:{}",employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
        return Result.success(pageResult);
    }

Service

        分页的核心就一行代码, PageHelper.startPage(page,pageSize) 这个就表示开始分页。加了这个之后 pagehelper 插件就会通过其内部的拦截器,将执行的 sql 语句,转化为分页的 sql 语句。

    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        //开启分页查询
        PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
        Page<Employee> page =  employeeMapper.pageQuery(employeePageQueryDTO);
        long total = page.getTotal();
        List<Employee> employees = page.getResult();
        return new PageResult(total,employees);
    }

 Mapper

使用动态SQL对name进行模糊查询,并以创建时间排降序。

    <select id="pageQuery" resultType="com.sky.entity.Employee">
        SELECT * FROM employee
        <where>
            <if test="name!=null and name!=''">
                and name like concat('%',#{name},'%')
            </if>
        </where>
        ORDER BY create_time DESC
    </select>

代码测试

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

炼码LintCode--数据库题库(级别:入门;数量:144道)--刷题笔记_01

目录 炼码LintCode数据库入门级别的笔记未完待续~~~ 炼码LintCode 数据库 入门级别的笔记 笔记如下&#xff0c;把所有涉及到的入门级别的知识点简单总结了一下。 以及一点点举一反三的写法。 增 INSERT INTO 表名 (列1, 列2, ...) VALUES (值1, 值2, ...);批量增 INSERT INT…

【C语言】连接陷阱探秘(1):声明与定义

目录 一、声明与定义的混淆 1.1. 声明(Declaration) 1.2. 定义(Definition) 1.3. 避免混淆的方法 1.4. 示例 二、声明与定义不匹配 2.1. 常见的不匹配情况 2.2. 解决方法 三、外部变量与静态变量的命名冲突 3.1. 外部变量命名冲突 3.2. 静态变量命名冲突 四、缺…

pycharm快速更换虚拟环境

目录 1. 选择Conda 虚拟环境2. 创建环境3. 直接选择现有虚拟环境 1. 选择Conda 虚拟环境 2. 创建环境 3. 直接选择现有虚拟环境

[代码+论文+讲解]2024数维杯A题:飞机激光测速中的频率估计问题

一、问题背景 空速是飞机相对于空气的速度&#xff0c;是飞行中需要监测的关键参数。空速与飞行状态如攻角和侧偏角密切相关。如果空速数据异常&#xff0c;很容易导致诸如失速等事故。因此&#xff0c;准确测量空速非常重要。 图1:空速激光测速的示意图 激光测速是一种可行的测…

vscode中执行git合并操作需要输入合并commit信息,打开的nano小型文本编辑器说明-

1.前提&#xff1a; VScode中的git组件执行任何合并动作的时候需要提交远程合并的commit信息&#xff0c;然后编辑器自动打开的是nano文本编辑器 2.nano编辑器说明&#xff1a; 1.保存文件&#xff1a;按 Ctrl O&#xff0c;然后按 Enter 来保存文件。 2.退出编辑器&#xf…

Docker 基础命令介绍和常见报错解决

介绍一些 docker 可能用到的基础命令&#xff0c;并解决三个常见报错&#xff1a; 权限被拒绝&#xff08;Permission Denied&#xff09;无法连接到 Docker 仓库&#xff08;Timeout Exceeded&#xff09;磁盘空间不足&#xff08;No Space Left on Device&#xff09; 命令以…

【电脑】解决DiskGenius调整分区大小时报错“文件使用的簇被标记为空闲或与其它文件有交叉”

【电脑】解决DiskGenius调整分区大小时报错“文件使用的簇被标记为空闲或与其它文件有交叉” 零、报错 在使用DiskGenius对磁盘分区进行调整时&#xff0c;DiskGenius检查出磁盘报错&#xff0c;报错信息&#xff1a;文件使用的簇被标记为空闲或与其它文件有交叉&#xff0c;…

YOLOv11 C++ TensorRT

引用 YOLOv11 C TensorRT项目是一个用C实现并使用NVIDIA TensorRT进行优化的高性能对象检测解决方案。该项目利用 YOLOv11 模型提供快速准确的对象检测&#xff0c;并利用 TensorRT 最大限度地提高推理效率和性能。 &#x1f4e2; 更新 主要特点&#xff1a; 模型转换&#x…

产品思维如何颠覆我的开发与盈利观-营销自己

之前&#xff0c;我独自一人开发了一个名为“心情追忆”的小程序&#xff0c;旨在帮助用户记录日常的心情变化及重要时刻。从项目的构思、设计、前端&#xff08;小程序&#xff09;开发、后端搭建到最终部署&#xff0c;所有环节都由我一人包办。经过一个月的努力&#xff0c;…

Spring Boot框架:电商系统的快速构建

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本网上商城系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&…

【网页设计】CSS3 进阶(动画篇)

1. CSS3 2D 转换 转换&#xff08;transform&#xff09;是CSS3中具有颠覆性的特征之一&#xff0c;可以实现元素的位移、旋转、缩放等效果 转换&#xff08;transform&#xff09;你可以简单理解为变形 移动&#xff1a;translate旋转&#xff1a;rotate缩放&#xf…

Android12的ANR解析

0. 参考&#xff1a; ANR分析 深入理解 Android ANR 触发原理以及信息收集过程 1.ANR的触发分类: ANR分为4类&#xff1a; InputDispatchTimeout&#xff1a;输入事件分发超时5s,包括按键和触摸事件。BroadcastTimeout&#xff1a;比如前台广播在10s内未执行完成&#xff0…

【eNSP】路由基础与路由来源——静态路由实验

路由是数据包从源地址到目的地址的传输路径&#xff0c;静态路由是指网络管理员手动配置的路由条目&#xff0c;用于指定数据包从源地址到目的地址的固定路径。以下是关于静态路由的详细介绍。 一、路由的基础知识点 路由的定义&#xff1a; 路由是指在计算机网络中&#xff…

【AI声音克隆整合包及教程】第二代GPT-SoVITS V2:创新与应用

一、引言 随着科技的迅猛发展&#xff0c;声音克隆技术已经成为一个炙手可热的研究领域。SoVITS&#xff08;Sound Voice Intelligent Transfer System&#xff09;&#xff0c;作为该领域的先锋&#xff0c;凭借其卓越的性能和广泛的适用性&#xff0c;正在为多个行业带来前所…

VScode-Java开发常用插件

中文——界面易读 字体主题——代码可观 头注释——项目信息明了 java开发包——java必备 git协作开发——版本控制

jmeter常用配置元件介绍总结之逻辑控制器

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之逻辑控制器 逻辑控制器1.IF控制器2.事务控制器3.循环控制器4.While控制器5.ForEach控制器6.Include控制器7.Runtime控制器8.临界部分控制器9.交替控制器10.仅一次控制器11.简单控制器12.随机控制器13.随机顺序控制器14.吞…

探索 HTML 和 CSS 实现的蜡烛火焰

效果演示 这段代码是一个模拟蜡烛火焰的HTML和CSS代码。它创建了一个具有动态效果的蜡烛火焰动画&#xff0c;包括火焰的摆动、伸缩和光晕的闪烁。 HTML <div class"holder"><div class"candle"><div class"blinking-glow"&g…

机器学习 - 为 Jupyter Notebook 安装新的 Kernel

https://ipython.readthedocs.io/en/latest/install/kernel_install.html 当使用jupyter-notebook --no-browser 启动一个 notebook 时&#xff0c;默认使用了该 jupyter module 所在的 Python 环境作为 kernel&#xff0c;比如 C:\devel\Python\Python311。 如果&#xff0c…

SwiftUI-基础入门

开发OS X 图形应用界面时有三种实现方式&#xff1a;XIB、Storyboard、SwiftUI。Storyboard基于XIB做了优化&#xff0c;但XIB基本被放弃了&#xff0c;而SwiftUI是苹果公司后来开发的一套编程语言&#xff0c;用来平替Objective-C。虽然现在Swift 6 还是有些不完善的地方&…

androidstudio入门到放弃配置

b站视频讲解传送门 android_studio安装包&#xff1a;https://developer.android.google.cn/studio?hlzh-cn 下载安装 开始创建hello-world 1.删除缓存 文件 下载gradle文件压缩&#xff1a;gradle-8.9用自己创建项目时自动生成的版本即可&#xff0c;不用和我一样 https://…