SpringCloud环境搭建与示例演示1(基于IDEA)

SpringCloud是一个工具集,它集成了多个工具,来解决微服务中遇到的各种问题,是微服务的整体解决方案。一个复杂的微服务系统,服务和服务之间不仅仅要解决远程调用的问题,随着整个系统复杂度的增加,在调用过程中也会出现大量新的问题(比如负载均衡,出错重试,降级熔断,监控,配置等),从而衍生出了许多新的技术和工具去解决这些问题。而SpringCloud的功能就是去将这些工具及其相关的问题整合起来,为开发者提供一站式的微服务系统的解决方案。我们学习SpringCloud,需要对其内集成的工具逐一学习,并理清每个工具的功能、配置及应用。

SpringCloud和Dubbo的区别:

它们都可以解决微服务的问题,SpringCloud的是微服务的全家桶,其内集成了很多的工具,每一个工具都可以解决一个单独的问题,比如远程调用,负载均衡,重试,降级,熔断,监控,配置中心,链路跟踪等,而dubbo仅仅是一个远程调用的工具,只解决远程调用这个单一的问题。

开发环境:IDEA,MAVEN,Lombok(辅助插件Maven Helper和EditStarters,插件如果无法通过IDEA直接安装,可以通过外部已经下载好的插件包,在本地安装)

Maven依赖问题:如果依赖报错或下载不了,就可以通过切换中央仓库与阿里云镜像仓库尝试解决,如果换仓库也无法解决,可以直接从外部复制粘贴,导入已下载好的依赖包到自己电脑的本地仓库即可。

注:如果指定了maven的settings文件中mirror为阿里镜像仓库(一般使用第二项配置),就是使用阿里云的镜像仓库,如果不配置任何仓库,默认就是用的中央仓库:中央仓库和阿里仓库还是不一样的,有些依赖的jar是可以的,但是有些maven的插件,阿里云maven私服可能没有,maven在构建项目,编译项目的时候,它需要很多自己的一些插件,比如一些编译的插件,一些打包的插件,或者是测试使用的一些插件,那这些maven插件,可能阿里的仓库里面根本没有,所以用阿里去更新是更新不到的,所以最好是用中心仓库更新,先用阿里更新,如果阿里更新不了,再换成中央仓库,如果中央仓库也更新不了,就再换回阿里的。

<mirror>
   <id>aliyun</id>
   <name>aliyun for maven</name>
   <mirrorOf>*</mirrorOf>
   <url>https://maven.aliyun.com/repository/public</url>
</mirror>
---------------------------------------------
     <mirror>
          <id>nexus-aliyun</id>
          <mirrorOf>central</mirrorOf>
          <name>Nexus aliyun</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public</url> 
      </mirror>

微服务项目案例:

业务需求:首先把一个项目拆分为三个微服务模块,分别为商品(item),用户(user) 和 订单(order)模块。业务服务包括商品服务item service,用户服务user service 和 订单服务order service,并让这三个微服务项目分别使用8001(item)、8101(user) 和 8201(order)三个端口。

 那么,在商品模块里面有两个功能,一个是根据订单的ID,来获取订单的商品列表(我们下一个订单,一个订单买了好几件商品,所以这个订单里会包含多个商品形成一个商品列表);还有一个是减少商品的库存(我们下订单购买商品时,那这些商品的库存量也要相应的减少)。在用户服务里也是两个功能,一个是根据用户ID获取用户(即获取一个用户的用户信息),另一个功能是增加用户积分(即在用户下订单时,用户会获得一定的积分,把这个积分累加到用户的积分信息里)。最后订单服务也是两个功能,一个是获取订单(根据订单ID,获取一个订单的订单信息);另一个是保存订单(即用户要购买商品,需要下一个订单)。需要注意的是,订单服务的两个功能,当我们获取订单时,这个订单当中要包含用户(即这个订单是哪个用户下的订单),所以这里我们要通过远程调用去调用用户服务中的,根据用户ID获取用户这个功能,获取这个订单的用户信息;然后,还要通过远程调用去调用商品服务中的,根据订单ID获取订单的商品列表功能(即我们获取一个订单,那这个订单里面要包含一组商品,在这个订单当中购买的一组商品也要相应的获取得到)。那当我们保存订单时,也是做了两个远程调用,一个是远程调用了用户服务中的增加用户积分功能(即用户下了一个订单,相应的把用户积分给他加上去);然后是远程调用了商品服务中的减少商品库存功能(即用户下订单购买了一些商品,这个商品的库存量也要相应的减掉),也都是做的远程调用。

 那么这个案例的代码只是个demo演示,但是涉及到了数据库存储数据,查询数据,我们这没有数据库,也没有Mybatis,我们的数据都是写死的,是模拟实现的,假的demo数据,比如要获取商品信息,商品的数据都是在代码里面写死的,假的数据,比如getItems(String orderId),根据订单的ID去获取商品,这里都是写在代码里的,真实项目中这些都是做数据库的查询,我们的重点在于微服务SpringCloud及其相关工具的使用,所以为了简化案例,具体的业务和数据库的交互过程,我们就一笔带过了。

 那么下面我们从第一个项目开始创建,第一个项目是公共项目,先不去关心商品,用户和订单,我们建立一个sp01-commons,通用项目,这个项目里存放一些公共的类,公共的代码,在商品,用户和订单这3个项目里面都会用到的类,使用的代码,把这些代码提取出来放到commons里,在后面创建商品,用户和订单项目时,让它们去依赖于commons项目,每一个子业务项目都依赖于commons通用项目,那这个commons项目写完之后,就相当于一个jar包,最终打包也是一个jar文件,在其他的子项目中就依赖于这个jar包即可,通用类,通用代码都放到这个jar包里面。那这个微服务案例的项目结构,并不是一个聚合项目,就做这种简单的依赖,创建一个通用项目commons,然后其他项目直接去依赖于这个通用项目就可以。通用代码放在一个通用项目当中,打成jar包,然后其他项目去依赖它。

项目一:创建通用项目sp01-commons

首先新建一个空的工程:File—New—Project:

 然后,在弹出的New Project创建中选择Empty Project,点击Next下一步:

修改Project location,自己规划目录:

 可以在这里新建自己要存放的目录:

 然后在Project location的路径之后,输入后面的子目录,上面的Project name输入框内容也会随之补全最后一级名称,我们取名为springcloud1:

 最后,点击Finish完成,可能弹出Direction Does Not Exist提示框,点击Create即可:

把这个新的工程创建好,然后点击File—New—Module...,新建一个Module模块:

 

 我们第一个通用项目commons,建的就是一个普通的maven项目,先不用SpringBoot项目,选择Maven,点击Next下一步:

 在弹出的New Module窗口中,首先选择目录,选择springcloud1这个工程目录,放在这个工程下面:

 然后,直接在Location位置最后一级目录下输入名称,其上的Name输入框也会随之联动补充:

 目录及名称相关设置如下:

 最后,点击Finish完成即可:

 第二步,选择File—Settings—Maven:

 关于使用的Maven版本Maven home directory无要求,用IDEA内置的Maven也是可以的,但一般建议换成自己安装的maven,并修改其配置文件settings.xml,这个User settings file配置文件只要写正确,存放的位置无所谓,放到任意的文件夹下都是可以的;还有Maven的本地仓库,一般也建议自己指定:

