当前位置: 代码网 > it编程>编程语言>Java > Spring Security前后端分离接入保姆级教程

Spring Security前后端分离接入保姆级教程

2025年11月26日 Java 我要评论
基 于 角 色 权 限 模 型 与 数 据 权 限 控 制以 小 明 的 摄 影 作 品 管 理 系 统 为 例 , 从 零 基 础 搭 建 到 完 整 认 证 鉴 权 , 逐 步 演 示 sprin

基 于 角 色 权 限 模 型 与 数 据 权 限 控 制

以 小 明 的 摄 影 作 品 管 理 系 统 为 例 , 从 零 基 础 搭 建 到 完 整 认 证 鉴 权 , 逐 步 演 示 spring security 的 接 入 流 程 。 涵 盖 基 础 接 入 、 角 色 权 限 扩 展 、 数 据 权 限 控 制 三 大 模 块 , 每 步 标 注 自 定 义 类 与 spring security 扩 展 点 , 配 代 码 、 注 释 和 逻 辑 说 明 。 使 用 mybatisplus 作 为 orm 框 架 。

** 第 一 部 分 : 基 础 接 入 流 程 — 前 后 端 分 离 认 证 鉴 权 **

** 第 一 步 : 环 境 准 备 与 依 赖 引 入 **

** 目 的 **

搭 建 spring boot 基 础 项 目 , 引 入 spring security 及 前 后 端 分 离 必 需 组 件 , 使 用 mybatisplus 替 代 jpa 。

** 操 作 **

  • ** 创 建 spring boot 项 目 **
  • 通 过 start.spring.io 创 建 , 选 择 maven 、 java 17+ , 添 加 依 赖 spring web 、 spring security 、 mybatis plus 、 mysql driver 、 lombok 。
  • ** pom.xml 核 心 依 赖 **
<dependencies>  
    <!-- spring web -->  
    <dependency>  
        <groupid>org.springframework.boot</groupid>  
        <artifactid>spring-boot-starter-web</artifactid>  
    </dependency>  
    <!-- spring security -->  
    <dependency>  
        <groupid>org.springframework.boot</groupid>  
        <artifactid>spring-boot-starter-security</artifactid>  
    </dependency>  
    <!-- mybatis plus -->  
    <dependency>  
        <groupid>com.baomidou</groupid>  
        <artifactid>mybatis-plus-boot-starter</artifactid>  
        <version>3.5.3.1</version>  
    </dependency>  
    <!-- mysql驱动 -->  
    <dependency>  
        <groupid>com.mysql</groupid>  
        <artifactid>mysql-connector-j</artifactid>  
        <scope>runtime</scope>  
    </dependency>  
    <!-- jwt支持 -->  
    <dependency>  
        <groupid>io.jsonwebtoken</groupid>  
        <artifactid>jjwt-api</artifactid>  
        <version>0.11.5</version>  
    </dependency>  
    <dependency>  
        <groupid>io.jsonwebtoken</groupid>  
        <artifactid>jjwt-impl</artifactid>  
        <version>0.11.5</version>  
        <scope>runtime</scope>  
    </dependency>  
    <dependency>  
        <groupid>io.jsonwebtoken</groupid>  
        <artifactid>jjwt-jackson</artifactid>  
        <version>0.11.5</version>  
        <scope>runtime</scope>  
    </dependency>  
    <!-- lombok -->  
    <dependency>  
        <groupid>org.projectlombok</groupid>  
        <artifactid>lombok</artifactid>  
        <optional>true</optional>  
    </dependency>  
</dependencies>  

** 第 二 步 : 数 据 库 设 计 与 实 体 类 **

** 目 的 **

设 计 用 户 、 角 色 表 , 并 让 用 户 po 类 实 现 spring security 的 userdetails 接 口 。

** 操 作 **

  1. ** 数 据 库 表 设 计 **
