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