前后端分离的security角色权限实现

news2024/12/24 8:25:31

本案例是使用SpringBoot2.7.6+security+MyBatis-plus+Vue2+axios实现

一、security简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,专为Java应用程序设计。

(1)基本功能

  • 身份验证(Authentication):确定用户身份的过程。在Spring Security中,这包括确认用户是谁,并验证用户提供的凭证(如用户名和密码)是否正确。
  • 授权(Authorization):确定用户是否有权进行某个操作的过程。根据用户的身份和角色,Spring Security授予用户访问应用程序资源的权限。

(2)主要特点

  1. 全面性:Spring Security提供了全面的安全性解决方案,包括认证、授权、攻击防范(如XSS、CSRF等)和会话管理等功能。
  2. 可扩展性:提供了一系列可扩展的模块,可以根据具体需求进行选择和配置,如不同的身份验证方式、授权方式、密码编码器等。
  3. 易用性:提供了快捷配置选项和基于注解的安全控制方式,使开发人员能够更轻松地实现认证和授权等功能。
  4. 社区支持:作为Spring生态系统的一部分,Spring Security得到了广泛的社区支持和更新维护。

(3)工作原理

        Spring Security通过一系列过滤器(Filter)来保护应用程序。这些过滤器按照特定的顺序组成过滤器链,每个过滤器都有特定的责任,如身份验证、授权、防止CSRF攻击等。当用户发起请求时,请求会经过过滤器链的处理,并在处理过程中进行安全验证和授权。

(4)核心组件

  1. SecurityContextHolder:用于存储安全上下文(SecurityContext)的信息,包括当前用户的身份信息、所拥有的权限等。
  2. AuthenticationManager:负责处理用户的身份验证,接收用户提交的凭据,并使用已配置的身份验证提供程序(AuthenticationProvider)进行验证。
  3. AuthenticationProvider:实际执行身份验证的组件,从用户存储源(如数据库、LDAP等)中获取用户信息,并进行密码比对或其他验证方式。
  4. UserDetailsService:用于加载UserDetails对象的接口,通常从数据库或LDAP服务器中获取用户信息。
  5. AccessDecisionManager:用于在授权过程中进行访问决策,根据用户的认证信息、请求的URL和配置的权限规则,判断用户是否有权访问资源。

二、依赖项

主要使用到Security、mysql和gson等依赖:

<!-- security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--MySQL驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
<!-- Gson: Java to Json conversion -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.9</version>
</dependency>

 其中gson是用于吧java对象转成json格式,便于响应数据。

三、配置和自定义处理器

(1)关于security的配置

        在config层中的security的配置主要有SecurityConfig核心配置类CorsConfig访问配置类

        SecurityConfig核心配置类

                在yaml配置文件中,可以自定义前端登录页面:

spring:
  security:
    # 前后端分离时自定义的security的登录页面
    loginPage: http://127.0.0.1:8081/#/login
import com.security.demo.handel.CustomAccessDeniedHandler;
import com.security.demo.handel.CustomAuthenticationFailureHandler;
import com.security.demo.handel.CustomAuthenticationSuccessHandler;
import com.security.demo.service.Impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;

/**
 * 配置security
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.loginPage}")
    private String loginPage;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    @Autowired
    private UserServiceImpl userServiceImpl;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 添加请求授权规则
        http.authorizeRequests()
                // 首页所有人都可以访问
                .antMatchers("/public/**").permitAll()
                // user下的所有请求,user角色权限才能访问
                .antMatchers("/user/**").hasRole("USER")
                // admin下的所有请求,ADMIN角色权限才能访问
                .antMatchers("/admin/**").hasRole("ADMIN")
                // 其他任何请求都要验证身份
                .anyRequest().authenticated();
        // 开启登录页面,即没有权限的话跳转到登录页面
        http.formLogin()
                // 登录页面
                .loginPage(loginPage)
                // 用户名的name
                .usernameParameter("user")
                // 密码的name
                .passwordParameter("pwd")
                // 处理登录的Controller
                .loginProcessingUrl("/login")
                // 验证成功处理器
                .successHandler(customAuthenticationSuccessHandler)
                // 验证失败处理器
                .failureHandler(customAuthenticationFailureHandler);
        http.csrf().disable();
        // 开启记住我功能,默认保存两周
        http.rememberMe()
                // name属性
                .rememberMeParameter("remember");
        // 开启退出登录功能
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)); // 自定义登出成功后的处理
        // 跨域
        http.cors();
        // 权限不足处理器
        http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
    }

    // 认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在新版本的SpringSecurity中新增了许多加密方法,这里使用的是BCrypt
        auth.userDetailsService(userServiceImpl).passwordEncoder(new BCryptPasswordEncoder());
    }

}

        CorsConfig访问配置类

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 *  配置security的跨域访问
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .exposedHeaders("Access-Control-Allow-Headers",
                        "Access-Control-Allow-Methods",
                        "Access-Control-Allow-Origin",
                        "Access-Control-Max-Age",
                        "X-Frame-Options")
                .maxAge(3600)
                .allowedHeaders("*");
    }

}

(2)处理器Handler

        业务开发时主要是自定义登录认证成功、登录认证失败和权限不足的处理器

        登录认证成功后的处理器CustomAuthenticationSuccessHandler,主要判断验证码是否正确和响应返回R统一返回类。CustomAuthenticationSuccessHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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


/**
 *  自定义的security的认证成功处理器
 */