create table sys_user (  
    id bigint auto_increment primary key,  
    username varchar(50) not null unique comment '用户名',  
    password varchar(100) not null comment 'bcrypt加密密码',  
    email varchar(100) comment '邮箱',  
    enabled boolean default true comment '账户是否启用'  
);  
create table sys_role (  
    id bigint auto_increment primary key,  
    role_name varchar(50) not null unique comment '角色名称(无role_前缀)'  
);  
create table sys_user_role (  
    user_id bigint,  
    role_id bigint,  
    primary key (user_id, role_id),  
    foreign key (user_id) references sys_user(id),  
    foreign key (role_id) references sys_role(id)  
);  
  1. ** 实 体 类 userpo 实 现 userdetails 接 口 **
import com.baomidou.mybatisplus.annotation.tablename;  
import lombok.data;  
import org.springframework.security.core.grantedauthority;  
import org.springframework.security.core.authority.simplegrantedauthority;  
import org.springframework.security.core.userdetails.userdetails;  
import java.util.collection;  
import java.util.set;  
import java.util.stream.collectors;  
@data  
@tablename("sys_user")  
public class userpo implements userdetails {  
    private long id;  
    private string username;  
    private string password;  
    private string email;  
    private boolean enabled = true;  
    private set<rolepo> roles;  
    @override  
    public collection<? extends grantedauthority> getauthorities() {  
        return roles.stream()  
            .map(role -> new simplegrantedauthority("role_" + role.getrolename()))  
            .collect(collectors.tolist());  
    }  
    @override  
    public boolean isaccountnonexpired() { return true; }  
    @override  
    public boolean isaccountnonlocked() { return true; }  
    @override  
    public boolean iscredentialsnonexpired() { return true; }  
    @override  
    public boolean isenabled() { return enabled; }  
}  
  1. ** 角 色 实 体 类 rolepo **
import com.baomidou.mybatisplus.annotation.tablename;  
import lombok.data;  
@data  
@tablename("sys_role")  
public class rolepo {  
    private long id;  
    private string rolename;  
}  

** 第 三 步 : mybatisplus mapper 层 **

** 目 的 **

使 用 mybatisplus mapper 操 作 数 据 库 。

** 操 作 **

  1. ** usermapper 接 口 **
import com.baomidou.mybatisplus.core.mapper.basemapper;  
import org.apache.ibatis.annotations.param;  
import java.util.optional;  
public interface usermapper extends basemapper<userpo> {  
    optional<userpo> findbyusername(@param("username") string username);  
}  
  1. ** rolemapper 接 口 **
import com.baomidou.mybatisplus.core.mapper.basemapper;  
import org.apache.ibatis.annotations.param;  
public interface rolemapper extends basemapper<rolepo> {  
    optional<rolepo> findbyrolename(@param("rolename") string rolename);  
}  

** 第 四 步 : service 层 — 实 现 userdetailsservice **

** 目 的 **

实 现 spring security 的 userdetailsservice 接 口 , 配 置 密 码 编 码 器 。

** 操 作 **

  1. ** 自 定 义 userdetailsservice 实 现 类 **
import lombok.requiredargsconstructor;  
import org.springframework.security.core.userdetails.userdetails;  
import org.springframework.security.core.userdetails.userdetailsservice;  
import org.springframework.security.core.userdetails.usernamenotfoundexception;  
import org.springframework.stereotype.service;  
@service  
@requiredargsconstructor  
public class userdetailsserviceimpl implements userdetailsservice {  
    private final usermapper usermapper;  
    @override  
    public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {  
        userpo user = usermapper.findbyusername(username)  
            .orelsethrow(() -> new usernamenotfoundexception("用户不存在:" + username));  
        return user;  
    }  
}  
  1. ** 配 置 密 码 编 码 器 **
import org.springframework.context.annotation.bean;  
import org.springframework.context.annotation.configuration;  
import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;  
import org.springframework.security.crypto.password.passwordencoder;  
@configuration  
public class passwordencoderconfig {  
    @bean  
    public passwordencoder passwordencoder() {  
        return new bcryptpasswordencoder();  
    }  
}  

** 第 五 步 : spring security 配 置 类 **

** 目 的 **

配 置 认 证 规 则 、 授 权 规 则 、 jwt 过 滤 器 。

** 操 作 **

  1. ** 自 定 义 security 配 置 类 **
