Spring boot 2.0 消息总线(Spring Cloud Bus) 高可用分布式配置中心 实例

版本

  • spring-boot 2.0.6.RELEASE
  • spring-cloud Finchley.SR2

目录结构

image.png

eurka-server 服务注册中心

pom.xml


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

application.yml

server:
  port: 18080

spring:
  application:
    name: eurka-server

eureka:
  instance:
    hostname: localhost   # 主机地址
  client:
    register-with-eureka: false  # 注册中心告诉自己不能向自己注册自己,默认为true
    fetch-registry: false  # 获取注册信息
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    enable-self-preservation: false     # 关闭自我保护
    eviction-interval-timer-in-ms: 60000  # 清理时间间隔  服务器清理服务列表的定时器 单位 毫秒

注册中心 启动类 EurekaServerApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer   //表示是一个 eureka 服务
@SpringBootApplication
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

在自己的git上上传一个配置文件(在统一配置中心config-server 配置git仓库地址时需要使用这个作为远程配置文件仓库)

image.png

config-server 统一配置中心

pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <!--配置中心监控-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-monitor</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>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

application.yml

server:
  port: 18082
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          search-paths: /**    #远程仓库的目录,本例子配置文件在根目录中
          uri: https://github.com/wangqiang/MySpringCloudConfig.git   # 配置自己的git仓库地址
          #username: ''   # 访问git仓库的用户名   如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写,
          #password: ''   # 访问git仓库的用户密码
          default-label: master  # 配置仓库的分支

    #消息总线配置
    bus:
      enabled: true
      trace:
        enabled: true
      refresh:
        enabled: true  #开启总线消息更新功能

  # rabbitmq 配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: rabbit
    password: 123456
    virtual-host: /


eureka:
  client:
    service-url:
      defaultZone: http://localhost:18080/eureka/  #注册中心地址
    registry-fetch-interval-seconds: 20      # 多久拉取服务注册信息 表示从“发现”服务器获取注册表信息的频率(以秒为单位)。
  instance:
    # 60s未收到心跳,剔除instance 要比心跳时间大 服务器端等待的时间,因为它收到了最后的心跳,然后才可以从它的视图中删除这个实例,并且不允许流量进入这个实例。将这个值设置得太长可能意味着,即使实例不存在,流量也可以被路由到实例。将这个值设置得太小可能意味着,由于临时网络故障,该实例可能会被排除在流量之外。这个值的设置至少要高于leaseRenewalIntervalInSeconds中指定的值。默认90s
    lease-expiration-duration-in-seconds: 60
    # 心跳时间 即每15秒发送一次心跳  表明客户端需要将心跳发送到服务器端,以表明它还活着。如果心跳停止的时间超过了服务器设置的等待时间,那么服务器端将会从它的服务列表中删除该实例,从而将流量排除在该实例之外。默认30s
    lease-renewal-interval-in-seconds: 15
    prefer-ip-address: true
    ip-address: 127.0.0.1
    hostname: localhost
    instance-id: ${spring.cloud.client.ip-address}:${server.port}   #eureka client向eureka server注册使用真实ip设置 默认为本机机器名



# 屏蔽安全验证
management:
  endpoints:
    web:
      exposure:
        include: '*'  # 可以选择【"health","mappings","bus-refresh"】三种选项暴露那些端点

统一配置中心 启动类 ConfigServerApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableConfigServer  //标记为 Config Server
@EnableEurekaClient  //标记为eureka client
@SpringBootApplication
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

auth-server 业务的微服务

image.png

pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <exclusions>
                <!-- 排除默认日志插件 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</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-web</artifactId>
            <exclusions>
                <!-- 排除默认日志插件 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>

        <!-- druid 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.11</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>
        <!-- 使用log4j2 日志 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <!-- Log4j2 异步支持 -->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!-- sql 日志待参数输出 -->
        <dependency>
            <groupId>com.googlecode.log4jdbc</groupId>
            <artifactId>log4jdbc</artifactId>
            <version>1.2</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <!-- 排除默认日志插件 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <dependencies>
                    <!-- spring热部署 -->
                    <dependency>
                        <groupId>org.springframework</groupId>
                        <artifactId>springloaded</artifactId>
                        <version>1.2.8.RELEASE</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

bootstrap.yml

配置中心的客户端要使用bootstrap.yml
bootstrap.yml优先于 application.yml 加载 即 bootstrap.yml加载完后才加载 application.yml

server:
  tomcat:
    uri-encoding: UTF-8
  port: 18088

spring:
  application:
    name: auth-server  # 对应配置中心配置文件中的{application}部分 名称需要与子服务名称以及读取的配置文件库中文件名称一致   
  cloud:
    config:
      discovery:
        enabled: true  # 配置中心的servieId,即服务名
        service-id: config-server  # 配置中心的服务名
      label: master    # 指明远程仓库的分支
      profile: dev     # 对应配置中心配置文件中的{profile}部分 本例子中读取的配置文件为 auth-server-dev.yml dev开发环境配置文件  test测试环境   pro正式环境
      name: auth-server  # 对应配置中心配置文件中的{application}部分  多个用,隔开
      fail-fast: true
    #消息总线配置
    bus:
      enabled: true
      trace:
        enabled: true
      refresh:
        enabled: true  #开启总线消息更新功能


eureka:
  client:
    service-url:
      defaultZone: http://localhost:18080/eureka/  #注册中心地址
    registry-fetch-interval-seconds: 20      # 多久拉取服务注册信息 表示从“发现”服务器获取注册表信息的频率(以秒为单位)。
  instance:
    # 60s未收到心跳,剔除instance 要比心跳时间大 服务器端等待的时间,因为它收到了最后的心跳,然后才可以从它的视图中删除这个实例,并且不允许流量进入这个实例。将这个值设置得太长可能意味着,即使实例不存在,流量也可以被路由到实例。将这个值设置得太小可能意味着,由于临时网络故障,该实例可能会被排除在流量之外。这个值的设置至少要高于leaseRenewalIntervalInSeconds中指定的值。默认90s
    lease-expiration-duration-in-seconds: 60
    # 心跳时间 即每15秒发送一次心跳  表明客户端需要将心跳发送到服务器端,以表明它还活着。如果心跳停止的时间超过了服务器设置的等待时间,那么服务器端将会从它的服务列表中删除该实例,从而将流量排除在该实例之外。默认30s
    lease-renewal-interval-in-seconds: 15
    prefer-ip-address: true
    ip-address: 127.0.0.1
    hostname: localhost
    instance-id: ${spring.cloud.client.ip-address}:${server.port}   #eureka client向eureka server注册使用真实ip设置 默认为本机机器名

# 屏蔽安全验证
management:
  endpoints:
    web:
      exposure:
        include: '*'  # 可以选择【"health","mappings","bus-refresh"】三种选项暴露那些端点

业务微服务端 启动类 AuthServerApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class AuthServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthServerApplication.class, args);
    }
}

业务微服务端 测试类

在需要更新的配置类上加@RefreshScope注解,@RefreshScope必须加,否则客户端会收到服务端的更新消息,但是更新不了,因为不知道更新哪里的。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope  // 需要更新的配置类上加@RefreshScope注解,@RefreshScope必须加,否则客户端会收到服务端的更新消息,但是更新不了,因为不知道更新哪里的
public class Test {

    @Value("${name}")
    private String name;

    @GetMapping("test")
    public String test() {
        return name;
    }
}

依次启动 eurka-server -> config-server -> auth-server

访问远程git上的配置文件 http://127.0.0.1:18082/auth-server/dev

18082: 是统一配置中心的端口
/auth-server/dev 对应远程配置文件的auth-server-dev.yml
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml
name : 文件名,一般以服务名来命名
profiles : 一般作为环境标识
lable : 分支(branch),指定访问某分支下的配置文件

image.png
{
    "name": "auth-server",
    "profiles": [
        "dev"
    ],
    "label": null,
    "version": "69b2e80b7f8dbd6f84f24152a389bf091d5333dc",
    "state": null,
    "propertySources": [
        {
            "name": "https://github.com/wangqiang/MySpringCloudConfig.git/auth-server-dev.yml",
            "source": {
                "name": "之歌刘",
                "spring.rabbitmq.host": "127.0.0.1",
                "spring.rabbitmq.port": 5672,
                "spring.rabbitmq.username": "rabbit",
                "spring.rabbitmq.password": 123456,
                "spring.rabbitmq.virtual-host": "/"
            }
        }
    ]
}

业务微服务端 获取远程配置文件中的属性值

业务微服端的测试接口:http://127.0.0.1:18088/test

image.png

远程配置文件更新后,客户端自动更新

修改配置文件并提交到git上

name: 啊哈哈哈哈
  1. 首先发布一个post请求 http://localhost:18088/actuator/bus-refresh 手动刷新配置
    或者请求 http://localhost::18082/actuator/refresh
    18088 是配置客户端应用 config-client
    18082 是配置服务中心 config-server
    image.png

    image.png
image.png
  1. 再次访问测试接口 http://127.0.0.1:18088/test
    image.png

我们发现再不重启服务的情况下就能获取到修改的属性值

只要开启 Spring Cloud Bus 后,不管是对 config-server 还是 config-client 执行/actuator/bus-refresh都是可以更新配置的,
如果有多个客户端,多个客户端都会接收到刷新配置的消息,并刷新配置。

局部刷新配置(只刷新某些客户端)

某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过/actuator/bus-refresh/{destination}端点的 destination 参数来定位要刷新的应用程序。例如:/actuator/bus-refresh/customers:8000,这样消息总线上的微服务实例就会根据 destination 参数的值来判断是否需要要刷新。其中,customers:8000指的是各个微服务的 ApplicationContext ID。destination 参数也可以用来定位特定的微服务。例如:/actuator/bus-refresh/customers:**,这样就可以触发 customers 微服务所有实例的配置刷新。

集成WebHooks实现动态更新

远程配置文件修改后,不需要手动调用http://localhost:18088/actuator/bus-refresh 即可刷新配置

  1. 在远程配置文件的MySpringCloudConfig.git仓库中添加密钥


    image.png

    image.png
  2. 在远程配置文件git仓库中找到Webhooks


    image.png
  3. 点击Add webhook,添加一个webhook


    image.png
  • Payload URL: push 代码后自动运行的脚本 http://localhost:18082/actuator/bus-refresh(配置能够被git访问到的请求地址,根据git内外网情况自行配置)

  • Content type :我们选择application/json

  • Secret: 通讯密钥 用来认证身份

选择Just the push event.,因为我们只需要push的时候进行回调,然后添加即可

每当我们更新远程配置文件后,push到git后,就会触发Payload URL(配置的是访问刷新配置的地址)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • (git上的源码:https://gitee.com/rain7564/spring_microservices_...
    sprainkle阅读 6,846评论 4 25
  • 前言 在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都能连接上...
    二月_春风阅读 10,494评论 0 14
  • 前言 在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来...
    Chandler_珏瑜阅读 6,555评论 2 39
  • 这一周,反复听秋叶老师的定位课,努力给自己找新的职场定位。目前的线下定位做正面管教双语早教中心。把正面管教作为切口...
    陪一颗心长大的故事阅读 412评论 1 3