将maven配置完成后,修改commons的pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.tedu</groupId>
    <artifactId>sp01-commons</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp01-commons</name>
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-parameter-names</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jdk8</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.9.8</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-guava</artifactId>
            <version>2.9.8</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.6</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

 如果导入依赖后,pom.xml文件引入的spring-boot-start-parent版本报错如下:

则可以通过修改maven配置文件settings.xml,注释掉阿里云镜像仓库,默认为中央仓库重新下载依赖,重新下载之前可以先删除之前的下载报错文件:

接下来,完成这个项目当中的代码,首先是封装数据的pojo对象有3个,放到cn.tedu.sp01.pojo包下,分别是封装商品的商品类:Item.java,封装订单的订单类:Order.java,最后是封装用户的用户类:User.java;然后是3个项目的业务接口service,也放到这个通用项目commons里面,在cn.tedu.sp01.service包下,一个是商品业务:ItemService.java,一个是订单业务:OrderServie.java,还有一个是用户的业务:UserService.java;然后是3个工具类,在cn.tedu.web.util包下,分别是针对cookie操作的工具类(添加cookie,获取cookie等):CookieUtil.java,然后还有是json工具类(将json数据转换成java对象,将对象转换成json格式):JsonUtil.java,最后是封装响应数据,向客户端发送响应的一个vo工具类:JsonResult.java。

先写pojo的第一个Item类,封装商品的数据,商品的数据一个是商品的id,一个商品的名称name,还有一个商品的数量number,就这3个属性,然后类名之上加3个注解,自动生成get/set方法:@Data,无参构造器:@NoArgsConstructor 和 全参构造器:@AllArgsConstructor。

代码如下:

package cn.tedu.sp01.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Item {
    private Integer id;
    private String name;
    private Integer number;
}

 还有另外两个类,一个是用户,新建一个用户类User.java,User类中的属性,一个是用户id,还有用户名username和密码password,注解同样是@Data,@NoArgsConstructor 和 @AllArgsConstructor。

package cn.tedu.sp01.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String password;
}

然后再新建一个订单类:Order.java,Order的订单,它的id是字符串类型,之前jt里面用的订单id也是一个字符串类型uuid,然后订单当中,还要包含订单的用户user,表示这个订单是谁的,是属于哪个用户的订单,还有一个是商品的列表List<Item> items,这个订单里面可能购买了多件商品,注解同样是@Data,@NoArgsConstructor 和 @AllArgsConstructor。

Order.java 代码如下:

package cn.tedu.sp01.pojo;

import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    private String id;
    private User user;
    private List<Item> items;
}

 说明: 这里的pojo类不需要序列化,它们之间的通信都不是基于java序列化来通信的,都是json,所有数据都会被转成json再传输,当然加上序列化也是可以的,在使用dubbo时是用序列化来传输数据的,但是对于springcloud,它是用json格式,所有的对象都转成json再传输,所以就不需要java的序列化。

那这3个pojo类写完,下一步是写它们的业务接口,先是商品的ItemService,这个业务接口当中是要定义两个方法,一个是根据订单ID获取商品列表,在这个订单当中包含的商品:List<Item> getItems(String orderId);,还有一个是减少商品的库存,在下订单时,把所购买的这一组商品的库存要减掉:void decreaseNumber(List<Item> items);,这个接收的是一组商品对象,这个商品对象里面包含商品的数量,即每个对象里都有一个商品的数量number,那这个数量就相当于是用户要购买的这个商品的数量,可能是买一件,也可能是买8件,然后根据这个数量来减少库存,

 代码如下:

package cn.tedu.sp01.service;

import java.util.List;

import cn.tedu.sp01.pojo.Item;

public interface ItemService {
    List<Item> getItems(String orderId);
    void decreaseNumbers(List<Item> list);
}

然后是用户的这个业务接口,UserService.java,用户的操作一个是获取用户,根据ID获取用户信息,返回的是User对象:User getUser(Integer id);,第二个是增加用户积分,是用户下订单购买商品给用户增加相应的积分:void addScore(Integer id, Integer score);

 代码如下:

package cn.tedu.sp01.service;

import cn.tedu.sp01.pojo.User;

public interface UserService {
    User getUser(Integer id);
    void addScore(Integer id, Integer score);
}

然后是订单的业务接口,新建一个OrderService.java,订单的业务也是两个操作,一个是获取订单信息,获取订单返回一个Order订单实例,参数就是订单的ID,订单的id是字符串类型,根据订单ID来获取订单:Order getOrder(String orderId);,还有一个是保存订单,接收一个订单实例Order:void addOrder();

 代码如下:

package cn.tedu.sp01.service;


import cn.tedu.sp01.pojo.Order;

public interface OrderService {
    Order getOrder(String orderId);
    void addOrder(Order order);
}

 那下一步是添加3个工具类,第一个是CookieUtil:添加Cookie,获取Cookie的工具类;新建一个cn.tedu.web.util包,然后在这个包下,新建一个工具类CookieUtil,代码如下:

package cn.tedu.web.util;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieUtil {

    /**
     * @param response
     * @param name
     * @param value
     * @param maxAge
     */
    public static void setCookie(HttpServletResponse response,
            String name, String value, String domain, String path, int maxAge) {
        Cookie cookie = new Cookie(name, value);
        if(domain != null) {
            cookie.setDomain(domain);
        }
        cookie.setPath(path);
        cookie.setMaxAge(maxAge);
        response.addCookie(cookie);
    }
    public static void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
        setCookie(response, name, value, null, "/", maxAge);
    }
    public static void setCookie(HttpServletResponse response, String name, String value) {
        setCookie(response, name, value, null, "/", 3600);
    }
    public static void setCookie(HttpServletResponse response, String name) {
        setCookie(response, name, "", null, "/", 3600);
    }

    /**
     * @param request
     * @param name
     * @return
     */
    public static String getCookie(HttpServletRequest request, String name) {
        String value = null;
        Cookie[] cookies = request.getCookies();
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    value = cookie.getValue();
                }
            }
        }
        return value;
    }

    /**
     * @param response
     * @param name
     * @return
     */
    public static void removeCookie(HttpServletResponse response, String name, String domain, String path) {
        setCookie(response, name, "", domain, path, 0);
    }

}

第二个是JsonUtil,这个JsonUtil里面是用的Jackson的API来处理json数据,json格式的转换,json转成对象,或者对象转成json,代码如下:

package cn.tedu.web.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JsonUtil {
    private static ObjectMapper mapper;
    private static JsonInclude.Include DEFAULT_PROPERTY_INCLUSION = JsonInclude.Include.NON_DEFAULT;
    private static boolean IS_ENABLE_INDENT_OUTPUT = false;
    private static String CSV_DEFAULT_COLUMN_SEPARATOR = ",";
    static {
        try {
            initMapper();
            configPropertyInclusion();
            configIndentOutput();
            configCommon();
        } catch (Exception e) {
            log.error("jackson config error", e);
        }
    }

    private static void initMapper() {
        mapper = new ObjectMapper();
    }

    private static void configCommon() {
        config(mapper);
    }

    private static void configPropertyInclusion() {
        mapper.setSerializationInclusion(DEFAULT_PROPERTY_INCLUSION);
    }

    private static void configIndentOutput() {
        mapper.configure(SerializationFeature.INDENT_OUTPUT, IS_ENABLE_INDENT_OUTPUT);
    }

    private static void config(ObjectMapper objectMapper) {
        objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
        objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);
        objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        objectMapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        objectMapper.enable(JsonParser.Feature.ALLOW_COMMENTS);
        objectMapper.disable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
        objectMapper.enable(JsonGenerator.Feature.IGNORE_UNKNOWN);
        objectMapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
        objectMapper.registerModule(new ParameterNamesModule());
        objectMapper.registerModule(new Jdk8Module());
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.registerModule(new GuavaModule());
    }
    public static void setSerializationInclusion(JsonInclude.Include inclusion) {
        DEFAULT_PROPERTY_INCLUSION = inclusion;
        configPropertyInclusion();
    }

    public static void setIndentOutput(boolean isEnable) {
        IS_ENABLE_INDENT_OUTPUT = isEnable;
        configIndentOutput();
    }

    public static <V> V from(URL url, Class<V> c) {
        try {
            return mapper.readValue(url, c);
        } catch (IOException e) {
            log.error("jackson from error, url: {}, type: {}", url.getPath(), c, e);
            return null;
        }
    }

    public static <V> V from(InputStream inputStream, Class<V> c) {
        try {
            return mapper.readValue(inputStream, c);
        } catch (IOException e) {
            log.error("jackson from error, type: {}", c, e);
            return null;
        }
    }

    public static <V> V from(File file, Class<V> c) {
        try {
            return mapper.readValue(file, c);
        } catch (IOException e) {
            log.error("jackson from error, file path: {}, type: {}", file.getPath(), c, e);
            return null;
        }
    }

    public static <V> V from(Object jsonObj, Class<V> c) {
        try {
            return mapper.readValue(jsonObj.toString(), c);
        } catch (IOException e) {
            log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), c, e);
            return null;
        }
    }

    public static <V> V from(String json, Class<V> c) {
        try {
            return mapper.readValue(json, c);
        } catch (IOException e) {
            log.error("jackson from error, json: {}, type: {}", json, c, e);
            return null;
        }
    }

    public static <V> V from(URL url, TypeReference<V> type) {
        try {
            return mapper.readValue(url, type);
        } catch (IOException e) {
            log.error("jackson from error, url: {}, type: {}", url.getPath(), type, e);
            return null;
        }
    }

    public static <V> V from(InputStream inputStream, TypeReference<V> type) {
        try {
            return mapper.readValue(inputStream, type);
        } catch (IOException e) {
            log.error("jackson from error, type: {}", type, e);
            return null;
        }
    }

    public static <V> V from(File file, TypeReference<V> type) {
        try {
            return mapper.readValue(file, type);
        } catch (IOException e) {
            log.error("jackson from error, file path: {}, type: {}", file.getPath(), type, e);
            return null;
        }
    }

    public static <V> V from(Object jsonObj, TypeReference<V> type) {
        try {
            return mapper.readValue(jsonObj.toString(), type);
        } catch (IOException e) {
            log.error("jackson from error, json: {}, type: {}", jsonObj.toString(), type, e);
            return null;
        }
    }

    public static <V> V from(String json, TypeReference<V> type) {
        try {
            return mapper.readValue(json, type);
        } catch (IOException e) {
            log.error("jackson from error, json: {}, type: {}", json, type, e);
            return null;
        }
    }

    public static <V> String to(List<V> list) {
        try {
            return mapper.writeValueAsString(list);
        } catch (JsonProcessingException e) {
            log.error("jackson to error, obj: {}", list, e);
            return null;
        }
    }

    public static <V> String to(V v) {
        try {
            return mapper.writeValueAsString(v);
        } catch (JsonProcessingException e) {
            log.error("jackson to error, obj: {}", v, e);
            return null;
        }
    }

    public static <V> void toFile(String path, List<V> list) {
        try (Writer writer = new FileWriter(new File(path), true)) {
            mapper.writer().writeValues(writer).writeAll(list);
            writer.flush();
        } catch (Exception e) {
            log.error("jackson to file error, path: {}, list: {}", path, list, e);
        }
    }

    public static <V> void toFile(String path, V v) {
        try (Writer writer = new FileWriter(new File(path), true)) {
            mapper.writer().writeValues(writer).write(v);
            writer.flush();
        } catch (Exception e) {
            log.error("jackson to file error, path: {}, obj: {}", path, v, e);
        }
    }

    public static String getString(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).toString();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get string error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static Integer getInt(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).intValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get int error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static Long getLong(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).longValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get long error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static Double getDouble(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).doubleValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get double error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static BigInteger getBigInteger(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return new BigInteger(String.valueOf(0.00));
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).bigIntegerValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get biginteger error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static BigDecimal getBigDecimal(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).decimalValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get bigdecimal error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static boolean getBoolean(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return false;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).booleanValue();
            } else {
                return false;
            }
        } catch (IOException e) {
            log.error("jackson get boolean error, json: {}, key: {}", json, key, e);
            return false;
        }
    }

    public static byte[] getByte(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        try {
            JsonNode node = mapper.readTree(json);
            if (null != node) {
                return node.get(key).binaryValue();
            } else {
                return null;
            }
        } catch (IOException e) {
            log.error("jackson get byte error, json: {}, key: {}", json, key, e);
            return null;
        }
    }

    public static <T> ArrayList<T> getList(String json, String key) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        String string = getString(json, key);
        return from(string, new TypeReference<ArrayList<T>>() {});
    }

    public static <T> String add(String json, String key, T value) {
        try {
            JsonNode node = mapper.readTree(json);
            add(node, key, value);
            return node.toString();
        } catch (IOException e) {
            log.error("jackson add error, json: {}, key: {}, value: {}", json, key, value, e);
            return json;
        }
    }

    private static <T> void add(JsonNode jsonNode, String key, T value) {
        if (value instanceof String) {
            ((ObjectNode) jsonNode).put(key, (String) value);
        } else if (value instanceof Short) {
            ((ObjectNode) jsonNode).put(key, (Short) value);
        } else if (value instanceof Integer) {
            ((ObjectNode) jsonNode).put(key, (Integer) value);
        } else if (value instanceof Long) {
            ((ObjectNode) jsonNode).put(key, (Long) value);
        } else if (value instanceof Float) {
            ((ObjectNode) jsonNode).put(key, (Float) value);
        } else if (value instanceof Double) {
            ((ObjectNode) jsonNode).put(key, (Double) value);
        } else if (value instanceof BigDecimal) {
            ((ObjectNode) jsonNode).put(key, (BigDecimal) value);
        } else if (value instanceof BigInteger) {
            ((ObjectNode) jsonNode).put(key, (BigInteger) value);
        } else if (value instanceof Boolean) {
            ((ObjectNode) jsonNode).put(key, (Boolean) value);
        } else if (value instanceof byte[]) {
            ((ObjectNode) jsonNode).put(key, (byte[]) value);
        } else {
            ((ObjectNode) jsonNode).put(key, to(value));
        }
    }

    public static String remove(String json, String key) {
        try {
            JsonNode node = mapper.readTree(json);
            ((ObjectNode) node).remove(key);
            return node.toString();
        } catch (IOException e) {
            log.error("jackson remove error, json: {}, key: {}", json, key, e);
            return json;
        }
    }

    public static <T> String update(String json, String key, T value) {
        try {
            JsonNode node = mapper.readTree(json);
            ((ObjectNode) node).remove(key);
            add(node, key, value);
            return node.toString();
        } catch (IOException e) {
            log.error("jackson update error, json: {}, key: {}, value: {}", json, key, value, e);
            return json;
        }
    }

    public static String format(String json) {
        try {
            JsonNode node = mapper.readTree(json);
            return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
        } catch (IOException e) {
            log.error("jackson format json error, json: {}", json, e);
            return json;
        }
    }

    public static boolean isJson(String json) {
        try {
            mapper.readTree(json);
            return true;
        } catch (Exception e) {
            log.error("jackson check json error, json: {}", json, e);
            return false;
        }
    }

    private static InputStream getResourceStream(String name) {
        return JsonUtil.class.getClassLoader().getResourceAsStream(name);
    }

    private static InputStreamReader getResourceReader(InputStream inputStream) {
        if (null == inputStream) {
            return null;
        }
        return new InputStreamReader(inputStream, StandardCharsets.UTF_8);
    }
}

 然后,还有一个是JsonResult.java,这个JsonResult是封装响应数据的,就是jt里面的和SysResult一样,这里面就封装了3个数据,一个是code状态码,一个是字符串msg提示消息,还有一个是任意类型的一个对象data:

在这个JsonRsult对象里面呢,可能是发送给客户端的响应数据,封装在这个对象里面,代码如下:

package cn.tedu.web.util;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class JsonResult<T> {
    /** 成功 */
    public static final int SUCCESS = 200;

    /** 没有登录 */
    public static final int NOT_LOGIN = 400;

    /** 发生异常 */
    public static final int EXCEPTION = 401;

    /** 系统错误 */
    public static final int SYS_ERROR = 402;

    /** 参数错误 */
    public static final int PARAMS_ERROR = 403;

    /** 不支持或已经废弃 */
    public static final int NOT_SUPPORTED = 410;

    /** AuthCode错误 */
    public static final int INVALID_AUTHCODE = 444;

    /** 太频繁的调用 */
    public static final int TOO_FREQUENT = 445;

    /** 未知的错误 */
    public static final int UNKNOWN_ERROR = 499;
    
    private int code;
    private String msg;
    private T data;
    
    

    public static JsonResult build() {
        return new JsonResult();
    }
    public static JsonResult build(int code) {
        return new JsonResult().code(code);
    }
    public static JsonResult build(int code, String msg) {
        return new JsonResult<String>().code(code).msg(msg);
    }
    public static <T> JsonResult<T> build(int code, T data) {
        return new JsonResult<T>().code(code).data(data);
    }
    public static <T> JsonResult<T> build(int code, String msg, T data) {
        return new JsonResult<T>().code(code).msg(msg).data(data);
    }
    
    public JsonResult<T> code(int code) {
        this.code = code;
        return this;
    }
    public JsonResult<T> msg(String msg) {
        this.msg = msg;
        return this;
    }
    public JsonResult<T> data(T data) {
        this.data = data;
        return this;
    }
    
    
    public static JsonResult ok() {
        return build(SUCCESS);
    }
    public static JsonResult ok(String msg) {
        return build(SUCCESS, msg);
    }
    public static <T> JsonResult<T> ok(T data) {
        return build(SUCCESS, data);
    }
    public static JsonResult err() {
        return build(EXCEPTION);
    }
    public static JsonResult err(String msg) {
        return build(EXCEPTION, msg);
    }
    
    @Override
    public String toString() {
        return JsonUtil.to(this);
    }
}

 那以上通用项目commons就完成了,接下来是Item,user 和 order,这个三个服务都要依赖于commons:首先新建商品项目itemservice,新建SpringBoot起步项目:File -- Module... -- Spring Initializr:

Group:cn.tedu 、Artifact:sp02-itemservice、Type:Maven、Language:Java、Packaging:Jar、Java Version:8、Version:0.0.1-SNAPSHOT、Name:sp02-itemservice、Description:Demo Project for Spring Boot、Package:cn.tedu.sp02:

 

 这里只添加Spring Web依赖,SpringBoot的相关依赖,后面用到时再加:

 项目存放位置如下:

 项目建好之后,有两个配置文件需要调整一下,一个是pom.xml;一个是application.yml, 目录结构如下:

 首先pom.xml里面需要添加sp01-commons的依赖,添加依赖快捷键是Alt+insert,找到Dependency,弹出框直接搜索sp01,找到sp01-commons:

 添加如下依赖,这里可能找不到sp01-commons项目,可以先对sp01-commons执行Maven -- install操作:

 然后是配置application.yml,显示两条基本配置,一个是app.name...,查看提示,先给自己的应用起一个名字,叫item-service,这个名字的作用是将来向注册中心注册的时候,这个是作为一个服务名来进行注册的;然后还有一个配置是商品服务的端口:server.port,我选择的端口号是8001这个端口,

 

 注意:application.yml文件可能会没有提示功能,可能原因当前项目并没有加入Maven管理,比如sp02-itemservice项目:

 对于没有被标记为Maven的项目,可以通过选中当前项目的pom.xml文件,鼠标右键选择倒数第二项Add as Maven Project:

 

然后,在Maven界面中选择当前加入到Maven管理的项目,右键选择Generate Sources and Update Folders,一键生成Maven目录结构,前后目录对比(多了一个target):

 重新打开application.yml,发现这个文件的图标变成了小叶子,稍等片刻,就可以正常提示信息了:

商品项目里的代码是非常简单的,一个是业务的实现:ItemServiceImpl,把根据商品ID查询商品列表的操作,和减少商品库存的操作,把业务操作的代码实现出来,这里的代码是demo案例,数据都是写死的,包括在减少商品库存的时候,也只是加一个日志而已,其实并没有具体的业务代码;然后是ItemController,是设置两个访问路径@GetMapping("/{orderId}"),和 @PostMapping("/decreaseNumber"),通过这两个路径去调用商品业务的实现;首先新建一个ItemServiceImpl实现类,这个类是在cn.tedu.sp02.item.service包下:

 然后去实现sp01-commons中的ItemService这个接口(刚刚在pom.xml把sp01-commons作为依赖添加到当前的sp02-itemservice这个项目),并通过Alt+Enter,重写接口中的两个抽象方法getItems 和 decreaseNumbers,之后先把@Service和@Slf4j这两个注解加上,把这个bean交给Spring管理,并用Slf4j去输入日志;那么获得商品列表的实现,我们在getItems方法里写死这几件商品,直接返回,里面的代码都是demo,演示数据,直接new一个集合放几件商品,都是假的,即先创建一个商品列表List<Item> list = new LinkedList<>();,然后往这个列表里面添加几件商品,每件商品包含商品id,商品名称,还有商品的数量,我们加5件商品,然后商品数量随便给,任意指定商品的数量,无所谓的,最后return list;,返回这个list。然后,第二个方法decreaseNumber(List<Item> items);,减少商品的库存,将传入这个方法中的参数,即商品列表List<Item> items,那这个列表当中的这一组商品,我们要把它的库存都减掉,那就是for循环先遍历商品列表,然后log.info("减少商品库存:"+item);,只是输出几条日志就行,并没有真的去删除,所以在getItems(String orderId)方法中模拟的商品库存数量并没有减少。代码演示如下:

package cn.tedu.sp02.item.service;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.service.ItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.LinkedList;
import java.util.List;

@Service
@Slf4j
public class ItemServiceImpl implements ItemService {
    @Override
    public List<Item> getItems(String orderId) {
        List<Item> list  = new LinkedList<>();
        list.add(new Item(1,"商品1",1));
        list.add(new Item(2,"商品1",4));
        list.add(new Item(3,"商品1",2));
        list.add(new Item(4,"商品1",6));
        list.add(new Item(5,"商品1",2));
        return list;
    }

    @Override
    public void decreaseNumbers(List<Item> items) {
        for (Item item: items){
            log.info("减少商品库存:"+item);
        }
    }
}

 商品业务实现类ItemServiceImpl写完了,然后是ItemController类,放在cn.tedu.sp02.item.controller包下:

 依然是先把@RestController 和 @Slf4j 这两个注解加上,把这个Controller交给spring管理,并指定返回值为Json字符串;利用Slf4j去输出日志,这个Controller要调用ItemServiceImpl,所以先注入ItemService;然后是加两个路径分别访问Service层中的两个方法getItems和decreaseNumbers,第一个是get请求:getMapping("/{order}"),通过这个商品服务的根路径去访问,并传入一个orderId参数,即通过订单ID来查询订单当中的商品列表;然后第二个是post请求:@PostMapping("/decreaseNumber"),通过这个"/decreaseNumber"路径,去访问商品服务的减少商品库存的接口实现;

package cn.tedu.sp02.item.controller;

import cn.tedu.sp01.service.ItemService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class ItemController {
    @Autowired
    private ItemService itemService;

    @GetMapping("/{orderId}")

    @PostMapping("/decreaseNumber")
}

首先@GetMapping("/{orderId}"),这个路径接口对应的方法是返回一个vo层对象JsonResult,里面包含的是要发送给客户端封装的数据对象,就是这个JsonResult里指定的泛型是订单当中商品列表的集合List<Item>;那这个方法接收的参数,就是路径上传入的orderId这个参数,从路径接收@PathVariable String orderId,方法的返回值就是,直接调用注入的业务层接口的ItemService中的查询方法getItems(orderId),根据orderId查询商品列表,从而得到这个商品列表并返回,再将返回的这个商品列表封装到JsonResult对象中,即调用JsonResult.ok.data(items);,这个JsonResult的静态方法ok()的作用就是直接创建一个JsonResult对象,并封装这么一个状态码"200 ok",然后再调用它的一个data方法,直接传入查询到商品列表这个数据对象items这个集合;另外,我在这里再加一个数据port,这个数据是为了后面的集群测试,因为后面的商品服务,我们要添加两台业务的服务器,这两台服务器,它们的端口分别是8001和8002,现在我们做的只是其中的一个8001,后面还会加一个集群,再加一个8002端口的商品服务,那这样我们在调用时,可能会调用8001,也可能会调用8002,后面如果做了负载均衡,那有可能会在两台服务器之间轮循调用,所以在后面做负载均衡调用时,我们当前调用的到底是那台服务器,那这里可以通过返回它的端口号,通过这个端口号,就可以直接的看到我们现在执行的,到底是哪台服务器,请求的是8001,还是请求的是8002,我们把这个端口号也做为一个结果,然后在浏览器里面显示一下,这样是为了方便演示后面有一步,负载均衡的测试可以直观的看到这个结果。那首先我把这个服务器端口注入进来,加一个@Value("${server.port}");,那这个server.port注入的,实际上就是,我们在application.yml中配置的server.port这个8001端口号,注入的是这个属性:

 我们再加一个private int port;属性,然后@Value("${server.port}"),将server.port注入到这个变量,然后我们再在@GetMapping("/{orderId}"),这个接口对应的Controller方法getItems里,把这端口号port,还有路径上传入的订单ID-orderId,输出一个日志,一个是orderId参数,在这显示一下,还有一个port端口在后面也显示一下,加这么一条日志:log.info("orderId="+orderId+",port="+port);,然后另外,我再在返回的结果里面,我也放入这个端口port,也封装到响应的结果当中,为这个发送到客户端的JsonResult对象,再加一个msg把端口号也封装进去:return JsonResult.ok().data(items).msg("port="+port);,加这个端口的目的,纯粹是为了后面的负载均衡测试,只是为了功能测试,方便我们查看返回的结果到底是由哪台服务器执行的,后面还会做集群,有8001和8002两台商品服务器,那现在调用的到底是8001呢,还是8002呢,端口号就可以注入到这,这样后面在日志里面就可以观察到这个端口,另外响应到浏览器的数据,浏览器里面也可以显示这个端口号,把这个端口放在日志,然后也向浏览器来输出,这是控制层的getItems方法:

package cn.tedu.sp02.item.controller;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.service.ItemService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@Slf4j
public class ItemController {
    @Autowired
    private ItemService itemService;

    @Value("${server.port}")
    private int port;

    @GetMapping("/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId){
        log.info("orderId="+orderId+",port"+port);
        List<Item> items = itemService.getItems(orderId);
        return JsonResult.ok().data(items).msg("port="+port);
    }

    @PostMapping("/decreaseNumber")
}

 然后还有一个是post请求,@PostMapping("/decreaseNumber"),减少商品库存,把这个接口对应的方法也加上,返回的是JsonResult,这里面呢,没有任何的返回数据,这里就返回一个 "200,ok" 的结果就行,只是一个"200,ok",不包含数据的,然后接收的参数呢,我是从请求的协议体@RequestBody来接收,接收的直接是一个集合,商品集合List<Item> items,我们要把这一组商品,它的库存减掉,直接调用itemService.decreaseNumber(items);,最后返回呢,就返回用JsonResult封装的一个ok()结果,不用加其他的数据,如果想加的话,也可以加一条msg,加一条提示消息也行,这个提示消息也无所谓,其实加不加都行,这里跟一条消息,减少商品库存成功,return JsonResult.ok().msg("减少商品库存成功");

 完整代码如下:

package cn.tedu.sp02.item.controller;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.service.ItemService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Slf4j
public class ItemController {
    @Autowired
    private ItemService itemService;

    @Value("${server.port}")
    private int port;

    @GetMapping("/{orderId}")
    public JsonResult<List<Item>> getItems(@PathVariable String orderId){
        log.info("orderId="+orderId+",port"+port);
        List<Item> items = itemService.getItems(orderId);
        return JsonResult.ok().data(items).msg("port="+port);
    }