import lombok.requiredargsconstructor;  
import org.springframework.context.annotation.bean;  
import org.springframework.context.annotation.configuration;  
import org.springframework.security.authentication.authenticationmanager;  
import org.springframework.security.config.annotation.authentication.configuration.authenticationconfiguration;  
import org.springframework.security.config.annotation.method.configuration.enableglobalmethodsecurity;  
import org.springframework.security.config.annotation.web.builders.httpsecurity;  
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;  
import org.springframework.security.config.http.sessioncreationpolicy;  
import org.springframework.security.crypto.password.passwordencoder;  
import org.springframework.security.web.securityfilterchain;  
import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;  
@configuration  
@enablewebsecurity  
@enableglobalmethodsecurity(prepostenabled = true)  
@requiredargsconstructor  
public class securityconfig {  
    private final userdetailsserviceimpl userdetailsservice;  
    private final jwtauthfilter jwtauthfilter;  
    private final passwordencoder passwordencoder;  
    @bean  
    public securityfilterchain securityfilterchain(httpsecurity http) throws exception {  
        http.csrf(csrf -> csrf.disable())  
            .sessionmanagement(session -> session.sessioncreationpolicy(sessioncreationpolicy.stateless))  
            .authorizehttprequests(auth -> auth  
                .requestmatchers("/api/auth/login", "/api/auth/register").permitall()  
                .requestmatchers("/api/admin/**").hasrole("admin")  
                .requestmatchers("/api/user/**").hasanyrole("admin", "user")  
                .anyrequest().authenticated()  
            )  
            .addfilterbefore(jwtauthfilter, usernamepasswordauthenticationfilter.class)  
            .userdetailsservice(userdetailsservice)  
            .passwordencoder(passwordencoder);  
        return http.build();  
    }  
    @bean  
    public authenticationmanager authenticationmanager(authenticationconfiguration config) throws exception {  
        return config.getauthenticationmanager();  
    }  
}  

** 第 六 步 : jwt 工 具 类 与 过 滤 器 **

** 目 的 **

实 现 jwt 生 成 、 验 证 、 提 取 用 户 名 , 并 通 过 过 滤 器 转 换 为 认 证 信 息 。

** 操 作 **

  1. ** jwt 工 具 类 jwtutils **
import io.jsonwebtoken.claims;  
import io.jsonwebtoken.jwts;  
import io.jsonwebtoken.signaturealgorithm;  
import org.springframework.beans.factory.annotation.value;  
import org.springframework.security.core.userdetails.userdetails;  
import org.springframework.stereotype.component;  
import java.util.date;  
import java.util.function.function;  
@component  
public class jwtutils {  
    @value("${app.jwt.secret}")  
    private string secret;  
    @value("${app.jwt.expiration}")  
    private long expiration;  
    public string generatetoken(userdetails userdetails) {  
        return jwts.builder()  
            .setsubject(userdetails.getusername())  
            .setissuedat(new date())  
            .setexpiration(new date(system.currenttimemillis() + expiration))  
            .signwith(signaturealgorithm.hs256, secret)  
            .compact();  
    }  
    public string extractusername(string token) {  
        return extractclaim(token, claims::getsubject);  
    }  
    public boolean validatetoken(string token, userdetails userdetails) {  
        final string username = extractusername(token);  
        return (username.equals(userdetails.getusername())) && !istokenexpired(token);  
    }  
    private <t> t extractclaim(string token, function<claims, t> claimsresolver) {  
        final claims claims = jwts.parser().setsigningkey(secret).parseclaimsjws(token).getbody();  
        return claimsresolver.apply(claims);  
    }  
    private boolean istokenexpired(string token) {  
        return extractclaim(token, claims::getexpiration).before(new date());  
    }  
}  
  1. ** jwt 认 证 过 滤 器 jwtauthfilter **
