SpringBoot实战项目第一天

news2024/11/16 17:42:07

环境搭建

后端部分需要准备:

sql数据库

创建SpringBoot工程,引入对应的依赖(web\mybatis\mysql驱动)

配置文件application.yml中引入mybatis的配置信息

创建包结构,并准备实体类

完成今日开发后项目部分内容如下图示

用户注册于登录部分相关内容

注册

谈到注册,首先就要看看数据库中用户表的构成:

然后对应的,完成User实体类的开发

@Data
//lombok  在编译阶段,为实体类自动生成setter  getter toString
// pom文件中引入依赖   在实体类上添加注解
public class User {

    private Integer id;//主键ID
    private String username;//用户名
    private String password;//密码
    private String nickname;//昵称
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}

此处用到了lombok技术,该技术可以在java文件编译时自动为变量生成getter、setter方法和tostring方法,后期实体类的开发也均会用到该技术。

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

接下来,就是要完成Mapper -> Service -> Controller这三层的对应开发。

在开发之前,我们要明确开发需求,即用户的注册。

而在用户注册时,会出现两种情况,注册成功与注册失败。此时我们粗略地将这两种情况对应为

 数据库中没有该用户名对应行 -> 该用户还尚不存在 ->允许注册 ->注册

 数据库中存在该用户名对应行 -> 该用户已经存在 -> 不允许注册 ->返回注册失败原因

理清逻辑后我们从Mapper层开始开发

        首先,我们要注意到,在用户注册时我们会首先对数据库中是否已经存在该用户进行检测,如果没有,再在数据库中录入新用户信息

        这就涉及到了sql中的两种操作,@select与@insert,所以,我们在Mapper层的接口中就要提供这两个操作

@Mapper
public interface UserMapper {
    @Select("select * from user where username = #{username}")
    User getByUserName(String username);

    @Insert("insert into user(username,password,create_time,update_time)"+
            "values (#{username},#{password},now(),now())")
    void add(String username, String password);
}

不要忘记使用@Mapper注册该类! 

 

再来看Service

service层开发较为简单,只需要将dao层的对应方法调用

service接口

public interface UserService {

    //用户名查询用户
    User getByUserName(String username);
    //新用户注册
    void register(String username, String password);
}

而impl文件中在实现这些方法之余,我们在service层会对用户输入的密码进行加密,这里使用到了MD5加密

@Service
public class SuerServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User getByUserName(String username) {
        User u = userMapper.getByUserName(username);
        return u;
    }

    @Override
    public void register(String username, String password) {
        //密码加密
        String p = Md5Util.getMD5String(password);
        userMapper.add(username,p);
    }
}

记得检查传入mapper层的密码,一定要是加密后的! 

MD5:

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

 

最后一步,我们进行Controller层的开发 

在controller层中,我们需要进行相应的逻辑判断来验证该用户名是否已经被占用,具体操作如下:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping("/register")
    public Result register(String username,String password){
        //查询该用户名是否已经存在
        User u = userService.getByUserName(username);
        if (u==null){
            //用户名没有被占用,注册
            userService.register(username,password);
            return Result.success();
        } else {
            return Result.error("用户名被占用!");
        }

    }
}

Result实体类如下:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
    private Integer code;//业务状态码  0-成功  1-失败
    private String message;//提示信息
    private T data;//响应数据

    //快速返回操作成功响应结果(带响应数据)
    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    //快速返回操作成功响应结果
    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result error(String message) {
        return new Result(1, message, null);
    }
}

最后的最后,我们使用测试软件对注册部分进行测试

先看新用户注册正常,注册成功情况

 数据库存储情况

可以看到密码的加密工作也顺利完成

再来看看注册失败的情况,这里我们直接使用刚才的账密注册

注册失败,至此,我们的注册功能已完成最基本的开发与测试。 

当然,我们在日常生活中会发现,账户与密码会有一个基本的校验,即a-b位的非空字符,显然,我们还需要对账密进行进行长度检验。

