项目--SpringBoot+Vue交易平台

项目–SpringBoot+Vue交易平台

概述

前言

本人做了一个交易平台,后端是SpringBoot,前端是Vue。这个电商平台有分商家页面普通用户页面,后续还会增加一个独属于管理员的页面。这个电商平台通过QQ登录进行注册,并且为每一个注册的的用户分配一个默认好友,可以进行聊天,每一个普通的用户都可以通过申请提交成为一名商家,成为商家之后才可以进行增加自家的商品。

其实本人之前也做过一个前后的耦合的thymeleaf和SpringBoot的商城,后续也曾想过下一个项目是不是该做一个博客,音乐平台,或者是视频网站,最后考虑到,用工具的方法有千千万万,重要的是对工具的了解程度如何,你能不能更加深入的对单个方面有较为深刻的理解,于是这个项目也是一个电商平台,但在实现了前后端分离的同时,还加入了很多新的技术,并且在一些方面有了更为优秀的解决方案。

特点

看文章有时候就和看别人家的注释一样,不一定能快速分清楚对方的注释是关键语句还是在水代码行数,所以前面先列出本人做的项目的特点,节约一下阅读的时间,后面会有较为详细的描述。

实现的模板:基本CRUD和购物车,秒杀商品,搜索,Socket通信,security安全拦截,微服务等。

一些特点

  • 使用了redis,在用户访问商品时候会把商品放入redis缓存器,减少数据库的频繁访问,并且使用redis集群实现数据的安全
  • 使用了redis实现了商品的秒杀,对突然间大量的请求会将其放入redis而不放入数据库,等秒杀活动过了一定时间再使用定时任务,慢慢写入数据库,由于redis的单线程保证了秒杀的安全,也减少了数据库的访问压力,后续的云服务还可以通过将商品放入消息队列的方式,进一步提高性能。
  • 图片的类型是String,而不是流文件,这里结合了七牛云做图床,实现图片数据库和本地数据库的分离
  • 每一个用户会有一个默认的好友,以用来测试socket模块,socket模块实现了用户的实时在线聊天,并且还会保存用户的聊天信息。
  • 申请了QQ互联的功能,只需要QQ扫码便可以进行登录,QQ互联是和VUE 进行结合的,会产生一个全局的JWT来保证用户的权限认证,后端的security也是用JWT来和前端进行权限认证
  • 搜索模块结合了Elasticsearch,以便于用户进行模糊类型的搜索
  • 商品平台里面的数据是使用Jsoup爬虫从京东爬取而来的,并且使用雪花算法为每一个商品生成了一个全局唯一的UID
  • 结合eureka和ribbon实现注册中心和负载均衡,降低访问压力

项目讲述

所用技术

  1. SpringBoot (后端)
  2. MySQL (数据库)
  3. MyBatis (访问数据库)
  4. swagger (集成文档)
  5. SpringSecurity (登录与权限控制)
  6. JWT (单点登录)
  7. Jsoup (爬虫,用于补充数据库的数据)
  8. fastjson (转JSON工具,用于前后端数据交互)
  9. 七牛云 (图床)
  10. netty-socketio (用于用户之间的通信)
  11. qq互联 (实现QQ登录)
  12. redis (中间件,用于数据的缓存)
  13. elasticsearch (搜索引擎)
  14. spring-boot-admin (管理后台)
  15. eureka (微服务的注册中心)
  16. ribbon (负载均衡)
  17. docker (容器化)
  18. Nginx (反向代理)
  19. aliyunEcs (服务器)
  20. vue (前端)
  21. axios (前端api)
  22. Element-ui (前端UI)

基本CRUD

无聊的操作总是千篇一律,有意思的源码也可能涉及跨域。

一个基本的CRUD,是由mybatis来操作mysql数据库来实现的,mybatis其实也很讲究,有一级缓存二级缓存等等很多原理,mysql也有innodb等等知识,然而这东西在讲系统时讲出来比较麻烦,所以在这篇展示项目的文章中,我仅仅根据我的所写的代码去讲述为什么需要这么写,暂时不写这些工具的原理。