import lombok.requiredargsconstructor;  
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;  
import org.springframework.security.core.context.securitycontextholder;  
import org.springframework.security.core.userdetails.userdetails;  
import org.springframework.security.web.authentication.webauthenticationdetailssource;  
import org.springframework.stereotype.component;  
import org.springframework.web.filter.onceperrequestfilter;  
import javax.servlet.filterchain;  
import javax.servlet.servletexception;  
import javax.servlet.http.httpservletrequest;  
import javax.servlet.http.httpservletresponse;  
import java.io.ioexception;  
@component  
@requiredargsconstructor  
public class jwtauthfilter extends onceperrequestfilter {  
    private final jwtutils jwtutils;  
    private final userdetailsserviceimpl userdetailsservice;  
    @override  
    protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain)  
            throws servletexception, ioexception {  
        try {  
            string jwt = parsejwt(request);  
            if (jwt != null && jwtutils.validatetoken(jwt)) {  
                string username = jwtutils.extractusername(jwt);  
                userdetails userdetails = userdetailsservice.loaduserbyusername(username);  
                usernamepasswordauthenticationtoken authentication =  
                    new usernamepasswordauthenticationtoken(userdetails, null, userdetails.getauthorities());  
                authentication.setdetails(new webauthenticationdetailssource().builddetails(request));  
                securitycontextholder.getcontext().setauthentication(authentication);  
            }  
        } catch (exception e) {  
            logger.error("无法设置用户认证: {}", e);  
        }  
        filterchain.dofilter(request, response);  
    }  
    private string parsejwt(httpservletrequest request) {  
        string headerauth = request.getheader("authorization");  
        if (headerauth != null && headerauth.startswith("bearer ")) {  
            return headerauth.substring(7);  
        }  
        return null;  
    }  
}  

** 第 七 步 : 登 录 接 口 与 资 源 接 口 **

** 目 的 **

编 写 登 录 接 口 认 证 并 生 成 token , 编 写 受 保 护 资 源 接 口 。

** 操 作 **

  1. ** 登 录 控 制 器 authcontroller **
import lombok.requiredargsconstructor;  
import org.springframework.http.responseentity;  
import org.springframework.security.authentication.authenticationmanager;  
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;  
import org.springframework.security.core.authentication;  
import org.springframework.security.core.userdetails.userdetails;  
import org.springframework.web.bind.annotation.*;  
@restcontroller  
@requestmapping("/api/auth")  
@requiredargsconstructor  
public class authcontroller {  
    private final authenticationmanager authenticationmanager;  
    private final jwtutils jwtutils;  
    @postmapping("/login")  
    public responseentity<jwtresponse> login(@requestbody loginrequest loginrequest) {  
        authentication authentication = authenticationmanager.authenticate(  
            new usernamepasswordauthenticationtoken(  
                loginrequest.getusername(),  
                loginrequest.getpassword()  
            )  
        );  
        securitycontextholder.getcontext().setauthentication(authentication);  
        userdetails userdetails = (userdetails) authentication.getprincipal();  
        string jwt = jwtutils.generatetoken(userdetails);  
        return responseentity.ok(new jwtresponse(jwt, userdetails.getusername()));  
    }  
}  

** 第 二 部 分 : 权 限 表 扩 展 指 南 — 基 于 角 色 权 限 模 型 **

** 一 、 数 据 库 扩 展 **

** 目 的 **

新 增 权 限 表 实 现 细 粒 度 权 限 控 制 。

** 操 作 **

  1. ** 新 增 权 限 表 结 构 **
create table sys_permission (  
    id bigint auto_increment primary key,  
    permission_symbol varchar(100) not null unique comment '权限符号(如 user:add)',  
    permission_name varchar(100) not null comment '权限名称'  
);  
create table sys_role_permission (  
    role_id bigint,  
    permission_id bigint,  
    primary key (role_id, permission_id),  
    foreign key (role_id) references sys_role(id),  
    foreign key (permission_id) references sys_permission(id)  
);  

** 二 、 实 体 类 扩 展 **

** 目 的 **

修 改 角 色 与 用 户 实 体 类 关 联 权 限 。

** 操 作 **

  1. ** 新 增 权 限 实 体 类 permissionpo **
import com.baomidou.mybatisplus.annotation.tablename;  
import lombok.data;  
import java.util.set;  
@data  
@tablename("sys_permission")  
public class permissionpo {  
    private long id;  
    private string permissionsymbol;  
    private string permissionname;  
    private set<rolepo> roles;  
}  
  1. ** 修 改 角 色 实 体 类 rolepo **
