任务概述:
- 使用SpringCloud重构账单项目,开发基于RESTful的webservice接口,创建Eureka Server,把账单微服务注册到注册中心
- 重构账单项目,使用SpringCloud,开发搭建网关微服务
- 账单配置微服务,统一由码云管理账单微服务配置文件
MySql数据库表结构
创建Maven父工程项目,导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wcf</groupId>
<artifactId>docker-bill-springcloud</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<mapper.starter.version>2.1.5</mapper.starter.version>
<mysql.version>5.1.46</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
新建Module (eureka-server)
导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>docker-bill-springcloud</artifactId>
<groupId>com.wcf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
springboot启动器:EurekaServerApplication
@SpringBootApplication
@EnableEurekaServer //声明当前服务是eureka服务
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
配置文件:application.yml
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# eureka服务地址,如果是集群的话,需要指定其他集群eureka地址
defaultZone: http://127.0.0.1:10086/eureka
# 不注册自己
register-with-eureka: false
# 不拉取服务
fetch-registry: false
新建Module (config-server)
导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>docker-bill-springcloud</artifactId>
<groupId>com.wcf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</project>
springboot启动器:ConfigServerApplication
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
配置文件:application.yml
server:
port: 12000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/request-peak/my-config.git
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
注意:config-server配置文件里的cloud.config.server.git.uri的地址是自己gitee仓库里配置文件的链接!需自己提前写好。例如我的是bill-dev.yml,对应的uri获取如下:
新建Module (bill-service)
导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>docker-bill-springcloud</artifactId>
<groupId>com.wcf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bill-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<!--Eureka客户端-->
<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>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
SpringBoot启动器:BillManagerApplication
@SpringBootApplication
@MapperScan("com.wcf.dao")
@EnableDiscoveryClient //开启Eureka客户端发现功能
public class BillManagerApplication {
public static void main(String[] args) {
SpringApplication.run(BillManagerApplication.class,args);
}
}
配置文件bootstrap.yml
spring:
cloud:
config:
# 要与仓库中的配置文件的application保持一致
name: bill
# 要与仓库中的配置文件的profile保持一致
profile: dev
# 要与仓库中的配置文件所属的版本(分支)一样
label: master
discovery:
# 使用配置中心
enabled: true
# 配置中心服务名
service-id: config-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
实体类
Bill
@Data
@Table(name = "bill_")
public class Bill implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_")
private Long id;
@Column(name = "title_")
private String title;
@Column(name = "bill_time_")
private Date bittTime;
@Column(name = "type_id_")
private Long typeId;
@Column(name = "price_")
private Double price;
@Column(name = "explain_")
private String explain;
/**
* 类别名称
*/
@Transient //表示当前属性为瞬时属性,跟字段无映射
private String typeName;
/**
* 开始时间
*/
@Transient
private Date date1;
/**
* 结束时间
*/
@Transient
private Date date2;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getBittTime() {
return bittTime;
}
public void setBittTime(Date bittTime) {
this.bittTime = bittTime;
}
public Long getTypeId() {
return typeId;
}
public void setTypeId(Long typeId) {
this.typeId = typeId;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getExplain() {
return explain;
}
public void setExplain(String explain) {
this.explain = explain;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public Date getDate1() {
return date1;
}
public void setDate1(Date date1) {
this.date1 = date1;
}
public Date getDate2() {
return date2;
}
public void setDate2(Date date2) {
this.date2 = date2;
}
}
BillType
@Table(name = "bill_type_")
public class BillType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_")
private Long id;
@Column(name = "name_")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Result
public class Result {
private int code; //响应状态
private String msg;//响应消息
private Long count;//数据条数
private List<Bill> data;//响应数据
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", count=" + count +
", data=" + data +
'}';
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
public List<Bill> getData() {
return data;
}
public void setData(List<Bill> data) {
this.data = data;
}
}
dao
BillMapper
public interface BillMapper extends Mapper<Bill> {
public List<Bill> select(Bill b);
}
TypeMapper
public interface TypeMapper extends Mapper<BillType> {
}
service
BillService
@Service
public class BillService {
@Autowired
private BillMapper billMapper;
public List<Bill> list(Bill b){
return billMapper.select(b);
}
public int add(Bill b){
return billMapper.insert(b);
}
public Bill get(Long id){
return billMapper.selectByPrimaryKey(id);
}
public int delete(Long id){
return billMapper.deleteByPrimaryKey(id);
}
public int update(Bill b){
return billMapper.updateByPrimaryKey(b);
}
public PageInfo<Bill> listPage(Bill b, int pageNum, int pageSize){
return PageHelper.startPage(pageNum, pageSize).doSelectPageInfo(()->{
billMapper.select(b);
});
}
}
TypeService
@Service
public class TypeService {
@Autowired
private TypeMapper typeMapper;
public List<BillType> list(){
return typeMapper.selectAll();
}
}
controller
@RestController
@RefreshScope //刷新配置
@RequestMapping("/bill")
public class BillController {
@Autowired
private BillService billService;
@Autowired
private TypeService typeService;
/**
* 分页查询
* @param page
* @param limit
* @param date1
* @param date2
* @param typeId
* @return
*/
@RequestMapping("/list-page")
public String listPage(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int limit,
Date date1, Date date2,@RequestParam(defaultValue = "0") String typeId){
Bill bill=new Bill();
bill.setTypeId(new Long(typeId));
bill.setDate1(date1);
bill.setDate2(date2);
List<BillType> types = typeService.list();
PageInfo<Bill> pageInfo = billService.listPage(bill, page, limit);
List<Bill> list = pageInfo.getList();
Result result=new Result();
result.setData(list);
result.setCode(0);
result.setCount(pageInfo.getTotal());
String jsonData = JSON.toJSONString(result);
return jsonData;
}
/**
* 查询
* @param b
* @param model
* @return
*/
@RequestMapping("/list")
public String list(Bill b, Model model){
List<BillType> types = typeService.list();
model.addAttribute("types", types);
List<Bill> bills = billService.list(b);
model.addAttribute("bills", bills);
Result result = new Result();
result.setData(bills);
result.setCode(0);
System.out.println(result);
String jsonData = JSON.toJSONString(result);
return jsonData;
}
/**
* 添加
* @param b
* @return
*/
@RequestMapping("/add")
public String add(Bill b,String feedback){
System.out.println(feedback);
System.out.println(b.getTitle());
int add = billService.add(b);
Result result = new Result();
if (add > 0) {
result.setMsg("1");
String jsonData = JSON.toJSONString(result);
return feedback+"["+jsonData+"]";
}
result.setMsg("0");
String jsonData = JSON.toJSONString(result);
System.out.println(jsonData);
return feedback+"["+jsonData+"]";
}
/**
* 修改
* @param b
* @return
*/
@RequestMapping("/update")
public String update(Bill b,String feedback){
System.out.println(b);
int update = billService.update(b);
Result result = new Result();
if (update > 0) {
result.setMsg("1");
String jsonData = JSON.toJSONString(result);
return feedback+"["+jsonData+"]";
}
result.setMsg("0");
String jsonData = JSON.toJSONString(result);
return feedback+"["+jsonData+"]";
}
@RequestMapping("/toUpdate/{id}")
public String toUpdate(@PathVariable("id") Long id, Model model){
List<BillType> types = typeService.list();
model.addAttribute("types",types);
Bill bill = billService.get(id);
model.addAttribute("bill",bill);
return "/bill/update";
}
/**
* 删除
* @param id
* @return
*/
@RequestMapping("/delete/{id}")
public String delete(@PathVariable("id") Long id){
int i = billService.delete(id);
Result result=new Result();
if(i>0){
result.setMsg("1");
String jsonData = JSON.toJSONString(result);
return jsonData;
}
result.setMsg("0");
String jsonData=JSON.toJSONString(result);
return jsonData;
}
}
mappers
BillMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wcf.dao.BillMapper">
<sql id="selectSql">
SELECT
b.id_ as id,
b.title_ as title,
b.bill_time_ as billTime,
b.type_id_ as typeId,
b.price_ as price,
b.explain_ as `explain`,
t.name_ as typeName
FROM
bill_ as b
left join
bill_type_ as t
on
b.type_id_ = t.id_
</sql>
<select id="select" resultType="bill">
<include refid="selectSql"/>
<where>
<if test="typeId !=null and typeId!=0">
b.type_id_ = #{typeId}
</if>
<if test="title !=null">
and b.title_ like '%${title}%'
</if>
<if test="date1 !=null">
and b.bill_time_ >= #{date1}
</if>
<if test="date2 !=null">
and b.bill_time_ <= #{date2}
</if>
</where>
</select>
</mapper>
PS:之前启动bill-service测试账单微服务一直都没问题,网页上能正常返回json格式的数据列表。后期做账单微服务测试时,启动bill-service后一直报错,提示信息“ Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured. ” ,自此再也无法正常启动bill-service。我试过很多方法,仔细检查修改了config-server和bill-service里配置文件里的路径、数据库的信息以及gitee仓库里的bill-dev.yml,但还是报这个(很魔性,明明之前都可以)。经过一番折腾最后归纳总结得出:原因可能就是没有读取到远程的配置文件
为了解决这个问题,我重写了一个配置文件application.properties加进来(就是将仓库里bill-dev.yml里的配置信息拷过来,如下),注释掉原来bootstrap.yml里的配置信息,就能再次成功启动账单微服务bill-service了!
server.port=${port:9091}
#连接池
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/bill-manager
spring.application.name=bill-service
spring.thymeleaf.cache=false
# 整合mybatis
mybatis.type-aliases-package=com.wcf.bean
mybatis.mapper-locations=classpath:/mappers/*.xml
#注册实例时,更倾向于使用ip地址,而不是host名
prefer-id-address: true
# #ip地址
ip-address: 127.0.0.1
#续约间隔,默认30s
lease-renewal-interval-in-seconds: 5
#服务时效时间,默认90s
lease-expiration-duration-in-seconds: 5
eureka.client.service-url.defaultZone: http://127.0.0.1:10086/eureka
最终bill-service项目结构如下:
新建Module (bill-gateway)
导入依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>docker-bill-springcloud</artifactId>
<groupId>com.wcf</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bill-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
SpringBoot启动器:GatewayApplication
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
配置文件:application.yml
server:
port: 8086
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#路由id,可以随意写
- id: bill-service-route
#代理的服务地址
# uri: http://127.0.0.1:9091
uri: lb://bill-service
#路由断言,可以配置映射路径
predicates:
- Path=/api/bill/list/**
filters:
# 表示过滤1个路径,2表示过滤2个路径,依次类推
- StripPrefix=1
# 自定义过滤器
- MyParam=name
# 默认过滤器,对所有路由都生效
default-filters:
- AddResponseHeader=X-Response-Foo, Bar
- AddResponseHeader=myname, wcf
globalcors:
corsConfigurations:
'[/**]':
#allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
allowedOrigins:
- "http://docs.spring.io"
allowedMethods:
- GET
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #服务降级超时时间,默认1s
ribbon:
ConnectTimeout: 1000 #连接超时时长
ReadTimeout: 2000 #数据通信超时时长
MaxAutoRetries: 0 # 当前服务器的重试次数
MaxAutoRetriesNextServer: 0 # 重试多少次服务
filter
@Component
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
public MyParamGatewayFilterFactory(){
super(Config.class);
}
public List<String> shortcutFieldOrder(){
return Arrays.asList("param");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request=exchange.getRequest();
if(request.getQueryParams().containsKey(config.param)){
request.getQueryParams().get(config.param).forEach((v)->{
System.out.printf("--局部过滤器--获得参数 %s = %s ----",config.param,v);
});
}
return chain.filter(exchange);//执行请求
};
}
//读取过滤器配置的参数
public static class Config{
//对应配置在application.yml配置文件中的过滤器参数名
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
到这里,eureka-server、config-server(账单配置微服务)、bill-gateway(账单网关微服务)、bill-service(账单微服务)均创建完毕!项目结构及启动微服务成功后如下:
3项微服务均成功注册进eureka界面如下:
使用postman访问账单微服务查询的返回界面:
访问账单微服务分页查询的返回界面:
网关微服务访问账单微服务(前端统一访问地址 http://locahost:8086/api/bill/list):