# 1.项目介绍
**学习目标**

#### 1）活动基本信息



```java
redisUtil.set(RedisKeys.INFO+game.getId(),game,-1);
```

#### 2）活动策略信息



```java
redisUtil.hset(RedisKeys.MAXGOAL + game.getId(),r.getUserlevel()+"",r.getGoalTimes());
redisUtil.hset(RedisKeys.MAXENTER + game.getId(),r.getUserlevel()+"",r.getEnterTimes());
```

#### 3）抽奖令牌桶



```java
redisUtil.rightPushAll(RedisKeys.TOKENS + game.getId(),tokenList);
```

#### 4）奖品映射信息



```java
redisUtil.set(RedisKeys.TOKEN + game.getId() +"_"+token,productMap.get(cgp.getProductid()),expire);
```

#### 5）令牌设计技巧



```java
//活动持续时间（ms）
long duration = end - start;
long rnd = start + new Random().nextInt((int)duration);
//为什么乘1000，再额外加一个随机数呢？ - 防止时间段奖品多时重复
long token = rnd * 1000 + new Random().nextInt(999);
```

#### 6）中奖计数



```java
redisUtil.incr(RedisKeys.USERHIT+gameid+"_"+user.getId(),1);
```





## 2.3框架选型


#### 3）接口模块api



```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
```


### 2.3.3 微服务框架集成



```properties
#服务名
spring.application.name=api
server.port=8001

#分布式session
spring.session.store-type=redis
server.servlet.session.timeout=36000

#以下部分，msg与api通用
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://1.1.1.1/prize?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=prize
spring.datasource.password=prize

mybatis.mapper-locations=classpath:mapper/*.xml

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

spring.rabbitmq.host=1.1.1.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

spring.redis.host=1.1.1.1
spring.redis.port=9010
```



### 2.3.4 工具





```xml
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>
```



```xml
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.31</version>
                    </dependency>
                    <dependency>
                        <groupId>com.itheima.prize</groupId>
                        <artifactId>coder</artifactId>
                        <version>0.0.1-SNAPSHOT</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <overwrite>true</overwrite>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                    <verbose>true</verbose>
                    <tableNames>%</tableNames>
                </configuration>
            </plugin>
```



```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="MysqlContext" targetRuntime="MyBatis3" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 指定生成的java文件的编码,没有直接生成到项目时中文可能会乱码 -->
        <property name="javaFileEncoding" value="UTF-8"/>

        <!-- 生成的pojo，将implements Serializable -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"></plugin>
        <!-- 这里的type里写的是你的实现类的类全路径 -->
        <!--<commentGenerator type="com.MyCommentGenerator">-->
            <!--<property name="suppressDate" value="true"/>-->
        <!--</commentGenerator>-->


        <commentGenerator>
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://1.1.1.1/prize?tinyInt1isBit=false"
                        userId="root"
                        password="root">
        </jdbcConnection>

        <javaTypeResolver type="com.JavaTypeResolverDefaultImpl">
        </javaTypeResolver>

        <!-- entity 包路径 -->
        <javaModelGenerator targetPackage="com.itheima.prize.commons.db.entity" targetProject="MAVEN">
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- xml 路径-->
        <sqlMapGenerator targetPackage="com.itheima.prize.commons.db.xml.mapper" targetProject="MAVEN"/>

        <javaClientGenerator type="MIXEDMAPPER" targetPackage="com.itheima.prize.commons.db.mapper" targetProject="MAVEN"/>

        <table tableName="%">
            <generatedKey column="id" sqlStatement="Mysql" identity="true"/>
        </table>
    </context>
</generatorConfiguration>
```



```java
public class JavaTypeResolverDefaultImpl implements JavaTypeResolver {
    ....

    public JavaTypeResolverDefaultImpl() {
        .....
        this.typeMap.put(-6, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TINYINT", new FullyQualifiedJavaType(Integer.class.getName())));
        .....
    }
    
    
    ....
｝
```

#### 3）分页PageHelper



```xml
        <!--mybatis分页插件-->
        <dependency>
             <groupId>com.github.pagehelper</groupId>
             <artifactId>pagehelper</artifactId>
        </dependency>
```



```java
@ApiModel("分页信息")
public class PageBean<T> {
    @ApiModelProperty(value = "当前页，1开始")
    private Integer currentPage = 1;
    @ApiModelProperty(value = "每页条数，默认10")
    private Integer pageSize = 10;
    @ApiModelProperty(value = "总条数")
    private Long totalNum;
    @ApiModelProperty(value = "是否有下一页")
    private Integer isMore;
    @ApiModelProperty(value = "总页数")
    private Integer totalPage;
    @ApiModelProperty(value = "开始索引")
    private Integer startIndex;
    @ApiModelProperty(value = "本页数据")
    private List<T> items;
    
    ...
｝
```



