第二章 客戶管理
1.認證模塊
1.1 需求分析
1.基礎概念
一般情況有用戶交互的項目都有認證授權功能,首先我們要搞清楚兩個概念:認證和授權
認證: 就是校驗用戶的身份是否合法,常見的認證方式有賬號密碼登錄、手機驗證碼登錄等
授權:則是該用戶登錄系統成功后當用戶去點擊菜單或操作數據時系統判斷該用戶是否有權限,有權限則允許繼續操作,沒有權限則拒絕訪問
2.小程序認證
本項目包括四個端:用戶端(小程序)、服務端(app)、機構端(PC)、運營管理端(PC)
分別對應四類用戶角色:家政需求方即c端用戶,家政服務人員、家政服務公司(機構)、平臺運營人員
用戶端通過小程序使用平臺,初次使用小程序會進行認證,如下圖:
點擊“快速登錄”彈出服務條款窗口
點擊“同意”進行認證,系統與微信進行交互獲取用戶在小程序中的唯一標識openid
初次認證通過會自動注冊用戶信息到本平臺
下邊是小程序的認證流程:
3.手機驗證碼認證
服務人員通過app登錄采用手機驗證碼認證方式,輸入手機號、發送驗證碼,驗證碼校驗通過則認證通過,初次認證通過將自動注冊服務人員信息
手機驗證碼認證流程如下:
4.賬號密碼認證
機構端認證方式是賬號密碼認證方式,通過pc瀏覽器進入登錄界面輸入賬號和密碼登錄系統
機構端提供單獨的注冊頁面,輸入手機號,接收驗證碼進行注冊
管理端的認證方式也是賬號密碼方式,管理端的賬號由管理員在后臺錄入,不提供注冊頁面
1.2 小程序認證
1.2.1 測試小程序認證
1.參考官方流程
小程序認證流程需要三部分:
小程序:即前端程序
開發者服務器:后端微服務程序
微信接口服務:即微信服務器
1.前端調用wx.login()獲取登錄憑證code
2.前端請求后端進行認證,發送code
3.后端請求微信獲取openid,發送appid、app密鑰、code參數,微信返回openid
4.后端生成認證成功憑證返回給前端。
5.前端存儲用戶認證成功憑證
2.申請小程序賬號
開發小程序首先要申請小程序賬號,點擊注冊小程序(https://mp.weixin.qq.com/wxopen/waregister?action=step1)填寫信息
進入郵箱激活小程序,進入下一步信息登記,選擇“個人”
進入小程序管理界面
填寫小程序信息,點擊上圖中小程序信息欄目的“去填寫按鈕”,小程序名稱一定謹慎填寫,每年是有一定修改次數限制的
注意:小程序名稱不能重復
下邊配置最關鍵的appid和密鑰,通過左側菜單找到開發管理菜單,點擊“開發管理”菜單進入下圖界面,點擊“開發設置”
使用自己的微信掃描二維碼生成密鑰成功,點擊“復制”將密鑰和AppID妥善保存,開發小程序要使用
AppID(小程序ID):wxec21e59c983b806f
AppSecret(小程序密鑰) :758cd204a5e49a9c5df95e1b48ca4d70
3.創建jzo2o-customer
小程序賬號申請成功,下邊部署配置后端程序
客戶管理工程jzo2o-customer提供了小程序認證接口支持,jzo2o-customer通過jzo2o-publics請求微信獲取openid
下邊創建jzo2o-customer工程,創建過程參考jzo2o-foundations工程,工程創建完成修改bootstrap-dev.yml配置文件:
接下來進入nacos修改jzo2o-publics.yaml中小程序的appid和密鑰
小程序認證需要啟動的微服務包括:網關jzo2o-gateway、客戶管理jzo2o-customer、公共服務jzo2o-publics,將其它服務也正常啟動,啟動這三個微服務成功,下邊開始部署前端
4.部署前端
用戶端是基于微信小程序開發的,首先需要下載并安裝微信開發者工具
配置小程序開發環境
打開微信開發者工具,初次使用彈出身份確認,如下圖,使用申請小程序賬號時用的微信進行掃碼,掃碼通過進入下邊的界面:
進入添加小程序項目界面
目錄:選擇小程序前端工程的 project-xzb-xcx-uniapp-java\unpackage\dist\dev\mp-weixin目錄
AppID:填寫申請小程序號獲取的AppID
選擇不使用云服務
修改project-xzb-xcx-uniapp-java\unpackage\dist\dev\mp-weixin\utils\env.js 配置文件,指定后端網關的地址
設置代理
如果要選擇其它目錄可以關閉當前項目,重新選擇
5.編譯運行
小程序認證需要啟動的微服務包括:網關jzo2o-gateway、客戶管理jzo2o-customer、公共服務jzo2o-publics,保證這三個服務全部啟動
注意:保證jzo2o-publics服務配置高德地圖key(參考:高德地圖web服務配置文檔)、微信的appid和app密鑰。配置完成將jzo2o-publics服務重新啟動。
首先清除緩存
然后編譯運行
編譯運行到登錄界面
點擊“快速登錄”按照前邊講的小程序認證流程進行操作,請求認證接口進行認證,進入調試器–>Network觀察請求記錄
認證接口的地址是:/customer/open/login/common/user
此接口最終從微信拿到用戶的openid(微信給用戶分配的唯一標識),并將openid存儲到數據庫,認證通過生成token令牌返回給前端
認證通過進入下邊的界面
7.小結
首先申請小程序賬號,然后配置后端工程(網關jzo2o-gateway、客戶管理jzo2o-customer、公共服務jzo2o-publics)、保證啟動成功
再部署前端工程,這里需要安裝微信小程序開發工具等,最后編譯運行前端工程測試認證流程,測試過程可進入調試界面跟蹤前后端的交互數據
1.2.2 閱讀代碼
1.小程序認證流程
整個過程包括三部分:
小程序:即前端程序
開發者服務器:后端微服務程序
微信接口服務:即微信服務器
具體的流程如下:
1.前端調用wx.login()獲取登錄憑證code
2.前端請求后端進行認證,發送code
3.后端請求微信獲取openid
4.后端生成認證成功憑證返回給前端
根據官方的認證流程我們定義本項目小程序認證的交互流程:
customer工程提供認證接口,publics工程作為一個公共服務提供與微信通信的接口
前端與cutomer交互不與publics交互
token包含用戶信息
2.閱讀代碼
下邊根據認證流程閱讀代碼,我們以斷點調試的方式跟蹤接口交互過程
customer提供的小程序認證接口
前端點擊快速登錄,授權獲取手機號,請求jzo2o-customer的普通用戶登錄接口LoginController,普通用戶登錄接口如下:
customer請求publics申請獲取openid
和微信相關的都在publics,publics服務獲取openid接口InnerWechatController如下:
publics請求weixin 獲取openid
customer收到openid查詢數據庫獲取用戶信息并生成token.
openid是微信用戶在家政o2o平臺的唯一標識,首先根據openid查詢jzo2o-customer的common_user表,是否存在用戶,如果不存在則自動注冊用戶信息,用戶信息token存儲到jzo2o-customer數據庫的common_user表中
common_user表的結構如下:
create table `jzo2o-customer`.common_user
(id bigint not null comment '用戶id' constraint `PRIMARY` primary key,status int default 0 not null comment '狀態,0:正常,1:凍結',nickname varchar(255) null comment '昵稱',phone varchar(25) null comment '電話',avatar varchar(255) null comment '頭像',open_id varchar(100) null,account_lock_reason varchar(255) null comment '賬號凍結原因',create_time datetime default CURRENT_TIMESTAMP not null comment '創建時間',update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新時間',is_deleted int default 0 not null
) charset = utf8mb4;
認證通過生成用戶token返回給前端
token令牌的格式我們使用的是JWT格式,JWT是一種常用的令牌格式,它可以防篡改
在JWT令牌中存儲了當前登錄用戶的信息(json),包括如下屬性:
用戶id: id,對應common_user表的主鍵
用戶名稱:String name,對應common_user表的nickname字段
用戶頭像:String avatar,對應common_user表的avatar字段
用戶類型:Integer userType,c端用戶的用戶類型代碼為1,具體定義在common.constants.UserType中
網關對token統一校驗
在網關對token進行解析校驗,token不合法直接返回失敗信息,token合法解析出用戶信息放在http的head中繼續請求微服務
在微服務中解析http頭信息中的用戶信息,寫入ThreadLocal方便應用程序使用
1.3 手機驗證碼認證
1.3.1 測試手機驗證碼認證
服務人員使用APP登錄平臺使用的是手機驗證碼認證方式,整個認證流程也需要部署前端、后端
客戶管理工程jzo2o-customer與公共服務jzo2o-publics提供手機驗證碼的接口,這兩個服務在小程序認證時已經部署這里不再部署,我們只需要部署前端工程即可
1.部署前端
服務端的前端工程需要使用 HBuilder 3.8.7 X 軟件編譯運行,啟動HBuilderX,打開project-xzb-app-uniapp-java目錄
配置網關地址,配置完成,使用HBuilderX運行到瀏覽器
運行成功進入登錄頁面:
2.認證測試
首先輸入手機號,服務人員的信息存儲在jzo2o-customer數據庫的serve_provider表中,從表中找一個手機號錄入
點擊發送驗證碼,此時前端請求后端發送驗證碼,在開發環境我們從控制臺獲取驗證碼,稍后后帶大家分析發送驗證碼的程序
注意此時因為請求后端發送驗證碼我們觀察在瀏覽器的Network窗口有一條記錄,如下圖,該請求必須響應狀態為200方可正常發送驗證
從控制臺獲取剛才發送的驗證碼
將從控制臺獲取的驗證碼填入驗證碼輸入框
點擊登錄進行認證,認證過程會先校驗驗證碼是否正確,如果驗證碼正確再根據手機號查詢serve_provider表是否存在相應記錄且用戶未被凍結,全部成功則認證通過。
認證通過進入首頁。
1.3.2 閱讀代碼
1.手機驗證碼認證流程
customer工程提供認證接口,publics工程作為一個公共服務提供與發送驗證碼接口
2.閱讀代碼
1.找到具體的接口
前端請求publics服務發送驗證碼接口:publics/sms-code/send
具體發送驗證碼邏輯:
前端請求customer服務的認證接口:/customer/open/login/worker,代碼如下:
機構和和服務人員認證接口是同一個,根據類型判斷是機構還是服務人員,下圖中紅色箭頭指向的代碼是服務人員認證的方法
customer服務請求publics服務校驗驗證碼
publics服務提供校驗驗證碼接口,如下圖:
2.具體校驗驗證碼邏輯
具體的驗證碼校驗邏輯是先查詢redis中的正確的驗證碼,再和用戶輸入的進行對比,如果不一致則說明輸入錯誤,輸入正確刪除驗證碼
在使用redisTemplate時需要在工程中引入下邊的依賴:
<dependency><groupId>com.jzo2o</groupId><artifactId>jzo2o-redis</artifactId>
</dependency>
在jzo2o-redis中定義了redisTemplate的定義,如下圖:
在使用時注入上圖中定義的redisTemplate即可
在測試驗證碼發送時可以打開redis進行跟蹤,下圖顯示了存入redis中的驗證碼,注意觀察key和value:
3.自動注冊
校驗驗證碼完成customer服務根據手機號查詢數據庫,如果用戶凍結則認證失敗,如果用戶不存在則自動注冊
服務人員和機構都存儲到serve_provider表,結果如下:
create table `jzo2o-customer`.serve_provider
(id bigint not null comment '主鍵' constraint `PRIMARY` primary key,code varchar(255) null comment '編號',type int not null comment '類型,2:服務人員,3:服務機構',name varchar(255) null comment '姓名',phone varchar(255) not null comment '電話',avatar varchar(255) null comment '頭像',status int not null comment '狀態,0:正常,1:凍結',settings_status int default 0 null comment '首次設置狀態,0:未完成設置,1:已完成設置',password varchar(255) null comment '機構登錄密碼',account_lock_reason varchar(255) null comment '賬號凍結原因',score double null comment '綜合評分',good_level_rate varchar(50) null comment '好評率',create_time datetime default CURRENT_TIMESTAMP not null comment '創建時間',update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新時間',is_deleted int default 0 not null comment '是否已刪除,0:未刪除,1:已刪除',constraint serve_provider_phone_type_uindex unique (phone, type)
) comment '服務人員/機構表' charset = utf8mb4;
最后生成token返回給前端
注意:為了方便驗證碼測試前端會自動填入123456,更改驗證碼發送接口固定生成的驗證碼為123456
1.4 完成機構注冊功能開發
界面原型:進入登錄頁面,點擊“去注冊”進入注冊頁面
接口定義如下:
接口地址:POST/customer/open/serve-provider/institution/register
注意:機構端注冊和服務端注冊完成要向serve_provider表寫入數據,具體查閱上圖的方法。
密碼加密方式:使用BCrypt方式,BCrypt是一種密碼哈希函數,通常用于存儲用戶密碼的安全性。它是基于 Blowfish 密碼算法的一種單向哈希函數
測試方法:
public static void main(String[] args) {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();/**$2a$10$1sp7I0OdYH3Azs/2lK8YYeuiaGZzOGshGT9j.IYArZftsGNsXqlma$2a$10$m983E2nmJ7ITlesbXzjbzO/M7HL2wP8EgpgX.pPACDm1wG38Lt.na$2a$10$rZvathrW98vVPenLhOnl0OMpUtRTdBkWJ45IkIsTebITS9AFgKqGK$2a$10$2gaMKWCRoKdc42E0jsq7b.munjzOSPOM4yr3GG9M6194E7dOH5LyS$2a$10$I/n93PIKpKL8m4O3AuT5kuZncZhfqV51bfx5sJrplnYoM7FimdboC*/for (int i = 0; i < 5; i++) {//對密碼進行哈希String encode = passwordEncoder.encode("11111");System.out.println(encode);}//校驗哈希串和密碼是否匹配boolean matches = passwordEncoder.matches("11111", "$2a$10$m983E2nmJ7ITlesbXzjbzO/M7HL2wP8EgpgX.pPACDm1wG38Lt.na");System.out.println(matches);
}
根據上邊的測試代碼可知,BCrypt的使用方法如下:
用戶輸入密碼,通過passwordEncoder.encode(“輸入的密碼”)得到哈希串,將哈希串存儲到數據庫。
用戶登錄校驗密碼,從數據庫取出哈希串,連同用戶輸入的密碼,調用下邊的方法:
passwordEncoder.matches(“用戶輸入的密碼”, “從數據庫查詢的密碼哈希串”);
1.4.1 mapper
單表查詢,用mybatisplus即可
1.4.2 service
在com.jzo2o.customer.service.IServeProviderService接口:
package com.jzo2o.customer.service;/*** 服務人員/機構表 服務類*/
public interface IServeProviderService extends IService<ServeProvider> {/*** 機構人員注冊*/ServeProvider registerInstitution(InstitutionRegisterReqDTO institutionRegisterReqDTO);}
實現:
package com.jzo2o.customer.service.impl;/*** 服務人員/機構表 服務實現類*/
@Service
public class ServeProviderServiceImpl extends ServiceImpl<ServeProviderMapper, ServeProvider> implements IServeProviderService {/*** 機構人員注冊*/@Overridepublic ServeProvider registerInstitution(InstitutionRegisterReqDTO institutionRegisterReqDTO) {//1.校驗手機驗證碼是否正確//1.1.數據校驗if(StringUtils.isEmpty(institutionRegisterReqDTO.getVerifyCode())){throw new BadRequestException("驗證碼錯誤,請重新獲取");}//1.2.遠程調用publics服務校驗驗證碼是否正確boolean verifyResult = smsCodeApi.verify(institutionRegisterReqDTO.getPhone(), SmsBussinessTypeEnum.INSTITION_REGISTER, institutionRegisterReqDTO.getVerifyCode()).getIsSuccess();if(!verifyResult) {throw new BadRequestException("驗證碼錯誤,請重新獲取");}//2.檢查手機號是否被注冊過BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String encode = passwordEncoder.encode(institutionRegisterReqDTO.getPassword());ServeProvider serveProvider = add(institutionRegisterReqDTO.getPhone(), UserType.INSTITUTION, encode);return serveProvider;}}
1.4.3 測試
手機號隨便輸入
成功返回,查看數據庫
1.5 完成忘記密碼功能開發
界面原型:進入登錄頁面,點擊“忘記密碼”進入找回密碼頁面
接口定義如下:
接口名稱:機構登錄密碼重置接口
接口路徑:POST/customer/agency/serve-provider/institution/resetPassword
設計須知:
首先校驗驗證碼是否正確
校驗手機號是否存在數據庫
通過校驗最后修改密碼,密碼的加密方式參考機構注冊接口
1.5.1 mapper
單表查詢,用mybatisplus即可,不用自己去寫SQL語句
1.5.2 service
在com.jzo2o.customer.service.IServeProviderService接口
package com.jzo2o.customer.service;/*** 服務人員/機構表 服務類*/
public interface IServeProviderService extends IService<ServeProvider> {/*** 忘記密碼*/ServeProvider resetPassword(InstitutionResetPasswordReqDTO institutionResetPasswordReqDTO);
}
InstitutionResetPasswordReqDTO,前端傳遞過來的
package com.jzo2o.customer.model.dto.request;@Data
@ApiModel("機構密碼重置接口")
public class InstitutionResetPasswordReqDTO {@ApiModelProperty(value = "新密碼",required = true)private String password;@ApiModelProperty(value = "手機號",required = true)private String phone;@ApiModelProperty(value = "短信驗證碼",required = true)@NotNull(message = "驗證碼錯誤,請重新輸入")private String verifyCode;}
實現:
package com.jzo2o.customer.service.impl;@Service
public class ServeProviderServiceImpl extends ServiceImpl<ServeProviderMapper, ServeProvider> implements IServeProviderService {/*** 忘記密碼*/@Overridepublic ServeProvider resetPassword(InstitutionResetPasswordReqDTO institutionResetPasswordReqDTO) {//0.校驗手機號是否存在ServeProvider existServeProvider = lambdaQuery().eq(ServeProvider::getPhone, institutionResetPasswordReqDTO.getPhone()).one();if (existServeProvider == null) {throw new BadRequestException("手機號不存在");}//1.校驗手機驗證碼是否正確//1.1.數據校驗if (StringUtils.isEmpty(institutionResetPasswordReqDTO.getVerifyCode())){throw new BadRequestException("驗證碼不能為空");}//1.2.遠程調用publics服務校驗驗證碼是否正確boolean verifyResult = smsCodeApi.verify(institutionResetPasswordReqDTO.getPhone(), SmsBussinessTypeEnum.INSTITUTION_RESET_PASSWORD, institutionResetPasswordReqDTO.getVerifyCode()).getIsSuccess();if(!verifyResult) {throw new BadRequestException("驗證碼錯誤,請重新獲取");}//2.修改密碼BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String encode = passwordEncoder.encode(institutionResetPasswordReqDTO.getPassword());boolean update = lambdaUpdate().eq(ServeProvider::getPhone, institutionResetPasswordReqDTO.getPhone()).set(ServeProvider::getPassword, encode).update();if (!update) {throw new BadRequestException("修改密碼失敗");}existServeProvider.setPassword(encode);return existServeProvider;}}
1.5.3 controller
在com.jzo2o.customer.controller.agency.ServeProviderController中
package com.jzo2o.customer.controller.agency;/*** 服務人員/機構表 前端控制器*/
@RestController("agencyServeProviderController")
@RequestMapping("/agency/serve-provider")
@Api(tags = "機構端 - 服務人員或機構相關接口")
public class ServeProviderController {@Resourceprivate IServeProviderService serveProviderService;@PostMapping("/institution/resetPassword")@ApiOperation("機構人員重置密碼")public void resetPassword(@RequestBody InstitutionResetPasswordReqDTO institutionResetPasswordReqDTO) {serveProviderService.resetPassword(institutionResetPasswordReqDTO);}}