Tensquare笔记
Docker
- docker run ‐di ‐‐name=tensquare_mysql ‐p 3306:3306 ‐e
- docker run ‐di ‐‐name=tensquare_redis ‐p 6379:6379 redis
- docker run ‐di ‐‐name=tensquare_mongo ‐p 27017:27017 mongo
- docker run ‐di ‐‐name=tensquare_elasticsearch ‐p 9200:9200 ‐p 9300:9300 elasticsearch:5.6.8
- docker run ‐di ‐‐name=tensquare_rabbitmq ‐p 5671:5617 ‐p 5672:5672 ‐p 4369:4369 ‐p 15671:15671 ‐p 15672:15672 ‐p 25672:25672 rabbitmq:management
SpringBoot
tensquare_parent
- 删除父工程不需要的资源
- 修改pom.xml,配置依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tensquare</groupId>
<artifactId>tensquare_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<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>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
- 依赖导入
搭建公共子模块
- 公共子模块 tensquare_common
- 右键点击工程,弹出菜单选择 New -Module 弹出窗口选择Maven - next - finish
- 新建entity包,包下创建类Result,用于控制器类返回结果
- 创建类PageResult ,用于返回分页结果
- 状态码实体类
- 分布式ID生成器
- 右键点击工程,弹出菜单选择 New -Module 弹出窗口选择Maven - next - finish
由于我们的数据库在生产环境中要分片部署(MyCat),所以我们不能使用数据库本身的自增功能来产生主键值,只能由程序来生成唯一的主键值。我们采用的是开源的 twitter( 非官方中文惯称:推特.是国外的一个网站,是一个社交网络及微博客服务) 的 snowflake (雪花)算法。
默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以 支持1024台机器,序列号支持1毫秒产生4096个自增序列id . SnowFlake的优点是,整 体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID 作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右
tensquare_common工程创建util包,将IdWorker.java直接拷贝到tensquare_common工程的util包中。
基础微服务-标签CRUD
- 搭建基础微服务模块tensquare_base
- pom.xml引入依赖
<dependencies>
<!-- 导入spring data jpa的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 导入common依赖 -->
<dependency>
<groupId>com.tensquare</groupId>
<artifactId>tensquare_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
- 创建启动类
com.tensquare.recruit.BaseApplication.java
/**
* 启动类
*/
@SpringBootApplication
public class BaseApplication {
public static void main(String[] args) {
SpringApplication.run(BaseApplication.class);
}
@Bean
public IdWorker idWorker() {
return new IdWorker(1, 1);
}
}
- 在resources下创建application.yml
server:
port:9001
spring:
application:
name: tensquare-base #指定服务名
datasource:
driveClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.184.134:3306/tensquare_base? characterEncoding=utf‐8
username: root
password: 123456
jpa:
database: MySQL
show-sql: true
generate-ddl: true
-
CRUD实现
-
在com.tensquare.base包下
- 创建pojo包 ,包下创建Label实体类
- 创建dao包,包下创建LabelDao接口
JpaRepository提供了基本的增删改查
JpaSpecificationExecutor用于做复杂的条件查询
-
tb_label表接口分析
-
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
id | ID | 文本 | |
labelname | 标签名称 | 文本 | |
state | 状态 | 文本 | 0:无效 1:有效 |
count | 使用数量 | 整型 | |
fans | 关注数 | 整型 | |
recommend | 是否推荐 | 文本 | 0:不推荐 1:推荐 |
- 业务逻辑类
- 创建service包,包下创建LabelService类,实现基本的增删改查功能
- 装配LabelDao
- 装配IdWorker
- 实现crud
- 创建service包,包下创建LabelService类,实现基本的增删改查功能
- 控制器类
- 创建controller包,创建UserController
- 设置路由映射
- 装配LabelService
- 映射GET|POST接口
- 创建controller包,创建UserController
- 接口测试
1. GET方法 查询全部数据
http://localhost:9090/label
2. GET方法 根据ID查询标签
http://localhost:9090/label/1
3. POST方法 增加数据
request
{
"labelname":"前端",
"state":"1",
"count":10,
"fans":4
}
response
{
"flag":true,
"code":20000,
"message":"增加成功",
"data":null
}
4. PUT方法 修改数据
request
{
"labelname":"JAVAEE",
"state":"1",
"count":10,
"fans":4
}
response
{
"flag":true,
"code":20000,
"message":"修改成功",
"data":null
}
5. DELETE方法 删除数据
http://localhost:9001/label/1002447157418135552
response
{
"flag":true,
"code":20000,
"message":"删除成功",
"data":null
}
- 异常处理
在com.tensquare.base.controller包下创建公共异常处理类BaseExceptionHandler.java
/**
* 统一处理异常
*/
@ControllerAdvice
public class BaseExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return new Result(false, StatusCode.ERROR,e.getMessage());
}
}
-
跨域处理
- 跨域是什么?浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、 协议任一不同,都是跨域 。我们是采用前后端分离开发的,也是前后端分离部署的,必 然会存在跨域问题。 怎么解决跨域?很简单,只需要在controller类上添加注解 @CrossOrigin 即可!这个注解其实是CORS的实现。
- CORS(Cross-Origin Resource Sharing, 跨源资源共享)是W3C出的一个标准,其思 想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成 功,还是应该失败。因此,要想实现CORS进行跨域,需要服务器进行一些设置,同时前 端也需要做一些配置和分析。
-
标签-条件查询
根据条件查询城市列表 POST /label/search
- Specification条件查询,修改LabelService.java,增加方法
/**
* 构建查询条件
*
* @param searchMap
* @return
*/
private Specification<Label> createSpecification(Map searchMap) {
return new Specification<Label>() {
@Override
public Predicate toPredicate(Root<Label> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicateList = new ArrayList<>();
if (!StringUtils.isEmpty(searchMap.get("labelname"))) {
Predicate labelname = criteriaBuilder.like(
root.get("labelname").as(String.class),
"%" + searchMap.get("labelname") + "%"
);
predicateList.add(labelname);
}
if (!StringUtils.isEmpty(searchMap.get("state"))) {
Predicate state = criteriaBuilder.equal(
root.get("state").as(String.class),
searchMap.get("state")
);
predicateList.add(state);
}
if (!StringUtils.isEmpty(searchMap.get("recommend"))) {
Predicate recommend = criteriaBuilder.equal(
root.get("recommend").as(String.class),
searchMap.get("recommend")
);
predicateList.add(recommend);
}
Predicate[] array = new Predicate[predicateList.size()];
array = predicateList.toArray(array);
return criteriaBuilder.and(array);
}
};
}
/**
* 条件查询
* 使用构建的 createSpecification
*
* @param searchMap
* @return
*/
public List<Label> findSearch(Map searchMap) {
Specification<Label> specification = createSpecification(searchMap);
return labelDao.findAll(specification);
}
- 修改LabelController,增加方法
/**
* 根据条件查询
*
* @param searchMap
* @return
*/
@RequestMapping(value = "/search", method = RequestMethod.POST)
public Result<List> findSearch(@RequestBody Map searchMap) {
return new Result<>(true, StatusCode.OK, "查询成功", labelService.findSearch(searchMap));
}
- 带分页的条件查询
- 修改LabelService,增加方法
/**
* 分页条件查询
*
* @param searchMap
* @param page
* @param size
* @return
*/
public Page<Label> findSearch(Map searchMap, int page, int size) {
Specification<Label> specification = createSpecification(searchMap);
PageRequest pageRequest = PageRequest.of(page - 1, size);
return labelDao.findAll(specification, pageRequest);
}
- 修改LabelController,增加方法
/**
* 根据条件查询
*
* @param searchMap
* @return
*/
@RequestMapping(value = "/search", method = RequestMethod.POST)
public Result<List> findSearch(@RequestBody Map searchMap) {
return new Result<>(true, StatusCode.OK, "查询成功", labelService.findSearch(searchMap));
}
@RequestMapping(value = "/search/{page}/{size}", method = RequestMethod.POST)
public Result<List> findSearch(@RequestBody Map searchMap, @PathVariable int page, @PathVariable int size) {
Page<Label> searchPage = labelService.findSearch(searchMap, page, size);
return new Result(true, StatusCode.OK, "查询成功",
new PageResult<>(searchPage.getTotalElements(), searchPage.getContent()));
}
招聘微服务开发
企业信息
- 企业信息tb_enterprise表结构分析
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
id | ID | 文本 | |
name | 企业名称 | 文本 | |
summary | 企业简介 | 文本 | |
address | 企业地址 | 文本 | |
labels | 标签列表 | 文本 | 用逗号分隔 |
coordinate | 企业位置座标 | 文本 | 经度,纬度 |
ishot | 是否热门 | 文本 | 0:非热门 1:热门 |
logo | LOGO | 文本 | |
jobcount | 职位数 | 数字 | |
url | URL | 文本 |
招聘信息
- 招聘信息tb_recruit表结构分析
字段名称 | 字段含义 | 字段类型 | 备注 |
---|---|---|---|
id | ID | 文本 | |
jobname | 招聘职位 | 文本 | |
salary | 薪资范围 | 文本 | |
condition | 经验要求 | 文本 | |
education | 学历要求 | 文本 | |
type | 任职方式 | 文本 | |
address | 办公地址 | 文本 | |
eid | 企业ID | 文本 | |
createtime | 发布日期 | 日期 | |
state | 状态 | 文本 | 0:关闭 1:开启 2:推荐 |
url | 原网址 | 文本 | |
label | 标签 | 文本 | |
content1 | 职位描述 | 文本 | |
content2 | 职位要求 | 文本 |
- 创建 tensquare_recruit 模块,配置pom.xml,参考tensquare_base模块
- 创建 RecruitApplication.java
- 配置 application.yml
- 创建 pojo
- Enterprise.java
- Recruit.java
- 创建 dao
- EnterpriseDao.java
- RecruitDao.java
- 创建 service
- EnterpriseService.java
- RecruitService.java
- 创建controller
- BaseExceptionHandler.java
- EnterpriseController.java
- RecruitController.java
问答微服务开发
- 相关表
- tb_problem表
- tb_reply表
- 创建 tensquare_qa 微服务模块,配置pom.xml
- 创建 QaApplication.java
- 配置 application.yml
- 创建 pojo
- Problem.java
- Reply.java
- 创建 dao
- ProblemDao.java
- ReplyDao.java
- 创建 service
- ProblemService.java
- ReplyService.java
- 创建 controller
- BaseExceptionHandler.java
- ProblemController.java
- ReplyController.java
文章微服务开发
- 相关表
- tb_article表
- 创建 tensquare_article 微服务模块,配置pom.xml
- 创建 ArticleApplication.java
- 配置 application.yml
- 创建 pojo
- Article.java
- Channel.java
- Column.java
- 创建 dao
- ArticleDao.java
- ChannelDao.java
- ColumnDao.java
- 创建 service
- ArticleService.java
- ChannelService.java
- ColumnService.java
- 创建 controller
- BaseExceptionHandler.java
- ArticleController.java
- ChannelController.java
- ColumnController.java
活动详情微服务开发
-
缓存实现
- Spring Cache使用方法与Spring对事务管理的配置相似。
- Spring Cache的核心就是对某 个方法进行缓存,其实质就是缓存该方法的返回结果,并把方法参数和结果用键值对的 方式存放到缓存中,当再次调用该方法使用相应的参数时,就会直接从缓存里面取出指 定的结果进行返回。
- 常用注解:
- @Cacheable-------使用这个注解的方法在执行后会缓存其返回结果。
- @CacheEvict--------使用这个注解的方法在其执行前或执行后移除Spring Cache中的某些
元素。
-
在SpringBoot中使用SpringCache可以由自动配置功能来完成CacheManager的注册,SpringBoot会自动发现项目中拥有的缓存系统,并注册对应的缓存管理器。当然我们也可以手动指定。
-
创建 tensquare_gathering 微服务模块,配置pom.xml
- 创建 GatheringApplication
- 为GatheringApplication添加@EnableCaching开启缓存支持
- 配置 application
- 创建 GatheringApplication
-
创建 pojo
- Gathering
-
创建 dao
- GatheringDao
-
创建 service
- GatheringService
-
创建 controller
- BaseExceptionHandler
- GatheringController
吐槽微服务开发
- 创建 tensquare_spit 微服务模块,配置pom.xml
- 采用SpringDataMongoDB框架实现吐槽微服务的持久层
文章评论功能开发
- 评论表 comment
- 修改tensquare_article工程的pom.xml
搜索微服务开发
- 分布式搜索引擎ElasticSearch
- Elasticsearch是一个实时的分布式搜索和分析引擎。
- ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分 布式多用户能力的全文搜索引擎,基于RESTful web接口。
- Elasticsearch是用Java开发 的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
- 设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
- ElasticSearch - Head插件的安装与使用
Head插件图形化界面来实现Elasticsearch的日常管理 - ElasticSearch - IK分词器
- ElasticSearch - IK分词器 - 自定义词库
- 创建模块tensquare_search ,pom.xml引入依赖
- 创建com.tensquare.search.dao包,包下建立接口
/**
* 文章数据访问层接口
*/
public interface ArticleSearchDao extends
ElasticsearchRepository<Article,String> {}
- 同步elasticsearch与MySQL数据 - Logstash
- Logstash是一款轻量级的日志搜集处理框架,可以方便的把分散的、多样化的日志搜集
起来,并进行自定义的处理,然后传输到指定的位置,比如某个服务器或者文件。
- Logstash是一款轻量级的日志搜集处理框架,可以方便的把分散的、多样化的日志搜集
用户微服务-用户注册
-
创建tensquare_user模块,配置pom.xml
-
消息中间件RabbitMQ
- 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量 削锋等问题实现高性能,高可用,可伸缩和最终一致性[架构]
- 使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
- RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
- AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放 标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不 受产品、开发语言等条件的限制。
- RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展 性、高可用性等方面表现。
- 可靠性(Reliability)
- 灵活的路由(Flexible Routing)
- 消息集群(Clustering)
- 高可用(Highly Available Queues)
- 多种协议(Multi-protocol)
- 多语言客户端(Many Clients)
- 管理界面(Management UI)
- 跟踪机制(Tracing)
- 插件机制(Plugin System)
- 主要概念
- RabbitMQ Server: 也叫broker server,它是一种传输服务
- Producer: 消息生产者,如图A、B、C,数据的发送方。
- Consumer:消息消费者,如图1、2、3,数据的接收方。
- Exchange:生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个 或多个Queue中(或者丢弃)。
- Queue:(队列)是RabbitMQ的内部对象,用于存储消息。
- RoutingKey:生产者在将消息发送给Exchange的时候,一般会指定一个routing key, 来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联 合使用才能最终生效。
- Connection: (连接):Producer和Consumer都是通过TCP连接到RabbitMQ Server 的。
- Channels: (信道):它建立在上述的TCP连接中。数据流动都是在Channel中进行 的。
- VirtualHost:权限控制的基本单位,一个VirtualHost里面有若干Exchange和 MessageQueue,以及指定被哪些user使用
-
发送短信验证码
- 在用户微服务编写API ,生成手机验证码,存入Redis并发送到RabbitMQ
短信微服务
- 创建tensquare_sms模块,配置pom.xml
- 消息监听类
- 短信工具类SmsUtil
密码加密与微服务鉴权JWT
- BCrypt密码加密,BCrypt强哈希方法,每次加密的结果都不一样。
- 添加了spring security依赖后,所有的地址都被spring security所控制了,我们目前只是需要用到BCrypt密码加密的部分,所以我们要添加一个配置类,配置为所有地址 都可以匿名访问。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 所有security全注解配置实现的开端,表示开始说明需要的权限,
// 需要的权限分两部分,
// 第一部分是拦截的路径,
// 第二部分访问该路径需要的权限
http.authorizeRequests()
// antMatchers表示拦截什么路径,
.antMatchers("/**")
//permitAll表示任何权限都可以访问
.permitAll()
// anyRequest任何请求,
.anyRequest()
//authenticated认证后才能访问
.authenticated()
// .and().csrf().disable();固定写法,使得csrf拦截失效
.and().csrf().disable();
}
}
- 管理员密码加密
- 用户密码加密
常见的认证机制
-
HTTP Basic Auth 暴露用户信息,避免使用
-
Cookie Auth 通过客户端带上来Cookie对象来与服务器端的 session对象匹配来实现状态管理的
-
OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提 供者的数据。
-
Token Auth
-
客户端使用用户名跟密码请求登录
-
服务端收到请求,去验证用户名与密码
-
验证成功后,服务端会签发一个Token,再把这个Token发送给客户端
-
客户端收到Token以后可以把它存储起来,比如放在Cookie里
-
客户端每次向服务端请求资源的时候需要带着服务端签发的Token
-
服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据
-
Token机制相对于Cookie机制又有什么好处呢?
- 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提 是传输的用户认证信息通过HTTP头传输.
- 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为 Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储 状态信息.
- 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript, HTML,图片等),而你的服务端只要提供API即可.
- 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在 你的API被调用的时候,你可以进行Token生成调用即可.
- 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等) 时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认 证机制就会简单得多。 CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防 范。
- 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256 计算 的Token验证和解析要费时得多.
- 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要 为登录页面做特殊处理.
- 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在 多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如: Firebase,Google, Microsoft).
-
基于JWT的Token认证机制实现
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用 户和服务器之间传递安全可靠的信息。由三部分组成,头部、载荷与签名
-
签证(signature)
- header (base64后的)
- payload (base64后的)
- secret
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用 来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流 露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
-
约定
- 前后端约定:前端请求微服务时需要添加头信息Authorization ,内容为Bearer+空格 +token
-
使用拦截器方式实现token鉴权
分别实现预处理,在preHandle中,可以进行编码、安全控制等处理;
后处理(调用了Service并返回ModelAndView,但未进行页面渲染),在postHandle中,有机会修改ModelAndView;
返回处理(已经渲染了页面),在afterCompletion中,可以根据ex是否为null判断是否发生了异常,进行日志记录。- 配置拦截器类,创建com.tensquare.user.ApplicationConfig
- 用户登陆签发 JWT