账户密码长度参数校验

这里我预设的账户长度为4-16位

密码长度位11-16位

校验我们在controller层完成

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping("/register")
    public Result register(String username,String password){
        if(username != null && username.length()>=4 && username.length() <= 16 &&
        password != null && password.length() >= 11 && password.length() <=16)
        {
            //查询该用户名是否已经存在
            User u = userService.getByUserName(username);
            if (u==null){
                //用户名没有被占用,注册
                userService.register(username,password);
                return Result.success();
            } else {
                return Result.error("用户名被占用!");
            }
        }else{
            return Result.error("用户名或密码输入不合法!");
        }
    }
}

当然,我们会发现,仅仅校验账户与密码这两个参数就会导致我们的逻辑判断代码如此繁琐,Spring当然也为我们提供了简化方法——Spring Validation框架 

Spring Validation框架 :Spring提供的一个参数校验框架,使用预定义的注解完成参数校验 

Spring Validation框架使用流程

第一步、引入Spring Validation框架起步依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
第二步、在参数前面添加@Pattren注解
 public Result register(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){
第三步、在Controller类上添加@Validated注解
@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    @Autowired
    UserService userService;

    @PostMapping("/register")
    public Result register(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){

            //查询该用户名是否已经存在
            User u = userService.getByUserName(username);
            if (u==null){
                //用户名没有被占用,注册
                userService.register(username,password);
                return Result.success();
            } else {
                return Result.error("用户名被占用!");
            }

    }
}

当我们输入不合法的账密时,我们会发现,这里的报错不如我们之前手动返回的,所以我们要对其进行参数校验失败异常处理

 

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleExxception(Exception e){
        e.printStackTrace();
        return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败,");
    }
}

再次使用非法账密测试

 

 

可以看到异常处理功能正常运行 

 登录

在开发之前,我们要明确开发需求,即用户的登录(成功登录后需求返回一个 jwt token 令牌)。

而在用户登录时,会出现三种情况,账密正确,成功登录、账号错误,登陆失败与密码错误,登陆失败。此时我们粗略地将这三种情况对应为

 数据库中没有该用户名对应行 -> 该用户还尚不存在 ->登陆失败 ->返回登陆失败原因

 数据库中存在该用户名对应行,但密码不对应 -> 密码输入错误 -> 登陆失败 -> 返回登录失败原因

 数据库中存在该用户名对应行,且密码对应 -> 账户密码输入正确 -> 登陆成功

分析完需求,我们会发现我们在书写登录模块时需要对用户的帐户密码进行相应的逻辑判断,用到的sql语句为@select与@insert,这两个方法我们在注册模块已经完成了书写,所以我们直接在Controller层编写登录模块即可!

Controller层技术开发

@PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){
        //查询用户是否存在
        User lUser = userService.getByUserName(username);
        if(lUser == null){return  Result.error("用户不存在!");}

        //查询密码是否正确(密码需要加密后再与数据库中密码比较)
        if (Md5Util.getMD5String(password).equals(lUser.getPassword())){
            return Result.success("jwt token 令牌");
        }

        return Result.error("密码错误!");
    }

可以看到,对于账密合法性判断我们采取了与注册时一样的操作, 逻辑判断部分也是简单的匹配,下面我们来对其进行测试

这是一组正确的账户密码测试,可以看到,此时我们暂时还没有编写jwt令牌的返回,暂时使用一个字符串代替

下面再进行一组错误账户名的测试

 

可以看到,错误信息如期。

最后在进行一组错误密码的测试

测试完毕,我们编辑的最最基础的登录部份功能正常运行!

 

登录认证

       当我们需要将某些界面设置为登陆后可见时,我们就需要对其加入登录认证的功能,接下来就进行该功能的开发

我们先建立一个chesscontroller,供下例使用:

@RestController
@RequestMapping("/article")
public class ChessController {