```java
// 在查询之前，调用startPage，设置页码及条数
PageHelper.startPage(curpage, limit);
List<ViewCardUserHit> all = hitMapper.selectByExample(example);
```



返回的数据体：

```json
{
  "code": 1,
  "msg": "成功",
  "data": {
    "currentPage": 1,
    "pageSize": 2,
    "totalNum": 5,
    "isMore": 1,
    "totalPage": 3,
    "startIndex": 0,
    "items": [
      {
        "id": 1,
        "title": "10周年庆典",
        "type": "随机类",
        "uname": "2",
        "realname": "2",
        "idcard": "2",
        "phone": "2",
        "level": "二级会员",
        "name": "iPhoneX一部",
        "price": 5000,
        "gameid": 1,
        "userid": 8,
        "productid": 1,
        "hittime": "2019-09-24 15:26:45"
      },
      {
        "id": 2,
        "title": "10周年庆典",
        "type": "随机类",
        "uname": "2",
        "realname": "2",
        "idcard": "2",
        "phone": "2",
        "level": "二级会员",
        "name": "iPhoneX一部",
        "price": 5000,
        "gameid": 1,
        "userid": 8,
        "productid": 1,
        "hittime": "2019-09-24 15:26:46"
      }
    ]
  }
}
```





# 3.代码实现



## 3.2抽奖业务

### 3.2.1 源码实现



### 3.2.2 并发与原子性



```lua
local token = redis.call('lpop',KEYS[1])
local curtime = tonumber(KEYS[2])

if token ~= false then
    if ( tonumber(token)/1000 > tonumber(KEYS[2]) ) then
       redis.call('lpush',KEYS[1],token)
       return 1
    else
       return tonumber(token)
    end
else
    return 0
end
```



```java
Long token = luaScript.tokenCheck("game_"+gameid,String.valueOf(new Date().getTime()));
if(token == 0){
    return new ApiResult(-1,"奖品已抽光",null);
}else if(token == 1){
    return new ApiResult(0,"未中奖",null);
}
```



## 3.3中奖处理



### 3.3.3 msg模块消费

```java
    @RabbitHandler
    public void processMessage3(Map message) {
        logger.info("user hit : " + message);
        CardUserHit hit = new CardUserHit();
        hit.setGameid(MapUtils.getIntValue(message,"gameid"));
        hit.setUserid(MapUtils.getIntValue(message,"userid"));
        hit.setProductid(MapUtils.getIntValue(message,"productid"));
        hit.setHittime(new Date(MapUtils.getLongValue(message,"hittime")));
        // 入库
        hitMapper.insert(hit);
    }
```



## 3.4缓存监控



### 3.6.2 镜像

```shell
docker build -t $app:$BUILD_NUMBER .
```



```dockerfile
FROM daocloud.io/library/java:openjdk-8u40-jdk
#语言字符，解决乱码问题
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8
ENV LANGUAGE C.UTF-8
#时区及时间，不设置会影响活动的开始结束
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
#将springboot的jar包打入镜像
COPY ROOT.jar /opt/ROOT.jar
WORKDIR /opt
#启动命令，注意环境配置 prod
CMD ["java", "-jar", "ROOT.jar", "--spring.profiles.active=prod"]
```



```shell
docker service rm $app
docker service create --name $app -p $port:8080 --hostname localhost $app:$version
```



### 3.6.3 中间件部署

```shell
#获取镜像
docker pull daocloud.io/library/redis:latest
docker pull daocloud.io/library/rabbitmq:3.6.10-management
docker pull daocloud.io/mysql:5.7.4
docker pull daocloud.io/nginx:latest

#启动镜像
docker run --name redis -p 6379:6379 -d daocloud.io/library/redis

docker run --name mysql -v /opt/data/mysql:/var/lib/mysql -p3306:3306 -e MYSQL_ROOT_PASSWORD=password -d daocloud.io/mysql:5.7.4

docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 daocloud.io/library/rabbitmq:3.6.10-management 

docker run --name nginx -v /opt/data/nginx/html:/usr/share/nginx/html:ro -v /opt/app/back/upload:/usr/share/nginx/upload:ro -v /opt/data/nginx/nginx.conf:/etc/nginx/nginx.conf:ro -p 80:80 -d daocloud.io/nginx

```



### 3.6.6 静态页面与nginx

```nginx
http {

    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location ^~ /api/ {
                proxy_pass http://172.17.0.1:9004;
        }
		location ^~ /upload/ {
                alias /usr/share/nginx/upload/;
        }
        location / {
                root /usr/share/nginx/html;
        }
    }
}
```

 