这个是mybatis中mapper的规范,所有的命名都是根据以下来确定的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Mapper
public interface ProductMapper {
long countByExample(ProductExample example);

int deleteByExample(ProductExample example);

/**
* delete by primary key
*
* @param id primaryKey
* @return deleteCount
*/
int deleteByPrimaryKey(Long id);

/**
* insert record to table
*
* @param record the record
* @return insert count
*/
int insert(Product record);

int insertOrUpdate(Product record);

int insertOrUpdateSelective(Product record);

/**
* insert record to table selective
*
* @param record the record
* @return insert count
*/
int insertSelective(Product record);

List<Product> selectByExample(ProductExample example);

/**
* select by primary key
*
* @param id primary key
* @return object by primary key
*/
Product selectByPrimaryKey(Long id);

int updateByExampleSelective(@Param("record") Product record, @Param("example") ProductExample example);

int updateByExample(@Param("record") Product record, @Param("example") ProductExample example);

/**
* update record selective
*
* @param record the updated record
* @return update count
*/
int updateByPrimaryKeySelective(Product record);

/**
* update record
*
* @param record the updated record
* @return update count
*/
int updateByPrimaryKey(Product record);

int updateBatch(List<Product> list);

int batchInsert(@Param("list") List<Product> list);
}

其中运用到了插件mybatiscodehelper

在控制层中有一个 BaseController

1
2
3
4
5
6
7
8
9
@RestController
public class BaseController {
@Autowired
ProductService productService;
@Autowired
UserService userService;
//...........
//...........
}

而全部的控制器都是通过继承来实现依赖注入的:

1
2
3
4
5
6
@RestController
public class ProductController extends BaseController{
//....
//....
//...
}

这样继承省去了很多代码量

在后端中,购物车模块的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@PostMapping("/add_all_cart_order")
public Result add_all_cart_order(@Validated @RequestBody List<OrderSheet> orderSheets , HttpServletResponse response){
BigDecimal sum=new BigDecimal(0);
for (OrderSheet o:orderSheets) {//List<OrderSheet> orderSheets
sum.add(o.getSumMoney());
}
String state="余额不足";
User user=userService.selectById(orderSheets.get(0).getUserId());
BigDecimal money=user
.getMoney()
.subtract(sum);
if (money.compareTo(new BigDecimal(0))==1){
for (OrderSheet o:orderSheets) {//List<OrderSheet> orderSheets
o.setState("未收货");
orderSheetService.insert(o);
shoppingCartService.deleteByPrimaryKey(o.getId());
user.setMoney(user.getMoney().subtract(sum));
userService.updateByPrimaryKey(user);
state="支付成功";
}}


return Result.succ(state);

}

大多数支付方面的代码都与其类似。

秒杀商品

首先我先举例一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @PostMapping("/addorder")
public Result add_order(@Validated @RequestBody OrderSheet orderSheet, HttpServletResponse response) {
orderSheet.setId(new RandomId().nextId());
orderSheet.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Product product=productService.selectOneById(orderSheet.getProductId());
orderSheet.setSumMoney(product.getPrice().multiply(new BigDecimal(orderSheet.getAmount())));
// redis缓存放入
Boolean success=orderRedis.addOrderByRedis(orderSheet);
String message = success ? "抢购成功" : "抢购失败";
if (Objects.equals(message, "抢购失败")){
return Result.succ(message);
}
// redis缓存放入
orderSheetService.insert(orderSheet);
return Result.succ(JSONObject.toJSONString(orderSheet.getId(),true));

}

