實戰(zhàn)分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信詳情
前言
本篇文章完成的需求:
1,登錄采取彈出層的形式。
2,登錄方式:
- (1)手機號碼+手機驗證碼
- (2)微信掃描(后文完成)
3,無注冊界面,第一次登錄根據(jù)手機號判斷系統(tǒng)是否存在,如果不存在則自動注冊。
4,微信掃描登錄成功必須綁定手機號碼,即:第一次掃描成功后綁定手機號,以后登錄掃描直接登錄成功。
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")注解開啟遠程服務(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)配置:由于項目使用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)方法,進而分別調(diào)用redisTemplate和baseMapper操作Redis和MySQL。
Controller層login(@RequestBody LoginVo loginVo)方法:
@Autowired private UserInfoService userInfoService; // 用戶手機號登錄接口 @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; // 用戶手機號登錄接口 @Override public Map<String, Object> loginUser(LoginVo loginVo) { // 從loginVo獲取輸入的手機號和驗證碼 String phone = loginVo.getPhone(); String code = loginVo.getCode(); // 判斷手機號和驗證碼是否為空 if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) { throw new YyghException(ResultCodeEnum.PARAM_ERROR); } // 校驗驗證碼 String redisCode = redisTemplate.opsForValue().get(phone); if (!code.equals(redisCode)) { throw new YyghException(ResultCodeEnum.CODE_ERROR); } // 判斷是否是第一次登錄:根據(jù)手機號查詢數(shù)據(jù)庫 QueryWrapper<UserInfo> wrapper = new QueryWrapper<>(); wrapper.eq("phone", phone); UserInfo userInfo = baseMapper.selectOne(wrapper); // 如果是第一次使用手機登錄 if (userInfo == null) { // 添加信息到數(shù)據(jù)庫 userInfo = new UserInfo(); userInfo.setName(""); userInfo.setPhone(phone); userInfo.setStatus(1); baseMapper.insert(userInfo); } // 校驗是否被禁用 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(); } // 如果昵稱還為空,就去得到手機號 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ù)提供者間傳遞被認證的用戶身份信息,以便于從資源服務(wù)器獲取資源。比如用在用戶登錄上。JWT最重要的作用就是對 token信息的防偽作用。
一個JWT是由三個部分組成:公共部分、私有部分、簽名部分。這三者組合進行base64編碼得到JWT。由于base64編碼并不是加密,只是把明文信息變成了不可見的字符串。但是其實只要用一些工具就可以把base64編碼解成明文,所以不要在JWT中放入涉及私密的信息。
整合JWT至common-util模塊:版本已在yygh-parent父模塊pom.xml添加
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency>
在common-util模塊編寫JwtHelper類:
public class JwtHelper { // 過期時間 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ù)源自動配置,因為發(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)配置:由于項目使用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é)點的阿里云服務(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.生成驗證碼類
此類中有生成4位數(shù)的驗證碼方法、6位數(shù)的驗證碼方法。
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個數(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>(); // 生成隨機數(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獲取生成的驗證碼,然后調(diào)用Service層的send(phone, code)方法通過阿里云發(fā)送手機驗證碼。
@RestController @RequestMapping("/api/msm") public class MsmApiController { @Autowired private MsmService msmService; @Autowired private RedisTemplate<String, String> redisTemplate; // 發(fā)送手機驗證碼 @GetMapping("send/{phone}") public Result sendCode(@PathVariable String phone) { // 從redis獲取手機驗證碼,如果獲取到就返回ok // (key:手機號,value:驗證碼) String code = redisTemplate.opsForValue().get(phone); if (!StringUtils.isEmpty(code)) { return Result.ok(); } // 如果獲取不到,生成6位驗證碼 code = RandomUtil.getSixBitRandom(); // 偷偷打印到控制臺 System.out.println(code); // 調(diào)用service返回,通過整合短信服務(wù)進行發(fā)送 boolean isSend = msmService.send(phone, code); // 將生成的驗證碼放入redis中,并設(shè)置有效時間 if (isSend) { // 驗證碼超過1分鐘失效 redisTemplate.opsForValue().set(phone, code, 1, TimeUnit.MINUTES); return Result.ok(); } else { return Result.fail().message("發(fā)送短信失敗"); } } }
Service層發(fā)送手機驗證碼:
@Service public class MsmServiceImpl implements MsmService { // 發(fā)送手機驗證碼 @Override public boolean send(String phone, String code) { // 判斷手機號是否為空 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"); //手機號 request.putQueryParameter("PhoneNumbers", phone); //簽名名稱 request.putQueryParameter("SignName", "袋鼠佳日"); //模板code request.putQueryParameter("TemplateCode", "SMS_215315088"); //驗證碼 使用json格式 {"code":"123456"} Map<String, Object> param = new HashMap(); param.put("code", code); request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param)); //調(diào)用方法進行短信發(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。
登錄彈窗組件是一個公共層,因此我們把它放在頭部組件里面,修改layouts/myheader.vue文件:具體代碼點擊這里查看倉庫。
3.登錄全局事件
目前登錄彈窗層在myheader組件中,登錄按鈕也在同一個組件里面,我們點擊登錄,調(diào)用showLogin()方法即可。
在預(yù)約掛號頁面,選擇科室去掛號時我們需要判斷當(dāng)前是否登錄,如果登錄可以進入下一個頁面;如果沒有登錄需要顯示登錄層。我們可以注冊一個全局登錄事件,當(dāng)需要登錄層時,我們發(fā)送一個登錄事件,頭部監(jiān)聽登錄事件,然后我們觸發(fā)登錄按鈕的點擊事件即可打開登錄層。
頭部注冊和監(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; },
附加:用戶認證與網(wǎng)關(guān)整合
思路:
所有請求都會經(jīng)過服務(wù)網(wǎng)關(guān),服務(wù)網(wǎng)關(guān)對外暴露服務(wù),在網(wǎng)關(guān)進行統(tǒng)一用戶認證;既然要在網(wǎng)關(guān)進行用戶認證,網(wǎng)關(guān)需要知道對哪些url進行認證,所以我們得對ur制定規(guī)則。Api接口異步請求的,我們采取url規(guī)則匹配,如:/api//auth/,凡是滿足該規(guī)則的都必須用戶認證。
因此,我們需要對server-gateway模塊進行調(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接口,異步請求,校驗用戶必須登錄 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ù)器端接口時我們默認帶上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實例 const service = axios.create({ baseURL: 'http://localhost:9000', timeout: 15000 // 請求超時時間 }) // http request (請求)攔截器 service.interceptors.request.use( config => { // token 先不處理,后續(xù)使用時在完善 // 判斷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)將阿里云短信整合到項目中,更多關(guān)于分布式醫(yī)療掛號系統(tǒng)登錄接口整合阿里云短信的資料請關(guān)注腳本之家其它相關(guān)文章!
- 分布式醫(yī)療掛號系統(tǒng)整合Gateway網(wǎng)關(guān)解決跨域問題
- 分布式醫(yī)療掛號系統(tǒng)Nacos微服務(wù)Feign遠程調(diào)用數(shù)據(jù)字典
- 實戰(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ù)字典模塊前后端實現(xiàn)
相關(guān)文章
JAVA發(fā)送http get/post請求,調(diào)用http接口、方法詳解
這篇文章主要介紹了Java發(fā)送http get/post請求調(diào)用接口/方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現(xiàn)
本文主要介紹了springboot大文件上傳、分片上傳、斷點續(xù)傳、秒傳的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java8 用Lambda表達式給List集合排序的實現(xiàn)
這篇文章主要介紹了Java8 用Lambda表達式給List集合排序的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Java實現(xiàn)XML格式與JSON格式互相轉(zhuǎn)換的方法
這篇文章主要介紹了Java實現(xiàn)XML格式與JSON格式互相轉(zhuǎn)換的方法,方法通過實例代碼給大家介紹的非常詳細,選擇使用哪種格式通常取決于項目的需求和上下文,所以格式轉(zhuǎn)換就成了我們必備的技能,具體實現(xiàn)代碼跟隨小編一起看看吧2023-10-10Java?properties?和?yml?的區(qū)別解析
properties和yml都是Spring?Boot支持的兩種配置文件,它們可以看做Spring?Boot在不同時期的兩種“產(chǎn)品”,這篇文章主要介紹了Java?properties?和?yml?的區(qū)別,需要的朋友可以參考下2023-02-02