SpringBoot实战项目第一天

环境搭建

后端部分需要准备:

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("读取所有的棋子数据!");
    }

测试结果如下:

 

 

 

 

 

 

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

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

相关文章

【MongoDB】跨库跨表查询(python版)

MongoDB跨表跨库查询 1.数据准备&#xff1a;2.跨集合查询3.跨库查询应该怎么做&#xff1f; 讲一个简单的例子&#xff0c;python连接mongodb做跨表跨库查询的正确姿势 1.数据准备&#xff1a; use order_db; db.createCollection("orders"); db.orders.insertMan…

机器学习 | 如何利用集成学习提高机器学习的性能?

目录 初识集成学习 Bagging与随机森林 Otto Group Product(实操) Boosting集成原理 初识集成学习 集成学习&#xff08;Ensemble Learning&#xff09;是一种通过组合多个基本模型来提高预测准确性和泛化能力的机器学习方法。它通过将多个模型的预测结果进行整合或投票来做…

增加Vscode引用路径

增加Vscode引用路径 增加Vscode引用路径问题说明解决思路1在Vscode中进行配置缺点 解决思路2 增加Vscode引用路径 问题说明 在嵌入式开发中需要经常用到库函数(SPL), Vscode需要配置引用路径才能对函数名或变量进行跳转 解决思路1 与Keil5 MDK类似, 在配置C/C的json文件中添…

计算机网络_1.6.2 计算机网络体系结构分层的必要性

1.6.2 计算机网络体系结构分层的必要性 一、五层原理体系结构每层各自主要解决什么问题1、物理层2、数据链路层3、网络层4、运输层5、应用层 二、总结三、练习 笔记来源&#xff1a; B站 《深入浅出计算机网络》课程 本节主要介绍实现计算机网络需要解决哪些问题&#xff1f;以…

【Crypto | CTF】BUUCTF 大帝的密码武器1

天命&#xff1a;这题真的是来刷经验的&#xff0c;有点吐血 首先这题是贼简单&#xff0c;但我居然跪到了&#xff0c;所以特此写这一篇来惩戒自己心太大 拿到文件&#xff0c;文件写着zip&#xff0c;改成zip后缀名即可&#xff0c;也不算啥难的 打开里面的两份文件&#x…

NLP入门系列—Attention 机制

NLP入门系列—Attention 机制 Attention 正在被越来越广泛的得到应用。尤其是 [BERT]火爆了之后。 Attention 到底有什么特别之处&#xff1f;他的原理和本质是什么&#xff1f;Attention都有哪些类型&#xff1f;本文将详细讲解Attention的方方面面。 Attention 的本质是什…

LabVIEW传感器通用实验平台

LabVIEW传感器通用实验平台 介绍了基于LabVIEW的传感器实验平台的开发。该平台利用LabVIEW图形化编程语言和多参量数据采集卡&#xff0c;提供了一个交互性好、可扩充性强、使用灵活方便的传感器技术实验环境。 系统由硬件和软件两部分组成。硬件部分主要包括多通道数据采集卡…

MySQL 安装配置 windows

一、下载 去官网MySQL :: MySQL Downloads 下载社区版 然后根据自己的系统选择 直接下载。 二、安装 点击安装程序 这边看样子缺少东西。 去这边下载 Latest supported Visual C Redistributable downloads | Microsoft Learn 然后再一次安装mysql 三、配置 安装完成后&a…

Vue3_基础使用_2

这节主要介绍&#xff1a;标签和组件的ref属性&#xff0c;父子组件间的传递值&#xff0c;ts的接口定义&#xff0c;vue3的生命周期 1.标签的ref属性。 1.1ref属性就是给标签打标识用的&#xff0c;相当于html的id&#xff0c;但是在vue3中用id可能会乱&#xff0c;下面是ref…

ncc匹配(一,理论)

前头从来没用过ncc&#xff0c;基于形状匹配搞定后&#xff0c;又翻了翻learning opencv&#xff0c;他并不推荐ncc&#xff0c;而是力推emd&#xff0c;这也是当初我没考虑用ncc的原因。 当初看到ncc公式很复杂&#xff0c;也就忘了。 我的第一个匹配&#xff0c;是最笨的&a…