orderSheetService就是可以被替换为秒杀模块,通过orderRedis.addOrderByRedis来实现将商品放入redis,而不涉及数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Service
public class OrderRedisImpl implements OrderRedis{
@Override
public Boolean addOrderByRedis(OrderSheet orderSheet) {
Long productId = orderSheet.getProductId();
Jedis jedis = null;
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
jedis = pool.getResource();
try {
if (!jedis.exists("product_stock_"+productId)){
byte[] bytes = jedis.get(("get_product_"+productId).getBytes());
Product p =(Product) SerializeUtil.unserialize(bytes);
jedis.set("product_stock_"+productId,p.getStock()+"");
}else {
if (jedis.get("product_stock_"+productId).equals("0")){
return false;
}
jedis.decrBy("product_stock_"+productId , 1);
jedis.lpush(("order_product_"+productId).getBytes() ,SerializeUtil.serialize(orderSheet));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}

pool.destroy();




return true;
}
}

这里的做法比较简易,这里使用的是Jedis,通过从线程池里获取Jedis的方式保证了Jedis的线程安全,然后由于redis是单线程的,所以Jedis的API的所有操作都具有着原子性。以此实现线程安全的同时能够把数据放入redis 当中,然后通过TaskService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public class TaskServiceImpl implements TaskService {


@Override
// 每天半夜1点钟开始执行任务
// @Scheduled(cron = "0 0 1 * * ?")
// 下面是用于测试的配置,每分钟执行一次任务
@Scheduled(fixedRate = 1000 * 5)
public void purchaseTask() {
Jedis jedis = null;
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
jedis = pool.getResource();
try {

System.out.println("定时任务开始......");
List<byte[]> bytes=jedis.lrange("order_product_885036".getBytes(), 0, -1);
for (byte[] b:bytes) {
OrderSheet orderSheet =(OrderSheet) SerializeUtil.unserialize(b);
System.out.println(JSON.toJSONString(orderSheet));
}
System.out.println("定时任务结束......");

} finally {
if (jedis != null) {
jedis.close();
}
}
/// ... 当关闭应用程序时:
pool.destroy();
}


}

在后半夜时分,再将任务写入数据库。

搜索

搜索模块用的是Elasticsearch,这里不得不吐槽一下Elasticsearch的Api更新速度飞快,版本更新也很快,动不动就方法废弃,部署起来真的是不容易:

现在版本,根据官方文档来看,需要配置 RestHighLevelClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class ElasticsearchConfig {


@Bean
RestHighLevelClient elasticsearchClient() {
ClientConfiguration configuration = ClientConfiguration.builder()
.connectedTo("192.168.78.128:9200")
// .connectedTo("localhost:9200")

//.withConnectTimeout(Duration.ofSeconds(5))
//.withSocketTimeout(Duration.ofSeconds(3))
//.useSsl()
//.withDefaultHeaders(defaultHeaders)
//.withBasicAuth(username, password)
// ... other options
.build();
RestHighLevelClient client = RestClients.create(configuration).rest();
return client;
}
}

然后通过继承ElasticsearchRepository

1
2
3
4
5
6
7
8
9
public interface ProductRepository extends ElasticsearchRepository<Product ,Long> {


List<Product> findAllByNameLike(String name);




}

来调用方法:

1
2
3
4
5
6
@GetMapping("/es_search_product_by_name/{name}")
public Result es_search_product_by_name(@PathVariable(name = "name") String name){
List<Product> allByNameLike = productRepository.findAllByNameLike(name);

return Result.succ(allByNameLike);
}

聊天模块

聊天模块需要配置SocketIoConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Configuration
public class SocketIoConfig {

@Value("${socketio.host}")
private String host;

@Value("${socketio.port}")
private Integer port;

@Value("${socketio.bossCount}")
private int bossCount;

@Value("${socketio.workCount}")
private int workCount;

@Value("${socketio.allowCustomRequests}")
private boolean allowCustomRequests;

@Value("${socketio.upgradeTimeout}")
private int upgradeTimeout;

@Value("${socketio.pingTimeout}")
private int pingTimeout;

@Value("${socketio.pingInterval}")
private int pingInterval;

/**
* 以下配置在上面的application.yml中已经注明
* @return 实例化socketIo的服务对象
*/
@Bean
public SocketIOServer socketIOServer() {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setSocketConfig(socketConfig);
//线上不能使用hostname,血坑
// config.setHostname(host);
config.setPort(port);
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
return new SocketIOServer(config);
}
}

这里不得不再提一下,聊天记录所用的类有哪些属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@ApiModel(value = "com-sodse-trade-domain-ChatRecord")
public class ChatRecord implements Serializable {
@ApiModelProperty(value = "")
private Long id;

/**
* 是自己发送为1,不是自己发送为0
*/
@ApiModelProperty(value = "是自己发送为1,不是自己发送为0")
private Integer isSend;

@ApiModelProperty(value = "")
private String createTime;

@ApiModelProperty(value = "")
private String content;

@ApiModelProperty(value = "")
private Long userId;

/**
* 聊天对象id
* 每当用户发送一条信息,都会写入一次数据库,其中is_send为1表示自己发的,然后同时给被接受者反向写入id但是send值为0,表示被接收。
* 第二,当用户删除自己的聊天框时,使用delete把该用户id和对象的id全部满足的记录删除
*/
@ApiModelProperty(value = "聊天对象id ,每当用户发送一条信息,都会写入一次数据库,其中is_send为1表示自己发的,然后同时给被接受者反向写入id但是send值为0,表示被接收。,第二,当用户删除自己的聊天框时,使用delete把该用户id和对象的id全部满足的记录删除")
private Long talkerId;

private static final long serialVersionUID = 1L;
}

然后就是最为关键的方法SocketIoService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@Service(value = "socketIOService")
public class SocketIoServiceImpl implements SocketIoService {

/**
* 用来存已连接的客户端
*/
private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

/**
* socketIo的对象
*/
@Autowired
private SocketIOServer socketIOServer;

/**
* 功能描述:当前的service被初始化的时候执行以下的方法
* @throws Exception
*/
@PostConstruct
private void autoStartUp() throws Exception {
start();
}

/**
* 功能描述:当我们的系统停止的时候关闭我们的socketIo
* @throws Exception
*/
@PreDestroy
private void autoStop() throws Exception {
stop();
}

@Override
public void start() throws Exception {
// 监听客户端连接
socketIOServer.addConnectListener(client -> {
/**
* 此处实现我们的socket的连接的用户的逻辑
*/
String loginUser = getParamsByClient(client).get("loginUser").get(0);
clientMap.put(loginUser, client);
});

// 监听客户端断开连接
socketIOServer.addDisconnectListener(client -> {
String loginUser = getParamsByClient(client).get("loginUser").get(0);
if (loginUser != null && !"".equals(loginUser)) {
clientMap.remove(loginUser);
client.disconnect();
}
});

// 处理自定义的事件,与连接监听类似
socketIOServer.addEventListener(PUSH_EVENT, PushMessage.class, (client, data, ackSender) -> {
// TODO do something
});
socketIOServer.start();

}

@Override
public void stop() {
if (socketIOServer != null) {
socketIOServer.stop();
socketIOServer = null;
}
}

/**
* 功能描述:发送消息到前端
* @param pushMessage 发送消息的实体
*/
@Override
public void pushMessageToUser(PushMessage pushMessage) {
clientMap.get(pushMessage.getLoginUser()).sendEvent(PUSH_EVENT, pushMessage);
}

/**
* 此方法为获取client连接中的参数,可根据需求更改
*
* @param client
* @return
*/
private Map<String, List<String>> getParamsByClient(SocketIOClient client) {
// 从请求的连接中拿出参数
Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
return params;
}
}

这里最为关键的语句我认为应该是:

1
private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();

通过建立一个ConcurrentHashMap,socket能够检测用户是否在线,在线就将用户put进ConcurrentHashMap,不在线就将其移除,若被发送消息的用户在线时,socket就会将这个消息通过ConcurrentHashMap中获得的用户信息来发送给相应的用户,这个特点在于这是后端主动发起消息给前端的,也是我在考虑到了聊天消息是怎么产生的时候,所了解的。

Security

之前实现过一个前后端耦合的商城,那里的Security配置都比较简单,而这里用的是前后端分离的vue和springboot结合,两者甚至都不在同一个端口,所以这里的security配置也有了很大的改观,并且利用了jwt来作为两个端口的认证凭证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
MyUserDetailsService myUserDetailsService;


@Bean
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();
}