@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        R result = R.ok().setMessage("登录成功。");
        response.setContentType("application/json;charset=UTF-8");
        // 校验验证码
        String code = (String) request.getSession().getAttribute("code");
        String codeInput = request.getParameter("codeInput");
        if(code==null || codeInput==null) {
            result.setCode(501).setMessage("验证码为空");
        }else {
            if (!code.equalsIgnoreCase(codeInput)) {
                result.setCode(501).setMessage("验证码错误");
            }
        }
        Gson gson = new Gson();
        response.getWriter().write(gson.toJson(result));
    }
}

        登录验证失败处理器CustomAuthenticationFailureHandler,主要用于响应返回R统一返回类。CustomAuthenticationFailureHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 *  自定义的security的认证失败处理器
 */
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        //以返回JSON数据为例
        R result = R.error().setMessage("用户名或密码错误。");
        response.setContentType("application/json;charset=UTF-8");
        Gson gson = new Gson();
        response.getWriter().write(gson.toJson(result));
    }
}

        用户访问权限不足处理器CustomAccessDeniedHandler,主要返回权限不足信息。CustomAccessDeniedHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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

/**
* 用户权限不足被拒绝处理器
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
            throws IOException {
        R result = R.error().setMessage("权限不足");
        response.setContentType("application/json;charset=UTF-8");
        Gson gson = new Gson();
        response.getWriter().write(gson.toJson(result));
    }
}

(3)重写登录验证逻辑

        在配置类中可以自定义指定的登录验证逻辑类,一般是写在service的实现类中,要求是该登录验证逻辑类必须实现UserDetailsService接口的loadUserByUsername方法,才可以在configure的auth.userDetailsService(自定义登录验证逻辑类)指定。这里我直接在UserServiceImpl类中实现:

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.security.demo.entity.User;
import com.security.demo.mapper.UserMapper;
import com.security.demo.service.UserService;
import com.security.demo.vo.R;
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.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 重写UserDetailsService的loadUserByUsername方法
     * 用于登录验证和角色授权
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = this.getUserByName(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名为null");
        }
        // 这里需要将User转换为UserDetails,并设置角色的GrantedAuthority集合
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
        // 如果是admin角色,就多添加USER权限
        if(user.getRole().equals("ADMIN")){
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        }
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String password = encoder.encode(user.getPassword());
        return new org.springframework.security.core.userdetails.User(user.getName(), password, authorities);
    }



    /**
     *  分页条件查询用户名
     */
    @Override
    public R getUserList(String name, int current, int size) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        Page<User> page = new Page<>(current, size);
        if(name!=null && !name.isEmpty()){
            queryWrapper.like("name",name);
        }
        queryWrapper.orderByDesc("id");  // 指定根据id倒序
        Page<User> userPage = userMapper.selectPage(page, queryWrapper);
        return R.ok().data("userPage", userPage);
    }

    /**
     * 新增用户
     */
    @Override
    public R addUser(User user) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", user.getName());
        User user1 = userMapper.selectOne(queryWrapper);
        if (user1 != null) {
            return R.error().setMessage("该用户名已存在");
        }
        userMapper.insert(user);
        return R.ok().setMessage("新增成功!");
    }

    /**
     * 更新用户
     */
    @Override
    public R updateUser(User user) {
        // 根据用户名查询是否重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", user.getName());
        User user1 = userMapper.selectOne(queryWrapper);
        if (user1 != null && user1.getId() != user.getId()) {
            return R.error().setMessage("该用户名已存在");
        }
        // 不重复就根据id来修改用户信息
        QueryWrapper<User> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("id", user.getId());
        userMapper.update(user, queryWrapper1);
        return R.ok().setMessage("更新成功!");
    }

    /**
     * 删除用户
     */
    @Override
    public R deleteUser(String name) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", name);
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            return R.error().setMessage("没有该用户");
        }
        userMapper.deleteById(user.getId());
        return R.ok().setMessage("删除成功");
    }

    /**
     * 根据用户名查询用户信息
     */
    @Override
    public User getUserByName(String name) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", name);
        return userMapper.selectOne(queryWrapper);
    }

}

