返回介绍

11.3 项目开发

发布于 2025-04-21 20:58:46 字数 46844 浏览 0 评论 0 收藏

通过对需求描述的分析可知,需要构建 3 个项目,即后台接口管理项目、促销活动微服务项目和网关项目。本节分别介绍这 3 个项目的开发过程。

11.3.1 后台接口管理项目

使用 https://start.spring.io/工具新建 promotion 项目,导入开发工具。新建相关的 package,整体项目结构如图 11.1 所示。

170-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 所示。

175-1

图 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 所示。

188-1

图 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。