项目微服务改造
服务划分原则
1.基于业务逻辑 : 将系统中的业务按照**职责范围**进行识别,职责相同的划分为一个单独的服务。
--------------------------------------------------------------
2.基于稳定性 : 将系统中的业务模块按照稳定性进行**排序**。稳定的、不经常修改的划分一块;将不稳定的,经常修改的划分为一个独立服务。比如日志服务、监控服务都是相对稳定的服务,可以归到一起。
--------------------------------------------------------------
3.基于可靠性 : 将系统中的业务模块按照可靠性进行排序。对可靠性要求比较高的核心模块归在一起,对可靠性要求不高的非核心模块归在一块。规避因为一颗老鼠屎坏了一锅粥的单体弊端,同时将来要做高可用方案也能很好的节省机器或带宽的成本。
-------------------------------------------------------------
4.基于高性能 : 将系统中的业务模块按照对性能的要求进行优先级排序。把对性能要求较高的模块独立成一个服务,对性能要求不高的放在一起。比如全文搜索,商品查询和分类,秒杀就属于高性能的核心模块。
分布式项目部署
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvedrDZw-1599298007419)(C:\Users\白米白9999\AppData\Roaming\Typora\typora-user-images\1599031877350.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5EeoJdXx-1599298007421)(C:\Users\白米白9999\AppData\Roaming\Typora\typora-user-images\1598848705510.png)]
新建基于springboot的父类项目
pom.xml配置
----------------------------------------子项目框架
<modules>
<module>eureka-server</module>
<module>config-server</module>
<module>zuul-server</module>
<module>common</module>
<module>provider-api</module>
<module>provider-server</module>
</modules>
---------------------------------------
<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>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
新建基于父项目的子项目common项目
pom.xml配置
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
常用工具类
public class AssertUtil {
private AssertUtil(){};
public static void hasLength(String value ,CodeMsg codeMsg){
if(!StringUtils.hasLength(value)){
throw new BusinessException(codeMsg);
}
}
public static void isEquals(String password, String rpassword,CodeMsg codeMsg) {
if(!password.equals(rpassword)){
throw new BusinessException(codeMsg);
}
}
}
-------------------------------------------------------------
/**
* 系统常量
*/
public class Consts {
//验证码有效时间
public static final int VERIFY_CODE_VAI_TIME = 5; //单位分
//token有效时间
public static final int USER_INFO_TOKEN_VAI_TIME = 30; //单位分
}
-------------------------------------------------------------
@Setter
@Getter
@NoArgsConstructor
public class JsonResult<T> {
public static final int CODE_SUCCESS = 200;
public static final String MSG_SUCCESS = "操作成功";
public static final int CODE_NOLOGIN = 401;
public static final String MSG_NOLOGIN = "请先登录";
public static final int CODE_ERROR = 500;
public static final String MSG_ERROR = "系统异常,请联系管理员";
public static final int CODE_ERROR_PARAM = 501; //参数异常
private int code; //区分不同结果, 而不再是true或者false
private String msg;
private T data; //除了操作结果之后, 还行携带数据返回
public JsonResult(int code, String msg, T data){
this.code = code;
this.msg = msg;
this.data = data;
}
public JsonResult(CodeMsg codeMsg, T data){
this.code = codeMsg.getCode();
this.msg = codeMsg.getMsg();
this.data = data;
}
public static <T> JsonResult success(T data){
return new JsonResult(CODE_SUCCESS, MSG_SUCCESS, data);
}
public static JsonResult success(){
return new JsonResult(CODE_SUCCESS, MSG_SUCCESS, null);
}
public static <T> JsonResult error(int code, String msg, T data){
return new JsonResult(code, msg, data);
}
public static JsonResult defaultError(){
return new JsonResult(CODE_ERROR, MSG_ERROR, null);
}
public static JsonResult noLogin() {
return new JsonResult(CODE_NOLOGIN, MSG_NOLOGIN, null);
}
}
--------------------------------------------------------------
@Getter
public enum RedisKeys {
TODAY_VISITOR_VIEW_NUM("today_visitor_view_num",-1L),
TOTAL_VISITOR_VIEW_NUM("total_visitor_view_num",-1L),
//接口防刷逻辑
BRUSH_PROOF("brush_proof",60L),
USER_STRATEGY_THUMBUP("user_strategy_thumbup",-1L),
USER_STRATEGY_FAVOR("user_strategy_favor",-1L),
STRATEGY_STATIS_VO("strategy_statis_vo",-1L),
VERIFY_CODE("verify_code", Consts.VERIFY_CODE_VAI_TIME*60L),
USER_LOGIN_TOKEN("user_login_token" ,Consts.USER_INFO_TOKEN_VAI_TIME*60L);
private String prefix ;
private Long time ;
private RedisKeys(String prefix, Long time){
this.prefix=prefix;
this.time=time;
}
public String join(String... values){
StringBuilder sb = new StringBuilder();
sb.append(this.prefix);
for (String value : values) {
sb.append(":").append(value);
}
return sb.toString();
}
}
新建基于父项目的子项目eureka-server项目
//启动类 贴注解
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
}
application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
#是否将自己注册进去eureka,false为不注册,true注册
registerWithEureka: false
#是否从eureka抓取注册信息,单点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
#默认是true,现在关闭自我保护机制,保证不可用服务被删除
enable-self-preservation: false
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
新建基于父项目的子项目config-server项目
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
application.yml
server:
port: 9100
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/xxxxxxxxxxxxx
username: hpsyj88@163.com
password: xxxxxxx
label: master
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
#eureka客户端向服务端发送心跳的时间间隔,单位为秒,默认是30
lease-renewal-interval-in-seconds: 1
#eureka服务端收到最后一次心跳等待的时间上限,单位为秒,默认是90,超时剔除
lease-expiration-duration-in-seconds: 2
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
新建基于父项目的子项目Zuul-server项目
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulServer {
//跨域访问
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
//重写父类提供的跨域请求处理的接口
public void addCorsMappings(CorsRegistry registry) {
//添加映射路径
registry.addMapping("/**")
//放行哪些原始域
.allowedOrigins("*")
//是否发送Cookie信息
.allowCredentials(true)
//放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "PUT", "DELETE","OPTIONS")
//放行哪些原始域(头部信息)
.allowedHeaders("*")
//暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Header1", "Header2");
}
};
}
-------------------------------------------------------------
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
// 允许Get的请求方法
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
public static void main(String[] args) {
SpringApplication.run(ZuulServer.class,args);
}
}
bootstrap.yml
spring:
application:
name: zuul-server
cloud:
config:
label: master #分支名称
name: zuul #配置文件名称
profile: server #读取后缀名称:
uri: http://localhost:9100
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
#eureka客户端向服务端发送心跳的时间间隔,单位为秒,默认是30
lease-renewal-interval-in-seconds: 1
#eureka服务端收到最后一次心跳等待的时间上限,单位为秒,默认是90,超时剔除
lease-expiration-duration-in-seconds: 2
码云上的zuul-server.yml
server:
port: 9000
ribbon:
ConnectTimeout: 6000
ReadTimeout: 60000
zuul:
sensitiveHeaders:
#强制采用原始请求的编码格式,即不对Get请求参数做编解码
forceOriginalQueryStringEncoding: true
#忽略匹配,既: order-server这种格式请求路径忽略掉
ignoredPatterns: /*-server/**
routes:
website-server-route:
path: /website/**
service-id: website-server
pom.xml
<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-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
</dependencies>
新建基于父项目的子项目provider-api项目
(非业务逻辑,只提供接口与熔断降级操作)
pomx.xml
<dependencies>
<dependency>
<groupId>cn.wolfcode.wolf2w</groupId>
<artifactId>common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
新建基于父项目provider-api的member-api项目
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<!---依赖不传递-->
<optional>true</optional>
</dependency>
新建feign接口 如
@FeignClient(name = "member-server",fallback = UserInfoFeignHystrix.class)
public interface UserInfoFeignApi {
@GetMapping("/users/get/")
JsonResult get(@RequestParam("id") String id);
@GetMapping("/users/checkPhone/")
JsonResult checkPhone(@RequestParam("phone") String phone);
@PostMapping("/users/regist/")
JsonResult regist(@RequestParam(value = "phone",required = false) String phone,@RequestParam(value = "password",required = false) String password,
@RequestParam(value = "nickname",required = false) String nickname, @RequestParam(value = "rpassword",required = false) String rpassword);
}
定义熔断,降级方法
@Component
public class UserInfoFeignHystrix implements UserInfoFeignApi {
@Override
public JsonResult get(String id) {
System.out.println("出问题啦,找降级方法啦.....");
return null;
}
@Override
public JsonResult checkPhone(String phone) {
System.out.println("出问题啦,找降级方法啦.....");
return null;
}
@Override
public JsonResult regist(String phone, String password, String nickname, String rpassword) {
System.out.println("出问题啦,找降级方法啦.....regist");
return null;
}
}
新建基于父项目provider-api的member-api项目
@FeignClient(name = "msg-server",fallback = MsmFeignHystrix.class)
public interface MsmFeignApi {
@GetMapping("/msms/sendVerifyCode")
JsonResult sendVerifyCode(@RequestParam("phone") String phone);
@GetMapping("/msms/checkVerifyCode")
JsonResult checkVerifyCode(@RequestParam("phone") String phone, @RequestParam("verifyCode") String verifyCode);
}
@Component
public class MsmFeignHystrix implements MsmFeignApi {
@Override
public JsonResult sendVerifyCode(String phone) {
System.out.println("出问题啦,找降级方法啦.....sendVerifyCode");
return null;
}
@Override
public JsonResult checkVerifyCode(String phone, String verifyCode) {
System.out.println("出问题啦,找降级方法啦.....checkVerifyCode");
return null;
}
}
新建基于父项目的子项目provider-server项目
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<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-client</artifactId>
</dependency>
</dependencies>
新建基于父项目provider-api的member-server项目
pom.xml
<dependencies>
<dependency>
<groupId>cn.wolfcode.wolf2w</groupId>
<artifactId>member-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
//@EnableHystrix
public class MemberServer {
public static void main(String[] args) {
SpringApplication.run(MemberServer.class, args);
}
}
bootstrap.yml
spring:
application:
name: member-server
cloud:
config:
label: master #分支名称
name: member #配置文件名称
profile: server #读取后缀名称:
uri: http://localhost:9100
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
#eureka客户端向服务端发送心跳的时间间隔,单位为秒,默认是30
lease-renewal-interval-in-seconds: 1
#eureka服务端收到最后一次心跳等待的时间上限,单位为秒,默认是90,超时剔除
lease-expiration-duration-in-seconds: 2
码云上的menber-server.yml
server:
port: 8081
spring:
data:
mongodb:
uri: mongodb://localhost:27017/member
feign:
client:
config:
default:
connectTimeout: 8000
readTimeout: 8000
hystrix:
enabled: true
各种业务逻辑实现
@RestController
public class UserInfoFeignApiClient implements UserInfoFeignApi {
@Autowired
IUserInfoService userInfoService;
@Autowired
private UserInfoRepository userInfoRepository;
@Override
public JsonResult get(String id) {
return JsonResult.success(userInfoService.get(id));
}
@Override
public JsonResult checkPhone(String phone) {
UserInfo userInfo = userInfoRepository.findByPhone(phone);
//int i =1/0;
return JsonResult.success(userInfo == null);
}
@Override
public JsonResult regist(String phone, String password, String nickname, String rpassword) {
//传入参数不能为空
AssertUtil.hasLength(phone, MemberCodeMsg.PHONE_ERROR);
AssertUtil.hasLength(password, MemberCodeMsg.PASSWORD_ERROR);
AssertUtil.hasLength(rpassword, MemberCodeMsg.REPASSWORD_ERROR);
AssertUtil.hasLength(nickname, MemberCodeMsg.NICKNAME_ERROR);
//验证二次密码是否一致
AssertUtil.isEquals(password, rpassword, MemberCodeMsg.EQUALSPASSWORD_ERROR);
//验证手机号码是否唯一
if (userInfoService.checkPhone(phone)) {
throw new BusinessException(MemberCodeMsg.HASE_REGIST_ERROR);
}
UserInfo userInfo = new UserInfo();
userInfo.setNickname(nickname);
userInfo.setPhone(phone);
userInfo.setPassword(password);
userInfo.setEmail("");
userInfo.setCity("");
userInfo.setGender(UserInfo.GENDER_SECRET);
userInfo.setLevel(1);
userInfo.setState(UserInfo.STATE_NORMAL);
userInfo.setHeadImgUrl("images/default.jpg");
userInfo.setId(null);
userInfoService.save(userInfo);
return JsonResult.success();
}
}
新建基于父项目provider-api的msg-server项目
pom.xml
<dependencies>
<dependency>
<groupId>cn.wolfcode.wolf2w</groupId>
<artifactId>msg-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
@SpringBootApplication
//@EnableHystrix
public class MsgServer {
public static void main(String[] args) {
SpringApplication.run(MsgServer.class, args);
}
}
bootstrap.yml
spring:
application:
name: msg-server
cloud:
config:
label: master #分支名称
name: msg #配置文件名称
profile: server #读取后缀名称:
uri: http://localhost:9100
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
#eureka客户端向服务端发送心跳的时间间隔,单位为秒,默认是30
lease-renewal-interval-in-seconds: 1
#eureka服务端收到最后一次心跳等待的时间上限,单位为秒,默认是90,超时剔除
lease-expiration-duration-in-seconds: 2
msg-server.yml
server:
port: 8082
spring:
redis:
host: 127.0.0.1
feign:
client:
config:
default:
connectTimeout: 8000
readTimeout: 8000
hystrix:
enabled: true
import java.util.concurrent.TimeUnit;
@Service
public class SmsRedisServiceImpl implements ISmsRedisService {
@Autowired
private StringRedisTemplate template ;
@Override
public void saveCode(String phone, String code) {
template.opsForValue().set(RedisKeys.VERIFY_CODE.join(phone),code ,RedisKeys.VERIFY_CODE.getTime(),TimeUnit.SECONDS );
}
@Override
public String getCode(String phone) {
String key =RedisKeys.VERIFY_CODE.join(phone);
String code = template.opsForValue().get(key);
return code;
}
}
g:
default:
connectTimeout: 8000
readTimeout: 8000
hystrix:
enabled: true
```java
import java.util.concurrent.TimeUnit;
@Service
public class SmsRedisServiceImpl implements ISmsRedisService {
@Autowired
private StringRedisTemplate template ;
@Override
public void saveCode(String phone, String code) {
template.opsForValue().set(RedisKeys.VERIFY_CODE.join(phone),code ,RedisKeys.VERIFY_CODE.getTime(),TimeUnit.SECONDS );
}
@Override
public String getCode(String phone) {
String key =RedisKeys.VERIFY_CODE.join(phone);
String code = template.opsForValue().get(key);
return code;
}
}