實(shí)戰(zhàn)分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信詳情
前言

本篇文章完成的需求:
1,登錄采取彈出層的形式。
2,登錄方式:
- (1)手機(jī)號碼+手機(jī)驗(yàn)證碼
- (2)微信掃描(后文完成)
3,無注冊界面,第一次登錄根據(jù)手機(jī)號判斷系統(tǒng)是否存在,如果不存在則自動注冊。
4,微信掃描登錄成功必須綁定手機(jī)號碼,即:第一次掃描成功后綁定手機(jī)號,以后登錄掃描直接登錄成功。
5,網(wǎng)關(guān)統(tǒng)一判斷登錄狀態(tài),如何需要登錄,頁面彈出登錄層。
步驟1:搭建service-user用戶模塊
1.啟動類&配置網(wǎng)關(guān)
搭建service-user模塊用來做用戶登錄,其中:
使用@EnableDiscoveryClient注解將服務(wù)注冊到Nacos。
使用@EnableFeignClients(basePackages = "com.gql")注解開啟遠(yuǎn)程服務(wù)調(diào)用。
使用@ComponentScan(basePackages = "com.gql")注解開啟swagger掃描。
@SpringBootApplication
@ComponentScan(basePackages = "com.gql")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.gql")
public class ServiceUserApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceUserApplication.class, args);
}
}
網(wǎng)關(guān)配置:由于項(xiàng)目使用Gateway作為網(wǎng)關(guān),現(xiàn)在添加了用戶模塊,需要在gateway模塊的配置文件中加上網(wǎng)關(guān)配置:
# 設(shè)置路由id spring.cloud.gateway.routes[2].id=service-user #設(shè)置路由的uri spring.cloud.gateway.routes[2].uri=lb://service-user #設(shè)置路由斷言,代理servicerId為auth-service的/auth/路徑 spring.cloud.gateway.routes[2].predicates= Path=/*/user/**
2.三層調(diào)用
Controller層的login(@RequestBody LoginVo loginVo)方法調(diào)用了Service層的loginUser(LoginVo loginVo)方法,進(jìn)而分別調(diào)用redisTemplate和baseMapper操作Redis和MySQL。
Controller層login(@RequestBody LoginVo loginVo)方法:
@Autowired
private UserInfoService userInfoService;
// 用戶手機(jī)號登錄接口
@PostMapping("login")
public Result login(@RequestBody LoginVo loginVo) {
Map<String, Object> info = userInfoService.loginUser(loginVo);
return Result.ok(info);
}
Service層loginUser(LoginVo loginVo) 方法:
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 用戶手機(jī)號登錄接口
@Override
public Map<String, Object> loginUser(LoginVo loginVo) {
// 從loginVo獲取輸入的手機(jī)號和驗(yàn)證碼
String phone = loginVo.getPhone();
String code = loginVo.getCode();
// 判斷手機(jī)號和驗(yàn)證碼是否為空
if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
throw new YyghException(ResultCodeEnum.PARAM_ERROR);
}
// 校驗(yàn)驗(yàn)證碼
String redisCode = redisTemplate.opsForValue().get(phone);
if (!code.equals(redisCode)) {
throw new YyghException(ResultCodeEnum.CODE_ERROR);
}
// 判斷是否是第一次登錄:根據(jù)手機(jī)號查詢數(shù)據(jù)庫
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("phone", phone);
UserInfo userInfo = baseMapper.selectOne(wrapper);
// 如果是第一次使用手機(jī)登錄
if (userInfo == null) {
// 添加信息到數(shù)據(jù)庫
userInfo = new UserInfo();
userInfo.setName("");
userInfo.setPhone(phone);
userInfo.setStatus(1);
baseMapper.insert(userInfo);
}
// 校驗(yàn)是否被禁用
if (userInfo.getStatus() == 0) {
throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
}
// 不是第一次,就直接登錄
// 返回登錄信息
// 返回登錄用戶名
// 返回tocken信息
HashMap<String, Object> map = new HashMap<>();
String name = userInfo.getName();
// 如果用戶名稱為空,就去得到昵稱
if (StringUtils.isEmpty(name)) {
name = userInfo.getNickName();
}
// 如果昵稱還為空,就去得到手機(jī)號
if (StringUtils.isEmpty(name)) {
name = userInfo.getPhone();
}
map.put("name", name);
// 使用JWT生成tocken字符串
String token = JwtHelper.createToken(userInfo.getId(), name);
map.put("tocken", token);
return map;
}
步驟2:整合JWT
JWT(Json Web Token)是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開放標(biāo)準(zhǔn)。JWT的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源。比如用在用戶登錄上。JWT最重要的作用就是對 token信息的防偽作用。
一個(gè)JWT是由三個(gè)部分組成:公共部分、私有部分、簽名部分。這三者組合進(jìn)行base64編碼得到JWT。由于base64編碼并不是加密,只是把明文信息變成了不可見的字符串。但是其實(shí)只要用一些工具就可以把base64編碼解成明文,所以不要在JWT中放入涉及私密的信息。
整合JWT至common-util模塊:版本已在yygh-parent父模塊pom.xml添加
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
在common-util模塊編寫JwtHelper類:
public class JwtHelper {
// 過期時(shí)間
private static long tokenExpiration = 24 * 60 * 60 * 1000;
// 簽名密鑰
private static String tokenSignKey = "123456";
// 根據(jù)參數(shù)生成token
public static String createToken(Long userId, String userName) {
String token = Jwts.builder()
.setSubject("YYGH-USER")
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.claim("userId", userId)
.claim("userName", userName)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}
// 根據(jù)token字符串得到用戶id
public static Long getUserId(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer) claims.get("userId");
return userId.longValue();
}
// 根據(jù)token字符串得到用戶的名稱
public static String getUserName(String token) {
if (StringUtils.isEmpty(token)) {
return "";
}
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("userName");
}
// 測試
public static void main(String[] args) {
String token = JwtHelper.createToken(1L, "Hudie");
// token = 頭信息+主體+簽名哈希
System.out.println(token);
System.out.println(JwtHelper.getUserId(token));
System.out.println(JwtHelper.getUserName(token));
}
}
步驟3: 搭建service-msm短信模塊(整合阿里云短信)
1.啟動類&配置網(wǎng)關(guān)
搭建service-msm模塊用來做短信登錄,其中:
使用@EnableDiscoveryClient注解將服務(wù)注冊到Nacos。
使用@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)注解取消數(shù)據(jù)源自動配置,因?yàn)榘l(fā)送短信不需要調(diào)用MySQL數(shù)據(jù)庫。
使用@ComponentScan(basePackages = "com.gql")注解開啟swagger掃描。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
// swagger掃描
@ComponentScan(basePackages = {"com.gql"})
public class ServiceMsmApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceMsmApplication.class, args);
}
}
網(wǎng)關(guān)配置:由于項(xiàng)目使用Gateway作為網(wǎng)關(guān),現(xiàn)在添加了短信模塊,需要在gateway模塊的配置文件中加上網(wǎng)關(guān)配置:
# 設(shè)置路由id spring.cloud.gateway.routes[3].id=service-msm #設(shè)置路由的uri spring.cloud.gateway.routes[3].uri=lb://service-msm #設(shè)置路由斷言,代理servicerId為auth-service的/auth/路徑 spring.cloud.gateway.routes[3].predicates= Path=/*/msm/**
2.短信配置文件&讀取配置類
短信配置文件:在短信模塊的properties中添加阿里云短信的regionId、accessKeyId、secret:
# 這里使用杭州結(jié)點(diǎn)的阿里云服務(wù)器 aliyun.sms.regionId=cn-hangzhou aliyun.sms.accessKeyId=[保密] aliyun.sms.secret=[保密]
讀取配置文件類:在配置類中讀取配置文件內(nèi)容:
@Component
public class ConstantPropertiesUtils implements InitializingBean {
@Value("${aliyun.sms.regionId}")
private String regionId;
@Value("${aliyun.sms.accessKeyId}")
private String accessKeyId;
@Value("${aliyun.sms.secret}")
private String secret;
public static String REGION_Id;
public static String ACCESS_KEY_ID;
public static String SECRECT;
@Override
public void afterPropertiesSet() throws Exception {
REGION_Id = regionId;
ACCESS_KEY_ID = accessKeyId;
SECRECT = secret;
}
}
3.生成驗(yàn)證碼類
此類中有生成4位數(shù)的驗(yàn)證碼方法、6位數(shù)的驗(yàn)證碼方法。
public class RandomUtil {
private static final Random random = new Random();
private static final DecimalFormat fourdf = new DecimalFormat("0000");
private static final DecimalFormat sixdf = new DecimalFormat("000000");
public static String getFourBitRandom() {
return fourdf.format(random.nextInt(10000));
}
public static String getSixBitRandom() {
return sixdf.format(random.nextInt(1000000));
}
/**
* 給定數(shù)組,抽取n個(gè)數(shù)據(jù)
* @param list
* @param n
* @return
*/
public static ArrayList getRandom(List list, int n) {
Random random = new Random();
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
// 生成隨機(jī)數(shù)字并存入HashMap
for (int i = 0; i < list.size(); i++) {
int number = random.nextInt(100) + 1;
hashMap.put(number, i);
}
// 從HashMap導(dǎo)入數(shù)組
Object[] robjs = hashMap.values().toArray();
ArrayList r = new ArrayList();
// 遍歷數(shù)組并打印數(shù)據(jù)
for (int i = 0; i < n; i++) {
r.add(list.get((int) robjs[i]));
System.out.print(list.get((int) robjs[i]) + "\t");
}
System.out.print("\n");
return r;
}
}
4.三層調(diào)用
Controller層的sendCode(@PathVariable String phone) 方法直接調(diào)用redisTemplate獲取生成的驗(yàn)證碼,然后調(diào)用Service層的send(phone, code)方法通過阿里云發(fā)送手機(jī)驗(yàn)證碼。
@RestController
@RequestMapping("/api/msm")
public class MsmApiController {
@Autowired
private MsmService msmService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 發(fā)送手機(jī)驗(yàn)證碼
@GetMapping("send/{phone}")
public Result sendCode(@PathVariable String phone) {
// 從redis獲取手機(jī)驗(yàn)證碼,如果獲取到就返回ok
// (key:手機(jī)號,value:驗(yàn)證碼)
String code = redisTemplate.opsForValue().get(phone);
if (!StringUtils.isEmpty(code)) {
return Result.ok();
}
// 如果獲取不到,生成6位驗(yàn)證碼
code = RandomUtil.getSixBitRandom();
// 偷偷打印到控制臺
System.out.println(code);
// 調(diào)用service返回,通過整合短信服務(wù)進(jìn)行發(fā)送
boolean isSend = msmService.send(phone, code);
// 將生成的驗(yàn)證碼放入redis中,并設(shè)置有效時(shí)間
if (isSend) {
// 驗(yàn)證碼超過1分鐘失效
redisTemplate.opsForValue().set(phone, code, 1, TimeUnit.MINUTES);
return Result.ok();
} else {
return Result.fail().message("發(fā)送短信失敗");
}
}
}
Service層發(fā)送手機(jī)驗(yàn)證碼:
@Service
public class MsmServiceImpl implements MsmService {
// 發(fā)送手機(jī)驗(yàn)證碼
@Override
public boolean send(String phone, String code) {
// 判斷手機(jī)號是否為空
if (StringUtils.isEmpty(phone)) {
return false;
}
// 整合阿里云短信服務(wù)
// 設(shè)置相關(guān)參數(shù)
DefaultProfile profile = DefaultProfile.
getProfile(ConstantPropertiesUtils.REGION_Id,
ConstantPropertiesUtils.ACCESS_KEY_ID,
ConstantPropertiesUtils.SECRECT);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
// 如果是HTTPS方式就需要設(shè)置↓
//request.setProtocol(ProtocolType.HTTPS);
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");
request.setVersion("2017-05-25");
request.setAction("SendSms");
//手機(jī)號
request.putQueryParameter("PhoneNumbers", phone);
//簽名名稱
request.putQueryParameter("SignName", "袋鼠佳日");
//模板code
request.putQueryParameter("TemplateCode", "SMS_215315088");
//驗(yàn)證碼 使用json格式 {"code":"123456"}
Map<String, Object> param = new HashMap();
param.put("code", code);
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));
//調(diào)用方法進(jìn)行短信發(fā)送
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
}
步驟4:登錄頁面前端
1.封裝api請求
創(chuàng)建api文件夾,創(chuàng)建/api/userInfo.js、/api/msm.js
import request from '@/utils/request'
const api_name = `/api/user`
export default {
login(userInfo) {
return request({
url: `${api_name}/login`,
method: `post`,
data: userInfo
})
}
}
import request from '@/utils/request'
const api_name = `/api/msm`
export default {
sendCode(mobile) {
return request({
url: `${api_name}/send/${mobile}`,
method: `get`
})
}
}
2.添加登錄組件
登錄成功后,我們需要把用戶信息記錄在cookie里面,所以在vscode的命令行執(zhí)行:npm install js-cookie。
登錄彈窗組件是一個(gè)公共層,因此我們把它放在頭部組件里面,修改layouts/myheader.vue文件:具體代碼點(diǎn)擊這里查看倉庫。
3.登錄全局事件
目前登錄彈窗層在myheader組件中,登錄按鈕也在同一個(gè)組件里面,我們點(diǎn)擊登錄,調(diào)用showLogin()方法即可。
在預(yù)約掛號頁面,選擇科室去掛號時(shí)我們需要判斷當(dāng)前是否登錄,如果登錄可以進(jìn)入下一個(gè)頁面;如果沒有登錄需要顯示登錄層。我們可以注冊一個(gè)全局登錄事件,當(dāng)需要登錄層時(shí),我們發(fā)送一個(gè)登錄事件,頭部監(jiān)聽登錄事件,然后我們觸發(fā)登錄按鈕的點(diǎn)擊事件即可打開登錄層。
頭部注冊和監(jiān)聽登錄事件,修改myheader.vue組件:
①.引入vue
import Vue from 'vue'
②注冊與監(jiān)聽事件
// 頁面渲染之后執(zhí)行
mounted() {
// 注冊全局登錄事件對象
window.loginEvent = new Vue();
// 監(jiān)聽登錄事件
loginEvent.$on("loginDialogEvent", function () {
document.getElementById("loginDialog").click();
});
// 觸發(fā)事件,顯示登錄層:loginEvent.$emit('loginDialogEvent')
},
預(yù)約掛號頁面調(diào)整,修改/pages/hospital/_hoscode.vue組件:
①引入cookie
import cookie from 'js-cookie'
②修改方法
schedule(depcode) {
// 登錄判斷
let token = cookie.get("token");
if (!token) {
loginEvent.$emit("loginDialogEvent");
return;
}
window.location.href =
"/hospital/schedule?hoscode=" + this.hoscode + "&depcode=" + depcode;
},
附加:用戶認(rèn)證與網(wǎng)關(guān)整合
思路:
所有請求都會經(jīng)過服務(wù)網(wǎng)關(guān),服務(wù)網(wǎng)關(guān)對外暴露服務(wù),在網(wǎng)關(guān)進(jìn)行統(tǒng)一用戶認(rèn)證;既然要在網(wǎng)關(guān)進(jìn)行用戶認(rèn)證,網(wǎng)關(guān)需要知道對哪些url進(jìn)行認(rèn)證,所以我們得對ur制定規(guī)則。Api接口異步請求的,我們采取url規(guī)則匹配,如:/api//auth/,凡是滿足該規(guī)則的都必須用戶認(rèn)證。
因此,我們需要對server-gateway模塊進(jìn)行調(diào)整。
1.在服務(wù)網(wǎng)關(guān)添加fillter
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println("===" + path);
//內(nèi)部服務(wù)接口,不允許外部訪問
if (antPathMatcher.match("/**/inner/**", path)) {
ServerHttpResponse response = exchange.getResponse();
return out(response, ResultCodeEnum.PERMISSION);
}
//api接口,異步請求,校驗(yàn)用戶必須登錄
if (antPathMatcher.match("/api/**/auth/**", path)) {
Long userId = this.getUserId(request);
if (StringUtils.isEmpty(userId)) {
ServerHttpResponse response = exchange.getResponse();
return out(response, ResultCodeEnum.LOGIN_AUTH);
}
}
return chain.filter(exchange);
}
網(wǎng)站網(wǎng)關(guān)filter代碼詳見倉庫。
2.調(diào)整前端代碼
請求服務(wù)器端接口時(shí)我們默認(rèn)帶上token,需要登錄的接口如果沒有token或者token過期,服務(wù)器端會返回208狀態(tài),然后發(fā)送登錄事件打開登錄彈出層登錄。需要修改utils/request.js文件:
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import cookie from 'js-cookie'
// 創(chuàng)建axios實(shí)例
const service = axios.create({
baseURL: 'http://localhost:9000',
timeout: 15000 // 請求超時(shí)時(shí)間
})
// http request (請求)攔截器
service.interceptors.request.use(
config => {
// token 先不處理,后續(xù)使用時(shí)在完善
// 判斷cookie中是否有token值
if (cookie.get('token')) {
// 將token值放到cookie里面
config.headers['token'] = cookie.get('token')
}
return config
},
err => {
return Promise.reject(err)
})
// http response (響應(yīng))攔截器
service.interceptors.response.use(
response => {
if (response.data.code === 208) {
// 彈出登錄輸入框
loginEvent.$emit('loginDialogEvent')
return
} else {
if (response.data.code !== 200) {
Message({
message: response.data.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(response.data)
} else {
return response.data
}
}
},
error => {
return Promise.reject(error.response)
})
export default service
至此,已經(jīng)將阿里云短信整合到項(xiàng)目中,更多關(guān)于分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信的資料請關(guān)注腳本之家其它相關(guān)文章!
- 分布式醫(yī)療掛號系統(tǒng)整合Gateway網(wǎng)關(guān)解決跨域問題
- 分布式醫(yī)療掛號系統(tǒng)Nacos微服務(wù)Feign遠(yuǎn)程調(diào)用數(shù)據(jù)字典
- 實(shí)戰(zhàn)分布式醫(yī)療掛號系統(tǒng)開發(fā)醫(yī)院科室及排班的接口
- 分布式醫(yī)療掛號系統(tǒng)SpringCache與Redis為數(shù)據(jù)字典添加緩存
- 分布式醫(yī)療掛號系統(tǒng)EasyExcel導(dǎo)入導(dǎo)出數(shù)據(jù)字典的使用
- 分布式開發(fā)醫(yī)療掛號系統(tǒng)數(shù)據(jù)字典模塊前后端實(shí)現(xiàn)
相關(guān)文章
IDEA新建bootstrap.yml文件不顯示葉子圖標(biāo)的問題
這篇文章主要介紹了IDEA新建bootstrap.yml文件不顯示葉子圖標(biāo)的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
使用Lombok時(shí)@JsonIgnore注解失效解決方案
這篇文章主要為大家介紹了使用Lombok時(shí)@JsonIgnore注解失效問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
springboot多數(shù)據(jù)源配置及切換的示例代碼詳解
這篇文章主要介紹了springboot多數(shù)據(jù)源配置及切換,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
SpringBoot整合Milvus的實(shí)現(xiàn)
本文主要介紹了SpringBoot整合Milvus的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Java 8 Lambda 表達(dá)式比較器使用示例代碼
這篇文章主要介紹了Java 8 Lambda 表達(dá)式比較器使用示例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
Java Graphics實(shí)現(xiàn)界面顯示文字并換行
Java中Graphics類提供了一些基本的幾何圖形繪制方法,本文將利用Graphics實(shí)現(xiàn)界面顯示文字并換行效果,感興趣的小伙伴可以動手嘗試一下2022-08-08
Java使用@Retryable注解實(shí)現(xiàn)HTTP請求重試
HTTP調(diào)用是Java應(yīng)用與外部API進(jìn)行交互時(shí)重要的訪問方式之一,為了確保在遇到臨時(shí)性問題時(shí)能自動重試,我們可以設(shè)計(jì)一個(gè)靈活的重試機(jī)制,在Java中,我們可以通過注解來實(shí)現(xiàn)這一功能,文將介紹如何使用注解@Retryable來實(shí)現(xiàn)HTTP調(diào)用的重試機(jī)制,需要的朋友可以參考下2024-10-10

