谷粒商城实战基础篇

参考博客:https://blog.csdn.net/weixin_44190665/article/details/121043585

1.项目介绍

1.1 谷粒商场微服务架构图

谷粒商城实战基础篇

1.2 微服务划分图

谷粒商城实战基础篇

2.项目环境搭建

2.1 安装docker

1. 卸载之前的docker
 sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2.需要的安装包
yum install -y yum-utils

3.设置镜像的仓库
推荐使用国内的
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新yum软件包索引
yum makecache

4.安装docker相关的 docker-ce 社区版 而ee是企业版
yum install docker-ce docker-ce-cli containerd.io
5、启动docker
systemctl start docker
6. 使用docker version查看是否按照成功
docker -v

配置镜像加速
https://cr.console.aliyun.com/cn-qingdao/instances/mirrors

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'eof' { "registry-mirrors": ["https: chqac97z.mirror.aliyuncs.com"] } eof sudo systemctl daemon-reload restart docker < code></-'eof'>

2.2 docker配置mysql

docker pull mysql:5.7
&#x8FD0;&#x884C;&#x5BB9;&#x5668;
sudo docker run -p 3312:3306 --name mysql01 \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
&#x53C2;&#x6570;&#x8BF4;&#x660E; -p 3312:3306 &#x5C06;&#x5BB9;&#x5668;&#x7684;3306&#x7AEF;&#x53E3;&#x6620;&#x5C04;&#x5230;&#x4E3B;&#x673A;&#x7684;3312&#x7AEF;&#x53E3;
-v &#x5BBF;&#x4E3B;&#x673A;&#x6587;&#x4EF6;&#x76EE;&#x5F55;:&#x5BB9;&#x5668;&#x5185;&#x76EE;&#x5F55;  &#x5C06;&#x5BB9;&#x5668;&#x5185;&#x6587;&#x4EF6;&#x6302;&#x8F7D;&#x5230;&#x5BBF;&#x4E3B;&#x673A;&#x4E0A;
-e MYSQL_ROOT_PASSWORD=root &#x8BBE;&#x7F6E;mysql&#x5BC6;&#x7801;&#x4E3A;root
-d &#x540E;&#x53F0;&#x542F;&#x52A8;
--name &#x7ED9;&#x542F;&#x52A8;&#x5BB9;&#x5668;&#x8D77;&#x540D;&#x5B57;

修改mysql配置文件

#&#x67E5;&#x770B;&#x5BB9;&#x5668;
 docker ps

谷粒商城实战基础篇
&#x8FDB;&#x5165;&#x914D;&#x7F6E;&#x6587;&#x4EF6;&#x6302;&#x8F7D;&#x7684;&#x76EE;&#x5F55;&#x4E0B;
cd /mydata/mysql/conf

&#x7F16;&#x8F91;&#x914D;&#x7F6E;&#x6587;&#x4EF6;my.cnf
vi my.cnf

&#x65B0;&#x589E;&#x914D;&#x7F6E;&#x6587;&#x4EF6;&#x5185;&#x5BB9;
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve

&#x4FEE;&#x6539;&#x914D;&#x7F6E;&#x6587;&#x4EF6;&#x4E4B;&#x540E;&#xFF0C;&#x91CD;&#x542F;&#x5BB9;&#x5668;
docker restart mysql01

2.3 docker配置redis

如果直接挂载的话docker会以为挂载的是一个目录,所以我们先创建一个文件然后再挂载

1&#x3001;&#x521B;&#x5EFA;&#x914D;&#x7F6E;&#x6587;&#x4EF6;
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf

2&#x3001;&#x4E0B;&#x8F7D;&#x955C;&#x50CF;
docker pull redis

3&#x3001;&#x542F;&#x52A8;&#x5BB9;&#x5668;
&#x4E91;&#x670D;&#x52A1;&#x5668;&#x4E00;&#x5B9A;&#x8981;&#x4FEE;&#x6539;&#x7AEF;&#x53E3;&#x6216;&#x914D;&#x7F6E;&#x5BC6;&#x7801;&#xFF0C;&#x5426;&#x5219;&#x4F1A;&#x88AB;&#x62C9;&#x53BB;&#x6316;&#x77FF;
docker run -p 6124:6379 --name redis \
-v /mydata/redis/data:/data  \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