@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( myUserDetailsService ).passwordEncoder( new BCryptPasswordEncoder() );
}

@Override
protected void configure( HttpSecurity httpSecurity ) throws Exception {

httpSecurity
.csrf()
.disable()

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()

//关闭防护

.antMatchers("/").permitAll()//允许/、/login的访问

.antMatchers("/user").hasRole("USER")//用户USER角色的用户访问有关/user下面的所有
.antMatchers("/admin").hasRole("ADMIN")//同上

.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/register").permitAll()


// 开启跨域
.and()
.cors()
.and()
// .exceptionHandling().accessDeniedPage("/accessDenied")
;


;

httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();


}

}

这里用的是:auth.userDetailsService( myUserDetailsService ).passwordEncoder( new BCryptPasswordEncoder() );也就是myUserDetailsService所继承的UserDetails通过重写方法来进行的密码认证。

在登录阶段,使用的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
public class AuthServiceImpl implements AuthService {

@Autowired
private AuthenticationManager authenticationManager;


@Autowired
@Qualifier("myUserDetailsService")
private UserDetailsService userDetailsService;

@Autowired
MyUserDetailsService myUserDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private UserService userService;

// 登录
@Override
public String login( String username, String password ) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername( username );
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
}

// 注册
@Override
public Integer register( User userToAdd ) {

final String username = userToAdd.getUsername();
if( !userService.findByUsername(username).isEmpty() ) {
return null;
}
//当用户不存在时
userToAdd=qq_register(userToAdd);
//qq注册的处理
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
final String rawPassword = userToAdd.getPassword();
userToAdd.setPassword( encoder.encode(rawPassword) );
return userService.insert(userToAdd);
}
}

