11.3 项目开发
通过对需求描述的分析可知,需要构建 3 个项目,即后台接口管理项目、促销活动微服务项目和网关项目。本节分别介绍这 3 个项目的开发过程。
11.3.1 后台接口管理项目
使用 https://start.spring.io/工具新建 promotion 项目,导入开发工具。新建相关的 package,整体项目结构如图 11.1 所示。
图 11.1 promotion 项目结构
(1)在 pom.xml 文件中添加相关依赖,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http: //www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https:// maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.9.RELEASE</version> <relativePath/> <!--lookup parent from repository --> </parent> <groupId>com.example.promotion</groupId> <artifactId>promotion</artifactId> <version>0.0.1-SNAPSHOT</version> <name>promotion</name> <description>Promotion project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config </artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery </artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.3</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
(2)修改 application.xml 配置文件,在其中配置数据库的连接方式,代码如下:
server: port: 8080 spring: application: name: promotion datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&character Encoding=utf8 username: test password: test type: com.zaxxer.hikari.HikariDataSource hikari: minimum-idle: 10 maximum-pool-size: 100 auto-commit: true idle-timeout: 30000 pool-name: UserHikariCP max-lifetime: 1800000 connection-timeout: 30000 jpa: database: MYSQL hibernate: ddl-auto: none show-sql: true
(3)由于集成了 Nacos 和 Sentinel 中间件,因此需要 bootstrap.xml 配置文件,具体配置如下:
spring: cloud: nacos: discovery: server-addr: 127.0.0.1:8848 ip: 127.0.0.1 port: 80 namespace: 40421527-56ff-410b-8ca8-e025aca9e946 group: default config: server-addr: 127.0.0.1:8848 file-extension: properties namespace: 40421527-56ff-410b-8ca8-e025aca9e946 group: default sentinel: enabled: true transport: dashboard: 127.0.0.1:8888 clientIp: 127.0.0.1 port: 8719 log: dir: /log/sentinel filter: enabled: false management: endpoint: metrics: enabled: true prometheus: enabled: true endpoints: web: base-path: / exposure: include: health,info,status,prometheus metrics: export: prometheus: enabled: true tags: application: ${spring.application.name} web: server: request: autotime: enabled: true percentiles-histogram: on percentiles: -0.9 -0.99 client: request: autotime: enabled: true percentiles-histogram: on percentiles: -0.9 -0.99
(4)本例使用 log4j2 日志架构,配置如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <properties> <property name="LOG_HOME">/log</property> </properties> <Appenders> <Console name="CONSOLE" target="SYSTEM_OUT" > <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c{1.} %msg%n"/> </Console> <RollingRandomAccessFile name="INFO_FILE" fileName= "${LOG_HOME}/info.log" filePattern="${LOG_HOME}/info-%d{HH} -%i.log" immediateFlush="true"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%traceId] %-5p %c{1.} %msg%n"/> <Policies> <TimeBasedTriggeringPolicy /> </Policies> <DefaultRolloverStrategy max="1"/> <Filters> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch= "NEUTRAL"/> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch= "DENY"/> </Filters> </RollingRandomAccessFile> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="CONSOLE" /> <AppenderRef ref="INFO_FILE" /> </Root> </Loggers> </Configuration>
(5)将 Redis 配置信息集成到 Nacos 上,配置详情如图 11.2 所示。
图 11.2 Nacos 配置详情
具体的 Redis 信息如下:
redis.promotion.host=127.0.0.1 redis.promotion.port=6379 redis.promotion.password=test redis.promotion.maxTotal=2000 redis.promotion.maxIdle=100 redis.promotion.minIdle=40 redis.promotion.maxWaitMillis=3000 redis.promotion.timeBetweenEvictionRunsMillis=30000 redis.promotion.commandTimeout=3000
(6)Redis 自动配置:
新建 RedisProperties.class 文件,代码如下:
package com.example.promotion.config; import lombok.Data; import org.springframework.boot.context.properties.Configuration Properties; @Data @ConfigurationProperties(prefix = "redis") public class RedisProperties { private RedisInfo promotion; @Data public static class RedisInfo{ protected int maxTotal = 2000; //最大连接数 protected int maxIdle = 100; //最大空闲数 protected int minIdle = 40; //最小空闲数 protected int maxWaitMillis = 3000; //最长等待时间 //空闲回收休眠时间 protected int timeBetweenEvictionRunsMillis = 30000; protected int commandTimeout = 3000; //命令执行超时时间 private String host; //Redis 地址 private int port; //Redis 端口 private String password; //Redis 密码 } }
新建 RedisAutoConfiguration.class 文件,代码如下:
package com.example.promotion.config; import java.time.Duration; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.boot.autoconfigure.condition.Conditional OnClass; import org.springframework.boot.autoconfigure.condition.Conditional OnProperty; import org.springframework.boot.context.properties.EnableConfiguration Properties; import org.springframework.cloud.context.config.annotation.Refresh Scope; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisStandalone Configuration; import org.springframework.data.redis.connection.lettuce.Lettuce ClientConfiguration; import org.springframework.data.redis.connection.lettuce.Lettuce ConnectionFactory; import org.springframework.data.redis.connection.lettuce.Lettuce PoolingClientConfiguration; import org.springframework.data.redis.core.StringRedisTemplate; @ConditionalOnClass(LettuceConnectionFactory.class) @Configuration @EnableConfigurationProperties(RedisProperties.class) @ConditionalOnProperty("redis.promotion.host") public class RedisAutoConfiguration { @Bean @RefreshScope public GenericObjectPoolConfig genericObjectPoolConfig(Redis Properties properties) { //通用线程池配置 GenericObjectPoolConfig genericObjectPoolConfig = new Generic ObjectPoolConfig(); //设置最大的连接数 genericObjectPoolConfig.setMaxTotal(properties.getPromotion(). getMaxTotal()); //设置最大的空闲数 genericObjectPoolConfig.setMaxIdle(properties.getPromotion(). getMaxIdle()); //设置最小的空闲数 genericObjectPoolConfig.setMinIdle(properties.getPromotion(). getMinIdle()); //设置最长的等待时间 genericObjectPoolConfig.setMaxWaitMillis(properties.get Promotion().getMaxWaitMillis()); //从连接池取出连接时检查有效性 genericObjectPoolConfig.setTestOnBorrow(true); //连接返回时检查有效性 genericObjectPoolConfig.setTestOnReturn(true); //空闲时检查有效性 genericObjectPoolConfig.setTestWhileIdle(true); //空闲回收休眠时间 genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis (properties.getPromotion().getTimeBetweenEvictionRunsMillis()); return genericObjectPoolConfig; } @Bean @RefreshScope public LettuceClientConfiguration lettuceClientConfiguration (RedisProperties properties, GenericObjectPoolConfig genericObject PoolConfig) { //Lettuce 客户端配置 LettucePoolingClientConfiguration build = LettucePooling ClientConfiguration.builder() .commandTimeout(Duration.ofMillis(properties.get Promotion().getCommandTimeout())) .shutdownTimeout(Duration.ZERO) .poolConfig(genericObjectPoolConfig) .build(); return build; } @Bean @RefreshScope public LettuceConnectionFactory lettuceConnectionFactory(Redis Properties properties, LettuceClientConfiguration lettuceClientConfiguration) { //Redis 配置 RedisStandaloneConfiguration redisConfiguration = new Redis StandaloneConfiguration(properties.getPromotion().getHost(), properties. getPromotion().getPort()); redisConfiguration.setPassword(properties.getPromotion(). getPassword()); //Lettuce 连接工厂 LettuceConnectionFactory lettuceConnectionFactory = new Lettuce ConnectionFactory(redisConfiguration, lettuceClientConfiguration); return lettuceConnectionFactory; } @Bean(name = "redisTemplate") public StringRedisTemplate stringRedisTemplate(LettuceConnection Factory lettuceConnectionFactory) { //RedisTemplate 声明 return new StringRedisTemplate(lettuceConnectionFactory); } }
(7)新增 Sentinel 切面配置,代码如下:
package com.example.promotion.config; import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResource Aspect; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SentinelConfig { @Bean public SentinelResourceAspect sentinelResourceAspect() { //Sentinel 切面声明 return new SentinelResourceAspect(); } }
(8)新建 Model 层的对象 PromotionEntyty,代码如下:
package com.example.promotion.model; import java.io.Serializable; import java.util.Date; 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 org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntity Listener; import lombok.Data; @Entity @Table(name="promotion") //声明表名 @Data @EntityListeners(AuditingEntityListener.class) public class PromotionEntity implements Serializable { private static final long serialVersionUID = 1L; //主键 ID @Id @Column(name="id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; //促销活动的名称 @Column(name="name") private String name; //促销活动的开始时间 @Column(name="begin_time") private Integer beginTime; //促销活动的结束时间 @Column(name="end_time") private Integer endTime; //奖品 @Column(name="prize") private String prize; //创建时间 @Column(name="create_time") @CreatedDate private Date createTime; //更新时间 @Column(name="update_time") @LastModifiedDate private Date updateTime; }
(9)本例采用 Spring Boot JPA。Repository 代码如下:
package com.example.promotion.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.promotion.model.PromotionEntity; @Repository public interface PromotionRepository extends JpaRepository<Promotion Entity,Integer>{ //自定义查询 PromotionEntity findByName(String name); }
(10)接口返回通用状态码及 Redis 操作 key 的声明。
新增 Constant.class 文件,代码如下:
package com.example.promotion.constants; public class Constant { //接口调用成功,返回成功状态码 public static final String SUCCESS_CODE = "S00000"; //接口失败,则返回状态码 public static final String ERROR_CODE = "F00001"; //接口成功返回信息 public static final String SUCCESS_MSG = "success"; //促销活动 Redis 存储 key,用来存储活动信息 public static final String REDIS_PROMOTION_KEY = "promotion:{0}"; //赠送奖品领取记录 public static final String REDIS_PRIZE_KEY = "promotion:{0}:{1}"; }
新建 AbstractResponse.class 文件,代码如下:
package com.example.promotion.constants; //通用返回类 public class AbstractResponse { private String code; private String msg; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
新建 JsonObjectResponse.class 文件,代码如下:
package com.example.promotion.constants; //扩展返回类 public class JsonObjectResponse<T> extends AbstractResponse { private T result; public T getResult() { return result; } public void setResult(T result) { this.result = result; } public JsonObjectResponse(T result, String code, String msg) { this.setCode(code); this.setMsg(msg); this.result = result; } public JsonObjectResponse(T result) { this.setCode(Constant.SUCCESS_CODE); this.setMsg(Constant.SUCCESS_MSG); this.result = result; } public JsonObjectResponse() { this.setCode(Constant.SUCCESS_CODE); this.setMsg(Constant.SUCCESS_MSG); this.result = null; } public JsonObjectResponse(String code, String msg) { this.setCode(code); this.setMsg(msg); } }
(11)PromotionController 接口代码如下:
package com.example.promotion.controller; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.example.promotion.constants.Constant; import com.example.promotion.constants.JsonObjectResponse; import com.example.promotion.model.PromotionEntity; import com.example.promotion.service.BlockHandlerService; import com.example.promotion.service.FallBackService; import com.example.promotion.service.PromotionService; import lombok.extern.slf4j.Slf4j; @Slf4j @RestController @RequestMapping("/api") public class PromotionController { @Autowired private PromotionService promotionService; //查询促销活动接口,路径:/api/queryPromotion?id=xx @GetMapping("queryPromotion") @ResponseBody //限流与降级注解 @SentinelResource(value = "queryPromotion", entryType = EntryType.IN, blockHandler = "queryPromotionBlockHandle", blockHandlerClass = {BlockHandlerService.class}, defaultFallback = "fallback", fallback Class = {FallBackService.class}) public JsonObjectResponse<PromotionEntity> queryPromotion (Integer id) { try { //调用促销活动服务类查询方法 return promotionService.queryPromotion(id); } catch (Exception e) { //记录错误日志 log.error("query promotion error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "query promotion error!"); } } //提交促销活动接口,路径:/api/addPromotion //参数:PromotionEntity @PostMapping("addPromotion") @ResponseBody @SentinelResource(value = "addPromotion", entryType = EntryType.IN, blockHandler = "addPromotionBlockHandle", blockHandlerClass = {BlockHandlerService.class}, defaultFallback = "fallback", fallback Class = {FallBackService.class}) public JsonObjectResponse<PromotionEntity> addPromotion (PromotionEntity promotionEntity) { if (StringUtils.isBlank(promotionEntity.getName()) || StringUtils. isBlank(promotionEntity.getPrize())) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "param is null! "); } try { //调用促销活动服务类新增方法 return promotionService.addPromotion(promotionEntity); } catch (Exception e) { //记录错误日志 log.error("add promotion error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "add promotion error!"); } } //更新促销活动接口,路径:/api/updatePromotion //参数:PromotionEntity @PostMapping("updatePromotion") @ResponseBody @SentinelResource(value = "updatePromotion", entryType = EntryType.IN, blockHandler = "updatePromotionBlockHandle", blockHandlerClass = {BlockHandlerService.class}, defaultFallback = "fallback", fallback Class = {FallBackService.class}) public JsonObjectResponse<PromotionEntity> updatePromotion (PromotionEntity promotionEntity) { if (promotionEntity.getId() == null) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "id is null! "); } try { //调用促销活动服务类更新方法 return promotionService.updatePromotion(promotionEntity); } catch (Exception e) { //记录错误日志 log.error("add promotion error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "add promotion error!"); } } //删除促销活动接口,路径:/api/delPromotion?id=xx @DeleteMapping("delPromotion") @ResponseBody @SentinelResource(value = "delPromotion", entryType = EntryType.IN, blockHandler = "delPromotionBlockHandle", blockHandlerClass = {BlockHandlerService.class}, defaultFallback = "fallback", fallback Class = {FallBackService.class}) public JsonObjectResponse<PromotionEntity> delPromotion(Integer id) { try { //调用删除服务类方法 return promotionService.delPromotion(id); } catch (Exception e) { //记录错误日志 log.error("delete promotion error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "delete promotion error!"); } } }
(12)PromotionService 代码如下:
package com.example.promotion.service; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import com.example.promotion.constants.Constant; import com.example.promotion.constants.JsonObjectResponse; import com.example.promotion.model.PromotionEntity; import com.example.promotion.repository.PromotionRepository; import lombok.extern.slf4j.Slf4j; //促销活动服务类 @Service @Slf4j public class PromotionService { //PromotionRepository 注入 @Autowired private PromotionRepository promotionRepository; //StringRedisTemplate 注入 @Autowired private StringRedisTemplate stringRedisTemplate; //查询促销活动信息 public JsonObjectResponse<PromotionEntity> queryPromotion(Integer id) { Optional<PromotionEntity> promotionEntity = promotionRepository. findById(id); if (promotionEntity.isPresent()) { return new JsonObjectResponse<>(promotionEntity.get()); } else { return new JsonObjectResponse<>(null); } } //添加促销活动信息 public JsonObjectResponse<PromotionEntity> addPromotion(Promotion Entity promotionEntity) { PromotionEntity promotionEntityOld = promotionRepository. findByName(promotionEntity.getName()); if (promotionEntityOld != null) { //查询促销活动是否已经存在 return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "promotion name is exist!"); } else { //如果不存在,则添加 PromotionEntity promotionEntityNew = promotionRepository. save(promotionEntity); //插入 MySQL 数据库 String key = MessageFormat.format(Constant.REDIS_PROMOTION_ KEY, String.valueOf(promotionEntityNew.getId())); Map<String, String> map = new HashMap<>(); map.put("name", promotionEntityNew.getName()); map.put("beginTime", String.valueOf(promotionEntityNew. getBeginTime())); map.put("endTime", String.valueOf(promotionEntityNew. getEndTime())); map.put("prize", promotionEntityNew.getPrize()); //添加到 Redis 中 stringRedisTemplate.opsForHash().putAll(key, map); log.info("addPromotion success"); } return new JsonObjectResponse<>(null); } //更新促销活动信息 public JsonObjectResponse<PromotionEntity> updatePromotion(Promotion Entity promotionEntity) { Optional<PromotionEntity> promotionEntityOpt = promotion Repository.findById(promotionEntity.getId()); if (promotionEntityOpt.isPresent()) { PromotionEntity promotionEntityOld = promotionEntityOpt. get(); promotionEntityOld.setName(promotionEntity.getName()); promotionEntityOld.setPrize(promotionEntity.getPrize()); promotionEntityOld.setBeginTime(promotionEntity.getBegin Time()); promotionEntityOld.setEndTime(promotionEntity.getEndTime()); //更新 MySQL 信息 promotionRepository.save(promotionEntityOld); String key = MessageFormat.format(Constant.REDIS_PROMOTION_ KEY, String.valueOf(promotionEntityOld.getId())); Map<String, String> map = new HashMap<>(); map.put("name", promotionEntityOld.getName()); map.put("beginTime", String.valueOf(promotionEntityOld. getBeginTime())); map.put("endTime", String.valueOf(promotionEntityOld. getEndTime())); map.put("prize", promotionEntityOld.getPrize()); //更新 Redis 信息 stringRedisTemplate.opsForHash().putAll(key, map); log.info("updatePromotion success"); } return new JsonObjectResponse<>(null); } //删除促销活动信息 public JsonObjectResponse<PromotionEntity> delPromotion(Integer id) { promotionRepository.deleteById(id); //删除 MySQL 信息 String key = MessageFormat.format(Constant.REDIS_PROMOTION_ KEY, String.valueOf(id)); stringRedisTemplate.delete(key); //删除 Redis 信息 log.info("delPromotion success"); return new JsonObjectResponse<>(null); } }
(13)限流代码如下:
package com.example.promotion.service; import com.example.promotion.constants.Constant; import com.example.promotion.constants.JsonObjectResponse; import com.example.promotion.model.PromotionEntity; //接口发生限流时触发 public final class BlockHandlerService { public static JsonObjectResponse<PromotionEntity> queryPromotion BlockHandle(Integer id) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "queryPromotion blcok"); } public static JsonObjectResponse<PromotionEntity> addPromotion BlockHandle(PromotionEntity promotionEntity) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "addPromotion blcok"); } public static JsonObjectResponse<PromotionEntity> updatePromotion BlockHandle(PromotionEntity promotionEntity) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "updatePromotion blcok"); } public static JsonObjectResponse<PromotionEntity> delPromotion BlockHandle(Integer id) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "delPromotion blcok"); } }
(14)降级代码如下:
package com.example.promotion.service; import com.example.promotion.constants.Constant; import com.example.promotion.constants.JsonObjectResponse; import com.example.promotion.model.PromotionEntity; //接口发生降级时触发 public final class FallBackService { public static JsonObjectResponse<PromotionEntity> defaultFall Back(Throwable ex){ return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "fallback"); } }
(15)PromotionApplication 代码如下:
package com.example.promotion; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.redis.RedisAuto Configuration; import org.springframework.cloud.client.discovery.EnableDiscovery Client; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; //自定义 Redis 配置 @SpringBootApplication(exclude = {RedisAutoConfiguration.class}) @EnableJpaAuditing //开启 JPA 审计 @EnableAspectJAutoProxy //开启切面 @EnableDiscoveryClient //开启服务发现 public class PromotionApplication { public static void main(String[] args) { SpringApplication.run(PromotionApplication.class, args); } }
此时,启动 PromotionApplication 主类即可访问接口,管理促销活动信息。例如添加一条活动信息,访问接口 http://localhost:8080/api/addPromotion,设置参数如下:
name:"会员促销活动" beginTime:"1614822680" endTime:" 1617176808" prize:"3 天免费会员"
访问查询接口 http://localhost:8080/api/queryPromotion?id=1,返回结果如下:
{ code: "S00000", msg: "success", result: { id: 3, name: "会员促销活动", beginTime: 1614822680, endTime: 1617176808, prize: "3 天免费会员", createTime: "2021-03-05T03:57:35.000+00:00", updateTime: "2021-03-05T03:59:12.000+00:00" } }
访问更新接口 http://localhost:8080/api/updatePromotion,添加参数即可修改促销活动信息,参数如下:
id:1 name:"会员促销活动" beginTime:"1614822680" endTime:" 1617176808" prize:"3 天免费会员"
访问删除接口 http://localhost:8080/api/ delPromotion?id=1,即可删除该促销活动信息。
11.3.2 促销活动微服务项目
新建促销活动微服务项目 microservice-promotion。新项目结构如图 11.3 所示。
图 11.3 microservice-promotion 项目结构
(1)microservice-promotion 的配置与 promotion 基本相同,application.yml 文件的配置如下:
server: port: 8081 spring: application: name: microservice-promotion
(2)启动类 MicroservicePromotionApplication 的代码如下:
package com.example.microservice.promotion; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.redis.RedisAuto Configuration; import org.springframework.cloud.client.discovery.EnableDiscovery Client; import org.springframework.context.annotation.EnableAspectJAutoProxy; @SpringBootApplication(exclude = {RedisAutoConfiguration.class}) @EnableAspectJAutoProxy //开启切面 @EnableDiscoveryClient //开启服务发现 public class MicroservicePromotionApplication { public static void main(String[] args) { SpringApplication.run(MicroservicePromotionApplication.class, args); } }
(3)PromotionPushController.class 接口类的代码如下:
package com.example.microservice.promotion.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.example.microservice.promotion.constants.Constant; import com.example.microservice.promotion.constants.JsonObject Response; import com.example.microservice.promotion.model.PromotionEntity; import com.example.microservice.promotion.service.BlockHandler Service; import com.example.microservice.promotion.service.FallBackService; import com.example.microservice.promotion.service.PromotionPush Service; import lombok.extern.slf4j.Slf4j; //促销活动微服务接口 @Slf4j @RestController @RequestMapping("/api") public class PromotionPushController { @Autowired private PromotionPushService promotionPushService; //促销活动投放接口,路径:/api/pushPromotion?id=xx @GetMapping("pushPromotion") @ResponseBody @SentinelResource(value = "pushPromotion", entryType = EntryType.IN, blockHandler = "promotionPushBlockHandle", blockHandlerClass = {BlockHandlerService.class}, defaultFallback = "fallback", fallback Class = {FallBackService.class}) public JsonObjectResponse<PromotionEntity> pushPromotion(Integer id) { try { //调用促销活动投放服务方法 return promotionPushService.pushPromotion(id); } catch (Exception e) { //记录错误日志 log.error("push promotion error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "push promotion error!"); } } //领取奖品接口,路径:/api/getPrize?id=xx&device=xx @GetMapping("getPrize") @ResponseBody @SentinelResource(value = "getPrize", entryType = EntryType.IN, blockHandler = "prizeBlockHandle", blockHandlerClass = {BlockHandler Service.class}, defaultFallback = "fallback", fallbackClass = {Fall BackService.class}) public JsonObjectResponse<String> getPrize(Integer id, String device) { try { //调用领取奖品服务方法 return promotionPushService.getPrize(id, device); } catch (Exception e) { //记录错误日志 log.error("get prize error!"); return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "get prize error!"); } } }
(4)PromotionPushService.class 服务类的代码如下:
package com.example.microservice.promotion.service; import java.text.MessageFormat; import java.util.Map; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import com.example.microservice.promotion.constants.Constant; import com.example.microservice.promotion.constants.JsonObject Response; import com.example.microservice.promotion.model.PromotionEntity; import lombok.extern.slf4j.Slf4j; //促销活动服务类 @Service @Slf4j public class PromotionPushService { @Autowired private StringRedisTemplate stringRedisTemplate; public JsonObjectResponse<PromotionEntity> pushPromotion(Integer id) { //组装 Redis 存储促销活动信息的 key String key = MessageFormat.format(Constant.REDIS_PROMOTION_ KEY, String.valueOf(id)); //Redis 操作,用于查询促销活动信息 Map<Object, Object> map = stringRedisTemplate.opsForHash(). entries(key); if (MapUtils.isNotEmpty(map)) { String name = (String) map.get("name"); String prize = (String) map.get("prize"); Integer beginTime = Integer.valueOf((String) map.get ("beginTime")); Integer endTime = Integer.valueOf((String) map.get("endTime")); Integer currentTime = (int) (System.currentTimeMillis()/ 1000); if (currentTime >= beginTime && currentTime <= endTime) { PromotionEntity promotionEntity = new PromotionEntity(); promotionEntity.setBeginTime(beginTime); promotionEntity.setEndTime(endTime); promotionEntity.setId(id); promotionEntity.setName(name); promotionEntity.setPrize(prize); log.info("push promotion success"); return new JsonObjectResponse<>(promotionEntity); } } return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "push promotion fail"); } public JsonObjectResponse<String> getPrize(Integer id, String device) { //组装奖品领取记录存储结构 key String key = MessageFormat.format(Constant.REDIS_PRIZE_KEY, String.valueOf(id), device); //查询该 device 下的领取记录 String value = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isEmpty(value)) { //没有领取记录,表示领取成功 String promotionKey = MessageFormat.format(Constant.REDIS_ PROMOTION_KEY, String.valueOf(id)); Map<Object, Object> map = stringRedisTemplate.opsForHash(). entries(promotionKey); if (MapUtils.isNotEmpty(map)) { String prize = (String) map.get("prize"); stringRedisTemplate.opsForValue().set(key, "1"); log.info("get prize success"); return new JsonObjectResponse<>("恭喜你获得:" + prize); } } return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "prize is exist"); } }
(5)限流类 BlockHandlerService.class 的代码如下:
package com.example.microservice.promotion.service; import com.example.microservice.promotion.constants.Constant; import com.example.microservice.promotion.constants.JsonObject Response; import com.example.microservice.promotion.model.PromotionEntity; //限流类 public final class BlockHandlerService { public static JsonObjectResponse<PromotionEntity> promotionPush BlockHandle(Integer id) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "pushPromotion blcok"); } public static JsonObjectResponse<String> prizeBlockHandle(Integer id, String device) { return new JsonObjectResponse<>(null, Constant.ERROR_CODE, "getprize blcok"); } }
微服务接口逻辑有两个,即促销活动投放接口和领取奖品接口。客户端访问某个活动 ID 时,接口判断当前时间是否在活动时间内,如果在,则返回促销活动信息,用户可以领取活动奖品,如果用户已领取过奖品,则不能再次领取。
访问促销活动接口 http://localhost:8081/api/pushPromotion?id=1,如接口正常,则返回如下结果:
{ code: "S00000", msg: "success", result: { id: 3, name: "会员促销活动", beginTime: 1614822680, endTime: 1617176808, prize: "3 天免费会员" } }
访问领取奖品接口 http://localhost:8081/api/getPrize?id=1,如接口正常,则返回数据如下:
{ code: "S00000", msg: "success", result: "恭喜你获得:3 天免费会员" }
11.3.3 网关项目
网关项目需要单独新建 gateway 工程,并在 pom.xml 文件中添加相关依赖,代码如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http:// www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https:// maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.9.RELEASE</version> <relativePath/> <!--lookup parent from repository --> </parent> <groupId>com.example.gateway</groupId> <artifactId>gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <name>gateway</name> <description>Gateway project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.RC2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
设置路由断言配置,具体信息如下:
server: port: 80 spring: application: name: gateway cloud: gateway: routes: -id: promotion_route uri: http://127.0.0.1:8081/ predicates: -Path=/api/**
启动网关服务与促销活动微服务项目,通过网关的路由断言,直接访问 http:// localhost/api/pushPromotion?id=1 接口,返回数据如下:
{ code: "S00000", msg: "success", result: { id: 3, name: "会员促销活动", beginTime: 1614822680, endTime: 1617176808, prize: "3 天免费会员" } }
可以看到,与直接访问微服务接口的返回数据一致。
11.3.4 项目部署
(1)采用 Spring Boot 的 Maven 打包插件,将 3 个项目分别打包为 promotion.jar、microservice-promotion.jar 和 gateway.jar。
(2)采用 java -jar ***.jar 命令分别启动 3 个项目,可以将它们部署在虚拟机或云平台之上。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论