    @PostMapping("/chess")
    public Result<String> chess(){
        return Result.success("读取所有的棋子数据!");
    }
}

登陆验证需要一个令牌,该令牌就是一段字符串,需要满足下列要求

        承载业务数据,减少后续请求查询数据库次数

        防篡改,保证信息的合法性和有效性

在web开发中最常用的令牌便是JWT令牌

JWT

        全称:JSON Web Token

        定义了一种简洁的、自包含的格式,用于通信双方以json数据格式安全的传输信息

组成:

        第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}

        第二部分:PayLoad(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","us"1"}

        第三部分:Signature(签名),防止Token被篡改、切薄安全性。将header、payload加入指定密钥,通过指定签名算法计算而来

JWT-生成

首先导入对应依赖坐标
       <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
 接下来编写对应的程序
public class JwtTest {

    @Test
    public void testGen(){

        Map<String, Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("username","zmx");
        //生成jwt代码
        String token = JWT.create()
                .withClaim("user",claims)//添加载荷
                .withExpiresAt(new Date(System.currentTimeMillis()+1000*60*12))//添加过期时间
                .sign(Algorithm.HMAC256("cacb"));//指定算法,配置密钥

        System.out.println(token);
    }
}

如上,我输入了一组简单的数据来测试

运行结果:

 

JWT-验证

    @Test
    public void testParse(){
        //定义字符串,模拟用户传递的token
        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6InpteCJ9LCJleHAiOjE3MDY5NTUzNTN9.pZ0fYD5rRMPHXzAq_sLC6RKprPGwIiIHoimAuChTzdk";
        JWTVerifier jwtVerifier =  JWT.require(Algorithm.HMAC256("cacb")).build();
        DecodedJWT decodedJWT =  jwtVerifier.verify(token);//验证token,生成一个解析后的JWT对象
        Map<String, Claim> claims = decodedJWT.getClaims();
        System.out.println(claims.get("user"));

    }

使用上例生成的JWT进行验证

结果如下:

 

JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的

如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法

登录认证书写

由于JWT生成与验证的代码过于繁琐,所以我们选择使用一个工具类来承载JWT生成与验证的方法

public class JwtUtil {

    private static final String KEY = "cacb";
	
	//接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }

	//接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}
第一步、在登录界时生成token
    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{4,16}$") String username, @Pattern(regexp = "^\\S{11,16}$")String password){
        //查询用户是否存在
        User lUser = userService.getByUserName(username);
        if(lUser == null){return  Result.error("用户不存在!");}

        //查询密码是否正确(密码需要加密后再与数据库中密码比较)
        if (Md5Util.getMD5String(password).equals(lUser.getPassword())){
            Map<String,Object> claims = new HashMap<>();
            claims.put("id",lUser.getId());
            claims.put("usernname",lUser.getUsername());
            String token = JwtUtil.genToken(claims);
            return Result.success(token);
        }

        return Result.error("密码错误!");
    }
第二步、在需要验证token界面对应的接口验证token(复杂) 

还是以list为例(token从请求头中的Authorization读取)

    @GetMapping("/list")
    public Result<String> chess(@RequestHeader(name = "Authorization")String token , HttpServletResponse response){
        //验证token
        try {
            Map<String,Object> claims =  JwtUtil.parseToken(token);
            return Result.success("读取所有的棋子数据!");
        } catch (Exception e)
        {
            //设置HTTP响应状态码为401
            response.setStatus(401);
            return Result.error("请登录!");}


    }

测试结果如下:

 

验证token高效方法、使用拦截器统一验证token令牌
第一步、将验证token转移到拦截器中
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //令牌验证
        String token =  request.getHeader("Authorization");
        //验证token
        try {
            Map<String,Object> claims =  JwtUtil.parseToken(token);
            //放行
            return true;
        } catch (Exception e)
        {
            //设置HTTP响应状态码为401
            response.setStatus(401);
            //不放行
            return false;}

    }
}
第二步、书写配置类,注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器,登录接口和注册接口不拦截
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