import com.baomidou.mybatisplus.annotation.tablename;  
import lombok.data;  
import java.util.set;  
@data  
@tablename("sys_role")  
public class rolepo {  
    private long id;  
    private string rolename;  
    private set<permissionpo> permissions;  
    private set<userpo> users;  
}  
  1. ** 修 改 用 户 实 体 类 userpo 权 限 加 载 **
@override  
public collection<? extends grantedauthority> getauthorities() {  
    set<grantedauthority> authorities = new hashset<>();  
    for (rolepo role : roles) {  
        authorities.add(new simplegrantedauthority("role_" + role.getrolename()));  
        for (permissionpo permission : role.getpermissions()) {  
            authorities.add(new simplegrantedauthority(permission.getpermissionsymbol()));  
        }  
    }  
    return authorities;  
}  

** 三 、 mybatisplus mapper 扩 展 **

** 目 的 **

添 加 权 限 相 关 mapper 接 口 。

** 操 作 **

  1. ** permissionmapper **
import com.baomidou.mybatisplus.core.mapper.basemapper;  
import org.apache.ibatis.annotations.param;  
import java.util.optional;  
public interface permissionmapper extends basemapper<permissionpo> {  
    optional<permissionpo> findbypermissionsymbol(@param("symbol") string symbol);  
}  

** 四 、 service 层 扩 展 **

** 目 的 **

实 现 权 限 管 理 服 务 。

** 操 作 **

  1. ** 权 限 管 理 服 务 permissionservice **
import lombok.requiredargsconstructor;  
import org.springframework.stereotype.service;  
import org.springframework.transaction.annotation.transactional;  
import java.util.arrays;  
import java.util.stream.collectors;  
@service  
@requiredargsconstructor  
public class permissionservice {  
    private final permissionmapper permissionmapper;  
    private final rolemapper rolemapper;  
    @transactional  
    public void assignpermissiontorole(long roleid, string permissionsymbol) {  
        rolepo role = rolemapper.selectbyid(roleid);  
        permissionpo permission = permissionmapper.findbypermissionsymbol(permissionsymbol)  
            .orelseget(() -> {  
                permissionpo p = new permissionpo();  
                p.setpermissionsymbol(permissionsymbol);  
                p.setpermissionname(parsepermissionname(permissionsymbol));  
                permissionmapper.insert(p);  
                return p;  
            });  
        role.getpermissions().add(permission);  
        rolemapper.updatebyid(role);  
    }  
    private string parsepermissionname(string symbol) {  
        return arrays.stream(symbol.split(":"))  
            .map(word -> word.substring(0, 1).touppercase() + word.substring(1))  
            .collect(collectors.joining(" "));  
    }  
}  

** 五 、 控 制 器 层 扩 展 — 使 用 权 限 注 解 **

** 目 的 **

通 过 @preauthorize 注 解 控 制 方 法 权 限 。

** 操 作 **

  1. ** 用 户 控 制 器 使 用 权 限 注 解 **
import org.springframework.security.access.prepost.preauthorize;  
import org.springframework.web.bind.annotation.*;  
@restcontroller  
@requestmapping("/api/users")  
public class usercontroller {  
    @postmapping  
    @preauthorize("hasauthority('user:add')")  
    public responseentity<?> adduser() {  
        return responseentity.ok("添加用户");  
    }  
    @deletemapping("/{id}")  
    @preauthorize("hasauthority('user:delete')")  
    public responseentity<?> deleteuser(@pathvariable long id) {  
        return responseentity.ok("删除用户");  
    }  
}  

** 第 三 部 分 : 数 据 权 限 控 制 — 订 单 查 询 只 查 看 自 己 创 建 的 订 单 **

** 一 、 数 据 库 扩 展 — 订 单 表 **

** 目 的 **

设 计 订 单 表 关 联 创 建 人 。

** 操 作 **

  1. ** 订 单 表 结 构 **
