一.项目技术架构
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-1020hrm-config-server-1030hrm-system-parent hrm-systemmanage-common //针对系统管理服务公共代码如:domain,queryhrm-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:1030name: application-zuulprofile: 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:1030name: application-systemprofile: 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 ResultMapgc.setBaseColumnList(false);// XML columListgc.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.systempc.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() {@Overridepublic 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") {@Overridepublic 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") {@Overridepublic 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") {@Overridepublic 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") {@Overridepublic 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 {@Beanpublic 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 {@Overridepublic 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 {@Beanpublic 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 {@Beanpublic 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 = newUrlBasedCorsConfigurationSource();configSource.registerCorsConfiguration("/**", config);//3.返回新的CorsFilter.return new CorsFilter(configSource);}
}
配置之后重启zuul服务。刷新前端就能访问了。
2.4.redis+feign
2.4.1.搭建项目结构
hrm-redis-parenthrm-redis-clienthrm-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:1030name: application-redisprofile: 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);}/*** 分页插件*/@Beanpublic 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 {@Autowiredprivate RedisFeignClient redisFeignClient;//重置Redis中的课程分类private List<CourseType> resetRedisForCourseType(){// 如果Reids没有就从Mysql中查List<CourseType> courseTypes = baseMapper.selectList(null);// Mysql查到之后同步一份到RedisredisFeignClient.set(RedisKeyConstants.COURSE_TYPE, JSON.toJSONString(courseTypes));return courseTypes;}@Overridepublic boolean insert(CourseType entity) {boolean insertSucess = super.insert(entity);resetRedisForCourseType();return insertSucess;}@Overridepublic boolean deleteById(Serializable id) {boolean deleteSucess = super.deleteById(id);resetRedisForCourseType();return deleteSucess;}@Overridepublic boolean updateById(CourseType entity) {boolean updateSuccess = super.updateById(entity);resetRedisForCourseType();return updateSuccess;}@Overridepublic List<CourseType> treeData() {List<CourseType> courseTypes = null;// 查询课程分类的时候先查询RedisAjaxResult ajaxResult = redisFeignClient.get(RedisKeyConstants.COURSE_TYPE);//判断是否有结果if(ajaxResult.isSuccess() && null != ajaxResult.getResultObj()){//Redis中有数据//如果Redis有就直接返回、String jsonFromRedis = ajaxResult.getResultObj().toString();//存在redis中的数据 ,要不要有层级结构 :放没有处理过的listcourseTypes = 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 就是父分类的idCourseType currentPrimaryCourseType = null; //1037for(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> {@Overridepublic RedisFeignClient create(Throwable throwable) {return new RedisFeignClient() {//托底方法@Overridepublic AjaxResult set(String key, String value) {throwable.printStackTrace();return AjaxResult.me().setSuccess(false).setMessage("Redis服务不可用["+throwable.getMessage()+"]");}@Overridepublic 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-ioString 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);//得到groupNameString groupName = pathTmp.substring(0, pathTmp.indexOf("/"));//得到fileNameString 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 = FastDFS1234567890tracker_server=118.25.154.214:22122 #服务器配置了fastDFS的IPconnection_pool.enabled = true
connection_pool.max_count_per_entry = 500
connection_pool.max_idle_time = 3600
connection_pool.max_wait_time_in_ms = 1000