注意:如例中的login与register接口不需要拦截,可以直接放行

第三步、修改简化被拦截的接口内容
    @GetMapping("/list")
    public Result<String> chess(@RequestHeader(name = "Authorization") String token, HttpServletResponse response) {
        return Result.success("读取所有的棋子数据!");
    }

测试结果如下:

 

 

 

 

 

 

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

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

相关文章

大规模机器学习简介

1. 非线性回归问题 1.1 问题描述 我们有一组实验数据&#xff0c;每个实验都给出了输入和输出对 (Xn, Yn)。每个输入 是空间中的一个点&#xff0c;每个输出 是 空间中的一个点。这些数据点被假设为独立同分布&#xff08;i.i.d&#xff09;。 我们的目标是找到一个函数 fw&…

Power Designer的使用 创建数据库表模型

几年前用过PowerDesigner&#xff0c;好几年没用&#xff0c;有点忘记了&#xff0c;在这里记个笔记&#xff0c;需要的时候翻一翻 PowerDesigner版本16.5 下面的例子是以MySQL数据库为准 生成C#实体类 一 安装 1.1 安装 不让放网盘链接&#xff0c;审核通不过。。。。 …

前端学习之路(5) vue样式穿透

前言 vue的style中设置scoped属性后&#xff0c;组件实现样式私有化。 但是该组件又使用的其他组件库时(vant,elementui,自定义等)&#xff0c;该组件的style中的样式&#xff0c;优先级低&#xff0c;不生效&#xff0c;这个时候需要使用样式穿透(作用得更深)。 一、scoped底…

深度学习技巧应用35-L1正则化和L2正则在神经网络模型训练中的应用

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用35-L1 正则化和L2正则在神经网络模型训练中的应用。L1正则化和L2正则化是机器学习中常用的两种正则化方法,用于防止模型过拟合并提高模型的泛化能力。这两种正则化方法通过在损失函数中添加惩罚项来控制模型的复杂性。…

Prometheus 采集Oracle监控数据

前言 oracledb_exporter是一个开源的Prometheus Exporter,用于从Oracle数据库中收集关键指标并将其暴露给Prometheus进行监控和告警。它可以将Oracle数据库的性能指标转换为Prometheus所需的格式,并提供一些默认的查询和指标。 download Oracle Oracle Windows Install …

2024年2月4日 十二生肖 今日运势

小运播报&#xff1a;2024年2月4日&#xff0c;星期日&#xff0c;农历腊月廿五 &#xff08;癸卯年乙丑月戊戌日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;兔、马、虎 需要注意&#xff1a;牛、鸡、龙 喜神方位&#xff1a;东南方 财神方位&#xff1a;正…

记录element-plus树型表格的bug

问题描述 如果数据的子节点命名时children,就没有任何问题&#xff0c;如果后端数据结构子节点是其他名字&#xff0c;比如thisChildList就有bug const tableData [{id: 1,date: 2016-05-02,name: wangxiaohu,address: No. 189, Grove St, Los Angeles,selectedAble: true,th…

低成本高效益,电子画册才是品牌的重要选择

​随着互联网的普及和数字化技术的进步&#xff0c;电子画册已成为许多品牌的重要选择。与传统印刷画册相比&#xff0c;电子画册具有低成本、高效益的优点&#xff0c;成为品牌宣传的新趋势。 具体来说&#xff0c;电子画册可以通过在线平台或移动设备轻松查看&#xff0c;无需…

2023-12蓝桥杯STEMA考试 C++ 中高级试卷解析

蓝桥杯STEMA考试 C++ 中高级试卷(12月) 一、选择题 第一题 定义字符串 string a = "Hello C++",下列选项可以获取到字符 C 的是(B)。 A、a[7] B、a[6] C、a[5] D、a[4] 第二题 下列选项中数值与其它项不同的是( C)。 A、 B、 C、 D、 第三题 定义变量 int i =…