create table sys_order (  
    id bigint auto_increment primary key,  
    order_no varchar(50) not null comment '订单编号',  
    amount decimal(10,2) not null comment '订单金额',  
    creator_id bigint not null comment '创建人id',  
    created_at timestamp default current_timestamp,  
    foreign key (creator_id) references sys_user(id)  
);  
  1. ** 订 单 实 体 类 orderpo **
import com.baomidou.mybatisplus.annotation.tablename;  
import lombok.data;  
import java.math.bigdecimal;  
import java.time.localdatetime;  
@data  
@tablename("sys_order")  
public class orderpo {  
    private long id;  
    private string orderno;  
    private bigdecimal amount;  
    private long creatorid;  
    private localdatetime createdat;  
}  

** 二 、 mybatisplus mapper 与 service **

** 目 的 **

实 现 订 单 数 据 访 问 与 权 限 过 滤 。

** 操 作 **

  1. ** ordermapper **
import com.baomidou.mybatisplus.core.mapper.basemapper;  
import org.apache.ibatis.annotations.param;  
import java.util.list;  
public interface ordermapper extends basemapper<orderpo> {  
    list<orderpo> findbycreatorid(@param("creatorid") long creatorid);  
}  
  1. ** 订 单 服 务 实 现 **
import lombok.requiredargsconstructor;  
import org.springframework.security.core.context.securitycontextholder;  
import org.springframework.stereotype.service;  
import java.util.list;  
@service  
@requiredargsconstructor  
public class orderserviceimpl implements orderservice {  
    private final ordermapper ordermapper;  
    private final usermapper usermapper;  
    @override  
    public list<orderpo> findmyorders() {  
        userpo currentuser = getcurrentuser();  
        return ordermapper.findbycreatorid(currentuser.getid());  
    }  
    @override  
    public orderpo findorderbyid(long id) {  
        orderpo order = ordermapper.selectbyid(id);  
        userpo currentuser = getcurrentuser();  
        if (!order.getcreatorid().equals(currentuser.getid())) {  
            throw new accessdeniedexception("无权访问此订单");  
        }  
        return order;  
    }  
    private userpo getcurrentuser() {  
        string username = securitycontextholder.getcontext().getauthentication().getname();  
        return usermapper.findbyusername(username).orelsethrow();  
    }  
}  

** 三 、 控 制 器 层 实 现 **

** 目 的 **

编 写 订 单 相 关 接 口 , 集 成 数 据 权 限 控 制 。

** 操 作 **

  1. ** 订 单 控 制 器 **
import org.springframework.security.access.prepost.preauthorize;  
import org.springframework.web.bind.annotation.*;  
@restcontroller  
@requestmapping("/api/orders")  
@requiredargsconstructor  
public class ordercontroller {  
    private final orderservice orderservice;  
    @getmapping  
    @preauthorize("hasauthority('order:view')")  
    public responseentity<list<orderpo>> getmyorders() {  
        list<orderpo> orders = orderservice.findmyorders();  
        return responseentity.ok(orders);  
    }  
    @getmapping("/{id}")  
    @preauthorize("hasauthority('order:view')")  
    public responseentity<orderpo> getorderbyid(@pathvariable long id) {  
        orderpo order = orderservice.findorderbyid(id);  
        return responseentity.ok(order);  
    }  
}  

** 总 结 **

通 过 以 上 三 部 分 整 合 , 我 们 实 现 了 spring security 前 后 端 分 离 接 入 、 角 色 权 限 扩 展 、 数 据 权 限 控 制 的 完 整 流 程 。 核 心 要 点 如 下 :

  1. ** 基 础 接 入 ** : 实 现 用 户 认 证 与 jwt 授 权 。
  2. ** 权 限 扩 展 ** : 通 过 权 限 表 与 角 色 关 联 , 实 现 细 粒 度 权 限 控 制 。
  3. ** 数 据 权 限 ** : 在 service 层 手 动 过 滤 数 据 , 确 保 用 户 只 能 查 看 自 己 创 建 的 订 单 。

整 个 系 统 兼 顾 了 安 全 性 与 灵 活 性 , 适 用 于 中 小 型 项 目 的 权 限 管 理 需 求 。

到此这篇关于spring security前后端分离接入流程保姆级教程的文章就介绍到这了,更多相关spring security前后端分离接入内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com