系统架构19 - 面向对象

面向对象设计 相关概念面向对象分析基本步骤基本原则分析模型 面向对象设计设计模型类的类型 面向对象编程基本特点需求建模设计原则面向对象软件测试 相关概念 接口&#xff1a;描述对操作规范的说明&#xff0c;其只说明操作应该做什么&#xff0c;并没有定义操作如何做。消…

服务器基础知识(IP地址与自动化技术的使用)

目录 ip地址是什么&#xff1f; 如何查看ip地址 Windows的命令提示符 图形化版本&#xff1a; 自动化技术的应用与意义 ip地址是什么&#xff1f; IP地址的主要作用是**为互联网上的每个网络和每台主机分配一个逻辑地址**。 它由32位二进制数字组成&#xff0c;通常分为四…

无人机激光雷达标定板

机载激光雷达标定板是用于校准和验证机载激光雷达系统的设备。由于机载激光雷达系统在测量地形、建筑物和植被等方面具有广泛的应用&#xff0c;因此标定板的使用对于确保测量结果的准确性和可靠性至关重要。 标定板通常由高反射率的材料制成&#xff0c;如镀金的玻璃或陶瓷&am…

关于爬取所有哔哩哔哩、任意图片、所有音乐、的python脚本语言-Edge浏览器插件 全是干货!

这些都是现成的并且实时更新的&#xff01;从次解放双手&#xff01; 首先有自己的edge浏览器基本上都有并且找到插件选项 1.哔哩哔哩视频下载助手&#xff08;爬取哔哩哔哩视频&#xff09; bilibili哔哩哔哩视频下载助手 - Microsoft Edge Addons 下面是效果&#xff1a; 2.图…

谷歌浏览器网站打不开,显示叹号

问题&#xff1a; 您与此网站之间建立的连接不安全请勿在此网站上输入任何敏感信息&#xff08;例如密码或信用卡信息&#xff09;&#xff0c;因为攻击者可能会盗取这些信息。 了解详情 解决方式&#xff1a; 网上有很多原因&#xff0c;亲测为DNS问题&#xff0c;设置&…

iPad“粘贴自”字样不消失解决办法

iPad“粘贴自”字样不消失解决办法 好无语&#xff0c;写论文主要就靠iPad看资料&#xff0c;复制粘帖的时候卡死搞得我无敌焦躁&#xff0c;问了&#x1f34e;支持的客服才解决&#xff0c;方法如下&#xff1a;1.音量上键按一下 2.音量下键按一下 3.一直按开关机键直到出现苹…

pytest的常用插件和Allure测试报告

pytest常用插件 pytest-html插件 安装&#xff1a; pip install pytest-html -U 用途&#xff1a; 生成html的测试报告 用法&#xff1a; ​在.ini配置文件里面添加 addopts --htmlreport.html --self-contained-html 效果&#xff1a; 执行结果中存在html测试报告路…

智能汽车竞赛摄像头处理(3)——动态阈值二值化(大津法)

前言 &#xff08;1&#xff09;在上一节中&#xff0c;我们学习了对图像的固定二值化处理&#xff0c;可以将原始图像处理成二值化的黑白图像&#xff0c;这里面的本质就是将原来的二维数组进行了处理&#xff0c;处理后的二维数组里的元素都是0和255两个值。 &#xff08;2…

RFID手持终端_智能pda手持终端设备定制方案

手持终端是一款多功能、适用范围广泛的安卓产品&#xff0c;具有高性能、大容量存储、高端扫描头和全网通数据连接能力。它能够快速平稳地运行&#xff0c;并提供稳定的连接表现和快速的响应时&#xff0c;适用于医院、物流运输、零售配送、资产盘点等苛刻的环境。通过快速采集…

javaScript的序列化与反序列化

render函数的基本实现 javaScript的序列化与反序列化 一&#xff0c;js中的序列化二&#xff0c;序列化三&#xff0c;反序列化四&#xff0c;总结 一&#xff0c;js中的序列化 js中序列化就是对象转换成json格式的字符串&#xff0c;使用JSON对象的stringify方法&#xff0c;…