4&#x3001;&#x76F4;&#x63A5;&#x8FDB;&#x5165;redis&#x5BA2;&#x6237;&#x7AEF;
docker exec -it redis redis-cli

修改redis配置文件

vi /mydata/redis/conf/redis.conf

&#x63D2;&#x5165;&#x4E0B;&#x9762;&#x5185;&#x5BB9;
appendonly yes    # &#x652F;&#x6301;&#x6301;&#x4E45;&#x5316;

&#x4FDD;&#x5B58;
docker restart redis

设置redis容器在docker启动的时候启动

docker update redis --restart=always

3.项目初始化

参考:https://www.yuque.com/zhangshuaiyin/guli-mall/lkh1bm

3.1 初始化项目

3.1.1根据脚本依次创建数据库

mall_oms:订单服务
mall_pms:商品服务
mall_sms:营销服务
mall_ums:用户服务
mall_wms:库存服务

谷粒商城实战基础篇
谷粒商城实战基础篇

3.1.2下载后台管理系统

https://gitee.com/renrenio
后台数据库初始化,因为使用mysql所以

谷粒商城实战基础篇
谷粒商城实战基础篇

3.1.3 代码生成器

谷粒商城实战基础篇
谷粒商城实战基础篇
将逆向生成的文件粘贴到对应的子模块下,根据renren-fast后端解决对应类不存在问题,得到如下
谷粒商城实战基础篇

4.分布式组件(nacos和gateway)

4.1 nacos注册中心

引入依赖(放入common中)

        <dependency>
            <groupid>com.alibaba.cloud</groupid>
            <artifactid>spring-cloud-starter-alibaba-nacos-discovery</artifactid>
        </dependency>

配置需要注册到注册中心的yml文件application.yml

server:
  port: 7000

