使用springcloud实现主页后端开发
中国加油!武汉加油!
项目准备
7. 搭建用户微服务
7.1 在homepage-service这个模块下 创建新的module
7.2 pom引入依赖
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- 引入 Web 功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
Eureka 客户端, 客户端向 Eureka Server 注册的时候会提供一系列的元数据信息, 例如: 主机, 端口, 健康检查url等
Eureka Server 接受每个客户端发送的心跳信息, 如果在某个配置的超时时间内未接收到心跳信息, 实例会被从注册列表中移除
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入 Feign, 可以以声明的方式调用微服务 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 引入服务容错 Hystrix 的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- Java Persistence API, ORM 规范 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动, 注意, 这个需要与 MySQL 版本对应 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<scope>runtime</scope>
</dependency>
<!-- 通用模块 -->
<dependency>
<groupId>com.wpj.homepage</groupId>
<artifactId>homepage-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
7.3 创建应用程序启动入口
package com.wpj.homepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableEurekaClient
@EnableJpaAuditing
@EnableCircuitBreaker
@EnableFeignClients
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
7.4 定义application.yml
server:
port: 7000
servlet:
context-path: /homepage-user
spring:
main:
allow-bean-definition-overriding: true
application:
name: eureka-client-homepage-user
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.format_sql: true
open-in-view: false
datasource:
url: jdbc:mysql://127.0.0.1:3306/springcloud-immoc?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
tomcat:
max-active: 4
min-idle: 2
initial-size: 2
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka/
feign:
hystrix:
enabled: true
7.5 定义User实体类和User_Course实体类
package com.wpj.homepage.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "homepage_user")
public class HomepageUser {
/** 数据表主键 */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
/** 用户名 */
@Basic
@Column(name = "username", nullable = false)
private String username;
/** 用户邮箱 */
@Basic
@Column(name = "email", nullable = false)
private String email;
/** 创建时间 */
@Basic
@CreatedDate
@Column(name = "create_time", nullable = false)
private Date createTime;
/** 更新时间 */
@Basic
@LastModifiedDate
@Column(name = "update_time", nullable = false)
private Date updateTime;
public HomepageUser(String username, String email) {
this.username = username;
this.email = email;
}
}
package com.wpj.homepage.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "homepage_user_course")
public class HomepageUserCourse {
/** 数据表主键 */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
/** 用户 id */
@Basic
@Column(name = "user_id", nullable = false)
private Long userId;
/** 课程 id */
@Basic
@Column(name = "course_id", nullable = false)
private Long courseId;
/** 创建时间 */
@Basic
@CreatedDate
@Column(name = "create_time", nullable = false)
private Date createTime;
/** 更新时间 */
@Basic
@LastModifiedDate
@Column(name = "update_time", nullable = false)
private Date updateTime;
}
7.6 定义数据表访问接口
package com.wpj.homepage.dao;
import com.wpj.homepage.entity.HomepageUser;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* 数据表访问接口定义
*/
public interface HomepageUserDao extends JpaRepository<HomepageUser, Long> {
/**
* 通过用户名寻找数据记录
* @param username
* @return
*/
HomepageUser findByUsername(String username);
}
package com.wpj.homepage.dao;
import com.wpj.homepage.entity.HomepageUserCourse;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
/**
* 数据表访问接口定义
*/
public interface HomepageUserCourseDao extends JpaRepository<HomepageUserCourse, Long> {
/**
* 通过用户 id 寻找数据记录
* */
List<HomepageUserCourse> findAllByUserId(Long userId);
}
7.7 定义Feign接口及值对象
package com.wpj.homepage.client;
import com.wpj.homepage.CourseInfo;
import com.wpj.homepage.CourseInfosRequest;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* 通过 Feign 访问课程微服务
*/
@FeignClient(value = "eureka-client-homepage-course", fallback = CourseClientHystrix.class)
public interface CourseClient {
@RequestMapping(value = "/homepage-course/get/course", method = RequestMethod.GET)
CourseInfo getCourseInfo(Long id);
@RequestMapping(value = "/homepage-course/get/courses", method = RequestMethod.POST)
List<CourseInfo> getCourseInfos(@RequestBody CourseInfosRequest request);
}
package com.wpj.homepage.client;
import com.wpj.homepage.CourseInfo;
import com.wpj.homepage.CourseInfosRequest;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 熔断降级
*/
@Component
public class CourseClientHystrix implements CourseClient {
@Override
public CourseInfo getCourseInfo(Long id) {
return CourseInfo.invalid();
}
@Override
public List<CourseInfo> getCourseInfos(CourseInfosRequest request) {
return Collections.emptyList();
}
}
7.8 扩展
package com.wpj.homepage.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.StringUtils;
/**
* 创建用户请求对象定义
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
/** 用户名 */
private String username;
/** 邮箱 */
private String email;
/**
* 请求有效性验证
* */
public boolean validate() {
return StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(email);
}
}
package com.wpj.homepage.vo;
import com.wpj.homepage.CourseInfo;
import com.wpj.homepage.UserInfo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Collections;
import java.util.List;
/**
* 用户课程信息对象定义
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserCourseInfo {
private UserInfo userInfo;
private List<CourseInfo> courseInfos;
public static UserCourseInfo invalid() {
return new UserCourseInfo(UserInfo.invalid(), Collections.emptyList()
);
}
}
7.9 定义User相关服务接口及其实现
package com.wpj.homepage.service;
import com.wpj.homepage.UserInfo;
import com.wpj.homepage.vo.CreateUserRequest;
import com.wpj.homepage.vo.UserCourseInfo;
/**
* 用户相关服务接口定义
*/
public interface IUserService {
/**
* 创建用户
* */
UserInfo createUser(CreateUserRequest request);
/**
* 获取用户的基本信息
* */
UserInfo getUserInfo(Long id);
/**
* 获取用户课程信息
* */
UserCourseInfo getUserCourseInfo(Long id);
}
package com.wpj.homepage.service.impl;
import com.wpj.homepage.CourseInfo;
import com.wpj.homepage.CourseInfosRequest;
import com.wpj.homepage.UserInfo;
import com.wpj.homepage.client.CourseClient;
import com.wpj.homepage.dao.HomepageUserCourseDao;
import com.wpj.homepage.dao.HomepageUserDao;
import com.wpj.homepage.entity.HomepageUser;
import com.wpj.homepage.entity.HomepageUserCourse;
import com.wpj.homepage.service.IUserService;
import com.wpj.homepage.vo.CreateUserRequest;
import com.wpj.homepage.vo.UserCourseInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 用户相关服务实现
*/
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
/** HomepageUser Dao */
private final HomepageUserDao homepageUserDao;
/** HomepageUserCourse Dao */
private final HomepageUserCourseDao homepageUserCourseDao;
private final CourseClient courseClient;
@Autowired
public UserServiceImpl(HomepageUserDao homepageUserDao, HomepageUserCourseDao homepageUserCourseDao,
CourseClient courseClient) {
this.homepageUserDao = homepageUserDao;
this.homepageUserCourseDao = homepageUserCourseDao;
this.courseClient = courseClient;
}
@Override
public UserInfo createUser(CreateUserRequest request) {
if (!request.validate()) {
return UserInfo.invalid();
}
HomepageUser oldUser = homepageUserDao.findByUsername(request.getUsername());
if (null != oldUser) {
return UserInfo.invalid();
}
HomepageUser newUser = homepageUserDao.save(new HomepageUser(
request.getUsername(), request.getEmail()
));
return new UserInfo(newUser.getId(), newUser.getUsername(), newUser.getEmail());
}
@Override
public UserInfo getUserInfo(Long id) {
Optional<HomepageUser> user = homepageUserDao.findById(id);
if (!user.isPresent()) {
return UserInfo.invalid();
}
HomepageUser curUser = user.get();
return new UserInfo(curUser.getId(), curUser.getUsername(), curUser.getEmail());
}
@Override
public UserCourseInfo getUserCourseInfo(Long id) {
Optional<HomepageUser> user = homepageUserDao.findById(id);
if (!user.isPresent()) {
return UserCourseInfo.invalid();
}
HomepageUser homepageUser = user.get();
UserInfo userInfo = new UserInfo(homepageUser.getId(), homepageUser.getUsername(),
homepageUser.getEmail());
List<HomepageUserCourse> userCourses = homepageUserCourseDao.findAllByUserId(id);
if (CollectionUtils.isEmpty(userCourses)) {
return new UserCourseInfo(userInfo, Collections.emptyList());
}
List<CourseInfo> courseInfos = courseClient.getCourseInfos(
new CourseInfosRequest(
userCourses.stream().map(HomepageUserCourse::getCourseId).collect(Collectors.toList())
)
);
return new UserCourseInfo(userInfo, courseInfos);
}
}
7.10 定义Controller
package com.wpj.homepage.controller;
import com.alibaba.fastjson.JSON;
import com.wpj.homepage.UserInfo;
import com.wpj.homepage.service.IUserService;
import com.wpj.homepage.vo.CreateUserRequest;
import com.wpj.homepage.vo.UserCourseInfo;
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.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户对外服务接口
*/
@Slf4j
@RestController
public class HomepageUserController {
/** 用户服务 */
private final IUserService userService;
@Autowired
public HomepageUserController(IUserService userService) {
this.userService = userService;
}
/**
* 创建用户
*/
@PostMapping("/create/user")
public UserInfo createUser(@RequestBody CreateUserRequest request) {
log.info("<homepage-user>: create user -> {}", JSON.toJSONString(request));
return userService.createUser(request);
}
/**
* 获取用户信息
*/
@GetMapping("/get/user")
public UserInfo getUserInfo(Long id) {
log.info("<homepage-user>: get user -> {}", id);
return userService.getUserInfo(id);
}
/**
* 获取用户课程信息
*/
@GetMapping("/get/user/course")
public UserCourseInfo getUserCourseInfo(Long id) {
log.info("<homepage-user>: get user course info -> {}", id);
return userService.getUserCourseInfo(id);
}
}
7.11 接口功能测试
-
在Test下创建resuource,将application.yml放进来
-
新建Application
package com.wpj.homepage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; /** * 测试用例启动入口 */ @EnableFeignClients(basePackages = {"com.wpj.homepage"}) @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
新建ServiceTest
package com.wpj.homepage.service; import com.alibaba.fastjson.JSON; import com.wpj.homepage.Application; import com.wpj.homepage.dao.HomepageUserCourseDao; import com.wpj.homepage.entity.HomepageUserCourse; import com.wpj.homepage.vo.CreateUserRequest; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; /** * 用户服务测试 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = {Application.class}) public class HomepageUserServiceTest { @Autowired private IUserService userService; @Autowired private HomepageUserCourseDao userCourseDao; @Test @Transactional public void testCreateUser() { System.out.println(JSON.toJSONString(userService.createUser( new CreateUserRequest("wpj", "wpj@imooc.com") ))); } @Test public void testGetUser() { System.out.println(JSON.toJSONString(userService.getUserInfo(2L))); } @Test public void testCreateHomepageUserCourse() { HomepageUserCourse course1 = new HomepageUserCourse(); course1.setUserId(6L); course1.setCourseId(6L); HomepageUserCourse course2 = new HomepageUserCourse(); course2.setUserId(6L); course2.setCourseId(7L); System.out.println(userCourseDao.saveAll(Arrays.asList(course1, course2)).size()); } }
8. 测试对外服务接口
8.1 homepage-zuul的application.yml
zuul:
prefix: /wpj
routes:
course:
path: /homepage-course/**
serviceId: eureka-client-homepage-course
strip-prefix: false
user:
path: /homepage-user/**
serviceId: eureka-client-homepage-user
strip-prefix: false