应用Dubbo框架打造仿猫眼项目 理解微服务核心思想
第2章 演示环境构建
基本的概念:
微服务的privodier
服务环境的搭建,第一步先建立一个maven的project
第二章环境搭建的内容请参考尚硅谷dubbo视频教程,和上面讲的一样
第3章 业务基础环境构建
dubbo中不需要进行动态路由的设置,但是spring cloud需要进行动态路由的测试
这里要因人gun工程
Guns基于Spring Boot2,致力于做更简洁的后台管理系统。包含系统管理,代码生成,多数据库适配,SSO单点登录,工作流,短信,邮件发送,OAuth2登录,任务调度,持续集成,docker部署等功。支持Spring Cloud Alibaba微服务。社区活跃,版本迭代快,加群免费技术支持。
将naan1993-guns-master.zip解压,elipse导入maven工程
导入之后整个工程如下所示
运行gun环境需要创建数据库,脚本位于目录下
/* Navicat MySQL Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50721 Source Host : localhost:3306 Source Schema : guns_rest Target Server Type : MySQL Target Server Version : 50721 File Encoding : 65001 Date: 26/01/2018 21:16:47 */ DROP DATABASE IF EXISTS guns_rest; CREATE DATABASE IF NOT EXISTS guns_rest DEFAULT CHARSET utf8 COLLATE utf8_general_ci; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for user -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `userName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES (1, 'admin'); SET FOREIGN_KEY_CHECKS = 1;
接下来需要修改guns-rest中数据库的配置
application.yml
接下来运行guns-rest
运行的时候报错
需要在guns-rest的pom.xml中添加log4j的依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> </dependency>
再次运行guns-rest还是会报错,需要修改application.yml中的数据库的url连接为下面的形式
rest: auth-open: true #jwt鉴权机制是否开启(true或者false) sign-open: true #签名机制是否开启(true或false) jwt: header: Authorization #http请求头所需要的字段 secret: mySecret #jwt秘钥 expiration: 604800 #7天 单位:秒 auth-path: auth #认证请求的路径 md5-key: randomKey #md5加密混淆key server: port: 80 #项目端口 mybatis-plus: mapper-locations: classpath*:com/stylefeng/guns/rest/**/mapping/*.xml typeAliasesPackage: com.stylefeng.guns.rest.common.persistence.model global-config: id-type: 0 #0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid) db-column-underline: false refresh-mapper: true configuration: map-underscore-to-camel-case: false cache-enabled: true #配置的缓存的全局开关 lazyLoadingEnabled: true #延时加载的开关 multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性 # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句,调试用 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 password: 123456 filters: log4j,wall,mergeStat logging: level.root: info level.com.stylefeng: debug path: logs/ file: guns-rest.log
再次运行程序运行成功了,在浏览器输入http://localhost/auth?userName=admin&password=admin会返回对应的token信息
现在guns环境已经搭建完成了,接下来我们要在guns项目的基础上新建立一个api网关的工程
我们直接将工程guns-rest直接复制一份,重新命名为
接下来,我们要修改对于的guns-gateway的pom.xml文件
<groupId>com.stylefeng.guns</groupId>
<artifactId>guns-rest</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>guns-rest</name>
<description>guns REST服务器</description>
需要将guns-rest修改为
<groupId>com.stylefeng.guns</groupId>
<artifactId>guns-gateway</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>guns-gateway</name>
<description>Meeting院线</description>
接下来还需要修改guns-parent的pom.xml文件,将<module>guns-gateway</module>添加进去
<modules> <module>guns-admin</module> <module>guns-core</module> <module>guns-rest</module> <module>guns-gateway</module> <module>guns-generator</module> </modules>
整个工程的目录如下
接下来,我们要将guns-gateway和dubbo进行整合
首先需要在pom.xml中添加对于的依赖包
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba.boot/dubbo-spring-boot-starter --> <dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency>
整个文件的依赖为
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.stylefeng.guns</groupId> <artifactId>guns-gateway</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>guns-gateway</name> <description>Meeting院线的api网关</description> <parent> <groupId>com.stylefeng</groupId> <artifactId>guns-parent</artifactId> <version>1.0.0</version> <relativePath>../pom.xml</relativePath> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.13</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency> <dependency> <groupId>com.stylefeng</groupId> <artifactId>guns-core</artifactId> </dependency> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba.boot/dubbo-spring-boot-starter --> <dependency> <groupId>com.alibaba.spring.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
接下来我们需要在application.yml配置dubbo相关的属性
我们新建立一个application.properties
#WEB\u670D\u52A1\u7AEF\u53E3
server.port=8081
## dubbo \u914D\u7F6E
spring.dubbo.application.name=meeting-gateway-provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.server=true
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
package com.stylefeng.guns.rest.modular.user; public interface UserAPI { boolean login(String username,String password); }
package com.stylefeng.guns.rest.modular.user; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; @Component @Service(interfaceClass=UserAPI.class) public class UserImpl implements UserAPI{ @Override public boolean login(String username, String password) { // TODO Auto-generated method stub return true; } }
这里首先要将UserImpl 添加到spring容器中,@Service是dubbo的不是spring的com.alibaba.dubbo.config.annotation.Service;
这样我们就将服务暴露出去了,同时需要在启动类中添加
@EnableDubbo注解
package com.stylefeng.guns.rest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.EnableLoadTimeWeaving; import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo; import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; @SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"}) @EnableDubbo public class GunsRestApplication { public static void main(String[] args) { SpringApplication.run(GunsRestApplication.class, args); } }
上面写成@EnableDubbo会报错
改成下面的形式@EnableDubboConfiguration就可以了,初步定为是版本问题
package com.stylefeng.guns.rest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; @SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"}) @EnableDubboConfiguration public class GunsRestApplication { public static void main(String[] args) { SpringApplication.run(GunsRestApplication.class, args); } }
在实际的开发过程中,所有的接口都应该放在一个单独的模块中,我们应该单独建立一个独立的api模块,我们将guns-core复制一份为guns-api
接下来需要修改guns-api的pom文件
<artifactId>guns-core</artifactId> <packaging>jar</packaging> <name>guns-core</name> <url>http://maven.apache.org</url>
修改为
<artifactId>guns-api</artifactId> <packaging>jar</packaging> <name>guns-api</name> <url>http://maven.apache.org</url>
接下来需要修改guns-parent的pom文件,增加子模块依赖 <module>guns-api</module>
<modules> <module>guns-admin</module> <module>guns-core</module> <module>guns-rest</module> <module>guns-generator</module> <module>guns-gateway</module> <module>guns-api</module> </modules>
接下来需要让guns-rest和guns-gateway依赖我们创建的guns-api模块,在pom.xml中增加下面的依赖
<dependency> <groupId>com.stylefeng</groupId> <artifactId>guns-api</artifactId> <version>1.0.0</version> </dependency>
第4章 Dubbo基本特性:用户模块开发
用户模块首先要有对于的建表语句
DROP TABLE IF EXISTS mooc_user_t; CREATE TABLE mooc_user_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', user_name VARCHAR(50) COMMENT '用户账号', user_pwd VARCHAR(50) COMMENT '用户密码', nick_name VARCHAR(50) COMMENT '用户昵称', user_sex INT COMMENT '用户性别 0-男,1-女', birthday VARCHAR(50) COMMENT '出生日期', email VARCHAR(50) COMMENT '用户邮箱', user_phone VARCHAR(50) COMMENT '用户手机号', address VARCHAR(50) COMMENT '用户住址', head_url VARCHAR(50) COMMENT '头像URL', biography VARCHAR(200) COMMENT '个人介绍', life_state INT COMMENT '生活状态 0-单身,1-热恋中,2-已婚,3-为人父母', begin_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' ) COMMENT '用户表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; insert into mooc_user_t(user_name,user_pwd,nick_name,user_sex,birthday,email,user_phone,address,head_url,life_state,biography) values('admin','0192023a7bbd73250516f069df18b500','隔壁泰山',0,'2018-07-31','admin@mooc.com','13888888888','北京市海淀区朝阳北路中南海国宾馆','films/img/head-img.jpg',0,'没有合适的伞,我宁可淋着雨'); insert into mooc_user_t(user_name,user_pwd,nick_name,user_sex,birthday,email,user_phone,address,head_url,life_state,biography) values('jiangzh','5e2de6bd1c9b50f6e27d4e55da43b9
17','阿里郎',0,'2018-08-20','jiangzh@mooc.com','13866666666','北京市朝阳区大望路万达广场','films/img/head-img.jpg',1,'我喜欢隔壁泰山');
接下来是用户对应的接口文档
目录
一、 用户模块接口 2
1、 用户注册接口 2
请求地址 2
请求方式 2
请求字段 2
应答报文 3
业务异常 3
系统异常 3
2、 用户名验证接口 3
请求地址 3
请求方式 3
请求字段 3
应答报文 4
业务异常 4
系统异常 4
3、 用户登陆接口 4
请求地址 4
请求方式 4
请求字段 4
应答报文 5
业务异常 5
系统异常 5
4、 用户退出接口 5
请求地址 5
请求方式 6
请求字段 6
应答报文 6
业务异常 6
系统异常 6
5、 用户信息查询接口 6
请求地址 6
请求方式 7
请求字段 7
应答报文 7
业务异常 7
系统异常 7
6、 用户信息修改接口 8
请求地址 8
请求方式 8
请求字段 8
应答报文 8
业务异常 9
系统异常 9
一、 用户模块接口
1、 用户注册接口
请求地址
/user/register
请求方式
post
请求字段
请求字段 字段含义 是否必填
username 用户名 是
password 用户密码 是
email 用户邮箱 否
phone 用户电话 否
address 用户住址 否
应答报文
{
“status” :0,
“msg” :“注册成功”
}
业务异常
{
“status” :1,
“msg” :“用户已存在”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
2、 用户名验证接口
请求地址
/user/check
请求方式
post
请求字段
请求字段 字段含义 是否必填
username 用户名 是
应答报文
{
“status” :0,
“msg” :“验证成功”
}
业务异常
{
“status” :1,
“msg” :“用户已存在”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
3、 用户登陆接口
请求地址
/auth
请求方式
post
请求字段
请求字段 字段含义 是否必填
username 用户登陆账号 是
password 用户登陆密码 是
应答报文
{
“status” : 0,
“data”:{
"randomKey":"nv0958",
"token":"eyJhbGciOiJIUzUxMiJ9.eyJyYW5kb21LZXkiOiJudjA5NTgiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTUzMjQ0MDY1MCwiaWF0IjoxNTMxODM1ODUwfQ.miIl1JXpb1ztkd-RHks1OPD6KQ53I-C4ESwhOywU0O7KDWu9SodHP0HMct0Di3BO2qtuc9EpVtSZcgTnnudrHA"
}
}
业务异常
{
“status” : 1,
“msg” :“用户名或密码错误”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
4、 用户退出接口
请求地址
/user/logout
请求方式
get
请求字段
无,但是要带上Authorization的头信息
应答报文
{
“status” : 0,
“msg” :“成功退出”
}
业务异常
{
“status” : 1,
“msg” :“退出失败,用户尚未登陆”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
5、 用户信息查询接口
请求地址
/user/getUserInfo
请求方式
get
请求字段
无,但是要带上Authorization的头信息
应答报文
{
"status": 0,
"data": {
"uuid": 12,
"username": "aaa",
“nickname”: ”咫尺天涯”,
"email": "aaa@163.com",
"phone": null,
“sex” : 0, [0-男,1-女],
“birthday”:“2018-12-12”,
“lifeState” :0, [0-单身,1-热恋中,2-已婚,3-为人父母],
“biography” :“没有合适的伞,我宁可淋着雨”,
“address”:”北京市东城区中南海12号楼主席办公室”,
“headAddress” : “http://img.meetingshop.cn/12324123.png“
"createTime": 1479048325000,
"updateTime": 1479048325000
}
}
业务异常
{
“status” : 1,
“msg” :“查询失败,用户尚未登陆”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
6、 用户信息修改接口
请求地址
/user/updateUserInfo
请求方式
post
请求字段
请求字段 字段含义 是否必填
uuid 用户编号 是
nickname 用户昵称 否
email 用户邮箱地址 否
phone 用户电话 否
sex 用户性别 0代表男,1代表女 否
birthday 用户出生年月日 否
lifeState 用户生活状态
0-单身,1-热恋中,2-已婚,3-为人父母 否
biography 个性标签 否
address 用户住址 否
应答报文
{
"status": 0,
"data": {
"id": 12,
"username": "aaa",
“nickname”: ”咫尺天涯”,
"email": "aaa@163.com",
"phone": null,
“sex” : 0, [0-男,1-女],
“birthday”:“2018-12-12”,
“lifeState” :0, [0-单身,1-热恋中,2-已婚,3-为人父母],
“biography” :“没有合适的伞,我宁可淋着雨”,
“address”:”北京市东城区中南海12号楼主席办公室”,
“headAddress” : “http://img.meetingshop.cn/12324123.png“
"createTime": 1479048325000,
"updateTime": 1479048325000
}
}
业务异常
{
“status” : 1,
“msg” :“用户信息修改失败”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
接下来,guns-gateway模块已经配置完成,接下来我们要创建一个用户的模块guns-user模块,通过gateway能够访问user模块
我们复制guns-gateway修改名称为guns-user,进入到guns-user修改起pom.xml的配置文件
<groupId>com.stylefeng.guns</groupId> <artifactId>guns-gateway</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>guns-gateway</name> <description>Meeting院线的api网关</description>
修改为
<groupId>com.stylefeng.guns</groupId> <artifactId>guns-users</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>guns-users</name> <description>用户管理模块</description>
同时需要在guns-parent中添加子模块guns-user的依赖
<modules> <module>guns-admin</module> <module>guns-core</module> <module>guns-rest</module> <module>guns-generator</module> <module>guns-gateway</module> <module>guns-api</module> <module>guns-users</module> </modules>
整个项目的结构如下
接下来要让guns-gateway和guns-users之间集成dubbo,二者相互调用,二者通信的接口要定义在guns-api中
guns-users是网关来进行调用的,所以不需要进行jwt安全的校验,安全的校验都是在网关中实现
我们修改guns-users,关闭jwt的校验,修改application.yml
rest: auth-open: false #jwt鉴权机制是否开启(true或者false) sign-open: false #签名机制是否开启(true或false) jwt: header: Authorization #http请求头所需要的字段 secret: mySecret #jwt秘钥 expiration: 604800 #7天 单位:秒 auth-path: auth #认证请求的路径 md5-key: randomKey #md5加密混淆key server: port: 8083 #项目端口 mybatis-plus: mapper-locations: classpath*:com/stylefeng/guns/rest/**/mapping/*.xml typeAliasesPackage: com.stylefeng.guns.rest.common.persistence.model global-config: id-type: 0 #0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid) db-column-underline: false refresh-mapper: true configuration: map-underscore-to-camel-case: false cache-enabled: true #配置的缓存的全局开关 lazyLoadingEnabled: true #延时加载的开关 multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性 # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句,调试用 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 123456 filters: log4j,wall,mergeStat logging: level.root: info level.com.stylefeng: debug path: logs/ file: guns-rest.log
auth-open: false #jwt鉴权机制是否开启(true或者false)
sign-open: false #签名机制是否开启(true或false)
都设置为false关闭jwt校验,应用的端口设置为8083
接下来在application.properties中配置guns-user的dubbo集成参数
#WEB\u670D\u52A1\u7AEF\u53E3
server.port=8083
## dubbo \u914D\u7F6E
spring.dubbo.application.name=meeting-user
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.server=true
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20881
这里guns-users集成dubbo还需要保证在pom.xml中引入dubbo的依赖,因为guns-user是通过guns-gateway复制的,guns-gateway中已经引入了dubbo的依赖,这里guns-user就不需要在做单独的配置了
这样guns-user和guns-gateway相关的dubbo的集成已经完成了,后面二者就可以通过dubbo相互调用了
接下来,在guns-gateway中我们要配置,某些http请求,我们不进行jwt的校验,进入到guns-gatawy的application.yml
jwt:
header: Authorization #http请求头所需要的字段
secret: mySecret #jwt秘钥
expiration: 604800 #7天 单位:秒
auth-path: auth #认证请求的路径
md5-key: randomKey #md5加密混淆key
ignore-url: /usr/,/film/ #对改类请求的url不进行jwt模块的校验
我们增加一个属性ignore-url: /usr/,/film/ #对改类请求的url不进行jwt模块的校验
在guns-gateway中存在一个类JwtProperties就是读取application.yml中以jwt开头的配置,我们要在该类中增加一个 private String ignoreUrl;属性并且生成get set方法,属性名称ignoreUrl必须和
ignore-url相互对应
package com.stylefeng.guns.rest.config.properties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * jwt相关配置 * * @author fengshuonan * @date 2017-08-23 9:23 */ @Configuration @ConfigurationProperties(prefix = JwtProperties.JWT_PREFIX) public class JwtProperties { public static final String JWT_PREFIX = "jwt"; private String header = "Authorization"; private String secret = "defaultSecret"; private Long expiration = 604800L; private String authPath = "auth"; private String md5Key = "randomKey"; private String ignoreUrl; public String getIgnoreUrl() { return ignoreUrl; } public void setIgnoreUrl(String ignoreUrl) { this.ignoreUrl = ignoreUrl; } public static String getJwtPrefix() { return JWT_PREFIX; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public Long getExpiration() { return expiration; } public void setExpiration(Long expiration) { this.expiration = expiration; } public String getAuthPath() { return authPath; } public void setAuthPath(String authPath) { this.authPath = authPath; } public String getMd5Key() { return md5Key; } public void setMd5Key(String md5Key) { this.md5Key = md5Key; } }
配置完成之后,接下来我们要在AuthFilter中对配置的ignore-url不进行拦截
package com.stylefeng.guns.rest.modular.auth.filter; import com.stylefeng.guns.core.base.tips.ErrorTip; import com.stylefeng.guns.core.util.RenderUtil; import com.stylefeng.guns.rest.common.exception.BizExceptionEnum; import com.stylefeng.guns.rest.config.properties.JwtProperties; import com.stylefeng.guns.rest.modular.auth.util.JwtTokenUtil; import io.jsonwebtoken.JwtException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 对客户端请求的jwt token验证过滤器 * * @author fengshuonan * @Date 2017/8/24 14:04 */ public class AuthFilter extends OncePerRequestFilter { private final Log logger = LogFactory.getLog(this.getClass()); @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtProperties jwtProperties; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (request.getServletPath().equals("/" + jwtProperties.getAuthPath())) { chain.doFilter(request, response); return; } //对ignore-url不进行拦截 String ignoreUrl = jwtProperties.getIgnoreUrl(); String[] ignoreUrls = ignoreUrl.split(","); for(int i=0; i< ignoreUrls.length;i++){ if(request.getServletPath().equals(ignoreUrls[i])){ chain.doFilter(request, response); return; } } final String requestHeader = request.getHeader(jwtProperties.getHeader()); String authToken = null; if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); //验证token是否过期,包含了验证jwt是否正确 try { boolean flag = jwtTokenUtil.isTokenExpired(authToken); if (flag) { RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage())); return; } } catch (JwtException e) { //有异常就是token解析失败 RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return; } } else { //header没有带Bearer字段 RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return; } chain.doFilter(request, response); } }
接下来我们需要在guns-api中实现我们的用户查询的业务接口
package com.stylefeng.guns.api.user; public interface UserAPI { boolean login(String username,String password); boolean register(UserModel userModel); boolean checkUsername(String username); UserInfoModel getUserInfo(int uuid); UserInfoModel updateUserInfo(UserInfoModel userInfoModel); }
package com.stylefeng.guns.api.user; import java.io.Serializable; public class UserInfoModel implements Serializable{ private String username; private String nickname; private String email; private String phone; private int sex; private String birthday; private String lifeState; private String biography; private String address; private String headAddress; private long beginTime; private long updateTime; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } public String getLifeState() { return lifeState; } public void setLifeState(String lifeState) { this.lifeState = lifeState; } public String getBiography() { return biography; } public void setBiography(String biography) { this.biography = biography; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getHeadAddress() { return headAddress; } public void setHeadAddress(String headAddress) { this.headAddress = headAddress; } public long getBeginTime() { return beginTime; } public void setBeginTime(long beginTime) { this.beginTime = beginTime; } public long getUpdateTime() { return updateTime; } public void setUpdateTime(long updateTime) { this.updateTime = updateTime; } }
package com.stylefeng.guns.api.user; import java.io.Serializable; public class UserModel implements Serializable{ private String username; private String password; private String email; private String phone; private String address; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "UserModel [username=" + username + ", password=" + password + ", email=" + email + ", phone=" + phone + ", address=" + address + "]"; } }
这里强调下三个类:userModel和UserInfoModel是guns-gateway和guns-user进行交互的类
后面MoocUserT是guns-user内部使用的是表mock_user_t自动代码生产的类
接下来,我们在guns-gateway中要实现对应的校验功能
AuthController类中我们要做如下的修改
package com.stylefeng.guns.rest.modular.auth.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.core.exception.GunsException; import com.stylefeng.guns.rest.common.exception.BizExceptionEnum; import com.stylefeng.guns.rest.modular.auth.VO.ReponseVO; import com.stylefeng.guns.rest.modular.auth.controller.dto.AuthRequest; import com.stylefeng.guns.rest.modular.auth.controller.dto.AuthResponse; import com.stylefeng.guns.rest.modular.auth.util.JwtTokenUtil; import com.stylefeng.guns.rest.modular.auth.validator.IReqValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * 请求验证的 * * @author fengshuonan * @Date 2017/8/24 14:22 */ @RestController public class AuthController { @Autowired private JwtTokenUtil jwtTokenUtil; @Resource(name = "simpleValidator") private IReqValidator reqValidator; @Reference(interfaceClass=UserAPI.class) private UserAPI userApi; @RequestMapping(value = "${jwt.auth-path}") public ReponseVO createAuthenticationToken(AuthRequest authRequest) { // boolean validate = reqValidator.validate(authRequest); boolean validate = true; int userId = userApi.login(authRequest.getUserName(), authRequest.getPassword()); if(userId ==0){ validate = false; } if (validate) { final String randomKey = jwtTokenUtil.getRandomKey(); final String token = jwtTokenUtil.generateToken(userId+"", randomKey); return ReponseVO.success(new AuthResponse(token, randomKey)); } else { return ReponseVO.serviceFail("用户名或者密码错误"); } } }
用户登录的接口,返回的结果是
Ø 应答报文
{
“status” : 0,
“data”:{
"randomKey":"nv0958",
"token":"eyJhbGciOiJIUzUxMiJ9.eyJyYW5kb21LZXkiOiJudjA5NTgiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTUzMjQ0MDY1MCwiaWF0IjoxNTMxODM1ODUwfQ.miIl1JXpb1ztkd-RHks1OPD6KQ53I-C4ESwhOywU0O7KDWu9SodHP0HMct0Di3BO2qtuc9EpVtSZcgTnnudrHA"
}
}
我们要新增加一个对于接口的返回类
package com.stylefeng.guns.rest.modular.auth.VO; public class ReponseVO<T> { private int status; private String msg; private T data; private ReponseVO(){ } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public static<T> ReponseVO serviceFail(String msg){ ReponseVO reponseVO =new ReponseVO(); reponseVO.setStatus(1); reponseVO.setMsg(msg); return reponseVO; } public static<T> ReponseVO success(T data){ ReponseVO reponseVO =new ReponseVO(); reponseVO.setStatus(0); reponseVO.setData(data); return reponseVO; } public static<T> ReponseVO appFail(String msg){ ReponseVO reponseVO =new ReponseVO(); reponseVO.setStatus(999); reponseVO.setMsg(msg); return reponseVO; } }
接下来我们在网关中要定义一个ThreadLocal变量来保存当前用户的信息
我们新建立一个CurrentUser类,将当前用户的用户ID存储在ThreadLocal中,这里不要存储UserInfoModel整个对象,当用户过多的时候会导致内存溢出,在ThreadLocal中我们尽量存储少一点的内容
package com.stylefeng.guns.rest.common; import com.stylefeng.guns.api.user.UserInfoModel; public class CurrentUser { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); //将用户信息放入存储空间中 public static void saveUserInfo(String userId){ threadLocal.set(userId); } //从线程中取出当前用户的信息 public static String getUserInfo(){ return threadLocal.get(); } }
接下来,我们要修改AuthFilter
1、改类主要的作用如下:第一判断当前那些http请求是否要做jwt的校验
2、对当前的请求做jwt的校验
我们在jwt校验的时候,如果jwt校验成功,我们需要将用户请求头中携带的用户ID存储到ThreadLocal中,便于后续的请求使用
当用户登录的时候,会访问AuthController类的createAuthenticationToken方法后台会产生一个token,token中携带了当前用户的id
final String token = jwtTokenUtil.generateToken(userId+"", randomKey);
然后,后面用户所有的请求中都会携带对应的token信息,所用的请求都会被authFilter拦截,我们可以在jwt校验的时候,从请求头中获得对于的用户ID存储到ThreadLocal中
package com.stylefeng.guns.rest.modular.auth.filter; import com.stylefeng.guns.core.base.tips.ErrorTip; import com.stylefeng.guns.core.util.RenderUtil; import com.stylefeng.guns.rest.common.CurrentUser; import com.stylefeng.guns.rest.common.exception.BizExceptionEnum; import com.stylefeng.guns.rest.config.properties.JwtProperties; import com.stylefeng.guns.rest.modular.auth.util.JwtTokenUtil; import io.jsonwebtoken.JwtException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 对客户端请求的jwt token验证过滤器 * * @author fengshuonan * @Date 2017/8/24 14:04 */ public class AuthFilter extends OncePerRequestFilter { private final Log logger = LogFactory.getLog(this.getClass()); @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtProperties jwtProperties; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (request.getServletPath().equals("/" + jwtProperties.getAuthPath())) { chain.doFilter(request, response); return; } //对ignore-url不进行拦截 String ignoreUrl = jwtProperties.getIgnoreUrl(); String[] ignoreUrls = ignoreUrl.split(","); for(int i=0; i< ignoreUrl.length();i++){ if(request.getServletPath().equals(ignoreUrls[i])){ chain.doFilter(request, response); return; } } final String requestHeader = request.getHeader(jwtProperties.getHeader()); String authToken = null; if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); //获得jwt中请求头中携带的userid String userId = jwtTokenUtil.getUsernameFromToken(authToken); //将userID存储到ThreadLocal中 CurrentUser.saveUserInfo(userId); //验证token是否过期,包含了验证jwt是否正确 try { boolean flag = jwtTokenUtil.isTokenExpired(authToken); if (flag) { RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage())); return; } } catch (JwtException e) { //有异常就是token解析失败 RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return; } } else { //header没有带Bearer字段 RenderUtil.renderJson(response, new ErrorTip(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return; } chain.doFilter(request, response); } }
以上整个用户的jwt校验已经完成了,接下来我们要开始用户业务操作的逻辑开发,我们要mooc_user_t表自动生产对于的实体类,我们利用guns提供的自动代码生成类
我们进入到guns-user模块
EntityGenerator类是guns给我们提供的自动代码生产类,能够将数据库表自动生成对于的实体类
package com.stylefeng.guns.generator; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import com.baomidou.mybatisplus.generator.config.rules.DbType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import org.junit.Test; import java.util.HashMap; import java.util.Map; /** * 实体生成 * * @author fengshuonan * @date 2017-08-23 12:15 */ public class EntityGenerator { @Test public void entityGenerator() { AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); //D:\work_project\naan1993-guns-master\guns\guns-user\src\main\java gc.setOutputDir("D:\\work_project\\naan1993-guns-master\\guns\\guns-user\\src\\main\\java");//这里写你自己的java目录 gc.setFileOverride(true);//是否覆盖 gc.setActiveRecord(true); gc.setEnableCache(false);// XML 二级缓存 gc.setBaseResultMap(true);// XML ResultMap gc.setBaseColumnList(false);// XML columList gc.setAuthor("weiyuan"); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); dsc.setTypeConvert(new MySqlTypeConvert() { // 自定义数据库表字段类型转换【可选】 @Override public DbColumnType processTypeConvert(String fieldType) { return super.processTypeConvert(fieldType); } }); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"); mpg.setDataSource(dsc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //strategy.setTablePrefix(new String[]{"_"});// 此处可以修改为您的表前缀 strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略 strategy.setInclude(new String[]{"mooc_user_t"}); mpg.setStrategy(strategy); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent(null); pc.setEntity("com.stylefeng.guns.rest.common.persistence.model"); pc.setMapper("com.stylefeng.guns.rest.common.persistence.dao"); pc.setXml("com.stylefeng.guns.rest.common.persistence.dao.mapping"); pc.setService("TTT"); //本项目没用,生成之后删掉 pc.setServiceImpl("TTT"); //本项目没用,生成之后删掉 pc.setController("TTT"); //本项目没用,生成之后删掉 mpg.setPackageInfo(pc); // 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { Map<String, Object> map = new HashMap<>(); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.setMap(map); } }; mpg.setCfg(cfg); // 执行生成 mpg.execute(); // 打印注入设置 System.err.println(mpg.getCfg().getMap().get("abc")); } }
上面要设置生产的文件存储的位置,生成文件对于的包名,生产文件对于的数据库的驱动,将那个mooc_user_t表自动生成
生成之后会在guns-users下面生成对应的类
现在就已经存在了三个业务实体类:UserModel和UserInfoModel是guns-gateway和guns-user模块中dubbo服务通信的实例类
MockUserT是mooc-user-t表自动代码生成的实体类,在guns-user模块中内部使用
接下来我们要在guns-user实现用户业务操作的业务类
我们要建立一个用户操作的业务类UserServiceImpl
package com.stylefeng.guns.rest.modular.user; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.api.user.UserInfoModel; import com.stylefeng.guns.api.user.UserModel; import com.stylefeng.guns.core.util.MD5Util; import com.stylefeng.guns.rest.common.persistence.dao.MoocUserTMapper; import com.stylefeng.guns.rest.common.persistence.dao.UserMapper; import com.stylefeng.guns.rest.common.persistence.model.MoocUserT; @Component @Service(interfaceClass=UserAPI.class) public class UserServiceImpl implements UserAPI{ //注入数据库的自动扫描类 @Autowired private MoocUserTMapper userMapper; @Override public int login(String username, String password) { // TODO Auto-generated method stub return 0; } @Override public boolean register(UserModel userModel) { // TODO Auto-generated method stub //userModel是用户在页面上输入的数据,密码为123456,存储在数据库中需要进行加密处理 //将注册实体转换成数据实体 MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(userModel.getUsername()); String md5Passwd= MD5Util.encrypt(userModel.getPassword()); moocUserT.setUserPwd(md5Passwd); moocUserT.setEmail(userModel.getEmail()); moocUserT.setAddress(userModel.getAddress()); moocUserT.setUserPhone(userModel.getPhone()); //将实体存储到数据库中 Integer insert = userMapper.insert(moocUserT); if(insert>0){ return true; }else{ return false; } } @Override public boolean checkUsername(String username) { // TODO Auto-generated method stub return false; } @Override public UserInfoModel getUserInfo(int uuid) { // TODO Auto-generated method stub return null; } @Override public UserInfoModel updateUserInfo(UserInfoModel userInfoModel) { // TODO Auto-generated method stub return null; } }
1、首先改业务类要实现UserAPI的接口
2、改类要使用@Compent添加到spring容器中,使用@Service暴露成dubbo服务接口,让guns-gateway调用
3.要注入操作数据库的接口类MoocUserTMapper
注册功能,需要将用户从页面封装到UserModel对象转换成数据库对应的MoocUser对象,存储到数据库中
@Override public boolean register(UserModel userModel) { // TODO Auto-generated method stub //userModel是用户在页面上输入的数据,密码为123456,存储在数据库中需要进行加密处理 //将注册实体转换成数据实体 MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(userModel.getUsername()); String md5Passwd= MD5Util.encrypt(userModel.getPassword()); moocUserT.setUserPwd(md5Passwd); moocUserT.setEmail(userModel.getEmail()); moocUserT.setAddress(userModel.getAddress()); moocUserT.setUserPhone(userModel.getPhone()); //将实体存储到数据库中 Integer insert = userMapper.insert(moocUserT); if(insert>0){ return true; }else{ return false; } }
接下来讲解下登录和检查用户是否存在的功能
package com.stylefeng.guns.rest.modular.user; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.api.user.UserInfoModel; import com.stylefeng.guns.api.user.UserModel; import com.stylefeng.guns.core.util.MD5Util; import com.stylefeng.guns.rest.common.persistence.dao.MoocUserTMapper; import com.stylefeng.guns.rest.common.persistence.dao.UserMapper; import com.stylefeng.guns.rest.common.persistence.model.MoocUserT; @Component @Service(interfaceClass=UserAPI.class) public class UserServiceImpl implements UserAPI{ //注入数据库的自动扫描类 @Autowired private MoocUserTMapper userMapper; @Override public int login(String username, String password) { // TODO Auto-generated method stub MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(username); //到数据库中查询 MoocUserT result = userMapper.selectOne(moocUserT); //判断密码是否相等 if(result !=null && result.getUuid() > 0){ String md5Passwd = MD5Util.encrypt(password); if(result.getUserPwd().endsWith(md5Passwd)){ return result.getUuid(); } } return 0; } @Override public boolean register(UserModel userModel) { // TODO Auto-generated method stub //userModel是用户在页面上输入的数据,密码为123456,存储在数据库中需要进行加密处理 //将注册实体转换成数据实体 MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(userModel.getUsername()); String md5Passwd= MD5Util.encrypt(userModel.getPassword()); moocUserT.setUserPwd(md5Passwd); moocUserT.setEmail(userModel.getEmail()); moocUserT.setAddress(userModel.getAddress()); moocUserT.setUserPhone(userModel.getPhone()); //将实体存储到数据库中 Integer insert = userMapper.insert(moocUserT); if(insert>0){ return true; }else{ return false; } } @Override public boolean checkUsername(String username) { // TODO Auto-generated method stub EntityWrapper<MoocUserT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("user_name", username); Integer result = userMapper.selectCount(entityWrapper); if(result!=null && result>0){ return false; }else{ return true; } } @Override public UserInfoModel getUserInfo(int uuid) { // TODO Auto-generated method stub return null; } @Override public UserInfoModel updateUserInfo(UserInfoModel userInfoModel) { // TODO Auto-generated method stub return null; } }
接下来讲解下查询用户的信息,通过uuid获得当前用户的信息
整个业务实现类的代码如下
package com.stylefeng.guns.rest.modular.user; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.api.user.UserInfoModel; import com.stylefeng.guns.api.user.UserModel; import com.stylefeng.guns.core.util.MD5Util; import com.stylefeng.guns.rest.common.persistence.dao.MoocUserTMapper; import com.stylefeng.guns.rest.common.persistence.dao.UserMapper; import com.stylefeng.guns.rest.common.persistence.model.MoocUserT; @Component @Service(interfaceClass=UserAPI.class) public class UserServiceImpl implements UserAPI{ //注入数据库的自动扫描类 @Autowired private MoocUserTMapper userMapper; @Override public int login(String username, String password) { // TODO Auto-generated method stub MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(username); //到数据库中查询 MoocUserT result = userMapper.selectOne(moocUserT); //判断密码是否相等 if(result !=null && result.getUuid() > 0){ String md5Passwd = MD5Util.encrypt(password); if(result.getUserPwd().equals(md5Passwd)){ return result.getUuid(); } } return 0; } @Override public boolean register(UserModel userModel) { // TODO Auto-generated method stub //userModel是用户在页面上输入的数据,密码为123456,存储在数据库中需要进行加密处理 //将注册实体转换成数据实体 MoocUserT moocUserT = new MoocUserT(); moocUserT.setUserName(userModel.getUsername()); String md5Passwd= MD5Util.encrypt(userModel.getPassword()); moocUserT.setUserPwd(md5Passwd); moocUserT.setEmail(userModel.getEmail()); moocUserT.setAddress(userModel.getAddress()); moocUserT.setUserPhone(userModel.getPhone()); //将实体存储到数据库中 Integer insert = userMapper.insert(moocUserT); if(insert>0){ return true; }else{ return false; } } @Override public boolean checkUsername(String username) { // TODO Auto-generated method stub EntityWrapper<MoocUserT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("user_name", username); Integer result = userMapper.selectCount(entityWrapper); if(result!=null && result>0){ return false; }else{ return true; } } private UserInfoModel do2UserInfoModel(MoocUserT moocUserT){ UserInfoModel userInfoModel = new UserInfoModel(); userInfoModel.setUsername(moocUserT.getUserName()); userInfoModel.setUpdateTime(moocUserT.getUpdateTime().getTime()); userInfoModel.setSex(moocUserT.getUserSex()); userInfoModel.setPhone(moocUserT.getUserPhone()); userInfoModel.setNickname(moocUserT.getNickName()); userInfoModel.setLifeState(moocUserT.getLifeState()+""); userInfoModel.setHeadAddress(moocUserT.getHeadUrl()); userInfoModel.setEmail(moocUserT.getEmail()); userInfoModel.setBirthday(moocUserT.getBirthday()); userInfoModel.setBiography(moocUserT.getBiography()); userInfoModel.setAddress(moocUserT.getAddress()); return userInfoModel; } @Override public UserInfoModel getUserInfo(int uuid) { // TODO Auto-generated method stub MoocUserT moocUserT = userMapper.selectById(uuid); UserInfoModel userInfoModel = do2UserInfoModel(moocUserT); return userInfoModel; } @Override public UserInfoModel updateUserInfo(UserInfoModel userInfoModel) { // TODO Auto-generated method stub return null; } }
接下来业务层实现完成之后,需要在guns-gateway中使用对远程服务的调用,我们编写一个UserController类,来实现guns-gateway对guns-user的访问
UserController
package com.stylefeng.guns.rest.modular.user; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.alibaba.dubbo.config.annotation.Reference; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.api.user.UserInfoModel; import com.stylefeng.guns.api.user.UserModel; import com.stylefeng.guns.rest.common.CurrentUser; import com.stylefeng.guns.rest.modular.auth.VO.ReponseVO; @RestController @RequestMapping("/user/") public class UserController { @Reference(interfaceClass= UserAPI.class) private UserAPI userAPI; @RequestMapping(value="register",method=RequestMethod.POST) public ReponseVO register(UserModel userModel){ if(userModel.getUsername() == null || userModel.getUsername().equals("") ){ return ReponseVO.serviceFail("用户名不能为空"); } if(userModel.getPassword() == null || userModel.getPassword().equals("")){ return ReponseVO.serviceFail("密码不能为空"); } boolean result = userAPI.register(userModel); if(result){ return ReponseVO.success(new String("用户注册成功")); }else{ return ReponseVO.appFail("用户注册异常"); } } //检查用户是否存在 @RequestMapping(value="check",method=RequestMethod.POST) public ReponseVO check(String username){ if(username == null || username.equals("") ){ return ReponseVO.serviceFail("用户名不能为空"); } boolean notExits = userAPI.checkUsername(username); if(notExits){ return ReponseVO.success(new String("用户不存在,可以注册")); }else{ return ReponseVO.serviceFail("改用户已经存在"); } } //获得当前用户的信息 @RequestMapping(value="getUserInfo",method=RequestMethod.GET) public ReponseVO getUserInfo(){ //首先从ThreadLocal中获得当前用户的id String userId = CurrentUser.getUserInfo(); if(userId==null ||"".equals(userId)){ return ReponseVO.serviceFail("当前用户没有登录"); }else{ int uuid = Integer.parseInt(userId); UserInfoModel userInfoModel = userAPI.getUserInfo(uuid); if(userInfoModel!=null){ return ReponseVO.success(userInfoModel); }else{ return ReponseVO.appFail("用户信息查询失败"); } } } }
完成之后,我们需要进行测试,通过guns-gateway来访问guns-user
业务测试:
1、访问gateway的注册、查询用户是否存在不需要进行url的校验
我们在guns-gateway中配置不进行jwt检查的url
rest: auth-open: true #jwt鉴权机制是否开启(true或者false) sign-open: true #签名机制是否开启(true或false) jwt: header: Authorization #http请求头所需要的字段 secret: mySecret #jwt秘钥 expiration: 604800 #7天 单位:秒 auth-path: auth #认证请求的路径 md5-key: randomKey #md5加密混淆key ignore-url: /user/register,/user/check #对改类请求的url不进行jwt模块的校验 server: port: 80 #项目端口 mybatis-plus: mapper-locations: classpath*:com/stylefeng/guns/rest/**/mapping/*.xml typeAliasesPackage: com.stylefeng.guns.rest.common.persistence.model global-config: id-type: 0 #0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid) db-column-underline: false refresh-mapper: true configuration: map-underscore-to-camel-case: false cache-enabled: true #配置的缓存的全局开关 lazyLoadingEnabled: true #延时加载的开关 multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性 # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句,调试用 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 123456 filters: log4j,wall,mergeStat logging: level.root: info level.com.stylefeng: debug path: logs/ file: guns-rest.log
接下来我们启动工程,因为guns-gateway通过dubbo访问了guns-user,我们要先启动zookeeper,在启动guns-user,最后在启动guns-gateway
我们可以使用postman对上面的接口进行验证
接下来我们来验证获得当前用户详情的接口,要获得用户的详细信息,需要用户已经登录,必须要有jwt接口
这里很关键:
第一步使用接口获得对于的jwt信息
http://localhost:8081/auth?userName=admin&password=admin123
这里后台做了几件事情访问AuthController的createAuthenticationToken方法,在改方法中首先调用guns-user的login登录接口,判断当前输入的用户名和密码是否正确
,如果当前的用户名和密码正确,会 final String token = jwtTokenUtil.generateToken(userId+"", randomKey);会使用当前用户的用户id生成一个token信息
接下来第二步:
访问用户详情的接口,在请求的请求头中携带了jwt的信息,所有的http请求都会被AuthFilter拦截,在业务处理中会从请求头中获得jwt的信息,验证当前的//验证token是否过期,包含了验证jwt是否正确,然后从jwt中获得
当前用户的id,然后将当前用户的id存储到ThreadLocal变量中,filter拦截器通过之后,接下来回访问controller的业务接口,从ThreadLocal从获得当前用户的id,通过dubbo远程
调用guns-user中获得用户详情的接口,返回用户详情信息
这里有几点需要注意的,head中请求头的名称是在中指定的
jwt:
header: Authorization #http请求头所需要的字段
第二点:请求头携带的内容是以Bearer 开头加上第一步中返回的jwt的token信息
启动检查的时候,例如guns-gateway要远程调用guns-user的服务,当guns-gateway启动的时候,会检查guns-user是否启动正确,如果guns-user没有启动,整个guns-gateway就会启动
失败,如果我们要取消启动检查,需要在服务消费者方配置一个check=false的属性,这样即使guns-user没有启动,guns-gateway启动的时候也不会报错
package com.stylefeng.guns.rest.modular.auth.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.stylefeng.guns.api.user.UserAPI; import com.stylefeng.guns.core.exception.GunsException; import com.stylefeng.guns.rest.common.exception.BizExceptionEnum; import com.stylefeng.guns.rest.modular.auth.VO.ReponseVO; import com.stylefeng.guns.rest.modular.auth.controller.dto.AuthRequest; import com.stylefeng.guns.rest.modular.auth.controller.dto.AuthResponse; import com.stylefeng.guns.rest.modular.auth.util.JwtTokenUtil; import com.stylefeng.guns.rest.modular.auth.validator.IReqValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * 请求验证的 * * @author fengshuonan * @Date 2017/8/24 14:22 */ @RestController public class AuthController { @Autowired private JwtTokenUtil jwtTokenUtil; @Resource(name = "simpleValidator") private IReqValidator reqValidator; @Reference(interfaceClass=UserAPI.class,check=false) private UserAPI userApi; @RequestMapping(value = "${jwt.auth-path}") public ReponseVO createAuthenticationToken(AuthRequest authRequest) { // boolean validate = reqValidator.validate(authRequest); boolean validate = true; int userId = userApi.login(authRequest.getUserName(), authRequest.getPassword()); //int userId = 8; if(userId ==0){ validate = false; } if (validate) { final String randomKey = jwtTokenUtil.getRandomKey(); final String token = jwtTokenUtil.generateToken(userId+"", randomKey); return ReponseVO.success(new AuthResponse(token, randomKey)); } else { return ReponseVO.serviceFail("用户名或者密码错误"); } } }
LeastActive:记录用户活跃数,第一个服务调用需要2秒,第二个服务需要调用3秒,那么调用的时候回转发到2秒那个服务上调用
dubbo框架支持下面的协议:
dubbo支持的dubbo协议的话,最大的报文数据包不能超过100K
上面就是将协议配置成了RMI协议
F:\第5章 Dubbo服务开发:影片模块开发\第5章 Dubbo服务开发:影片模块开发
接下来我们要构建一个guns-film模块,我们直接将guns-user复制一份,变成guns-film
进入到guns-film,修改起pom.xml文件
将
<groupId>com.stylefeng.guns</groupId> <artifactId>guns-users</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>guns-users</name> <description>用户管理模块</description>
修改为
<groupId>com.stylefeng.guns</groupId> <artifactId>guns-film</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>guns-film</name> <description>电影模块</description>
接下来进入到guns-parent模块,添加子模块guns-film的依赖
<modules> <module>guns-admin</module> <module>guns-core</module> <module>guns-rest</module> <module>guns-generator</module> <module>guns-gateway</module> <module>guns-api</module> <module>guns-user</module> <module>guns-film</module> </modules>
接下来我们来看看影片的业务接口文档
目录
一、 影片模块接口 2
1、 首页接口 2
请求地址 2
请求方式 2
请求字段 2
应答报文 2
业务异常 5
系统异常 5
2、 影片条件列表查询接口 5
请求地址 5
请求方式 5
请求字段 5
应答报文 5
业务异常 7
系统异常 7
3、 影片查询接口 7
请求地址 7
请求方式 7
请求字段 7
应答报文 8
业务异常 8
系统异常 8
4、 影片详情查询接口 9
请求地址 9
请求方式 9
请求字段 9
应答报文 9
业务异常 10
系统异常 10
一、 影片模块接口
1、 首页接口
请求地址
/film/getIndex
请求方式
get
请求字段
无
应答报文
{
status: 0,
imgPre:’http://img.meetingshop.cn/’,
data: {
banners : [
{
bannerId:’001’,
bannerAddress:‘img/banner/001.jpg’
bannerUrl:‘http://www.meetingshop.cn/002.html’
},
{
bannerId:’002’,
bannerAddress:‘img/banner/002.jpg’
bannerUrl:‘http://www.meetingshop.cn/002.html’
}
],
hotFilms : {
filmNum : 28,
filmInfo : [
{
filmId:’001’,
filmType:1, [0-2D,1-3D,2-3DIMAX,4-无]
imgAddress:‘img/film/001.jpg’
filmName:‘我不是药神’,
filmScore:‘8.3’
},
{
filmId:’002’,
filmType:4, [0-2D,1-3D,2-3DIMAX,4-无]
imgAddress:‘img/film/002.jpg’
filmName:‘摩天营救’,
filmScore:‘9.0’
}
]
},
soonFilms : {
filmNum : 295,
filmInfo : [
{
filmId:’001’,
filmType:1, [0-2D,1-3D,2-3DIMAX,4-无]
imgAddress:‘img/film/001.jpg’
filmName:‘我不是药神’,
expectNum:283000,
showTime : ‘2018-08-04’
},
{
filmId:’002’,
filmType:0, [0-2D,1-3D,2-3DIMAX,4-无]
imgAddress:‘img/film/002.jpg’
filmName:‘狄仁杰之四大天王’,
expectNum:283000,
showTime : ‘2018-09-04’
}
]
},
boxRanking : [
{
filmId:’002’,
imgAddress:‘img/film/002.jpg’
filmName:‘狄仁杰之四大天王’,
boxNum:231
},
{
filmId:’003’,
imgAddress:‘img/film/003.jpg’
filmName:‘小猪佩奇’,
boxNum:200
}
],
expectRanking : [
{
filmId:’002’,
imgAddress:‘img/film/002.jpg’
filmName:‘狄仁杰之四大天王’,
expectNum:231850
},
{
filmId:’003’,
imgAddress:‘img/film/003.jpg’
filmName:‘小猪佩奇’,
expectNum:200321
}
].
top100 : [
{
filmId:’002’,
imgAddress:‘img/film/002.jpg’
filmName:‘狄仁杰之四大天王’,
score:’9.3’
},
{
filmId:’003’,
imgAddress:‘img/film/003.jpg’
filmName:‘小猪佩奇’,
score:’9.0’
}
]
}
}
业务异常
{
“status” : 1,
“msg” :“查询失败,无banner可加载”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
2、 影片条件列表查询接口
请求地址
/film/getConditionList
请求方式
get
请求字段
字段名称 字段含义 是否必填
catId 类型编号 否,默认为99
sourceId 片源编号 否,默认为99
yearId 年代编号 否,默认为99
应答报文
{
status: 0,
data: {
catInfo:[
{
catId:”001”,
catName:”爱情”,
isActive: true
},
{
catId:”002”,
catName:”喜剧”
isActive: false
}
],
sourceInfo:[
{
sourceId:”001”,
sourceName:”大陆”,
isActive: false
},
{
sourceId:”002”,
sourceName:”美国”
isActive: true
}
],
yearInfo:[
{
yearId:”001”,
yearName:”2018年”,
isActive: true
},
{
yearId:”002”,
yearName:”2018年以后”
isActive: false
}
]
}
}
业务异常
{
“status” : 1,
“msg” :“查询失败,无条件可加载”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
3、 影片查询接口
请求地址
/film/getFilms
请求方式
get
请求字段
字段名称 字段含义 是否必填
showType 查询类型,1-正在热映,2-即将上映,3-经典影片 否,默认为1
sortId 排序方式,1-按热门搜索,2-按时间搜索,3-按评价搜索 否,默认为1
catId 类型编号 否,默认为99
sourceId 区域编号 否,默认为99
yearId 年代编号 否,默认为99
nowPage 影片列表当前页,翻页使用 否,默认为1
pageSize 每页显示条数 否,默认为18
应答报文
{
status: 0,
imgPre:’http://img.meetingshop.cn/’,
nowPage : 1,
totalPage : 3,
data: [
{
filmId:’001’,
filmType:1, [0-2D,1-3D,2-3DIMAX,4-无],
imgAddress:‘img/film/001.jpg’,
filmName:‘我不是药神’,
filmScore:‘8.3’
},
{
filmId:’002’,
filmType:4, [0-2D,1-3D,2-3DIMAX,4-无],
imgAddress:‘img/film/002.jpg’,
filmName:‘摩天营救’,
filmScore:‘9.0’
}
]
}
业务异常
{
“status” : 1,
“msg” :“查询失败,无banner可加载”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
4、 影片详情查询接口
请求地址
/film/films/{影片编号或影片名称}
请求方式
get
请求字段
searchType : ‘0表示按照编号查找,1表示按照名称查找’
应答报文
{
status: 0,
imgPre:’http://img.meetingshop.cn/’,
data: {
filmName : ‘动物世界’,
filmEnName : ‘Animal World’,
imgAddress : ‘img/films/23412.jpg’,
score : ‘8.5’,
scoreNum : ‘43万人评分’,
totalBox : ‘5.07亿’,
info01 : ‘动作,悬疑,冒险’,
info02 : ‘中国大陆,美国 / 132分钟’,
info03 : ‘2018-06-29大陆上映’,
info04 : {
biography : ‘男主角郑开司(李易峰 饰)因被朋友欺骗而背负上数百万的债务,面对重病的母亲和痴心等待的青梅竹马,他决心登上“命运号”游轮,改变自己一事无成的人生。只要能在渡轮上的游戏中获胜,他就将有机会将债务一笔勾销,并给家人带来更好的生活。这场游戏看似简单,参与者只需以标着“石头,剪刀,布”的扑克为道具,赢取对手身上的星星标志。但游轮上的亡命徒们毫无底线的欺诈争夺,却让人性的自私与残酷暴露无遗,局中局、计中计,让游戏场最终沦为“动物世界”斗兽场。面对绝境的郑开司,能否坚守自我底线保持善良本性?能否凭借自己的智慧和坚韧摆脱困境?这是一场自我救赎的残酷游戏,多重考验也将接踵而至。’,
actors : {
director : {
imgAddress : ‘actors/02134.jpg’,
directorName : ‘韩延’
},
actors : [
{
imgAddress : ‘actors/02136.jpg’,
directorName : ‘李易峰’,
roleName : ‘郑开司’
},
{
imgAddress : ‘actors/02332.jpg’,
directorName : ‘周冬雨’,
roleName : ‘刘青’
}
]
}
},
imgs : {
mainImg : ‘fims/123123.png’,
img01 : ‘fims/12312.jpg’
img02 : ‘films/16345.png’,
img03 : ‘films/12938.jpg’,
img04 : ‘films/21365.jpg’
},
filmId : ‘12’
}
}
业务异常
{
“status” : 1,
“msg” :“查询失败,无影片可加载”
}
系统异常
{
“status” : 999,
“msg” :“系统出现异常,请联系管理员”
}
我们现在guns-gateway中网关中先建立一个film模块
影片模块需要下面的6类数据
1、首先获得横幅的数据
2、获得当前正在热映的数据
3、获得即将上演的数据
4、票房排行榜的数据
5、获取受欢迎榜单的数据
6.获取前一百的数据
在首页面展示的时候需要获得上面6个模块的数据,这里如果前段页面分别发起6次http请求去获得对于的数据,比较麻烦
我们可以在guns-gateway中提供一个接口,当前段查询整个接口的时候,把上面6个接口的数据返回给前段,在网关层对业务进行聚合操作
接下来我们创建对于的实体类对象BannerVO为横幅对于的实体类
package com.stylefeng.guns.rest.modular.film.VO; public class BannerVO { private String bannerID; private String bannerAddress; private String bannerUrl; public String getBannerID() { return bannerID; } public void setBannerID(String bannerID) { this.bannerID = bannerID; } public String getBannerAddress() { return bannerAddress; } public void setBannerAddress(String bannerAddress) { this.bannerAddress = bannerAddress; } public String getBannerUrl() { return bannerUrl; } public void setBannerUrl(String bannerUrl) { this.bannerUrl = bannerUrl; } @Override public String toString() { return "BannerVO [bannerID=" + bannerID + ", bannerAddress=" + bannerAddress + ", bannerUrl=" + bannerUrl + "]"; } }
我们创建实体类的时候,每次都要给实体类编写getter和setter方法,还有toString方法,非常的麻烦,lombok框架已经帮我们完成这个功能了
我们只需要添加下面的依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> <scope>provided</scope> </dependency>
我们在编写实体类的时候使用@Data注解就会自动帮实体类生成getter和setter 还有toString等方法
package com.stylefeng.guns.rest.modular.film.VO; import lombok.Data; @Data public class BannerVO { private String bannerID; private String bannerAddress; private String bannerUrl; }
上面的代码
package com.stylefeng.guns.rest.modular.film.VO; public class BannerVO { private String bannerID; private String bannerAddress; private String bannerUrl; public String getBannerID() { return bannerID; } public void setBannerID(String bannerID) { this.bannerID = bannerID; } public String getBannerAddress() { return bannerAddress; } public void setBannerAddress(String bannerAddress) { this.bannerAddress = bannerAddress; } public String getBannerUrl() { return bannerUrl; } public void setBannerUrl(String bannerUrl) { this.bannerUrl = bannerUrl; } @Override public String toString() { return "BannerVO [bannerID=" + bannerID + ", bannerAddress=" + bannerAddress + ", bannerUrl=" + bannerUrl + "]"; } }
接下来按照业务接口
创建hotFilm的对象中包含了filmNum,和fileinfo对象
我们先创建filmInfo对象
package com.stylefeng.guns.rest.modular.film.VO; public class FilmInfo implements Serializable{ private String filmId; private int filmType; private String imgAddress; private String filmName; private String filmScore; private int expectNum; private String showTime ; public String getFilmId() { return filmId; } public void setFilmId(String filmId) { this.filmId = filmId; } @Override public String toString() { return "FilmInfo [filmId=" + filmId + ", filmType=" + filmType + ", imgAddress=" + imgAddress + ", filmName=" + filmName + ", filmScore=" + filmScore + ", expectNum=" + expectNum + ", showTime=" + showTime + "]"; } public int getFilmType() { return filmType; } public void setFilmType(int filmType) { this.filmType = filmType; } public String getImgAddress() { return imgAddress; } public void setImgAddress(String imgAddress) { this.imgAddress = imgAddress; } public String getFilmName() { return filmName; } public void setFilmName(String filmName) { this.filmName = filmName; } public String getFilmScore() { return filmScore; } public void setFilmScore(String filmScore) { this.filmScore = filmScore; } public int getExpectNum() { return expectNum; } public void setExpectNum(int expectNum) { this.expectNum = expectNum; } public String getShowTime() { return showTime; } public void setShowTime(String showTime) { this.showTime = showTime; } }
一个完整的FileInfo对象包括下面的内容:
package com.stylefeng.guns.rest.modular.film.VO; public class FilmInfo implements Serializable { private String filmId; private int filmType; private String imgAddress; private String filmName; private String filmScore; private int expectNum; private String showTime ; private int boxNum; private String score; public String getScore() { return score; } public void setScore(String score) { this.score = score; } public int getBoxNum() { return boxNum; } public void setBoxNum(int boxNum) { this.boxNum = boxNum; } public String getFilmId() { return filmId; } public void setFilmId(String filmId) { this.filmId = filmId; } @Override public String toString() { return "FilmInfo [filmId=" + filmId + ", filmType=" + filmType + ", imgAddress=" + imgAddress + ", filmName=" + filmName + ", filmScore=" + filmScore + ", expectNum=" + expectNum + ", showTime=" + showTime + "]"; } public int getFilmType() { return filmType; } public void setFilmType(int filmType) { this.filmType = filmType; } public String getImgAddress() { return imgAddress; } public void setImgAddress(String imgAddress) { this.imgAddress = imgAddress; } public String getFilmName() { return filmName; } public void setFilmName(String filmName) { this.filmName = filmName; } public String getFilmScore() { return filmScore; } public void setFilmScore(String filmScore) { this.filmScore = filmScore; } public int getExpectNum() { return expectNum; } public void setExpectNum(int expectNum) { this.expectNum = expectNum; } public String getShowTime() { return showTime; } public void setShowTime(String showTime) { this.showTime = showTime; } }
package com.stylefeng.guns.rest.modular.film.VO; public class BannerVO { private String bannerID; private String bannerAddress; private String bannerUrl; public String getBannerID() { return bannerID; } public void setBannerID(String bannerID) { this.bannerID = bannerID; } public String getBannerAddress() { return bannerAddress; } public void setBannerAddress(String bannerAddress) { this.bannerAddress = bannerAddress; } public String getBannerUrl() { return bannerUrl; } public void setBannerUrl(String bannerUrl) { this.bannerUrl = bannerUrl; } @Override public String toString() { return "BannerVO [bannerID=" + bannerID + ", bannerAddress=" + bannerAddress + ", bannerUrl=" + bannerUrl + "]"; } }
接下来我们定义一个首页对象,改对象包含上面的6个对象性质
package com.stylefeng.guns.rest.modular.film.VO; import java.util.List; public class FilmIndexVO implements Serializable{ private List<BannerVO> bannners; private FilmVO hotFilms; private FilmVO soonFils; private List<FilmInfo> boxRanking ; private List<FilmInfo> expectRanking ; private List<FilmInfo> top100 ; public List<BannerVO> getBannners() { return bannners; } public void setBannners(List<BannerVO> bannners) { this.bannners = bannners; } public FilmVO getHotFilms() { return hotFilms; } public void setHotFilms(FilmVO hotFilms) { this.hotFilms = hotFilms; } public FilmVO getSoonFils() { return soonFils; } public void setSoonFils(FilmVO soonFils) { this.soonFils = soonFils; } public List<FilmInfo> getBoxRanking() { return boxRanking; } public void setBoxRanking(List<FilmInfo> boxRanking) { this.boxRanking = boxRanking; } public List<FilmInfo> getExpectRanking() { return expectRanking; } public void setExpectRanking(List<FilmInfo> expectRanking) { this.expectRanking = expectRanking; } public List<FilmInfo> getTop100() { return top100; } public void setTop100(List<FilmInfo> top100) { this.top100 = top100; } }
package com.stylefeng.guns.rest.modular.film.VO; public class BannerVO implements Serializable { private String bannerID; private String bannerAddress; private String bannerUrl; public String getBannerID() { return bannerID; } public void setBannerID(String bannerID) { this.bannerID = bannerID; } public String getBannerAddress() { return bannerAddress; } public void setBannerAddress(String bannerAddress) { this.bannerAddress = bannerAddress; } public String getBannerUrl() { return bannerUrl; } public void setBannerUrl(String bannerUrl) { this.bannerUrl = bannerUrl; } @Override public String toString() { return "BannerVO [bannerID=" + bannerID + ", bannerAddress=" + bannerAddress + ", bannerUrl=" + bannerUrl + "]"; } }
整个影片模块的实体类对象就已经创建完成了
接下来我们就开始进行影片接口模块的实现,我们要到guns-api中实现对于的影片接口模块的业务实现类
因为guns-gateway和guns-film都要使用到上面创建的FilmVO,FilmInfo,以及BannerVO对象,我们把三个对象存储在公共的guns-api中,FilmIndexVO仅仅是guns-film模块使用的
并且上面的几个类一定要实现序列号接口,接下来我们编写影片的业务接口
FillmServiceAPI.java
package com.stylefeng.guns.api.film; import java.util.List; import com.stylefeng.guns.rest.modular.film.VO.BannerVO; import com.stylefeng.guns.rest.modular.film.VO.FilmInfo; import com.stylefeng.guns.rest.modular.film.VO.FilmVO; public interface FillmServiceAPI { /* 1、首先获得横幅的数据 2、获得当前正在热映的数据 3、获得即将上演的数据 4、票房排行榜的数据 5、获取受欢迎榜单的数据 6.获取前一百的数据*/ //1、首先获得横幅的数据 List<BannerVO> getBanners(); //2、获得当前正在热映的数据 isLimit是否对影片的数目做限制,nums表示最大的数目是多少 FilmVO getHotFilms(boolean isLimit ,int nums); //3、获得即将上演的数据 FilmVO getSoonFilms(boolean isLimit ,int nums); //4、票房排行榜的数据 List<FilmInfo> getboxRanking(); //5、获取受欢迎榜单的数据 List<FilmInfo> getexpectRanking(); //6.获取前一百的数据*/ List<FilmInfo> getTop(); }
接下来我们开始编写我们的业务功能,guns-film模块也是通过网关来调用的mguns-film中也不需要进行jwt的校验,所以在配置文件中application.yml,需要关闭jwt的校验
rest:
auth-open: false #jwt鉴权机制是否开启(true或者false)
sign-open: false #签名机制是否开启(true或false)
接来下我们在guns-films模块中来实现具体的业务操作,我们新建两个模块dao,存储影片模块的dao,Service来实现具体的业务操作功能
我们首先要在数据库中建立下面的表,采用下面的建表语句进行操作
-- ---------------------------- -- Table structure for mooc_banner_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_banner_t`; CREATE TABLE mooc_banner_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', banner_address VARCHAR(50) COMMENT 'banner图存放路径', banner_url VARCHAR(200) COMMENT 'banner点击跳转url', is_valid INT DEFAULT 0 COMMENT '是否弃用 0-失效,1-失效' ) COMMENT 'banner信息表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_banner_t -- ---------------------------- INSERT INTO mooc_banner_t(banner_address,banner_url) VALUES('banners/9d75708ae91d5fc918369b76e9e2edba197666.jpg','www.meetingshop.cn'); INSERT INTO mooc_banner_t(banner_address,banner_url) VALUES('banners/15b3730838f35d56a76d88a1787aaaa5186414.jpg','www.meetingshop.cn'); INSERT INTO mooc_banner_t(banner_address,banner_url) VALUES('banners/51afa73f0347e9b98957c53fa971d41491652.jpg','www.meetingshop.cn'); INSERT INTO mooc_banner_t(banner_address,banner_url) VALUES('banners/6605d3518e2bba10f29a6f9ae32b361987015.jpg','www.meetingshop.cn'); INSERT INTO mooc_banner_t(banner_address,banner_url) VALUES('banners/c1a713981cabef02c88ae5f42888de70183835.jpg','www.meetingshop.cn'); -- ---------------------------- -- Table structure for mooc_cat_dict_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_cat_dict_t`; CREATE TABLE mooc_cat_dict_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', show_name VARCHAR(100) COMMENT '显示名称' ) COMMENT '类型信息表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_cat_dict_t -- ---------------------------- INSERT INTO mooc_cat_dict_t(uuid,show_name) values(99,'全部'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(1,'爱情'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(2,'喜剧'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(3,'动画'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(4,'剧情'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(5,'恐怖'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(6,'惊悚'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(7,'科幻'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(8,'动作'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(9,'悬疑'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(10,'犯罪'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(11,'冒险'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(12,'战争'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(13,'奇幻'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(14,'运动'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(15,'家庭'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(16,'古装'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(17,'武侠'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(18,'西部'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(19,'历史'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(20,'传记'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(21,'歌舞'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(22,'短片'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(23,'纪录片'); INSERT INTO mooc_cat_dict_t(uuid,show_name) values(24,'黑色电影'); -- ---------------------------- -- Table structure for mooc_source_dict_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_source_dict_t`; CREATE TABLE mooc_source_dict_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', show_name VARCHAR(100) COMMENT '显示名称' ) COMMENT '区域信息表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_source_dict_t -- ---------------------------- INSERT INTO mooc_source_dict_t(uuid,show_name) values(99,'全部'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(1,'大陆'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(2,'美国'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(3,'韩国'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(4,'日本'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(5,'中国香港'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(6,'中国台湾'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(7,'印度'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(8,'法国'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(9,'英国'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(10,'俄罗斯'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(11,'意大利'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(12,'西班牙'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(13,'德国'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(14,'波兰'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(15,'澳大利亚'); INSERT INTO mooc_source_dict_t(uuid,show_name) values(16,'伊朗'); -- ---------------------------- -- Table structure for mooc_year_dict_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_year_dict_t`; CREATE TABLE mooc_year_dict_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', show_name VARCHAR(100) COMMENT '显示名称' ) COMMENT '年代信息表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_year_dict_t -- ---------------------------- INSERT INTO mooc_year_dict_t(uuid,show_name) values(99,'全部'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(1,'更早'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(2,'70年代'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(3,'80年代'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(4,'90年代'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(5,'2000-2010'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(6,'2011'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(7,'2012'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(8,'2013'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(9,'2014'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(10,'2015'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(11,'2016'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(12,'2017'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(13,'2018'); INSERT INTO mooc_year_dict_t(uuid,show_name) values(14,'2018以后'); -- ---------------------------- -- Table structure for mooc_film_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_film_t`; CREATE TABLE mooc_film_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', film_name VARCHAR(100) COMMENT '影片名称', film_type INT COMMENT '片源类型: 0-2D,1-3D,2-3DIMAX,4-无', img_address VARCHAR(200) COMMENT '影片主图地址', film_score VARCHAR(20) COMMENT '影片评分', film_preSaleNum INT COMMENT '影片预售数量', film_box_office INT COMMENT '影片票房:每日更新,以万为单位', film_source INT COMMENT '影片片源,参照片源字典表', film_cats VARCHAR(50) COMMENT '影片分类,参照分类表,多个分类以#分割', film_area INT COMMENT '影片区域,参照区域表', film_date INT COMMENT '影片上映年代,参照年代表', film_time TIMESTAMP COMMENT '影片上映时间', film_status INT COMMENT '影片状态,1-正在热映,2-即将上映,3-经典影片' ) COMMENT '影片主表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_film_t -- ---------------------------- INSERT INTO mooc_film_t(uuid,film_name,film_source,film_type,film_cats,film_area,film_date,film_time,film_preSaleNum,film_box_office,film_score,film_status,img_address) values(2,'我不是药神',1,0,'#2#4#22#',1,13,'2018-07-05',231432491,309600,'9.7',1,'films/238e2dc36beae55a71cabfc14069fe78236351.jpg'); -- ---------------------------- -- Table structure for mooc_film_info_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_film_info_t`; CREATE TABLE mooc_film_info_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', film_id VARCHAR(100) COMMENT '影片编号', film_en_name VARCHAR(50) COMMENT '影片英文名称', film_score VARCHAR(20) COMMENT '影片评分', film_score_num INT COMMENT '评分人数,以万为单位', film_length INT COMMENT '播放时长,以分钟为单位,不足取整', biography TEXT COMMENT '影片介绍', director_id INT COMMENT '导演编号', film_imgs TEXT COMMENT '影片图片集地址,多个图片以逗号分隔' ) COMMENT '影片主表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_film_info_t -- ---------------------------- INSERT INTO mooc_film_info_t(film_id,film_en_name,film_score,film_score_num,film_length,director_id,film_imgs,biography) values(2,'Dying To Survive','9.7',225,117,1, 'films/3065271341357040f5f5dd988550951e586199.jpg,films/6b2b3fd6260ac37e5ad44d00ea474ea3651419.jpg,films/4633dd44c51ff15fc7e939679d7cdb67561602.jpg,films/df2d30b1a3bd58fb1d38b978662ae844648169.jpg,films/c845f6b04aa49059951fd55e6b0eddac454036.jpg', '一位不速之客的意外到访,打破了神油店老板程勇(徐峥 饰)的平凡人生,他从一个交不起房租的男性保健品商贩,一跃成为印度仿制药“格列宁”的独家代理商。收获巨额利润的他,生活剧烈变化,被病患们冠以“药神”的称号。但是,一场关于救赎的拉锯战也在波涛暗涌中慢慢展开......' ); -- ---------------------------- -- Table structure for mooc_actor_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_actor_t`; CREATE TABLE mooc_actor_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', actor_name VARCHAR(50) COMMENT '演员名称', actor_img VARCHAR(200) COMMENT '演员图片位置' ) COMMENT '演员表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_actor_t -- ---------------------------- INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(1,'徐峥','actors/2b98c9d2e6d23a7eff25dcac8b584b0136045.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(2,'王传君','actors/b782d497577baffb5ed14de52841dcb164365.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(3,'谭卓','actors/acf7db57456cb1aed1a42f7ebffedaa842002.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(4,'黄渤','actors/c6594ef2705dcaf7d9df857d228b5e1645712.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(5,'舒淇','actors/6b32a489467283bb739a2bac3b2b929742175.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(6,'张艺兴','actors/b738d5e78a1f5c3379d9d42a9b18286f32246.jpeg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(7,'强森','actors/7e3067d066c1e285b0cc17bfd5f1b34e108474.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(8,'杰森·斯坦森','actors/7ec0c90aec03c7904c1db3af1153162f77864.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(9,'李冰冰','actors/d2258cd0529950cf5099206519d91d0e51803.jpg'); INSERT INTO mooc_actor_t(uuid,actor_name,actor_img) value(10,'汤姆·克鲁斯','actors/6afaea1cb4ca2b346e86e265347c78b833970.jpg');
-- ---------------------------- -- Table structure for mooc_film_actor_t -- ---------------------------- DROP TABLE IF EXISTS `mooc_film_actor_t`; CREATE TABLE mooc_film_actor_t( UUID INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键编号', film_id INT COMMENT '影片编号,对应mooc_film_t', actor_id INT COMMENT '演员编号,对应mooc_actor_t', role_name VARCHAR(100) COMMENT '角色名称' ) COMMENT '影片与演员映射表' ENGINE = INNODB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of mooc_film_actor_t -- ---------------------------- INSERT INTO mooc_film_actor_t(UUID,film_id,actor_id,role_name) values(1,2,1,'演员1'); INSERT INTO mooc_film_actor_t(UUID,film_id,actor_id,role_name) values(2,2,2,'演员2'); INSERT INTO mooc_film_actor_t(UUID,film_id,actor_id,role_name) values(3,2,3,'演员3'); INSERT INTO mooc_film_actor_t(UUID,film_id,actor_id,role_name) values(4,2,4,'演员4');
表建立完成之后,接下来我们采用自动代码生成的方式帮助我们生成对于的代码,我们使用EntityGenerator帮助我们自动生成对于的代码类
package com.stylefeng.guns.generator; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import com.baomidou.mybatisplus.generator.config.rules.DbType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import org.junit.Test; import java.util.HashMap; import java.util.Map; /** * 实体生成 * * @author fengshuonan * @date 2017-08-23 12:15 */ public class EntityGenerator { @Test public void entityGenerator() { AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); //D:\work_project\naan1993-guns-master\guns\guns-user\src\main\java gc.setOutputDir("D:\\work_project\\naan1993-guns-master\\guns\\guns-film\\src\\main\\java");//这里写你自己的java目录 gc.setFileOverride(true);//是否覆盖 gc.setActiveRecord(true); gc.setEnableCache(false);// XML 二级缓存 gc.setBaseResultMap(true);// XML ResultMap gc.setBaseColumnList(false);// XML columList gc.setAuthor("weiyuan"); mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setDbType(DbType.MYSQL); dsc.setTypeConvert(new MySqlTypeConvert() { // 自定义数据库表字段类型转换【可选】 @Override public DbColumnType processTypeConvert(String fieldType) { return super.processTypeConvert(fieldType); } }); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/guns_rest?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"); mpg.setDataSource(dsc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); //strategy.setTablePrefix(new String[]{"_"});// 此处可以修改为您的表前缀 strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略 strategy.setInclude(new String[]{"mooc_actor_t","mooc_banner_t","mooc_cat_dict_t","mooc_film_actor_t","mooc_film_info_t","mooc_film_t", "mooc_source_dict_t","mooc_year_dict_t"}); mpg.setStrategy(strategy); // 包配置 PackageConfig pc = new PackageConfig(); pc.setParent(null); pc.setEntity("com.stylefeng.guns.rest.common.persistence.model"); pc.setMapper("com.stylefeng.guns.rest.common.persistence.dao"); pc.setXml("com.stylefeng.guns.rest.common.persistence.dao.mapping"); pc.setService("TTT"); //本项目没用,生成之后删掉 pc.setServiceImpl("TTT"); //本项目没用,生成之后删掉 pc.setController("TTT"); //本项目没用,生成之后删掉 mpg.setPackageInfo(pc); // 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { Map<String, Object> map = new HashMap<>(); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.setMap(map); } }; mpg.setCfg(cfg); // 执行生成 mpg.execute(); // 打印注入设置 System.err.println(mpg.getCfg().getMap().get("abc")); } }
我们运行EntityGenerator,就能够帮助我们生成mybatis对于的dao文件
dao层生成好了,接下来我们来实现具体的业务操作
接下来实现我们的banners横幅的业务代码
package com.stylefeng.guns.rest.modular.film.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; import com.stylefeng.guns.api.film.FillmServiceAPI; import com.stylefeng.guns.rest.common.persistence.dao.MoocBannerTMapper; import com.stylefeng.guns.rest.common.persistence.model.MoocBannerT; import com.stylefeng.guns.rest.modular.film.VO.BannerVO; import com.stylefeng.guns.rest.modular.film.VO.FilmInfo; import com.stylefeng.guns.rest.modular.film.VO.FilmVO; @Component @Service(interfaceClass=FillmServiceAPI.class) public class DefaultFilmServiceImpl implements FillmServiceAPI{ @Autowired private MoocBannerTMapper moocBannerTMapper; @Override public List<BannerVO> getBanners() { // TODO Auto-generated method stub //查询出所有的banners List<MoocBannerT> selectList = moocBannerTMapper.selectList(null); List<BannerVO> datas = new ArrayList<>(); for( MoocBannerT moocBannerT:selectList){ BannerVO bannerVO = new BannerVO(); bannerVO.setBannerID(moocBannerT.getUuid()+""); bannerVO.setBannerUrl(moocBannerT.getBannerUrl()); bannerVO.setBannerAddress(moocBannerT.getBannerAddress()); datas.add(bannerVO); } return datas; } @Override public FilmVO getHotFilms(boolean isLimit, int nums) { // TODO Auto-generated method stub return null; } @Override public FilmVO getSoonFilms(boolean isLimit, int nums) { // TODO Auto-generated method stub return null; } @Override public List<FilmInfo> getboxRanking() { // TODO Auto-generated method stub return null; } @Override public List<FilmInfo> getexpectRanking() { // TODO Auto-generated method stub return null; } @Override public List<FilmInfo> getTop() { // TODO Auto-generated method stub return null; } }
接下来实现热映电影的业务代码
首页具有有热映的电影,默认8个电影,在电影菜单下也存在正在热映的电影
所有在热映的实现类中加入了一个来判断当前是否在首页的热映
整个业务层的代码如下
package com.stylefeng.guns.rest.modular.film.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.alibaba.dubbo.config.annotation.Service; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.baomidou.mybatisplus.plugins.Page; import com.stylefeng.guns.api.film.FillmServiceAPI; import com.stylefeng.guns.core.util.DateUtil; import com.stylefeng.guns.rest.common.persistence.dao.MoocBannerTMapper; import com.stylefeng.guns.rest.common.persistence.dao.MoocFilmTMapper; import com.stylefeng.guns.rest.common.persistence.model.MoocBannerT; import com.stylefeng.guns.rest.common.persistence.model.MoocFilmT; import com.stylefeng.guns.rest.modular.film.VO.BannerVO; import com.stylefeng.guns.rest.modular.film.VO.FilmInfo; import com.stylefeng.guns.rest.modular.film.VO.FilmVO; @Component @Service(interfaceClass=FillmServiceAPI.class) public class DefaultFilmServiceImpl implements FillmServiceAPI{ @Autowired private MoocBannerTMapper moocBannerTMapper; @Autowired private MoocFilmTMapper moocFilmMapper; @Override public List<BannerVO> getBanners() { // TODO Auto-generated method stub //查询出所有的banners List<MoocBannerT> selectList = moocBannerTMapper.selectList(null); List<BannerVO> datas = new ArrayList<>(); for( MoocBannerT moocBannerT:selectList){ BannerVO bannerVO = new BannerVO(); bannerVO.setBannerID(moocBannerT.getUuid()+""); bannerVO.setBannerUrl(moocBannerT.getBannerUrl()); bannerVO.setBannerAddress(moocBannerT.getBannerAddress()); datas.add(bannerVO); } return datas; } @Override public FilmVO getHotFilms(boolean isLimit, int nums) { // TODO Auto-generated method stub FilmVO filmVO = new FilmVO(); List<FilmInfo> filmInfos = new ArrayList<>(); //按照影片的限制条件进行查找,热映影片对于的film_state为1 EntityWrapper<MoocFilmT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("film_status", "1"); //判断当前是否是首页加载热映影片 if(isLimit){ //这里首页也要进行分页查询,这里使用mybatis-plus插件提供的分页查询 //1表示第一页,nums表示当前页要显示的数目 Page<MoocFilmT> page = new Page<>(1, nums); //接下来执行查询操作 List<MoocFilmT> moocFilmTs = moocFilmMapper.selectPage(page, entityWrapper); filmInfos = getFilmInfos(moocFilmTs); filmVO.setFilmNum(filmInfos.size()+""); filmVO.setFilminfo(filmInfos); } return filmVO; } List<FilmInfo> getFilmInfos(List<MoocFilmT> moocFilmTs){ List<FilmInfo> filmInfos = new ArrayList<>(); for(MoocFilmT data:moocFilmTs){ FilmInfo filmInfo = new FilmInfo(); filmInfo.setScore(data.getFilmScore()); filmInfo.setImgAddress(data.getImgAddress()); filmInfo.setFilmType(data.getFilmType()); filmInfo.setFilmScore(data.getFilmScore()); filmInfo.setFilmName(data.getFilmName()); filmInfo.setFilmId(data.getUuid()+""); filmInfo.setExpectNum(data.getFilmPresalenum()); filmInfo.setBoxNum(data.getFilmBoxOffice()); filmInfo.setShowTime(DateUtil.getDay( data.getFilmTime())); filmInfos.add(filmInfo); } return filmInfos; } @Override public FilmVO getSoonFilms(boolean isLimit, int nums) { // TODO Auto-generated method stub FilmVO filmVO = new FilmVO(); List<FilmInfo> filmInfos = new ArrayList<>(); //按照影片的限制条件进行查找,热映影片对于的film_state为1 EntityWrapper<MoocFilmT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("film_status", "2"); //判断当前是否是首页加载热映影片 if(isLimit){ //这里首页也要进行分页查询,这里使用mybatis-plus插件提供的分页查询 //1表示第一页,nums表示当前页要显示的数目 Page<MoocFilmT> page = new Page<>(1, nums); //接下来执行查询操作 List<MoocFilmT> moocFilmTs = moocFilmMapper.selectPage(page, entityWrapper); filmInfos = getFilmInfos(moocFilmTs); filmVO.setFilmNum(filmInfos.size()+""); filmVO.setFilminfo(filmInfos); } return filmVO; } @Override public List<FilmInfo> getboxRanking() { // TODO Auto-generated method stub //正在上映的票房前十名 EntityWrapper<MoocFilmT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("film_status", "1"); //封装一个Page分析对象进行分页查询,安装票房进行排序 //1表示第一页,10表示当前页显示10条记录 //film_box_office表示当前排序的字段 Page<MoocFilmT> page = new Page<>(1, 10, "film_box_office"); List<MoocFilmT> moocFilmTs = moocFilmMapper.selectPage(page,entityWrapper); List<FilmInfo> filmInfos = getFilmInfos(moocFilmTs); return filmInfos; } @Override public List<FilmInfo> getexpectRanking() { //即将上映的 ,提前售票的票房前十秒 EntityWrapper<MoocFilmT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("film_status", "2"); //封装一个Page分析对象进行分页查询,安装票房进行排序 //1表示第一页,10表示当前页显示10条记录 //film_box_office表示当前排序的字段 Page<MoocFilmT> page = new Page<>(1, 10, "film_preSaleNum"); List<MoocFilmT> moocFilmTs = moocFilmMapper.selectPage(page,entityWrapper); List<FilmInfo> filmInfos = getFilmInfos(moocFilmTs); return filmInfos; } @Override public List<FilmInfo> getTop() { //已经上映的,打分的前十名 EntityWrapper<MoocFilmT> entityWrapper = new EntityWrapper<>(); entityWrapper.eq("film_status", "1"); //封装一个Page分析对象进行分页查询,安装票房进行排序 //1表示第一页,10表示当前页显示10条记录 //film_box_office表示当前排序的字段 Page<MoocFilmT> page = new Page<>(1, 10, "film_score"); List<MoocFilmT> moocFilmTs = moocFilmMapper.selectPage(page,entityWrapper); List<FilmInfo> filmInfos = getFilmInfos(moocFilmTs); return filmInfos; } }
业务层需要注意几点:
1、业务层要使用@compent添加到spring的容器中
2、业务层代码要使用dubbo的@Service暴露服务,让guns-gateway网关可以来调用
3、具体的业务操作,例如即将上映的提前预售的前十名的电影信息
首先限定的条件是即将上映的,film_state的状态是2表示即将上映的,我们创建一个EntityWrapper对象,改类作为查询的限定条件
然后要分页查询,,我们使用mybatis-plus插件提供的分页查询Page类
current表示当前第几页,size表示当页显示的数目,oderByFileld表示按照那个字段进行排序,isAsc表示升序还是降序
guns-film的业务层代码实现完成之后,接下来要实现guns-gateway来调用业务层的代码,我们在guns-gateway中新建立一个controller来调用业务层的代码
package com.stylefeng.guns.rest.modular.film.VO; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.alibaba.dubbo.config.annotation.Reference; import com.stylefeng.guns.api.film.FillmServiceAPI; import com.stylefeng.guns.rest.modular.auth.VO.ReponseVO; @RestController @RequestMapping("/film/") public class FilmController { @Reference(interfaceClass=FillmServiceAPI.class) private FillmServiceAPI fillmServiceAPI; @RequestMapping(value="/getIndex",method=RequestMethod.GET) public ReponseVO<FilmIndexVO> getIndex(){ FilmIndexVO filmIndexVO = new FilmIndexVO(); //获得横幅的信息 filmIndexVO.setBannners(fillmServiceAPI.getBanners()); //获得票房的信息 filmIndexVO.setBoxRanking(fillmServiceAPI.getboxRanking()); filmIndexVO.setExpectRanking(fillmServiceAPI.getexpectRanking()); filmIndexVO.setHotFilms(fillmServiceAPI.getHotFilms(true, 8)); filmIndexVO.setSoonFils(fillmServiceAPI.getSoonFilms(true, 10)); filmIndexVO.setTop100(fillmServiceAPI.getTop()); return ReponseVO.success(filmIndexVO); } }
首页的需要展示横幅的内容,票房前十的内容,我们在网关的地方将全部首页要展示的数据通过一个getIndex就可以获得首先要展示的全部内容,不需要各个模块分别发起请求
接下来我们来进行测试通过guns-gateway来访问guns-film,来获得首页展示的内容,在测试之前要配置下guns-film的dubbo配置文件
application.properties
#WEB\u670D\u52A1\u7AEF\u53E3
server.port=8084
## dubbo \u914D\u7F6E
spring.dubbo.application.name=meeting-film
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.server=true
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20882
我们修改下应用启动的端口为8084,和远程消费者通信的协议是dubbo协议,通信的端口为20882,不能和之前的通信端口20881重复,这样就可以启动dubbo了,启动类要使用@EnableDubboConfiguration注解
package com.stylefeng.guns.rest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration; @SpringBootApplication(scanBasePackages = {"com.stylefeng.guns"}) @EnableDubboConfiguration public class GunsFilmApplication { public static void main(String[] args) { SpringApplication.run(GunsFilmApplication.class, args); } }
我们来测试下
演示的时候,先启动guns-film模块,在启动guns-gateway,因为之前guns-gateway可以远程访问guns-user,所有启动guns-gateway之前,也要先把guns-user先启动起来,否则会报错
在测试的时候,获得首页不需要进行jwt的AuthFilter的拦截,我们需要在application.yml中进行配置
jwt:
header: Authorization #http请求头所需要的字段
secret: mySecret #jwt秘钥
expiration: 604800 #7天 单位:秒
auth-path: auth #认证请求的路径
md5-key: randomKey #md5加密混淆key
ignore-url: /user/register,/user/check,/film/getIndex #对改类请求的url不进行jwt模块的校验
这样上面的业务接口我们就接口调通了,这样首页的接口我们已经完成了
接下来,我们来实现第二个接口2、 影片条件列表查询接口
F:\第9章 分布式事务\第9章 分布式事务
事务是用来保证一组数据操作的完整性和一致性,注意是保证数据一组操作的正确性和完整性的
例如购买一个东西,需要三个操作扣减库存,下订单,和给商户打钱,这三个动作要不同时成功,要不同时失败
事务的原子性:在物理上最小的物理可分割单元是原子性,原子是最小不可分割单元,事务所有的内部操作就是原子操作是一个完整的不可再分割的整体,不可能再进行分割了
事务的一致性:事务的操作要不是全部成功,要不就是全部失败
事务的隔离性:不同事务的操作是相互隔离的,例如你给小明打款,和晓东给小明打款是分开的
事务的持久性:事务一万完成就是持久性的,不可逆恢复的
分布式事务:例如现在有10个机器,每个机器上部署了不同的应用,在10个机器上部署了独立的数据库,例如扣减库存应用安装在一台机器上,下订单服务在一台机器上,支付服务在一台机器上,如果其中的
例如下订单服务失败了,其他两个服务如何进行处理了,这就是分布式事务
分布式事务一般由事务的参与者、资源服务器、事务管理器三个部分构成。上面的扣减库存服务、订单服务、支付服务就是事务的参与者,资源管理器一般指数据库底层对事务的支持指的就是数据库,
事务管理器指当一个服务失败了,如何通知其他两个服务,一般需要事务管理器来通知
现在使用最多的是基于TCC编程式的补偿性事务
两阶段式事务
事务管理器会告诉事务的参与者,现在可以开始事务了,发出预备的指令通知事务参赛者
如果事务参与者已经准备就绪,就会想事务管理器提交预备的指令
事务管理者必须等待所有的就绪指令都收到之后,然后向事务参与者发出提交指令
事务参与者开始做提交的动作,然后向事务管理器发送已提交的质量,如果2个事务参与者,事务管理器只收到一个已提交指令,说明发生了异常
三段式事务在两段式基础上引入了一个预状态。但是两段式和三段式事务在生产上都很少使用,我们通过一个XA的案例来看下两段式事务
基于消息的一致性方案的使用场景
我们假定支付系统是A系统,下单之后会存在一个支付的操作,当你扫描支付操作完成之后,钱就会到支付宝
基于TCC的事务补偿性方案
接下来我们来搭建TCC-transaction的环境下载对于的1.2版本
我们把整个框架导入到eclipse中
我们选择导入存在的maven工程
tcc的核心模块主要是上面的几个部分,第一个是api的包,第二个是tcc的核心模块,第三个是tcc对dubbo的支持,server是tcc提供的对事务的监控,spring的tcc对事务的支持
在运行tcc之前我们先初始化我们的数据库脚本
在tcc-transaction-tutorial-sample中给我们提供了集成tcc的一些案例,接下来我们要初始化我们的数据库
这里给我们提供了初始化数据库的脚本
create_db_cap.sql
CREATE DATABASE `TCC_CAP` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_CAP; CREATE TABLE `CAP_CAPITAL_ACCOUNT` ( `CAPITAL_ACCOUNT_ID` int(11) NOT NULL AUTO_INCREMENT, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`CAPITAL_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; CREATE TABLE `CAP_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` bigint(11) DEFAULT NULL, `OPPOSITE_USER_ID` bigint(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `UX_MERCHANT_ORDER_NO` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (1,10000,1000); INSERT INTO `CAP_CAPITAL_ACCOUNT`(CAPITAL_ACCOUNT_ID, BALANCE_AMOUNT, USER_ID) VALUE (2,10000,2000);
create_db_ord.sql
CREATE DATABASE `TCC_ORD` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_ORD; CREATE TABLE `ORD_ORDER` ( `ORDER_ID` int(11) NOT NULL AUTO_INCREMENT, `PAYER_USER_ID` int(11) DEFAULT NULL, `PAYEE_USER_ID` int(11) DEFAULT NULL, `RED_PACKET_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `CAPITAL_PAY_AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ORDER_ID`), UNIQUE KEY `MERCHANT_ORDER_NO_UNIQUE` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1188 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_ORDER_LINE` ( `ORDER_LINE_ID` int(11) NOT NULL AUTO_INCREMENT, `PRODUCT_ID` int(11) DEFAULT NULL, `QUANTITY` decimal(10,0) DEFAULT NULL, `UNIT_PRICE` decimal(10,0) DEFAULT NULL, PRIMARY KEY (`ORDER_LINE_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_SHOP` ( `SHOP_ID` int(11) NOT NULL, `OWNER_USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`SHOP_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `ORD_PRODUCT`( `PRODUCT_ID` int(11) NOT NULL, `SHOP_ID` int(11) NOT NULL, `PRODUCT_NAME` VARCHAR(64) DEFAULT NULL , `PRICE` DECIMAL(10,0) DEFAULT NULL, PRIMARY KEY (`PRODUCT_ID`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `ORD_SHOP` (`SHOP_ID`,`OWNER_USER_ID`) VALUES (1,1000); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (1,1,'IPhone6S',5288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (2,1,'MAC Pro',10288); INSERT INTO `ORD_PRODUCT` (`PRODUCT_ID`,`SHOP_ID`,`PRODUCT_NAME`,`PRICE`) VALUES (3,1,'IWatch',2288);
create_db_red.sql
CREATE DATABASE `TCC_RED` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC_RED; CREATE TABLE `RED_RED_PACKET_ACCOUNT` ( `RED_PACKET_ACCOUNT_ID` int(11) NOT NULL, `BALANCE_AMOUNT` decimal(10,0) DEFAULT NULL, `USER_ID` int(11) DEFAULT NULL, PRIMARY KEY (`RED_PACKET_ACCOUNT_ID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `RED_TRADE_ORDER` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `SELF_USER_ID` bigint(11) DEFAULT NULL, `OPPOSITE_USER_ID` bigint(11) DEFAULT NULL, `MERCHANT_ORDER_NO` varchar(45) NOT NULL, `AMOUNT` decimal(10,0) DEFAULT NULL, `STATUS` varchar(45) DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`ID`), UNIQUE KEY `MERCHANT_ORDER_NO_UNIQUE` (`MERCHANT_ORDER_NO`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (1,950,1000); INSERT INTO `RED_RED_PACKET_ACCOUNT` (`RED_PACKET_ACCOUNT_ID`,`BALANCE_AMOUNT`,`USER_ID`) VALUES (2,500,2000);
create_db_tcc.sql
CREATE DATABASE `TCC` /*!40100 DEFAULT CHARACTER SET utf8 */; use TCC; CREATE TABLE `TCC_TRANSACTION_CAP` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_CAP` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_ORD` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_ORD` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_RED` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_RED` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL; CREATE TABLE `TCC_TRANSACTION_UT` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ALTER TABLE `TCC_TRANSACTION_UT` ADD `IS_DELETE` tinyint(1) DEFAULT 0 NOT NULL;
这几个库的作用我们后面会详细进行讲解
接下来我们启动应用,来做我们的数据库的测试,
posted on 2019-07-22 17:56 luzhouxiaoshuai 阅读(1690) 评论(0) 编辑 收藏 举报