通过jwtTokenUtil.generateToken(userDetails),来对Security内置的UserDetails生成JWT

这里还有一个JWT的拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component
public class JwtTokenFilter extends OncePerRequestFilter {

@Qualifier("myUserDetailsService")
@Autowired
private UserDetailsService userDetailsService;

@Autowired
MyUserDetailsService myUserDetailsService;

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Override
protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

String authHeader = request.getHeader( Const.HEADER_STRING );
if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {
final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}

这拦截器首先在WebSecurityConfig中被定义了,Security就会使用这个拦截器来作为authenticationTokenFilter的认证

1
2
3
4
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();

}

微服务

在这一模块仅仅使用了eureka和ribbon,eureka作为注册中心,而ribbon作为负载均衡,当然,后续可以使用feign来进行负载均衡。并且还可在多模块的时候再继续使用Hystrix来实现服务熔断,以及和zipkin来实现分布式的链路追踪。

新建一个工程,配置eureka-server,并且在application.yml文件中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: eureka_server
server:
port: 8761
eureka:
instance:
hostname: eureka_server_8761
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka/

同样的在原来的主体工程中添加:

1
2
3
4
5
6
7
8
server:
port: 8001
eureka:
instance:
prefer-ip-address: true #注册服务的时候使用ip
client:
service-url:
defaultZone: http://localhost:8761/eureka/

就已经实现了注册中心,然后就是负载均衡:

在主体工程中添加:

1
@EnableDiscoveryClient //开启发现服务功能

并且添加RestTemplate:

1
2
3
4
5
@Bean
@LoadBalanced//使用负载均衡机制
public RestTemplate restTemplate(){
return new RestTemplate();
}

从而controller中注入RestTemplate来实现负载均衡。

后续暂无。。。。