搭建一个完整的微服务项目
一.项目技术架构1.技术栈前台技术Node.js、Npm、Vue.js、WebPack、Vue Cli、Element UI后台架构微服务架构:按照功能拆分N多个服务,每个服务可以独立技术选型,独立开发,独立部署,独立运维.,单个服务使用基于ssm的springboot,服务间通过spring cloud协调。2.后端项目微服务原型搭建2.1 项目基本模块搭建hrm-parenthrm-basic
一.项目技术架构
1.技术栈
前台技术
Node.js、Npm、Vue.js、WebPack、Vue Cli、Element UI
后台架构
微服务架构:按照功能拆分N多个服务,每个服务可以独立技术选型,独立开发,独立部署,独立运维.,单个服务使用基于ssm的springboot,服务间通过spring cloud协调。
2.后端项目微服务原型搭建
2.1 项目基本模块搭建
hrm-parent
hrm-basic-parent //项目基本模块
hrm-basic-utils //公共工具模块
hrm-basic-common //公共代码模块
hrm-support-parent //springcloud微服务支持模块
hrm-eureka-server-1010
hrm-gateway-zuul-1020
hrm-config-server-1030
hrm-system-parent
hrm-systemmanage-common //针对系统管理服务公共代码如:domain,query
hrm-systemmanage-service-2010 //针对于系统管理的微服务
2.1.1 hrm-parent的搭建
Maven结构
先在顶层父模块进行设置管理依赖包和版本号以及一些公共的jar包。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<!--所有子模块一定要用到的公共的jar包-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<!--springcloud版本管理,springcloud相关模块引入是就不需要制定版本了-->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
SpringCloud组件之五大神兽:
服务注册发现——Netflix Eureka : 帮我们服务的通信地址的
客服端负载均衡——Netflix Ribbon\Feign : 解决网络通信的
断路器——Netflix Hystrix :解决微服务故障的
服务网关——Netflix Zuul :微服务的大门(安保部门)
分布式配置——Spring Cloud Config :统一管理微服务的配置
2.1.2.Eureka注册中心
Eureka是netflix的一个子模块,也是核心模块之一,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务,而不需要修改服务调用的配置文件了,功能类似于dubbo的注册中心,比如zookeeper。
2.1.2.1创建项目
在hrm-parent里面的hrm-support-parent进行模块化搭建注册中心
在注册中心的pom.xml导包
<!--Eureka服务端支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
配置yml
server:
port: 1010
eureka: #Eureka的配置
instance:
hostname: localhost #主机
client: #对Eureka客户端配置
registerWithEureka: false #注册中心自己 , 不准向注册中心自己注册
fetchRegistry: false #注册中心不需要 获取服务的通信地址清单
serviceUrl: #注册中心 服务的注册地址
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://localhost:1010/eureka/
在配置类上写上相应注解之后main启动
@SpringBootApplication
@EnableEurekaClient
public class EurekaServerApplication1010 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication1010.class);
}
}
2.1.3.config-server
创建网关项目
导包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 集成Web的jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
配置yml文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: config-server:1030 #服务注册到注册中心的id
server:
port: 1030
#应用的名字
spring:
application:
name: config-server
#码云配置
cloud:
config:
server:
git:
uri: https://gitee.com/lxx/xx.git #你的仓库地址(gtihub、gtilab、码云)
username: xx@qq.com #你的仓库的账户
password: xxx #你账户的密码
search-paths: hrm-parent/configfiles #从git 仓库的哪个目录找配置文件
配置类打上注解
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication1030 {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication1030.class);
}
}
启动之后测试
http://localhost:1030/application-zuul-dev.yml 能读取配置文件,配置中心就ok了
2.1.4.Zuul GateWay
创建项目
导包
<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.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
配置application-zuul-dev.yml文件,上传你的仓库。
server:
port: 1020
#应用的名字
spring:
application:
name: zuul-gateway
zuul:
ignored-services: "*" #禁止使用服务名字进行访问
prefix: "/hrm" #统一的前缀
routes: #配置路由,指定服务的访问路径
pay-server: "/pay/**"
course-server: "/course/**"
system-server: "/system/**"
redis-server: "/redis/**"
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 3000 # 熔断超时时长:3000ms
配置bootstrap.yml文件
spring:
cloud:
config:
uri: http://localhost:1030
name: application-zuul
profile: dev #环境 组成完整的文件名
在配置类配置
@SpringBootApplication
@EnableZuulProxy
public class ZuulServerApplication1020 {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication1020.class);
}
}
启动之后能从你的仓库拿到你配置文件启动就ok了(如果报错,例如端口8080或者其他都是没有从你仓库拿到你的配置文件)。
2.1.5 system-2010(步骤同上,差不多)
创建项目
导包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
在application-system-dev.yml配置之后上传你的仓库
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true #使用ip进行注册
instance-id: system-server:2010 #服务注册到注册中心的id
server:
port: 2010
#应用的名字
spring:
application:
name: system-server
配置bootstrap.yml文件
spring:
cloud:
config:
uri: http://localhost:1030
name: application-system
profile: dev #环境 组成完整的文件名
在配置类打上注解并启动,启动成功就OK
@SpringBootApplication
public class SystemServerApplication2010 {
public static void main(String[] args) {
SpringApplication.run(SystemServerApplication2010.class);
}
}
在hrm-basic-parent里面创建hrm-code-generate(代码生成)
创建项目
导包
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
创建代码生成的类
package com.tys.hrm;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DbType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.*;
//代码生成的主类
public class GenteratorCode {
//运行main方法就可以生成代码了
public static void main(String[] args) throws InterruptedException {
//用来获取Mybatis-Plus.properties文件的配置信息
//不要加后缀
ResourceBundle rb = ResourceBundle.getBundle("mybatiesplus-config-course");
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(rb.getString("OutputDir"));
gc.setFileOverride(true);
gc.setActiveRecord(true);// 开启 activeRecord 模式
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);// XML ResultMap
gc.setBaseColumnList(false);// XML columList
gc.setAuthor(rb.getString("author"));
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDbType(DbType.MYSQL);
dsc.setTypeConvert(new MySqlTypeConvert());
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername(rb.getString("jdbc.user"));
dsc.setPassword(rb.getString("jdbc.pwd"));
dsc.setUrl(rb.getString("jdbc.url"));
mpg.setDataSource(dsc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setTablePrefix(new String[] { "t_" });// 此处可以修改为您的表前缀
strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
strategy.setInclude(new String[]{"t_course_type"}); // 需要生成的表 :
mpg.setStrategy(strategy);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent(rb.getString("parent")); //基本包 cn.itsource.system
pc.setController("web.controller");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setEntity("domain");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
// 注入自定义配置,可以在 VM 中使用 cfg.abc 【可无】
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-rb");
this.setMap(map);
}
};
List<FileOutConfig> focList = new ArrayList<FileOutConfig>();
// 调整 controller 生成目录演示
focList.add(new FileOutConfig("/templates/controller.java.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
//controller输出完整路径
return rb.getString("OutputDir")+ "/com/tys/hrm/course/web/controller/" + tableInfo.getEntityName() + "Controller.java";
}
});
// 调整 query 生成目录演示
focList.add(new FileOutConfig("/templates/query.java.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
//query输出完整路径
return rb.getString("OutputDirBase")+ "/com/tys/hrm/course/query/" + tableInfo.getEntityName() + "Query.java";
}
});
// 调整 domain 生成目录演示 , 你的domain到底要输出到哪儿????,你的domain怎么输出
focList.add(new FileOutConfig("/templates/entity.java.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
//domain输出完整路径
return rb.getString("OutputDirBase")+ "/com/tys/hrm/course/domain/" + tableInfo.getEntityName() + ".java";
}
});
// 调整 xml 生成目录演示
focList.add(new FileOutConfig("/templates/mapper.xml.vm") {
@Override
public String outputFile(TableInfo tableInfo) {
return rb.getString("OutputDirXml")+ "/com/tys/course/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 自定义模板配置,可以 copy 源码 mybatis-plus/src/main/resources/templates 下面内容修改,
// 放置自己项目的 src/main/resources/templates 目录下, 默认名称一下可以不配置,也可以自定义模板名称
TemplateConfig tc = new TemplateConfig();
tc.setService("/templates/service.java.vm");
tc.setServiceImpl("/templates/serviceImpl.java.vm");
tc.setEntity(null);
tc.setMapper("/templates/mapper.java.vm");
tc.setController(null);
tc.setXml(null);
// 如上任何一个模块如果设置 空 OR Null 将不生成该模块。
mpg.setTemplate(tc);
// 执行生成
mpg.execute();
}
}
创建mybatiesplus-config-course.properties文件
#此处为本项目src所在路径(代码生成器输出路径),注意一定是当前项目所在的目录哟
#mapper,servier,controller输出目录
OutputDir=E:/IdeaProjects/hrm/hrm-parent/hrm-course-parent/hrm-course-service-2020/src/main/java
#mapper.xml SQL映射文件目录
OutputDirXml=E:/IdeaProjects/hrm/hrm-parent/hrm-course-parent/hrm-course-service-2020/src/main/resources
#domain,query输出的目录
OutputDirBase=E:/IdeaProjects/hrm/hrm-parent/hrm-course-parent/hrm-course-common/src/main/java
#设置作者
author=tys
#自定义包路径
parent=com.tys.hrm.course
#数据库连接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///hrm-course
jdbc.user=root
jdbc.pwd=123456
然后去system模块添加依赖包
在hrm-system-common导包
<dependencies>
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-basic-common</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
因为生成的mapper、service、controller需连接数据库,所以hrm-system-service-2010也要导包
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-system-common</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-basic-utils</artifactId>
</dependency>
让后直接点代码生成类的main方法自动生成代码,这样,domain、query、mapper、service、controller都创建完成了
2.1.6 course-server(步骤同system,差不多)
创建完成之后,用代码生成器生成course的所有。
2.2.接口文档Swagger
在创建的所有代码生成的服务(system、course)导包 和网关zuul服务也到入swagger包
<!--引入swagger支持-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
在这些包里面创建swagger的类创建接口文档
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class Swagger2 {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//对外暴露服务的包,以controller的方式暴露,所以就是controller的包.
.apis(RequestHandlerSelectors.basePackage("com.tys.hrm.course.web.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("平台服务api")
.description("平台服务接口文档说明")
.contact(new Contact("yhptest", "", "yhp@itsource.cn"))
.version("1.0")
.build();
}
}
然后重新启动,访问http://localhost:2020/swagger-ui.html、http://localhost:2010/swagger-ui.html
在zuul创建一个配置config包,创建swagger类
package com.tys.hrm.config;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Component
@Primary
public class DocumentationConfig implements SwaggerResourcesProvider {
@Override
public List<SwaggerResource> get() {
List resources = new ArrayList<>();
resources.add(swaggerResource("系统管理", "/hrm/system/v2/api-docs", "2.0"));
resources.add(swaggerResource("课程管理", "/hrm/course/v2/api-docs", "2.0"));
return resources;
}
private SwaggerResource swaggerResource(String name, String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
package com.tys.hrm.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("资源管理系统")
.description("资源管理系统接口文档说明")
.termsOfServiceUrl("http://localhost:1020")
.contact(new Contact("yphtest", "", "yhp@itsoruce.cn"))
.version("1.0")
.build();
}
}
然后重启zuul服务,访问http://localhost:1020/swagger-ui.html
2.3.elementui+vue
前端启动 npm run dev
因为前后端分离,访问后台会出现跨域问题,跨越配置-在zuul进行配置(所有前端统一入口)。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//1) 允许的域,不要写*,否则cookie就无法使用了
config.addAllowedOrigin("http://127.0.0.1:6001");
config.addAllowedOrigin("http://localhost:6001");
//2) 是否发送Cookie信息
config.setAllowCredentials(true);
//3) 允许的请求方式
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 4)允许的头信息
config.addAllowedHeader("*");
//2.添加映射路径,我们拦截一切请求
UrlBasedCorsConfigurationSource configSource = new
UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
配置之后重启zuul服务。刷新前端就能访问了。
2.4.redis+feign
2.4.1.搭建项目结构
hrm-redis-parent
hrm-redis-client
hrm-redis-service-2030
2.4.1.1 redis
搭建 hrm-redis-service-2030 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-basic-utils</artifactId>
</dependency>
</dependencies>
准备Redis工具类
配置文件 redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
redis.timeout=5000
RedisUtil
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.util.Properties;
/**
* 获取连接池对象
*/
public enum RedisUtils {
INSTANCE;
static JedisPool jedisPool = null;
static {
//1 创建连接池配置对象
JedisPoolConfig config = new JedisPoolConfig();
//2 进行配置-四个配置
config.setMaxIdle(1);//最小连接数
config.setMaxTotal(11);//最大连接数
config.setMaxWaitMillis(10 * 1000L);//最长等待时间
config.setTestOnBorrow(true);//测试连接时是否畅通
//3 通过配置对象创建连接池对象
Properties properties = null;
try {
properties = new Properties(); properties.load(RedisUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
} catch (IOException e) {
e.printStackTrace();
}
String host = properties.getProperty("redis.host");
String port = properties.getProperty("redis.port");
String password = properties.getProperty("redis.password");
String timeout = properties.getProperty("redis.timeout");
jedisPool = new JedisPool(config, host, Integer.valueOf(port),Integer.valueOf(timeout), password);
}
//获取连接
public Jedis getSource() {
return jedisPool.getResource();
}
//关闭资源
public void closeSource(Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
/**
* 设置字符值
*
* @param key
* @param value
*/
public void set(String key, String value) {
Jedis jedis = getSource();
jedis.set(key, value);
closeSource(jedis);
}
/**
* 设置
* @param key
* @param value
*/
public void set(byte[] key, byte[] value) {
Jedis jedis = getSource();
jedis.set(key, value);
closeSource(jedis);
}
/**
*
* @param key
* @return
*/
public byte[] get(byte[] key) {
Jedis jedis = getSource();
try {
return jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSource(jedis);
}
return null;
}
/**
* 设置字符值
*
* @param key
*/
public String get(String key) {
Jedis jedis = getSource();
try {
return jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSource(jedis);
}
return null;
}
}
编写RedisController
/**
* redis的接口
*/
@RestController
@RequestMapping("/redis")
public class RedisController {
@GetMapping("/get/{key}")
public AjaxResult get(@PathVariable("key")String key){
String result = RedisUtils.INSTANCE.get(key);
return AjaxResult.me().setResultObj(result);
}
@PostMapping("/set")
public AjaxResult set(@RequestParam("key")String key,@RequestParam("value")String value){
RedisUtils.INSTANCE.set(key,value);
return AjaxResult.me();
}
@PostMapping("/setex")
public AjaxResult setex(@RequestParam("key")String key,
@RequestParam("value")String value,
@RequestParam("seconds")int seconds){
RedisUtils.INSTANCE.setex(key,value,seconds);
return AjaxResult.me();
}
}
配置application-redis-dev.yml,配置成功上传仓库
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1010/eureka/ #注册中心地址
instance:
prefer-ip-address: true #使用ip地址注册
instance-id: redis-service #指定服务的id
server:
port: 2030
spring:
application:
name: redis-service
再配合bootstrap.yml
spring:
cloud:
config:
uri: http://localhost:1030
name: application-redis
profile: dev #环境 组成完整的文件名
2.4.1.2 feign
hrm-redis-feign导包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-basic-utils</artifactId>
</dependency>
</dependencies>
再里面写一个feign接口
//value属性:调用目标服务的服务名
@FeignClient(value = "redis-server")
public interface RedisFeignClient {
//设置值
@PostMapping("/redis/set")
AjaxResult set(@RequestParam("key")String key, @RequestParam("value")String value);
@GetMapping("/redis/get/{key}")
AjaxResult get(@PathVariable("key")String key);
}
在需要缓存的地方依赖hrm-redis-feign项目
在这个微服务类开启配置
@SpringBootApplication
@MapperScan("com.tys.hrm.course.mapper")
@EnableTransactionManagement
@EnableFeignClients("com.tys.hrm.feignclients")
public class CourseServerApplication2020 {
public static void main(String[] args) {
SpringApplication.run(CourseServerApplication2020.class);
}
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
修改这个服务的service.iml的增删改查方法,添加缓存
package com.tys.hrm.course.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.tys.hrm.constants.RedisKeyConstants;
import com.tys.hrm.course.domain.CourseType;
import com.tys.hrm.course.mapper.CourseTypeMapper;
import com.tys.hrm.course.service.ICourseTypeService;
import com.tys.hrm.feignclients.RedisFeignClient;
import com.tys.hrm.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 课程目录 服务实现类
* </p>
*/
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {
@Autowired
private RedisFeignClient redisFeignClient;
//重置Redis中的课程分类
private List<CourseType> resetRedisForCourseType(){
// 如果Reids没有就从Mysql中查
List<CourseType> courseTypes = baseMapper.selectList(null);
// Mysql查到之后同步一份到Redis
redisFeignClient.set(RedisKeyConstants.COURSE_TYPE, JSON.toJSONString(courseTypes));
return courseTypes;
}
@Override
public boolean insert(CourseType entity) {
boolean insertSucess = super.insert(entity);
resetRedisForCourseType();
return insertSucess;
}
@Override
public boolean deleteById(Serializable id) {
boolean deleteSucess = super.deleteById(id);
resetRedisForCourseType();
return deleteSucess;
}
@Override
public boolean updateById(CourseType entity) {
boolean updateSuccess = super.updateById(entity);
resetRedisForCourseType();
return updateSuccess;
}
@Override
public List<CourseType> treeData() {
List<CourseType> courseTypes = null;
// 查询课程分类的时候先查询Redis
AjaxResult ajaxResult = redisFeignClient.get(RedisKeyConstants.COURSE_TYPE);
//判断是否有结果
if(ajaxResult.isSuccess() && null != ajaxResult.getResultObj()){
//Redis中有数据
//如果Redis有就直接返回、
String jsonFromRedis = ajaxResult.getResultObj().toString();
//存在redis中的数据 ,要不要有层级结构 :放没有处理过的list
courseTypes = JSON.parseArray(jsonFromRedis , CourseType.class);
}else{
courseTypes = resetRedisForCourseType();
}
//1.查询所有的课程类型
//List<CourseType> courseTypes = baseMapper.selectList(null);
//2.先过滤出一级分类
//用来封装一级分类,当然每个一级分类的children中有其子分类
List<CourseType> primaryCourseType = new ArrayList<>();
for(CourseType courseType : courseTypes){
//如果pid==0,那么就是一级分类
if(courseType.getPid().longValue() == 0){
primaryCourseType.add(courseType);//1037
}else{
//2.如果不是一级分类,就要知道自己的父分类,装到自己的父分类的 children
//courseType :当前分类,根据当前分类的pid 就是父分类的id
CourseType currentPrimaryCourseType = null; //1037
for(CourseType pcourseType : courseTypes ){
if(courseType.getPid().longValue() == pcourseType.getId().longValue()){
//如果当前分类(courseType)的pid 和某个分类的id相等,那么这个某个分类就是当前分类的父分类
currentPrimaryCourseType = pcourseType;
break;
}
}
if(currentPrimaryCourseType != null){
//3.如果找到了父分类,就把当前分类加入父分类的children中
currentPrimaryCourseType.getChildren().add(courseType);
}
}
}
return primaryCourseType;
}
}
重启这个微服务和redis服务,开启redis
redis-server.exe redis.windows.conf
然后去测试,第一次进数据库查询,并缓存到redis中,第二次查询则直接进缓存,其他操作 增删改 操作之后,进行更新缓存。
在feign接口打上注解,调用托底类
@FeignClient(value = "redis-server",fallbackFactory = RedisFeignFallbackFactory.class)
在feign接口实现方法 重写方法
package com.tys.hrm.fallback;
import com.tys.hrm.feignclients.RedisFeignClient;
import com.tys.hrm.util.AjaxResult;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
@Component
public class RedisFeignFallbackFactory implements FallbackFactory<RedisFeignClient> {
@Override
public RedisFeignClient create(Throwable throwable) {
return new RedisFeignClient() {
//托底方法
@Override
public AjaxResult set(String key, String value) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("Redis服务不可用["+throwable.getMessage()+"]");
}
@Override
public AjaxResult get(String key) {
throwable.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("Redis服务不可用["+throwable.getMessage()+"]");
}
};
}
}
2.4.1.3 fastdfs
导包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 集成Web的jar包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>com.tys</groupId>
<artifactId>hrm-basic-utils</artifactId>
</dependency>
<!--引入swagger支持-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.csource</groupId>
<artifactId>fastdfs-client-java</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
导入工具类fastdfs
package com.tys.hrm.utils;
import org.csource.common.NameValuePair;
import org.csource.fastdfs.*;
public class FastDfsApiOpr {
public static String CONF_FILENAME = FastDfsApiOpr.class.getClassLoader()
.getResource("fdfs_client.conf").getFile();
/**
* 上传文件
* @param file
* @param extName
* @return
*/
public static String upload(byte[] file,String extName) {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getTrackerServer();
//TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
NameValuePair nvp [] = new NameValuePair[]{
new NameValuePair("age", "18"),
new NameValuePair("sex", "male")
};
String fileIds[] = storageClient.upload_file(file,extName,nvp);
System.out.println(fileIds.length);
System.out.println("组名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
return "/"+fileIds[0]+"/"+fileIds[1];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 上传文件
* @param extName
* @return
*/
public static String upload(String path,String extName) {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getTrackerServer();
//TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
String fileIds[] = storageClient.upload_file(path, extName,null);
System.out.println(fileIds.length);
System.out.println("组名:" + fileIds[0]);
System.out.println("路径: " + fileIds[1]);
return "/"+fileIds[0]+"/"+fileIds[1];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 下载文件
* @param groupName
* @param fileName
* @return
*/
public static byte[] download(String groupName,String fileName) {
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getTrackerServer();
//TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer, storageServer);
byte[] b = storageClient.download_file(groupName, fileName);
return b;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 删除文件
* @param groupName
* @param fileName
*/
public static void delete(String groupName,String fileName){
try {
ClientGlobal.init(CONF_FILENAME);
TrackerClient tracker = new TrackerClient();
TrackerServer trackerServer = tracker.getTrackerServer();
//TrackerServer trackerServer = tracker.getConnection();
StorageServer storageServer = null;
StorageClient storageClient = new StorageClient(trackerServer,
storageServer);
int i = storageClient.delete_file(groupName,fileName);
System.out.println( i==0 ? "删除成功" : "删除失败:"+i);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("删除异常,"+e.getMessage());
}
}
}
直接复制swagger
创建web.controller层
package com.tys.hrm.web.controller;
import com.tys.hrm.util.AjaxResult;
import com.tys.hrm.utils.FastDfsApiOpr;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
//文件统一处理
@RestController
@RequestMapping("/fastdfs")
public class FastDfsController {
//文件上传
@PostMapping("/upload")
public AjaxResult upload(MultipartFile file) throws Exception {
//把文件上传到Fastdfs云服务器
try {
//原生的文件名:a.jpg :commons-io
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String filePath = FastDfsApiOpr.upload(file.getBytes() , extension);
return AjaxResult.me().setResultObj(filePath);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setMessage("文件上传失败");
}
}
@DeleteMapping("/remove")
public AjaxResult delete(@RequestParam("path") String path) throws Exception{
try{
/*把组名前面的/去掉
* substring(int beginIndex) 返回字符串的子字符串。
* substring(int beginIndex, int endIndex) beginIndex起始索引(包含)索引从0开始。endIndex结束索引(不包括).
* indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
* */
String pathTmp = path.substring(1);
//得到groupName
String groupName = pathTmp.substring(0, pathTmp.indexOf("/"));
//得到fileName
String fileName = pathTmp.substring(pathTmp.indexOf("/")+1);
System.out.println(groupName);
System.out.println(fileName);
FastDfsApiOpr.delete(groupName, fileName);
return AjaxResult.me();
}catch (Exception e){
e.printStackTrace();
return AjaxResult.me().setSuccess(false).setResultObj("删除失败!" + e.getMessage());
}
}
}
设置fastDFS配置文件(fdfs_client.conf)
connect_timeout = 2
network_timeout = 30
charset = UTF-8
http.tracker_http_port = 80
http.anti_steal_token = no
http.secret_key = FastDFS1234567890
tracker_server=118.25.154.214:22122 #服务器配置了fastDFS的IP
connection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000
更多推荐
所有评论(0)