    @PostMapping("/decreaseNumber")
    public JsonResult decreaseNumber(@RequestBody List<Item> items){
        itemService.decreaseNumbers(items);
        return JsonResult.ok().msg("减少商品库存成功");
    }
}

 说明:在上述代码中用到了一个注解@RequestBody。如果是get请求则通过地址栏发送请求URL和请求参数,请求参数跟在请求路径之后,以问号分隔,每个请求参数之间,以&符号进行分隔;post请求的请求参数则是通过协议实体进行传输,那在服务端,如果是Servlet去接收请求的单个参数,无论是get,还是post请求,都是通过Request对象getParameter("param")方法,其中传入参数名称去接收,如果是用springmvc去接收的话,则可以通过@RequestParam String param;,通过@RequestParam的注解直接去接收名为param的单个参数(如果是接收单个参数@RequestParam是可以省略的);那对于post请求,如果提交的是一个json格式的数据,不是键值对的形式,那就需要把post请求协议体中的内容完整的接收过来,那如何完整的得到协议体中的所有数据呢,就是通过这个注解@RequestBody,这个协议体数据如果是字符串,可以直接指定String类型去接收,@RequestBody String json;,也可以直接完成类型的转换,直接指定为所需要的对象,比如@RequestBody List<Item> items;,我们这里就是这样定义的,因为我们传输的是一个一个商品对象的json数据,所以直接转成了我们所需要的List集合,里面包含商品对象。即完整接收协议体的json数据,并自动转换成一个商品的集合,就是@RequestBody可以直接从post请求它的协议体当中来接收完整的协议体数据,即@RequestBody注解的作用。接收JSON用@RequestBody;返回JSON用@ResponseBody。

那到这里,这个商品项目sp02-itemservice这个服务中的一个ItemServiceImpl,还有一个ItemController,就都写完了,那下一步就可以直接启动这个项目进行测试,那项目的测试连接为:

(1)根据orderId,查询商品:http://localhost:8001/35

(2)减少商品库存:http://localhost:8001/decreaseNumber

那么第二个请求是post请求,它是不能直接用浏览器访问的,我们需要使用postman工具。

--------------------------------------------------------------------------------------------------------------------------------------------------------

首选启动sp02-itemservice服务,这里通过 idea 显示services窗口,管理所有服务

 1、找到项目中.idea文件夹中的workspace.xml

2、找到RunDashboard坐标,将下面内容替换上去,然后清除缓存重启

 <component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
  </component>

3、最后找到tool windows

 -----------------------------------------------------------------------------------------------------------------------------------------------------

首先是启动sp02-itemservice项目:

选择Spring Boot:

 

 或者选择Application:

 启动之后访问商品列表,http://localhost:8001/323223 ;端口8001,然后后面给一个订单id,这个订单ID无所谓,给什么都行,我们的数据都是假的代码,任意给订单ID都可以,写一个订单ID来获取这个订单的商品:

 然后,还有一个地址是 http://localhost:8001/decreaseNumber,那这个地址就不能用浏览器去直接访问,因为我们服务器端代码里设置的处理的HTTP请求是只能处理POST请求:

@PostMapping("/decreaseNumber")

 GET请求不接受,用POST请求调用这个路径,那这样我们就得用postman这个工具来做,那postman的使用如下:

响应结果如下:

控制台输出如下:

工具Postman在 课前资料/软件/postman中,在安装postman时可能会要求更新install the .NET Framework...,安装即可(会提示自动安装):

 

第一次启动postman会提示注册/登录,无需注册或登录,直接关闭窗口,即可使用:

另外,可以发送post请求的工具有很多,比如Idea自带的 rest client插件:

还有IDEA自带的HTTP Client:在 Tools目录下:

 使用IDEA自带的HTTP Client工具,必须自己指定协议类型:Content-Type:application/json

 发送请求,也是可以成功获得响应结果的,如下(看起来并不如postman好用):

那我们的商品项目sp02-itemservice就完成了,下面来看userservice用户服务项目,用户项目和商品项目类似,代码也是一个UserServiceImpl和一个UserController,业务服务里也是有两个操作,一个是getUser(Integer),根据用户的ID来查询用户信息这么一个业务方法,因为这里没有底层数据库,没有数据库的话,那在这里加一个测试数据,可以在application.yml里面放一个 sp.user-service.users 的自定义属性,提供用于测试的用户数据,是一个json字符串,里面包含3个用户的用户数据:

sp:
  user-service:
    users: "[{"id":7, "username":"abc","password":"123"},{"id":8, "username":"def","password":"456"},{"id":9, "username":"ghi","password":"789"}]"

 那这个数据,我们需要把它注入到这个UserService的业务对象里面,

    @Value("${sp.user-service.users}")
    private String userJson;

然后在这个业务当中需要去查询用户的时候,就从这个userJson中查询,从userJson在application.yml文件中配置的那3个用户数据里面查询,循环遍历一个用户一个用户的比较id,找到id后返回,如果这id为789的这3个用户里面没有,没有就返回一个新建的用户实例,用户名和密码都用拼接的方式拼出来,代码如下:

    @Override
    public User getUser(Integer id) {
        log.info("users json string : "+userJson);
        List<User> list = JsonUtil.from(userJson, new TypeReference<List<User>>() {});
        for (User u : list) {
            if (u.getId().equals(id)) {
                return u;
            }
        }
        
        return new User(id, "name-"+id, "pwd-"+id);
    }

那现在先把这个项目sp03-userservice,先创建出来,新建一个Module模块:新建SpringBoot起步项目:File -- Module... -- Spring Initializr:

 Group:cn.tedu 、Artifact:sp03-userservice、Type:Maven、Language:Java、Packaging:Jar、Java Version:8、Version:0.0.1-SNAPSHOT、Name:sp03-userservice、Description:Demo Project for Spring Boot、Package:cn.tedu.sp03:

 

然后, 后面添加的依赖还是只有一个Spring Web依赖:

这个项目放到的位置在springcloud1这个工程之下:

 创建好sp03项目之后,还是修改pom.xml和application.yml这两个配置文件:

注意:可能当前项目并没有加入Maven管理,比如sp03-userservice项目,点击Maven视图中的加号:

选择sp03-userservice中的pom.xml,添加到Maven视图进行管理:

 首先修改pom.xml文件,添加sp01-commons的依赖,直接在dependencies里键入sp,就会自动回显sp01-commons的依赖,选择即可:

 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>sp03-userservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp03-userservice</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>sp01-commons</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        
    </dependencies>

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

</project>

注:关于引入spring-boot-maven-plugin这个插件时可能会报错: Not found version...,此时可以为其指定一个低版本的插件重新下载,这样即便删除了版本号,springboot也会默认记住当前引入的插件版本,报错解决;

还有一个要修改的是application.yml,首先把application.properties的后缀修改为yml:

 首先是app.name,自动提示,应用名起一个名叫user-service,然后是服务端口号server: port:8101,刚才商品的端口号是8001,另外就是测试用的用户数据,添加了id为7,8,9的3个用户数据,代码如下:

sp:
  user-service:
    users: "[{"id":7, "username":"abc","password":"123"},{"id":8, "username":"def","password":"456"},{"id":9, "username":"ghi","password":"789"}]"

spring:
  application:
    name: user-service
    
