提示:需要的一定的maven,Spring Boot基础
这里使用的Spring Cloud版本是2.2.0.RELEASE
代码参考:https://github.com/phone15082037343/demo
文章目录
一、Sping Cloud与微服务概述
1.单体应用
简单的理解为:将所有的功能全部堆积在一起,形成的一个jar或者war、随着业务的发展,功能的增多,这个单体应用会变得越来越臃肿,代码变得不易维护。
2.什么是微服务
微服务是一种架构风格,即将单体应用划分为小型的服务单元,微服务之间使用http的API进行资源访问与操作 。
微服务的优势和劣势
臃肿的系统、复杂的代码、超长的启动时间带给开发人员的只有无限的抱怨,丝毫没有那种很舒服、很流畅的写代码的感觉。你会发现,解决问题的大部分时间都用在了平台的启动上。
- 优势
- 服务独立部署
- 服务的快速启动
- 更加适合敏捷开发
- 职责专一
- 服务动态扩容
- 代码复用
- 劣势
微服务其实是一把双刃剑,有利必然有弊。
- 分布式部署,调用的复杂性高
- 独立的数据库,分布式事务的挑战
- 测试难度的提升
- 运维难度的提升
3.什么是Spring Cloud
Spring Cloud是一系列框架的集合。它利用Spring Boot的开发便捷性,巧妙地简化了分布式基础设施的开发。
Spring Cloud的模块介绍
- Eureka:微服务注册中心,用于服务管理
- Ribbon:基于客户端的负载均衡组件
- Hystrix:容错框架,能够防止服务的雪崩效应
- Feign:web服务的客户端,能够简化Http接口的调用。
- Zuul:API网关,提供路由转发,请求过滤等功能
- Config:分布式配置管理
- Sleuth:服务跟踪
- …
Spring Cloud还提供了很多其他模块,这里就不一一列举了。
4.项目模块以及说明
开发、测试、产品(正式)环境
通常情况下,在windows上开发,在Linux上部署。这里演示的是windows上开发,Linux单机环境测试,Linux多台机器部署正式环境。
环境 | 机器名 | 备注 |
---|---|---|
开发 | (windows10) | 开发以及打包都用windows来操作 |
测试 | server(centos8) | 单机环境用来测试,所有的application都在这台机器上启动 |
正式 | master、worker1、worker2 (centos8) | eureka注册中心集群部署,其他application视情况而定 |
产品环境模块部署如下
机器名 | app |
---|---|
master | eureka、dashboard、gateway |
worker1 | eureka、provider |
worker2 | eureka、client |
由于机器资源有限,只能这样部署。
模块说明
模块截图如下:
demo作为父模块,子模块所有的依赖都从父模块继承,方便统一管理。
- demo-common:存放一些公共的代码
- demo-eureka:注册中心
- demo-provider:服务提供者,负责数据库访问
- demo-client:服务调用者,为客户端调用提供访问接口
- demo-dashboard:提供一个建议界面,可以看到demo-client调用demo-provider接口的情况,成功或者失败
- demo-gateway:统一路由,客户端直接访问这个路由,再有路由转发请求到demo-client,客户端代码也放在这里面
二、Eureka注册中心
1. Eureka
主要负责实现微服务架构中的服务治理功能。Spring Cloud Eureka是一个基于REST的服务,并且提供了Java客户端组件,能够非常方便地将服务注册到Spring Cloud Eureka中进行统一管理。
这里有几种角色,注册中心、服务提供者与服务消费者。注册中心负责服务的注册于管理,提供提供者提供服务给服务消费者消费。
2. Eureka注册中心
设置版本号
<properties>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>2.2.0.RELEASE</spring-cloud.version>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
<spring.version>5.2.1.RELEASE</spring.version>
<gson.version>2.8.6</gson.version>
<jackson.version>2.10.0</jackson.version>
<lombok.version>1.18.12</lombok.version>
<druid.version>1.1.20</druid.version>
<postgresql.version>42.2.11</postgresql.version>
<servlet-api.version>2.5</servlet-api.version>
</properties>
- 添加依赖
<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-security</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
spring-cloud-starter-netflix-eureka-server:注册中心
gson:会报Class找不到的错
spring-boot-starter-security:通常情况下,后台管理界面不允许直接访问,需要登录认证
- application启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@SpringBootApplication
@EnableEurekaServer
@EnableWebSecurity
public class EurekaApplication extends WebSecurityConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
@EnableEurekaServer:启动eureka注册中心
@EnableWebSecurity:启动spring security认证
- application.yml
server:
port: 8761
spring:
application:
name: demo-eureka
profiles:
active: dev
security:
user:
name: admin
password: admin
eureka:
client:
register-with-eureka: false
fetch-registry: false
spring.application.name:指定应用名称,很重要
spring.profiles.active:指定环境,值为 application-*.yml
eureka.client.register-with-eureka:不向注册中心注册自己,因为自己就是注册中心
eureka.client.fetch-registry:不需要检索服务
spring.security.user.name:用户名
spring.security.user.password:密码
- application-dev.yml
eureka:
server:
enable-self-preservation: false # 关闭保护机制
-
application-test.yml
空 -
application-master.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@worker1:8761/eureka/,http://admin:admin@worker2:8761/eureka/
注意:这里设置了用户名和密码
- application-worker1.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@master:8761/eureka/,http://admin:admin@worker2:8761/eureka/
- application-worker2.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@master:8761/eureka/,http://admin:admin@worker1:8761/eureka/
打包后,java -jar执行会出现以下错误,是因为jackson的jar找不到
添加依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
浏览器访问http://127.0.0.1:8761,需要登录
3. 服务提供者(demo-provider)
这里提供接口使用jdbc访问数据库,接口提供给服务消费者使用,需要注册到注册中心。
- 添加依赖
<dependency>
<groupId>com.demo</groupId>
<artifactId>demo-common</artifactId>
<version>${project.version}</version>
</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
demo-common:里面存放了一些公有代码
spring-cloud-starter-netflix-eureka-server:eureka客户端
spring-boot-starter-data-jpa:jpa框架访问数据库
lombok:需要idea安装lombok插件
druid:alibaba数据源
postgresql:pg连接,因为pg在Linux上安装比较方便,所以这里用pg
- 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
@EnableDiscoveryClient:标识这是一个eureka的客户端
- application.yml
server:
port: 9000
spring:
profiles:
active: dev
application:
name: demo-provider
- application-dev.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@127.0.0.1:8761/eureka/
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:postgresql://127.0.0.1:5432/platform
driver-class-name: org.postgresql.Driver
username: postgres
password: 123456
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
eureka.client.service-url.defaultZone:eureka注册中心的地址
spring.datasource.*:数据源连接
- application-test.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@server:8761/eureka/
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:postgresql://server:5432/platform
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
- application-pro.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@master:8761/eureka/,http://admin:admin@worker1:8761/eureka/,http://admin:admin@worker2:8761/eureka/
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:postgresql://master:5432/platform
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: false
hibernate:
ddl-auto: none
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false
这里注意一下注册到eureka集群的方式
- controller
import com.demo.common.dto.req.UserRequest;
import com.demo.common.dto.res.UserResponse;
import com.demo.common.utils.page.PageModel;
import com.demo.provider.entity.User;
import com.demo.provider.repository.UserRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserRepository userRepository;
@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
public UserResponse save(@RequestBody UserRequest userRequest) {
User user = new User();
BeanUtils.copyProperties(userRequest, user);
userRepository.save(user);
UserResponse userResponse = new UserResponse();
BeanUtils.copyProperties(user, userResponse);
return userResponse;
}
@DeleteMapping("/{id}")
public String deleteById(@PathVariable(name = "id") String id) {
userRepository.deleteById(id);
return id;
}
@GetMapping("/{id}")
public UserResponse findById(@PathVariable(name = "id") String id) {
User user = userRepository.findById(id).orElse(null);
if (user != null) {
UserResponse userResponse = new UserResponse();
BeanUtils.copyProperties(user, userResponse);
return userResponse;
}
return null;
}
@GetMapping
public PageModel<UserResponse> find(@RequestParam(name = "pageNo", defaultValue = "1") int pageNo,
@RequestParam(name = "size", defaultValue = "10") int size) {
Page<User> page = userRepository.findAll(PageRequest.of(pageNo - 1, size));
PageModel<UserResponse> pm = new PageModel<>();
pm.setPageNo(pageNo);
pm.setSize(size);
pm.setTotalCount((int) page.getTotalElements());
pm.setTotalPage(page.getTotalPages());
List<UserResponse> list = page.stream().map(user -> {
UserResponse userResponse = new UserResponse();
BeanUtils.copyProperties(user, userResponse);
return userResponse;
}).collect(Collectors.toList());
pm.setList(list);
return pm;
}
}
这里只是简答的做了一下增删改查
- 实体对象
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@Table(name = "user_info")
public class User {
@Id
@Column(name = "user_id", length = 32)
@GenericGenerator(name = "uuidGenerator", strategy = "uuid")
@GeneratedValue(generator = "uuidGenerator")
private String userId;
@Column(name = "name", length = 64)
private String name;
@Column(name = "sex")
private int sex;
@Column(name = "age")
private int age;
@Column(name = "birthday")
private Date birthday;
}
- dto:也是这几个字段
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class UserRequest {
private String userId;
private String name;
private int sex;
private int age;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date birthday;
}
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class UserResponse {
private String userId;
private String name;
private int sex;
private int age;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date birthday;
}
- repository
import com.demo.provider.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, String> {
}
4. 服务消费者
见【三、Feign客户端】
三、Feign客户端(demo-client)
java调用http接口,httpclient、okhttp、httpurlconnection、resttemplate都不是很方便,使用Feign,简化了调用http接口的过程。
添加依赖
<dependency>
<groupId>com.demo</groupId>
<artifactId>demo-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
spring-cloud-starter-netflix-eureka-server:
spring-cloud-starter-openfeign:Feign的依赖
spring-cloud-starter-netflix-hystrix:监控与容错
- 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
public class ClientApplication {
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
@EnableDiscoveryClient:作为eureka的客户端
@EnableFeignClients:启用feign
@EnableHystrix:启用监控与容错
- application.yml
server:
port: 8000
spring:
profiles:
active: dev
application:
name: demo-client
# 启动feign
feign:
hystrix:
enabled: true
# 需要加上
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
- application-dev.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@127.0.0.1:8761/eureka/
-
application-test.yml
略 -
application-pro.yml
略 -
FeignConfiguration
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfiguration {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
- UserController
import com.demo.client.api.UserClient;
import com.demo.common.dto.req.UserRequest;
import com.demo.common.utils.ajax.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserClient userClient;
@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
public Result save(@RequestBody UserRequest userRequest) {
Result result = new Result();
result.setData(userClient.save(userRequest));
result.setSuccess(true);
result.setMessage("用户保存成功");
return result;
}
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable(name = "id") String id) {
Result result = new Result();
result.setData(userClient.deleteById(id));
result.setSuccess(true);
result.setMessage("用户删除成功");
return result;
}
@GetMapping("/{id}")
public Result findById(@PathVariable(name = "id") String id) {
Result result = new Result();
result.setData(userClient.findById(id));
result.setSuccess(true);
return result;
}
@GetMapping
public Result find(@RequestParam(name = "pageNo", defaultValue = "1") int pageNo,
@RequestParam(name = "size", defaultValue = "10") int size) {
Result result = new Result();
result.setData(userClient.find(pageNo, size));
result.setSuccess(true);
return result;
}
}
- UserClient
import com.demo.client.conf.FeignConfiguration;
import com.demo.client.fallback.UserClientFallbackFactory;
import com.demo.common.dto.req.UserRequest;
import com.demo.common.dto.res.UserResponse;
import com.demo.common.utils.page.PageModel;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@FeignClient(value = "demo-provider", path = "/user", configuration = FeignConfiguration.class, fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@PostMapping
UserResponse save(@RequestBody UserRequest userRequest);
@DeleteMapping("/{id}")
public String deleteById(@PathVariable(name = "id") String id);
@GetMapping("/{id}")
public UserResponse findById(@PathVariable(name = "id") String id);
@GetMapping
PageModel<UserResponse> find(@RequestParam(name = "pageNo", defaultValue = "1") int pageNo,
@RequestParam(name = "size", defaultValue = "10") int size);
}
@FeignClient():value设置服务提供者,path设置统一访问前缀,fallbackFactory设置http请求失败之后默认返回值
- Fallback
import com.demo.client.api.UserClient;
import com.demo.common.dto.req.UserRequest;
import com.demo.common.dto.res.UserResponse;
import com.demo.common.utils.page.PageModel;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public UserResponse save(UserRequest userRequest) {
return null;
}
@Override
public String deleteById(String id) {
return id;
}
@Override
public UserResponse findById(String id) {
return null;
}
@Override
public PageModel<UserResponse> find(int pageNo, int size) {
return null;
}
};
}
}
四、Dashboard监控(demo-dashboard)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
- 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableHystrixDashboard
@EnableDiscoveryClient
@EnableTurbine
public class DashboardApplication {
public static void main(String[] args) {
SpringApplication.run(DashboardApplication.class, args);
}
}
@EnableHystrixDashboard:启用dashboard
@EnableTurbine:启用turbine
- application.yml
server:
port: 9210
spring:
application:
name: demo-dashboard
profiles:
active: dev
turbine:
app-config: demo-client
aggregator:
cluster-config: default
cluster-name-expression: new String("default")
- application-dev.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@127.0.0.1:8761/eureka/
- application-test.yml
略 - application-pro.yml
略
浏览器访问http://127.0.0.1:9210/hystrix
五、zuul路由(demo-gateway)
提供客户端接口访问统一地址
- 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
- 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
@EnableZuulProxy
- application.yml
server:
port: 8080
spring:
application:
name: demo-gateway
profiles:
active: dev
resources:
static-locations: classpath:/static/, classpath:/templates/
# 注意这里的配置,不过多解释
zuul:
routes:
demo-client:
path: /client/**
sensitive-headers: # 转换的时候带上cookie
- application-dev.yml
eureka:
client:
service-url:
defaultZone: http://admin:admin@127.0.0.1:8761/eureka/
- application-test.yml
略 - application-pro.yml
略
六、vue+element ui客户端
获取客户端模板源码,是由vue提供的,百度搜索关键字“vue-admin-template”
把客户端源码放在路由里面,原因后面再解释
开发环境下,客户端是由nodejs启动,所有需要配置代理
- vue.config.js
注意:这两个文件.env.development、.env.production
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: 'http://127.0.0.1:8080',
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
启动客户端之前需要先用一下命令安装nodejs依赖
npm install --registry https://registry.npm.taobao.org
- 客户端请求,src/utils/request.js
response => {
const result = response.data;
if (!result.success) {
// 错误提示
Message({
message: result.message || 'Error',
type: 'error',
duration: 3 * 1000
});
} else {
const { message } = result;
// 成功提示
if (message != null && message.length > 0) {
Message({
message: message,
type: 'success',
duration: 3 * 1000
});
}
}
return response;
},
统一使用这个工具类来发送请求
- 用户登录,src/api/user.js
这里有三个接口,需要服务端根据对应的情况来弄,这里就不做更多的演示,提供简单的接口就行。
import com.demo.client.api.UserClient;
import com.demo.common.dto.req.UserRequest;
import com.demo.common.utils.ajax.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserClient userClient;
@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
public Result save(@RequestBody UserRequest userRequest) {
Result result = new Result();
result.setData(userClient.save(userRequest));
result.setSuccess(true);
result.setMessage("用户保存成功");
return result;
}
@DeleteMapping("/{id}")
public Result deleteById(@PathVariable(name = "id") String id) {
Result result = new Result();
result.setData(userClient.deleteById(id));
result.setSuccess(true);
result.setMessage("用户删除成功");
return result;
}
@GetMapping("/{id}")
public Result findById(@PathVariable(name = "id") String id) {
Result result = new Result();
result.setData(userClient.findById(id));
result.setSuccess(true);
return result;
}
@GetMapping
public Result find(@RequestParam(name = "pageNo", defaultValue = "1") int pageNo,
@RequestParam(name = "size", defaultValue = "10") int size) {
Result result = new Result();
result.setData(userClient.find(pageNo, size));
result.setSuccess(true);
return result;
}
}
- vue路由配置,src/router/index.js
{
path: '/user',
component: Layout,
children: [
{
path: 'manage',
name: 'Manage',
component: () => import('@/views/user/manage/index'),
meta: {
title: 'UserManage',
icon: 'form'
}
},
{
path: 'add',
name: 'UserAdd',
component: () => import('@/views/user/add/index'),
meta: {
title: 'UserAdd',
icon: 'form'
},
hidden: true
},
{
path: 'update',
name: 'UserUpdate',
component: () => import('@/views/user/update/index'),
meta: {
title: 'UserUpdate',
icon: 'form'
},
hidden: true
}
]
},
- 管理,src/views/user/manage/index.vue
<template>
<div class="app-container">
<div class="btns">
<el-tooltip class="item" effect="dark" content="添加" placement="top">
<el-button type="primary" size="medium" icon="el-icon-plus" @click="toAddPage" />
</el-tooltip>
</div>
<el-table :data="tableData" stripe border style="width: 100%">
<el-table-column type="selection" width="55" prop="userId" align="center" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="sex" label="性别">
<template slot-scope="scope">
<span v-if="scope.row.sex == 0">男</span>
<span v-else-if="scope.row.sex == 1">女</span>
</template>
</el-table-column>
<el-table-column prop="age" label="年龄" />
<el-table-column prop="birthday" label="生日" />
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="block">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="page.currentPage"
:page-sizes="[10, 20, 50, 100]" :page-size="page.size" layout="total, sizes, prev, pager, next, jumper" :total="page.total" />
</div>
</div>
</template>
<script>
import request from '@/utils/request'
export default {
data() {
return {
tableData: [],
page: {
currentPage: 1,
size: 10,
total: 101
}
}
},
methods: {
handleSizeChange(val) {
this.page.size = val;
this.page.currentPage = 1;
this.loadTableData();
},
handleCurrentChange(val) {
this.page.currentPage = val;
this.loadTableData();
},
loadTableData() {
request({
url: "/client/user",
method: "get",
params: {
pageNo: this.page.currentPage,
size: this.page.size
}
}).then(response => {
const result = response.data;
const {
data
} = result;
this.tableData = data.list;
this.page.total = data.totalCount;
});
},
handleEdit(index, row) {
// 跳转到更新页面
this.$router.push({
name: 'UserUpdate',
params: {
userId: row.userId
}
});
},
handleDelete(index, row) {
request({
url: "/client/user/" + row.userId,
method: "delete"
}).then(response => {
var result = response.data;
if (result.success) {
this.loadTableData();
}
});
},
toAddPage() {
this.$router.push({
path: '/user/add'
});
}
},
created() {
this.loadTableData();
}
}
</script>
<style scoped>
.block {
text-align: right;
margin-top: 10px;
}
.btns {
margin-bottom: 10px;
}
</style>
- 添加,src/views/user/add/index.vue
<template>
<div class="app-container">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="form.name" clearable placeholder="在这里输入性名" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio label="男" />
<el-radio label="女" />
</el-radio-group>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age" type="number" min="0" clearable placeholder="在这里输入年龄" />
</el-form-item>
<el-form-item label="生日">
<el-date-picker v-model="form.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="cancel">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import request from '@/utils/request'
export default {
data() {
return {
form: {
name: '',
sex: '男',
age: 18,
birthday: ''
}
}
},
methods: {
onSubmit() {
var data = {
name: this.form.name,
age: this.form.age,
birthday: this.form.birthday
}
const { sex } = this.form;
if (sex == '男') {
data.sex = 0;
} else if (sex == '女') {
data.sex = 1;
}
request({
url: "/client/user",
method: "post",
data
}).then(response => {
var result = response.data;
if (result.success) {
this.$router.push({
path: '/user/manage'
});
}
});
},
cancel() {
this.$router.push({
path: '/user/manage'
});
}
}
}
</script>
<style scoped>
</style>
- 更新,src/views/user/update/index.vue
<template>
<div class="app-container">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="姓名">
<el-input v-model="form.name" clearable placeholder="在这里输入性名" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.sex">
<el-radio label="男" />
<el-radio label="女" />
</el-radio-group>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age" type="number" min="0" clearable placeholder="在这里输入年龄" />
</el-form-item>
<el-form-item label="生日">
<el-date-picker v-model="form.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button @click="cancel">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import request from '@/utils/request'
export default {
data() {
return {
form: {
userId: '',
name: '',
sex: '男',
age: 18,
birthday: ''
}
}
},
methods: {
onSubmit() {
var data = {
userId: this.form.userId,
name: this.form.name,
age: this.form.age,
birthday: this.form.birthday
}
const {
sex
} = this.form;
if (sex == '男') {
data.sex = 0;
} else if (sex == '女') {
data.sex = 1;
}
request({
url: "/client/user",
method: "post",
data
}).then(response => {
var result = response.data;
if (result.success) {
this.$router.push({
path: '/user/manage'
});
}
});
},
cancel() {
this.$router.push({
path: '/user/manage'
});
}
},
created() {
const userId = this.$route.params.userId;
request({
url: '/client/user/' + userId,
method: 'get'
}).then(response => {
const result = response.data;
const { data } = result;
this.form.name = data.name;
this.form.age = data.age;
this.form.birthday = data.birthday;
if (data.sex == 0) {
this.form.sex = '男';
} else if (data.sex == 1) {
this.form.sex = '女';
}
this.form.userId = data.userId;
});
}
}
</script>
<style scoped>
</style>
打包
这是重点,客户端需要打包,打包结果如下
这些是可以直接放到nginx下面去跑的,但是会出现跨域的问题,所有考虑将这个客户端的打包结果放到路由里面去,这样就不会出现跨域的问题了。
如果需要客户端先打包,在把打包结果放到路由里面,这样就有点不方便了,所以使用maven插件,一次性就能把包打出来。
<build>
<finalName>demo-gateway</finalName>
<plugins>
<!-- spring boot 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
<mainClass>com.demo.gateway.ZuulApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>exec-npm-install</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>install</argument>
<argument>--registry</argument>
<argument>https://registry.npm.taobao.org</argument>
</arguments>
<workingDirectory>${project.basedir}/src/main/front-end</workingDirectory>
</configuration>
</execution>
<execution>
<id>exec-npm-run-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build:prod</argument>
</arguments>
<workingDirectory>${project.basedir}/src/main/front-end</workingDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
<executions>
<execution>
<id>copy-spring-boot-resources</id>
<!-- here the phase you need -->
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>utf-8</encoding>
<outputDirectory>${project.basedir}/src/main/resources/templates</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>${basedir}/src/main/front-end/dist</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
结果演示
eureka集群部署