四、postman测试登录接口

        使用postman测试登录login接口时要注意,由于security的限制,需要模仿表单提交,而不是常规的json数据,获取验证码后,使用x-www-form-urlencoded格式。如下:

五、前端axios注意事项

        获取验证码后,使用x-www-form-urlencoded格式的post请求如下,参数是json数据。

// 登录
    login(data){
        return axios({
            method: 'post',
            url: '/login',
            data: data,
            // 模仿表单提交
            transformRequest: [
                function (data) {
                    let ret = ''
                    for (let it in data) {
                        ret +=
                            encodeURIComponent(it) +
                            '=' +
                            encodeURIComponent(data[it]) +
                            '&'
                    }
                    return ret
                }
            ],
            headers:{
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        })
    },

六、案例完整源码

        以上是security的核心代码和调用踩坑点,完整代码请转到码云仓库查看:

=========================================================================

Gitee仓库源码:https://gitee.com/BuLiangShuai01033/security-demo

作者:不凉帅 https://blog.csdn.net/yueyue763184

=========================================================================

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

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

相关文章

关于安装MySQL遇到的问题

数据库相关概念 &#x1f4a1;数据库系统 &#xff08; DataBase System&#xff0c; 简称 DBS&#xff09; 是指计算机系统引入数据库后的系统构成&#xff0c; 是一个具有管理数据库功能的计算机软硬件综合系统。 数据库系统可以实现有组织地、动态地存储大量数据、提供数…

【补-办公室】拟批语的区别

拟批语 常见拟批语 批示、审示、阅示、核注 审批、审核、审阅、审定&#xff08;订&#xff09;、审发、审议、审处、阅改、阅知、阅研、阅处、研提、研办、研复、核&#xff08;转&#xff09;报、核示、核批、批办等 阅示和审示 区分是收文还是发文 发文审&#xff0c;收文阅…

本地部署AList并挂载小雅超集结合内网穿透实现无公网IP远程访问

文章目录 前言1. 本地部署AList2. AList挂载网盘3. 部署小雅alist3.1 Token获取3.2 部署小雅3.3 挂载小雅alist到AList中 4. Cpolar内网穿透安装5. 创建公网地址6. 配置固定公网地址 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff…

jenkins-gitee-genkins

在电脑下载git 用一个有war包的项目 下载插件 添加 .ignore文件 添加target 建git仓库 提交 推送 推送完成在gitee能看到 这时候已经完成了前两部 项目如果添加功能

[嵌入式 C 语言] int main(int argc, char *argv[])

一、含义 在C语言中&#xff0c;main 函数是程序的起点&#xff0c;也就是执行的入口点。main 函数可以接受命令行参数&#xff0c;并且通常定义如下&#xff1a; int main(int argc, char *argv[]) int argc: 这个参数代表“参数计数”&#xff08;Argument Count&#xff0…

实验记录 | PointMLP | Grouping layer + Geometric Affine

引言 自 PointNet 腾空出世&#xff0c;点云分析的深度框架便成为了该领域的热点&#xff0c;点云学习网络的发展便一发不可收拾。和大部分深度网络一样&#xff0c;点云网络遵循着 “降采样&#xff0c;聚合特征” 的基本思路&#xff0c;逐步提取点云的深度特征。 大部分点…

【九芯电子】星空灯语音识别芯片方案选型——NRK3301

在快节奏的现代生活中&#xff0c;人们对于居家环境的舒适性与便捷性追求日益增强&#xff0c;而星空灯语音控制的技术诞生&#xff0c;正是这一追求下的智慧结晶&#xff0c;极大地提升了居住的愉悦感与科技感。 九芯NRK3301语音识别芯片‌被广泛应用于智能照明产品中&#xf…

【python因果推断库2】使用 PyMC 模型进行差分-in-差分(Difference in Differences, DID)分析

目录 使用 PyMC 模型进行差分-in-差分&#xff08;Difference in Differences, DID&#xff09;分析 导入数据 分析 使用 PyMC 模型建模银行业数据集 导入数据 分析 1 - 经典 22 差分-in-差分 (DiD) 分析 2 - 具有多个干预前后观测值的差分-in-差分 (DiD) 分析 使用 PyMC…

VSCode + Git的常规操作(一)【描述详细直白,小白一学就会】

目录 一、文章简介 二、具体操作流程 1、操作前提 2、设置用户名、用户邮箱 &#xff08;1&#xff09;打开命令框 &#xff08;2&#xff09;配置用户名 &#xff08;3&#xff09;配置用户名邮箱 &#xff08;4&#xff09;查看配置 3、SSH密钥对的介绍、生成及配置…

008、架构_MDS

​架构 什么是元数据 什么是元数据 元数据又称中介数据、中继数据,为描述数据的数据,主要是描述数据属性的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能;GoldenDB 数据库元数据大致分为两类: 数据字典:库、表、字段属性信息、视图、函数、存储过程属…

【代码随想录训练营第42期 Day48打卡 - 单调栈 - LeetCode 739. 每日温度 496.下一个更大元素 I 503.下一个更大元素II

目录 一、做题心得 二、题目与题解 题目一&#xff1a;739. 每日温度 题目链接 题解1&#xff1a;暴力--超时 题解2&#xff1a;单调栈 题目二&#xff1a;496.下一个更大元素 I 题目链接 题解&#xff1a;单调栈哈希 题目三&#xff1a;503.下一个更大元素II 题目链…

神经网络训练不起来怎么办(五)| Batch Normalization

Ⅰ&#xff0c;领域背景 训练困境&#xff1a;当 input feature 在不同 dimension 上差距很大的时候&#xff0c;会产生一个非常崎岖的 error surface&#xff08;误差平面&#xff09;。这种崎岖多变的误差平面容易导致训练陷入以下的几个困境。 收敛困难&#xff1a;在崎岖…

注释1111

3。3 Batch Normalization (BN) 的工作原理 Batch Normalization 是在处理一个 "批次" 数据时&#xff0c;计算这个批次内所有样本的平均值和方差&#xff0c;然后使用这些统计量对每个样本进行归一化。这就是说&#xff1a; 批次&#xff08;batch&#xff09;&a…

局部整体(五)利用python绘制旭日图

局部整体&#xff08;五&#xff09;利用python绘制旭日图 旭日图&#xff08; Sunburst Charts&#xff09;简介 由于其形状像太阳光由内向外辐射出来&#xff0c;所以叫SunBurst(太阳爆发)&#xff0c;中文也叫日出图。是多个层级的环图/饼图的拓展&#xff0c;可以显示多个…

GNU的编译工具链

文章目录 GNU的编译工具链 GNU的编译工具链 预编译器cpp 编译器 cc1 汇编器 as 链接器 ld 其中cpp和cc1属于gcc的一部分&#xff0c;as和ld属于binutils的一部分。

MySQL-如何定位慢查询

慢查询&#xff1a;页面加载过慢&#xff0c;接口压测响应时间过长&#xff08;超过1s&#xff09;

STM32基础篇:PWR

PWR简介 PWR&#xff08;Power Control&#xff09;&#xff0c;为电源控制模块&#xff0c;负责管理STM32内部的电源供电部分&#xff0c;可以实现可编程电压监测器和低功耗模式的功能。 1、可编程电压监测器 简称PVD&#xff0c;可以监控VDD电源电压。当VDD下降到PVD阀值以…

yum安装nexus3详细教程分享

创建nexus用户&#xff0c;类似于这种中间件&#xff0c;尽量做到专户管理&#xff0c;当然如果你喜欢直接用root权限安装&#xff0c;更改配置文件也是可以支持的。但是实际上大多情况下&#xff0c;在生产环境是拿不到root权限的。 useradd -m nexus为nexus用户设置密码 pass…

AS-V1000视频监控平台客户端播放实时视频时,一些视频画面显示的时间不准确的解决方法

目录 一、背景说明 二、解决过程 1、查看设备时间 2、查看服务器时间 3、ntp介绍 1) ntp的概念 2) ntp的同步方式 3) ntp的优势 4、自动校准服务器和设备时间 1) 下载ntp 2) 修改ntp.conf 3) 重启ntp服务&#xff0c;自动校准时间 4) 国标重新接入设备自动同步时间 三、问题解…

zStorage在海光CPU架构上的性能调优

前言 随着"信创"的东风吹遍大江南北&#xff0c;各家公司都开始了国产化的适配道路。zStorage团队当然也没有缺席&#xff0c;去年我们适配了华为的鲲鹏架构&#xff0c;整体性能水平达到了Intel架构的70%以上。今年我们开始着力于海光CPU架构的适配。与鲲鹏架构相比…