Java8 中文指南(一)

Java8 中文指南&#xff08;一&#xff09; 文章目录 Java8 中文指南&#xff08;一&#xff09;《Java8 指南》中文翻译接口的默认方法(Default Methods for Interfaces)Lambda 表达式(Lambda expressions)函数式接口(Functional Interfaces)方法和构造函数引用(Method and Co…

SpringBoot、SpringCloud项目打包,target目录没有生成jar包

Maven中有一个 Execute goals recursively 递归执行 的复选框&#xff0c;如果这个没有勾选在IDEA中本地打包聚合工程的子模块target目录不会生成jar包&#xff0c;递归执行即对聚合工程执行的命令子模块也会执行 clean package install

图书|基于Springboot的图书管理系统设计与实现(源码+数据库+文档)

图书管理系统目录 目录 基于Springboot的图书管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、个人中心 2、管理员管理 3、用户管理 4、图书出版社管理 5、公告类型管理 6、所在书架管理 7、图书类型管理 8、论坛管理 9、公告信息管理 10、图书信…

蓝桥杯备战——10.超声波模块

1.分析原理图 蓝桥杯单片机板子的原理图做的简直是依托答辩&#xff0c;乱糟糟的不说还弄成黑白的&#xff0c;明明很简单的东西&#xff0c;弄成一大堆。 可以看到&#xff0c;J2跳线帽如果P10接N_A1,P11接N_B1就是用作超声波功能。N_A1用作发生超声波功能&#xff0c;而N_B1…

mysql5.0自定义存储过程实现递归

mysql8.0以上才有查询树形结构数据的递归函数RECURSIVE&#xff0c;比如需求是查询一张树形结构表的级别名称上下层级关系路径&#xff1a; WITH RECURSIVE subordinates AS ( SELECT id, region_name, parent_id, CAST(region_name AS CHAR(200)) AS path FROM mr_industr…

计算机设计大赛 深度学习 python opencv 火焰检测识别

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

nba2k24 灌篮高手Q版流川枫面补

nba2k24 灌篮高手Q版流川枫面补 此面补nba2k23-nba2k24通用 下载地址&#xff1a; https://www.changyouzuhao.cn/9979.html

利用VPN设备漏洞入侵!新型勒索软件CACTUS攻击手法分析

近期&#xff0c;亚信安全应急响应中心截获了利用VPN设备已知漏洞传播的新型勒索软件CACTUS&#xff0c;该勒索于2023年3月首次被发现&#xff0c;一直保持着活跃状态。CACTUS勒索软件通过Fortinet VPN的已知漏洞进行入侵&#xff08;黑客首先获取到VPN账号&#xff0c;再通过V…

20240130在ubuntu20.04.6下卸载NVIDIA显卡的驱动

20240130在ubuntu20.04.6下卸载NVIDIA显卡的驱动 2024/1/30 12:58 缘起&#xff0c;为了在ubuntu20.4.6下使用whisper&#xff0c;以前用的是GTX1080M&#xff0c;装了535的驱动。 现在在PDD拼多多上了入手了一张二手的GTX1080&#xff0c;需要将安装最新的545的驱动程序&#…

ctfshow web-77

开启环境: 先直接用伪协议获取 flag 位置。 c?><?php $anew DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString(). );} exit(0); ?> 发现 flag36x.txt 文件。同时根目录下还有 readflag&#xff0c;估计需要调用 readflag 获…

图灵之旅--ArrayList顺序表LinkedList链表栈Stack队列Queue

目录 线性表顺序表ArrayList简介ArrayList使用ArrayList的构造ArrayList常见操作ArrayList的遍历ArrayList的扩容机制利用ArrayList洗牌ArrayList的优缺点 链表链表的实现双向链表的实现 LinkedListLinkedList引入LinkedList的使用LinkedList的构造LinkedList的常用方法介绍Lin…