server:
  port: 8101

 然后写用户的业务实现:UserServiceImpl.java;放在cn.tedu.sp03.user.service包下,

 

 然后这个业务实现类里,首先添加注解@Service和@Slf4j,然后注入刚才在application.yml配置文件里面配置的测试用的用户数据@("${sp.user-service.users}"),把这个数据注入到这个UserServiceImpl业务对象里面,放到private String userJson;,这个是一个用户的json格式数据;然后后面getUser的时候,就先把这个userJson先做一下转换,先转换成用户的一个集合,这里我们直接用JsonUtil这个工具来转,JsonUtil.from(userJson, new TypeReference<List<User>>(){});,从userJson这个json格式来做转换,那转换的类型,这里是使用的是TypeReference来设置这个类型,这里设置成用户对象的List集合类型,这里的TypeReference是由com.fasterxml.jackson.core.type提供的,这个TypeReference这个工具的作用,仅仅是为了能使用这个泛型语法List<Item>,来设置这个转换的类型而已,写转换的类型,转换成一个用户的集合,那这样我们得到的就是一个用户集合,List<User> users。那JsonUtil.from(userJson, new TypeReference<List<User>>(){});,这个就是把用户的Json数据转换成用户集合;

这里需要注意的是JsonUtil.from(userJson, new TypeReference<List<User>>(){});,意思是把这个userJson的json格式,转换成一个对象,那一般写转换类型的时候,是这么写,比如要转换成User对象,那可以写User.class,即JsonUtil.from(userJson, User.class);,把userJson字符串直接转成一个User对象,那或者这个userJson的json字符串是一个集合,有多个User对象,那集合的话,写的是List.class,即JsonUtil.from(userJson, List.class);,可以转成User对象,也可以转成List集合;但是如果要想精确的写出List里面它包含的是User,如果要用泛型语法List<User>.class,或者List<User.class>.class,这么写的话,那这个语法上是不支持的,没有这种写法,不能这么写,就java对泛型的语法有的是不支持的,那如果想写List集合类型,这类型里面要包含一个User用户类型的对象,那没有现成的语法可以写,那com.fasterxml.jackson.core.type,这个jackson的API提供了这么一个解决办法,就是用匿名内部类的格式来写,即new TypeReference<List<User>>(){},这个是一个匿名内部类的格式,继承TypeReference,写它的一个匿名子类,但这个匿名子类定义出来其实没什么用,包括创建的这个TypeReference对象,本身也是没什么用的,任何的作用都没有,那我们这么写的目的呢,仅仅是为了使用继承这种语法,在继承语法里面,可以写这个List<User>的泛型类型,其实这里创建的TypeReference,它的这个匿名子类对象都没用,主要就是为了写这个List<User>,要List<User>这个语法而已,即用继承的语法来写参数中指定集合套对象的类型,因为java是没有提供现成的参数为嵌套泛型类型的语法,所以Jackson的API提供了这么一个解决办法,用匿名内部类的格式来指定参数为嵌套泛型的语法格式。

之后就是for循环,遍历这些用户,一个用户一个用户的去判断,去判断这些用户的id,if(user.getId().equals(id)){...},看这个id和参数传过来的id是不是相同,如果相同的话呢,就直接返回这个用户,那如果找不到的话,那最后我也返回一个假的,这么一个用户数据,new一个User,

后面的用户名和密码,我都用拼接的方式,来把它拼出来,一个是name拼一个id,一个是password,拼一个id,这是这个获取用户;首先for循环遍历id为7,8,9这个3个测试用的数据,找到的话,直接返回,那如果找不到用户,最后也new一个User,里面的数据直接写死,写死这个用户的用户名和密码。

然后下面第二个业务操作addScore(Integer id, Integer score),增加这个用户积分,增加用户积分呢,我们在这是仅仅记录一条日志输出传入的参数id和score,即log.info("增加用户积分,userId="+id+",score="+score);,用户的业务实现类UserServiceImpl码如下:

package cn.tedu.sp03.user.service;

import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.UserService;
import cn.tedu.web.util.JsonUtil;
import com.fasterxml.jackson.core.type.TypeReference;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Value("${sp.user-service.users}")
    private String userJson;

    @Override
    public User getUser(Integer id) {
        List<User> users = JsonUtil.from(userJson, new TypeReference<List<User>>() {});
        for (User user:users){
            if (user.getId().equals(id)){
                return user;
            }
        }
        return new User(id, "name-"+id, "pwd-"+id);
    }

    @Override
    public void addScore(Integer id, Integer score) {
      log.info("增加用户积分,userId="+id+",score="+score);
    }
}

那接下来,再来写这个UserController.java,放在cn.tedu.sp03.user.controller包下:

 然后加注解@RestController,也加一个@Slf4j,首先是注入业务对象UserService,然后是它的两个接口路径,一个是@GetMapping("/{userId}"),接收一个userId,根据用户ID来查询用户,然后还有一个也GetMapping("/{userId}/score"),它的路径呢是接收一个userId路径参数,然后后面再跟一个score子路径;那这个路径是这样的,比如为用户ID为8的用增加1000积分,则路径为http://localhost:8101/8/score?score=1000,即路径里面有一个用户ID,然后后面用问号的格式一个score参数,再跟一个积分值,那后面我们接收参数的话呢,要同时接收userId和score这两个,一个用户ID,一个积分值,给8 这个用户增加1000积分,这样来传参。

那首先写第一个路径GetMapping("/{userId}/score")对应的方法:返回的是JsonResult封装的一个User对象,参数呢是路径参数@PathVariable Integer userId;,接收一个userId,public JsonResult<User> getUser(@PathVariable Integer userId){...},在这个方法里呢,直接调用User user = userService.getUser(userId);,然后后面返回的时候呢,我们是用JsonResult做一下封装,JsonResult.ok(),先创建一个JsonResult对象,然后呢,再用data去放入要封装的数据对象,return JsonResult.ok().data(user);,这是第一个方法;

然后第二个方法,public JsonResult addScore(@PathVariable Integer userId, Integer score){...},返回的是JsonResult,然后是增加用户的积分addScore,那接收参数的话,我们要接收suerId和Score这两个参数,一个路径路径参数用户ID,然后还有一个参数是用问号携带的这个Score参数,那就直接写一个变量名score就行;然后这个方法内部调用的是userService.addScore(userId, score);,传入一个用户的userID,和一个score积分值,然后是返回一个JsonResult,它的ok结果,然后再加一条msg,提示消息,增加用户积分成功,return JsonResult.ok().msg("增加用户积分成功");,

那么,UserController完整代码如下:

package cn.tedu.sp03.user.controller;

import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.UserService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{userId}")
    public JsonResult<User> getUser(@PathVariable Integer userId){
        User user = userService.getUser(userId);
        return JsonResult.ok().data(user);
    }

    //http://..../8/score?score=1000
    @GetMapping("/{userId}/score")
    public JsonResult addScore(@PathVariable Integer userId, Integer score){
        userService.addScore(userId, score);
        return JsonResult.ok().msg("增加用户积分成功");
    }
}

那这个用户业务的项目sp03-userservice 就完成了,现在就可以启动这个sp03项目来测试了,测试的链接如下:

(1) 根据userId查询用户信息:http://localhost:8101/7 ,分别访问用户7 , 用户8 和 用户9,这789的3个用户是我们设置的测试用的用户数据,运行结果:

 那如果访问用户10,那10是直接拼出来的,代码里面如果不是789,就拼了一个name,连上一个id,然后password,也连上 一个id:

(2)根据userid,为用户增加积分:http://localhost:8101/7/score?score=100,返回结果如下:

然后控制台里面,输出了一条日志,显示增加用户积分,给哪一个用户增加了多少积分:

 那sp03-userservice这个项目就完成了,下面就开始订单项目sp03-orderservice,再新建一个Module模块:新建SpringBoot起步项目:File -- Module... -- Spring Initializr:

 Group:cn.tedu 、Artifact:sp04-orderservice、Type:Maven、Language:Java、Packaging:Jar、Java Version:8、Version:0.0.1-SNAPSHOT、Name:sp04-orderservice、Description:Demo Project for Spring Boot、Package:cn.tedu.sp04:

 

 然后, 后面添加的依赖依然还是只有一个Spring Web依赖:

这个项目放到的位置也在springcloud1这个工程之下:

 

  创建好sp04项目之后,还是修改pom.xml和application.yml这两个配置文件;首先是pom.xml,依然是添加好sp01-commons的依赖:

 代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>sp04-orderservice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sp04-orderservice</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>cn.tedu</groupId>
            <artifactId>sp01-commons</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        
    </dependencies>

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

</project>

 如果sp04-orderservice项目没有自动加入到Maven管理,则手动添加:Maven视图点击加号,选择sp04-orderservice的pom.xml,点击即可:

 在Maven视图中,右键选择Generate Source and Update Folders,点击刷新Maven视图,就可以看到sp04-orderservice就添加到Maven管理了:

然后是application.yml配置文

这个application.yml的配置呢,一个是app.name...提示信息,是order-service;然后还有一个是server.port端口,是8201:

 代码如下:

spring:
  application:
    name: order-service

server:
  port: 8201

然后是控制器和业务层,先是实现类OrderServiceImple.java,放在cn.tedu.sp04.order.service包下,在这个类中实现了OrderService接口中的两个方法,一个是获取订单:getOrder(String orderId){...};一个是提交订单:addOrder(Order order){...};那么获取订单的时候,我们这的代码是假的,直接新建一个Order实例,Order order = new Order();,然后把传入的参数orderId放进去,放到订单对象里面,order.setId(orderId);

    @Override
    public Order getOrder(String orderId) {
        //TODO: 调用user-service获取用户信息
        //TODO: 调用item-service获取商品信息
        Order order = new Order();
        order.setId(orderId);
        return order;
    }

那么这个Order对象里,除了id之外,还有两属性,用户:private User user;和商品列表:private List<Item> items;,那如果要获取用户的话,就需要去远程调用用户的服务userService.getUser(Integer id);,那如果是商品列表的话,那么也是一样,去远程调用商品的服务,来活动商品列表itemService.getItems(String orderId);,远程调用用户获取用户,远程调用商品获取商品列表,Oder订单中的user和items是需要远程调用来获取这两个数据的:

public class Order {
    private String id;
    private User user;
    private List<Item> items;
}

但是现在在做远程调用之前需要先把item,user,order这几个项目案例先完成,完成案例之后,我们再去用springcloud怎么去做这个远程调用,之前使用dubbo做远程调用,那现在用springcloud做远程调用,那等后面开始做远程调用时,再回来补充订单服务这里获取用户信息和商品信息的代码,后面要通过远程调用用户服务user-service获取用户信息,比如得到一个user,远程调用商品服务item-service得到一个商品的集合,比如items,那获得这两个数据之后,再去order.setUser(user);和order.setItems(items);,把用户放到订单里面:

 那么现在呢,还没有做远程调用,那么就暂时往里面只放一个id,就没有用户,也没有商品,这里分别 用TODO注释去标识后面将要完成的工作,等到springcloud远程调用时再说,现在先空着;然后addOrder(Order order){...},保存订单这也是一样,保存订单的时候,也要去远程调用商品服务item-service的接口方法,void decreaseNumbers(List<Item> list);,减少商品库存,他在这个订单里面购买了几件商品,我们要把这几件商品的库存减掉,然后还有是远程调用用户服务的user-service的addScore接口方法,void addScore(Integer id, Integer score);,来增加用户的积分,把它加上去,他购买了几件商品,下了一个订单,增加这个用户的积分,这也都是通过远程调用来实现这两个功能,这里远程调用的代码都先空着,后面用springcloud远程调用的时候,再回来补充这段代码:

代码如下:

package cn.tedu.sp04.order.service;

import org.springframework.stereotype.Service;

import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.service.OrderService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Override
    public Order getOrder(String orderId) {
        //TODO: 调用user-service获取用户信息
        //TODO: 调用item-service获取商品信息
        Order order = new Order();
        order.setId(orderId);
        return order;
    }

    @Override
    public void addOrder(Order order) {
        //TODO: 调用item-service减少商品库存
        //TODO: 调用user-service增加用户积分
        log.info("保存订单:"+order);
    }

}

这是订单的OrderServiceImpl,然后是OrderController.java,放在cn.tedu.sp04.order.controller包下:

这里的代码也是假的代码,获取订单就直接调用orderService.getOrder(orderId);去获取;然后保存订单,保存订单在这是创建了一个假的订单对象,新建这么一个订单实例,Order order = new Order();,然后给这订单设置一个订单id,order.setId("123abc");,这个id呢,随便,写啥都行,随便给一个ID,然后setUser也是,随便给一个用户,order.setUser(new User(7,null,null));,其实就要一个用户id就行,设置一个用户,然后再set一个Items,商品列表,order.setItems(...);在后面是模拟这么几件商品,加到订单里面,这是创建一个订单是里,然后在订单里面放入几项数据,id,user,还有商品列表,之后就是调用orderService.addOrder(order);,这个保存订单的方法,最后返回一个return JsonResult.ok();,这么一个结果:

这就是获取订单 和 保存订单, 代码如下:

package cn.tedu.sp04.order.controller;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.OrderService;
import cn.tedu.web.util.JsonResult;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;

    @GetMapping("/{orderId}")
    public JsonResult<Order> getOrder(@PathVariable String orderId) {
        log.info("get order, id="+orderId);

        Order order = orderService.getOrder(orderId);
        return JsonResult.ok(order);
    }

    @GetMapping("/")
    public JsonResult addOrder() {
        //模拟post提交的数据
        Order order = new Order();
        order.setId("123abc");
        order.setUser(new User(7,null,null));
        order.setItems(Arrays.asList(new Item[] {
                new Item(1,"aaa",2),
                new Item(2,"bbb",1),
                new Item(3,"ccc",3),
                new Item(4,"ddd",1),
                new Item(5,"eee",5),
        }));
        orderService.addOrder(order);
        return JsonResult.ok();
    }
}

这就是sp04-orderservice,订单的业务案例。那现在就可以启动这个sp04项目来测试了,测试的链接如下:

(1)根据orderId,获取订单:http://localhost:8201/123abc ,测试结果如下:

(2)保存订单:http://localhost:8201/

观察控制台日志输出:

 到这里,sp01到sp04的各个服务项目的准备工作就做好了。后续内容参见:SpringCloud环境搭建与示例演示2(基于IDEA):

参考文章:
1. 稻火 / idea 显示services窗口,管理所有服务:https://blog.csdn.net/lzkcsdn1/article/details/105268815/

后续:当前Chrome浏览器打开文件乱码时,是因为浏览器不能自动识别文件的编码格式,可以通过一个插件选择浏览器的编码格式;

原文地址:https://www.cnblogs.com/HarryVan/p/13717267.html