spring:
  datasource:
    username: root
    password: xxxxxx
    url: jdbc:mysql://64.114.116.33:1234/gulimall_sms?userUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: gulimall-coupon
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 6d3e7dd2-09a0-4515-b97c-2dda60199ba1`

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto   # &#x4E3B;&#x952E;&#x81EA;&#x589E;

在主启动类上使用 @EnableDiscoveryClient 注解开启服务注册与发现功能

@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallCouponApplication.class, args);
    }
}

启动gulimall-coupon, 查看服务注册中心

4.2 openfegin远程调用

声明式远程调用

feign是一个声明式的HTTP客户端,他的目的就是让远程调用更加简单。给远程服务发的是HTTP请求。

会员服务想要远程调用优惠券服务,只需要给会员服务里引入openfeign依赖,他就有了远程调用其他服务的能力。
引入依赖:

<dependency>
    <groupid>org.springframework.cloud</groupid>
    <artifactid>spring-cloud-starter-openfeign</artifactid>
</dependency>

演示member服务调用coupon服务
在gulimall-coupon中的CouponController中添加测试方法

    @RequestMapping("/member/list")
    public R membercoupons(){    //&#x5168;&#x7CFB;&#x7EDF;&#x7684;&#x6240;&#x6709;&#x8FD4;&#x56DE;&#x90FD;&#x8FD4;&#x56DE;R
        // &#x6A21;&#x62DF;&#x53BB;&#x6570;&#x636E;&#x5E93;&#x67E5;&#x7528;&#x6237;&#x5BF9;&#x4E8E;&#x7684;&#x4F18;&#x60E0;&#x5238;
        CouponEntity couponEntity = new CouponEntity();
        couponEntity.setCouponName("&#x6EE1;100-10");//&#x4F18;&#x60E0;&#x5238;&#x7684;&#x540D;&#x5B57;
        return R.ok().put("coupons",Arrays.asList(couponEntity));
    }

在member的主启动类上加注解,告诉member是一个远程调用客户端,member要调用东西的

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients(basePackages="com.xmh.gulimall.member.feign")//&#x626B;&#x63CF;&#x63A5;&#x53E3;&#x65B9;&#x6CD5;&#x6CE8;&#x89E3;
public class GulimallMemberApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallMemberApplication.class, args);
    }
}

在com.xmh.gulimall.member.feign中新建接口CouponFeignService

@FeignClient("gulimall-coupon")//&#x544A;&#x8BC9;spring cloud&#x8FD9;&#x4E2A;&#x63A5;&#x53E3;&#x662F;&#x4E00;&#x4E2A;&#x8FDC;&#x7A0B;&#x5BA2;&#x6237;&#x7AEF;&#xFF0C;&#x8981;&#x8C03;&#x7528;coupon&#x670D;&#x52A1;(nacos&#x4E2D;&#x5BF9;&#x5E94;&#x7684;&#x670D;&#x52A1;&#x6A21;&#x5757;&#x540D;&#x4E5F;&#x5C31;&#x662F;yml&#x4E2D;&#x7684;application.name)
public interface CouponFeignService {

    // &#x8FDC;&#x7A0B;&#x670D;&#x52A1;&#x7684;url
    @RequestMapping("/coupon/coupon/member/list")//&#x6CE8;&#x610F;&#x5199;&#x5168;&#x4F18;&#x60E0;&#x5238;&#x7C7B;&#x4E0A;&#x8FD8;&#x6709;&#x6620;&#x5C04;
    public R membercoupons();//&#x5F97;&#x5230;&#x4E00;&#x4E2A;R&#x5BF9;&#x8C61;
}

在member的MemberController写一个测试

    @Autowired
    private CouponFeignService couponFeignService; //&#x6CE8;&#x5165;&#x521A;&#x624D;&#x7684;CouponFeignService&#x63A5;&#x53E3;

    @RequestMapping("/coupons")
    public R coupons(){
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setNickname("&#x4F1A;&#x5458;&#x6635;&#x79F0;&#x5F20;&#x4E09;");
        R membercoupons = couponFeignService.membercoupons();

        return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
    }

4.3nacos用作配置中心

common中添加依赖 nacos配置中心

<dependency>
     <groupid>com.alibaba.cloud</groupid>
     <artifactid>spring-cloud-starter-alibaba-nacos-config</artifactid>
 </dependency>

在coupons项目中创建/src/main/resources/bootstrap.yml,优先级别application.properties高

spring:
  cloud:
    nacos:
      config:
        namespace: 6d3e7dd2-09a0-4515-b97c-2dda60199ba1
        server-addr: 127.0.0.1:8848
        ext-config:
          - dataId: mall-coupon.yml
            group: DEFAULT_GROUP
            refresh: true

读取 mall-coupon.yml里面的配置上加@RefreshScope支持动态刷新

4.4 gateway网关

新建maven项目,引入依赖

<dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-gateway</artifactid>
        </dependency>

配置application.yml

server:
  port: 88
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-gateway

配置bootstrap.yml

spring:
  application:
    name: gulimall-gateway
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: d717d0ee-7a07-4125-9881-3ef57d696ad3

主启动类

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) //&#x4E0D;&#x7528;&#x6570;&#x636E;&#x6E90;&#xFF0C;&#x8FC7;&#x6EE4;&#x6389;&#x6570;&#x636E;&#x6E90;&#x914D;&#x7F6E;
@EnableDiscoveryClient
public class GulimallGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallGatewayApplication.class, args);
    }
}

测试访问http://localhost:88?url=baidu 切换到百度, http://localhost:88?url=qq 切换到qq

在网关的application.yml中配置路由

server:
  port: 88
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: baidu_route              # &#x6BCF;&#x4E00;&#x4E2A;&#x8DEF;&#x7531;&#x7684;&#x540D;&#x5B57;&#xFF0C;&#x552F;&#x4E00;&#x5373;&#x53EF;
          uri: https://www.baidu.com   # &#x5339;&#x914D;&#x540E;&#x63D0;&#x4F9B;&#x670D;&#x52A1;&#x7684;&#x8DEF;&#x7531;&#x5730;&#x5740;
          predicates:                 # &#x65AD;&#x8A00;&#x89C4;&#x5219;
            - Query=url,baidu         #&#x5982;&#x679C;url&#x53C2;&#x6570;&#x7B49;&#x4E8E;baidu &#x7B26;&#x5408;&#x65AD;&#x8A00;&#xFF0C;&#x8F6C;&#x5230;uri

        - id: qq_route                  # &#x6BCF;&#x4E00;&#x4E2A;&#x8DEF;&#x7531;&#x7684;&#x540D;&#x5B57;&#xFF0C;&#x552F;&#x4E00;&#x5373;&#x53EF;
          uri: https://www.qq.com   # &#x5339;&#x914D;&#x540E;&#x63D0;&#x4F9B;&#x670D;&#x52A1;&#x7684;&#x8DEF;&#x7531;&#x5730;&#x5740;
          predicates: # &#x65AD;&#x8A00;&#x89C4;&#x5219;
            - Query=url,qq         #&#x5982;&#x679C;url&#x53C2;&#x6570;&#x7B49;&#x4E8E;baidu &#x7B26;&#x5408;&#x65AD;&#x8A00;&#xFF0C;&#x8F6C;&#x5230;uri

  application:
    name: gulimall-gateway

启动网关,访问http://localhost:88?url=baidu测试

谷粒商城实战基础篇

5跨域

是浏览器不能执行其他网站的脚本,它是由浏览器额同源策略造成的,是浏览器对js施加的安全限制。同源策略是指协议、域名、端口都要相同,其中有一个不同就会产生跨域问题
跨域请求的实现是通过预检请求实现的,先发送一个OPSTIONS探路,收到响应允许跨域后再发送真实请求

谷粒商城实战基础篇
(1)解决办法
配置filter,每个请求来了以后,返回给浏览器之前都添加上那些字段,在gulimall-gateway中新建配置类
@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration= new CorsConfiguration();
        //1&#x3001;&#x914D;&#x7F6E;&#x8DE8;&#x57DF;
        // &#x5141;&#x8BB8;&#x8DE8;&#x57DF;&#x7684;&#x5934;
        corsConfiguration.addAllowedHeader("*");
        // &#x5141;&#x8BB8;&#x8DE8;&#x57DF;&#x7684;&#x8BF7;&#x6C42;&#x65B9;&#x5F0F;
        corsConfiguration.addAllowedMethod("*");
        // &#x5141;&#x8BB8;&#x8DE8;&#x57DF;&#x7684;&#x8BF7;&#x6C42;&#x6765;&#x6E90;
        corsConfiguration.addAllowedOrigin("*");
        // &#x662F;&#x5426;&#x5141;&#x8BB8;&#x643A;&#x5E26;cookie&#x8DE8;&#x57DF;
        corsConfiguration.setAllowCredentials(true);

        // &#x4EFB;&#x610F;url&#x90FD;&#x8981;&#x8FDB;&#x884C;&#x8DE8;&#x57DF;&#x914D;&#x7F6E;
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);

    }
}

6逻辑删除

(1)在yml文件中配置,配置统一的全局规则(可以省略)

&#x914D;&#x7F6E;&#x903B;&#x8F91;&#x5220;&#x9664;
mybatis-plus.global-config.db-config.logic-delete-value=0
mybatis-plus.global-config.db-config.logic-not-delete-value=1

(2)配置逻辑删除组件(3.1.1开始不需要这一步)

(3)实体类在对应的字段上加上@TableLogic(value = “1”, delval = “0”)注解,三级分类把showStatus字段作为逻辑删除字段

7.文件上传功能

谷粒商城实战基础篇
上传策略:服务端签名后直传
谷粒商城实战基础篇
谷粒商城实战基础篇
阿里云存储帮助文档:https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.768.549d59aaWuZMGJ

https://github.com/alibaba/aliyun-spring-boot/blob/master/aliyun-spring-boot-samples/aliyun-oss-spring-boot-sample/README-zh.md
流程:
(1)开通阿里云OSS对象存储服务,创建新的Bucket

谷粒商城实战基础篇
(2)获取Endpoint、AccessKey ID、AccessKey Secret
创建子账户
谷粒商城实战基础篇
点击创建用户
谷粒商城实战基础篇
新建成功后得到AccessKey ID、AccessKey Secret
对子账户分配权限,管理OSS对象存储服务
谷粒商城实战基础篇
获取endpoint
谷粒商城实战基础篇

7.1代码实现

(1)新建module,gulimall-third-party,引入依赖

    <dependencies>
        <dependency>
            <groupid>com.xmh.gulimall</groupid>
            <artifactid>gulimall-common</artifactid>
            <version>1.0.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupid>com.baomidou</groupid>
                    <artifactid>mybatis-plus-boot-starter</artifactid>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupid>com.alibaba.cloud</groupid>
            <artifactid>spring-cloud-starter-alicloud-oss</artifactid>
            <version>2.2.0.RELEASE</version>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupid>org.springframework.cloud</groupid>
            <artifactid>spring-cloud-starter-openfeign</artifactid>
        </dependency>
        <!--web-->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-actuator</artifactid>
        </dependency>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupid>org.junit.jupiter</groupid>
            <artifactid>junit-jupiter</artifactid>
            <scope>test</scope>
        </dependency>
    </dependencies>

oss一定要写版本号
(2)yaml文件
oss.yml

spring:
  cloud:
    alicloud:
      oss:
        endpoint:
        bucket:
      access-key:
      secret-key:

bootstrap.yml

spring:
  application:
    name: gulimall-third-party
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: cf3e63e0-da0b-4ee2-a535-cf4266d1b8fc
        extension-configs[0]:
          data-id: oss.yaml
          group: DEFAULT_GROUP
          refresh: true

application.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000

新建主启动类com/xmh/gulimall/thirdparty/GulimallThirdPartyApplication.java

@SpringBootApplication
@EnableDiscoveryClient
public class GulimallThirdPartyApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallThirdPartyApplication.class, args);
    }
}

新建controller

@RestController
public class OssController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    String endpoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    String accessKey;

    @RequestMapping("/oss/policy")
    public Map<string, string> policy(){

        String host = "https://" + bucket + "." + endpoint; // host&#x7684;&#x683C;&#x5F0F;&#x4E3A; bucketname.endpoint

        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dir = format; // &#x7528;&#x6237;&#x4E0A;&#x4F20;&#x6587;&#x4EF6;&#x65F6;&#x6307;&#x5B9A;&#x7684;&#x524D;&#x7F00;&#x3002;

        Map<string, string> respMap=null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap= new LinkedHashMap<string, string>();
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));

        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
        } finally {
            ossClient.shutdown();
        }
        return respMap;
    }

}
</string,></string,></string,>

配置网关

        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
</segment>

访问http://localhost:88/api/thirdparty/oss/policy测试

前端联调,实现文件上传功能
修改组件中el-upload中的action属性,替换成自己的Bucket域名

谷粒商城实战基础篇
把单个文件上传组件应用到brand-add-or-update.vue
谷粒商城实战基础篇
解决跨域问题
谷粒商城实战基础篇
在阿里云oss中
谷粒商城实战基础篇
创建规则
谷粒商城实战基础篇
谷粒商城实战基础篇
配置成功后,点击图片上传,进行测试。

以上参考:https://note.youdao.com/ynoteshare/index.html?id=48d2f99a12c3b435f34a6b7526d4007d&type=notebook&_time=1657446304581#/WEBf544113d84b90ed53a5f00c1e105293d

Original: https://www.cnblogs.com/cgy1995/p/16420619.html
Author: spiderMan1-1
Title: 谷粒商城实战基础篇

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/589781/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球