尚籌網09使用者註冊
總體思路
建立資料庫表
create table t_member( id int(11) not null auto_increment, loginacct varchar(255) not null, userpswd char(200) not null, username varchar(255), email varchar(255), authstatus tinyint(4) comment '實名認證狀態 0 - 未實名認證, 1 - 實名認證申請中, 2 - 已實名認證', usertype tinyint(4) comment ' 0 - 個人, 1 - 企業', realname varchar(255), cardnum varchar(255), accttype tinyint(4) comment '0 - 企業, 1 - 個體, 2 - 個人, 3 - 政府', primary key (id));
api介面呼叫微服務service
傳送驗證碼
目標
1、將驗證碼傳送到使用者手機上
2、將驗證碼存入redis
思路
準備簡訊傳送API
匯入依賴
<!--ali簡訊--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-util</artifactId> <version>9.3.7.v20160115</version> </dependency>
加入HttpUtil類
依賴
<!--阿里簡訊--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.15</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.2.1</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.2.1</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-util</artifactId> <version>9.3.7.v20160115</version> </dependency>
程式碼
package com.aliyun.api.gateway.demo.util; public class HttpUtils { /** * get * * @param host * @param path * @param method * @param headers * @param querys * @return * @throws Exception */ public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception { HttpClient httpClient = wrapClient(host); HttpGet request = new HttpGet(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } return httpClient.execute(request); } /** * post form * * @param host * @param path * @param method * @param headers * @param querys * @param bodys * @return * @throws Exception */ public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, Map<String, String> bodys) throws Exception { HttpClient httpClient = wrapClient(host); HttpPost request = new HttpPost(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (bodys != null) { List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>(); for (String key : bodys.keySet()) { nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key))); } UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8"); formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8"); request.setEntity(formEntity); } return httpClient.execute(request); } /** * Post String * * @param host * @param path * @param method * @param headers * @param querys * @param body * @return * @throws Exception */ public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception { HttpClient httpClient = wrapClient(host); HttpPost request = new HttpPost(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (StringUtils.isNotBlank(body)) { request.setEntity(new StringEntity(body, "utf-8")); } return httpClient.execute(request); } /** * Post stream * * @param host * @param path * @param method * @param headers * @param querys * @param body * @return * @throws Exception */ public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception { HttpClient httpClient = wrapClient(host); HttpPost request = new HttpPost(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (body != null) { request.setEntity(new ByteArrayEntity(body)); } return httpClient.execute(request); } /** * Put String * * @param host * @param path * @param method * @param headers * @param querys * @param body * @return * @throws Exception */ public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, String body) throws Exception { HttpClient httpClient = wrapClient(host); HttpPut request = new HttpPut(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (StringUtils.isNotBlank(body)) { request.setEntity(new StringEntity(body, "utf-8")); } return httpClient.execute(request); } /** * Put stream * * @param host * @param path * @param method * @param headers * @param querys * @param body * @return * @throws Exception */ public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers, Map<String, String> querys, byte[] body) throws Exception { HttpClient httpClient = wrapClient(host); HttpPut request = new HttpPut(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } if (body != null) { request.setEntity(new ByteArrayEntity(body)); } return httpClient.execute(request); } /** * Delete * * @param host * @param path * @param method * @param headers * @param querys * @return * @throws Exception */ public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers, Map<String, String> querys) throws Exception { HttpClient httpClient = wrapClient(host); HttpDelete request = new HttpDelete(buildUrl(host, path, querys)); for (Map.Entry<String, String> e : headers.entrySet()) { request.addHeader(e.getKey(), e.getValue()); } return httpClient.execute(request); } private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException { StringBuilder sbUrl = new StringBuilder(); sbUrl.append(host); if (!StringUtils.isBlank(path)) { sbUrl.append(path); } if (null != querys) { StringBuilder sbQuery = new StringBuilder(); for (Map.Entry<String, String> query : querys.entrySet()) { if (0 < sbQuery.length()) { sbQuery.append("&"); } if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) { sbQuery.append(query.getValue()); } if (!StringUtils.isBlank(query.getKey())) { sbQuery.append(query.getKey()); if (!StringUtils.isBlank(query.getValue())) { sbQuery.append("="); sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8")); } } } if (0 < sbQuery.length()) { sbUrl.append("?").append(sbQuery); } } return sbUrl.toString(); } private static HttpClient wrapClient(String host) { HttpClient httpClient = new DefaultHttpClient(); if (host.startsWith("https://")) { sslClient(httpClient); } return httpClient; } private static void sslClient(HttpClient httpClient) { try { SSLContext ctx = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(X509Certificate[] xcs, String str) { } @Override public void checkServerTrusted(X509Certificate[] xcs, String str) { } }; ctx.init(null, new TrustManager[]{tm}, null); SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = httpClient.getConnectionManager(); SchemeRegistry registry = ccm.getSchemeRegistry(); registry.register(new Scheme("https", 443, ssf)); } catch (KeyManagementException ex) { throw new RuntimeException(ex); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } } }
建立工具方法
/** * @param host 簡訊介面呼叫url地址 * @param path 具體傳送簡訊地址 * @param method 請求方法 * @param phoneNum 手機號 * @param appCode 應用碼 * @param sign * @param skin * @return 成功返回驗證碼 */ public static ResultEntity<String> sendCodeByShortMessage( String host, String path, String method, String phoneNum, String appCode, String sign, String skin) { Map<String, String> headers = new HashMap<String, String>(); //最後在header中的格式(中間是英文空格)為Authorization:APPCODE 83359fd73fe94948385f570e3c139105 headers.put("Authorization", "APPCODE " + appCode); Map<String, String> querys = new HashMap<String, String>(); // 驗證碼 StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 4; i++) { int random = (int) (Math.random() * 10); stringBuilder.append(random); } String code = stringBuilder.toString(); querys.put("param", code); querys.put("phone", phoneNum); querys.put("sign", sign); querys.put("skin", skin); //JDK 1.8示例程式碼請在這裡下載: http://code.fegine.com/Tools.zip try { /** * 重要提示如下: * HttpUtils請從 * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java * 或者直接下載: * http://code.fegine.com/HttpUtils.zip * 下載 * * 相應的依賴請參照 * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml * 相關jar包(非pom)直接下載: * http://code.fegine.com/aliyun-jar.zip */ HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys); //System.out.println(response.toString());如不輸出json, 請開啟這行程式碼,列印除錯頭部狀態碼。 //狀態碼: 200 正常;400 URL無效;401 appCode錯誤; 403 次數用完; 500 API網管錯誤 StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); String reasonPhrase = statusLine.getReasonPhrase(); if (statusCode == 200) { return ResultEntity.sucessWithData(code); } return ResultEntity.failed(reasonPhrase); } catch (Exception e) { e.printStackTrace(); return ResultEntity.failed(e.getMessage()); } }
從yml配置檔案讀取資料
匯入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
編寫配置檔案
編寫簡訊property類
package com.adom.authentication.config; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "short.message") public class ShortMessageProperties { // 呼叫簡訊傳送介面時的訪問地址 private String host; // 具體訪問路徑 private String path; // 請求方式 private String method; // 登入阿里雲後,進入管理控制檯->雲市場->已購買服務,複製AppCode private String appCode; // 簽名編號 private String sign; // 模板編號 private String skin; public ShortMessageProperties() { } public ShortMessageProperties(String host, String path, String method, String appCode, String sign, String skin) { this.host = host; this.path = path; this.method = method; this.appCode = appCode; this.sign = sign; this.skin = skin; }
前端思路(忽略)
後端程式碼
建立傳送簡訊驗證碼的controller
1、先發送簡訊,若傳送成功,則把驗證碼加入到redis
@ResponseBody @RequestMapping(value = "/auth/member/send/short/message.json", method = RequestMethod.POST) public ResultEntity<String> sendMessage(@RequestParam("phoneNum") String phoneNum) { // 1.傳送驗證碼到手機 ResultEntity<String> sendMeassageResultEntity = CrowdUtil.sendCodeByShortMessage( shortMessageProperties.getHost(), shortMessageProperties.getPath(), shortMessageProperties.getMethod(), phoneNum, shortMessageProperties.getAppCode(), shortMessageProperties.getSign(), shortMessageProperties.getSkin()); // 2,判斷簡訊傳送的結果 if (ResultEntity.SUCCESS.equals(sendMeassageResultEntity.getResult())) { // 3.如果傳送成功,則將驗證碼存入redis String code = sendMeassageResultEntity.getData(); String key = ConstantUtil.REDIS_CODE_PREFIX + phoneNum; ResultEntity<String> saveCodeResultEntity = redisRemoteService.setRedisKeyValueRemoteWithTimeout(key, code, 15, TimeUnit.MINUTES); if (ResultEntity.SUCCESS.equals(saveCodeResultEntity.getResult())) { return ResultEntity.successWithoutData(); } else { return saveCodeResultEntity; } } else { return sendMeassageResultEntity; } }
redis中儲存驗證碼資料的key
宣告redis-provider功能api
@FeignClient("adom-crowd-redis") public interface RedisRemoteService { @RequestMapping("/set/redis/key/value/remote") ResultEntity<String> setRedisKeyValueRemote( @RequestParam("key") String key, @RequestParam("value") String value); @RequestMapping("/set/redis/key/value/remote/with/timeout") ResultEntity<String> setRedisKeyValueRemoteWithTimeout( @RequestParam("key") String key, @RequestParam("value") String value, @RequestParam("time") long time, @RequestParam("timeUnix") TimeUnit timeUnit); @RequestMapping("/get/redis/string/value/by/key") ResultEntity<String> getRedisStringValueByKeyRemote(@RequestParam("key") String key); @RequestMapping("/remove/redis/key/remote") ResultEntity<String> removeRedisKeyRemote(@RequestParam("key") String key); }
Redis-provider功能實現
@RestController public class RedisController { @Autowired private StringRedisTemplate redisTemplate; @RequestMapping("set/redis/key/value/remote") ResultEntity<String> setRedisKeyValueRemote( @RequestParam("key") String key, @RequestParam("value")String value){ try { ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set(key,value); return ResultEntity.successWithoutData(); } catch (Exception e) { e.printStackTrace(); return ResultEntity.failed(e.getMessage()); } } @RequestMapping("set/redis/key/value/remote/with/timeout") ResultEntity<String> setRedisKeyValueRemoteWithTimeout( @RequestParam("key") String key, @RequestParam("value")String value, @RequestParam("time") long time, @RequestParam("timeUnit") TimeUnit timeUnit){ try { ValueOperations<String, String> operations = redisTemplate.opsForValue(); operations.set(key,value,time,timeUnit); return ResultEntity.successWithoutData(); } catch (Exception e) { e.printStackTrace(); return ResultEntity.failed(e.getMessage()); } } @RequestMapping("get/redis/string/value/by/key") ResultEntity<String> getRedisStringValueByKey(@RequestParam("key") String key){ try { ValueOperations<String, String> operations = redisTemplate.opsForValue(); String value = operations.get(key); return ResultEntity.sucessWithData(value); } catch (Exception e) { e.printStackTrace(); return ResultEntity.failed(e.getMessage()); } } @RequestMapping("remove/redis/key/remote") ResultEntity<String> removeRedisKeyRemote(@RequestParam("key") String key){ try { redisTemplate.delete(key); return ResultEntity.successWithoutData(); } catch (Exception e) { e.printStackTrace(); return ResultEntity.failed(e.getMessage()); } } }
程式碼缺陷(未解決)
分散式事務
舉例
傳送簡訊操作和redis儲存操作在執行時都有可能失敗.如果發生其中一個操作失敗,另一個成功,那麼資料整體就會發生不一致的情況.
分析
如果是在以前關係型資料庫的事務操作中,可以採用回滾機制,一個事物多個操作中有任何
一個失敗,則整個事務全部回滾.
在分散式環境下,沒辦法將多個具體操作納入到一個統一的事務中,一起提交、一起回滾.
也不能使用逆操作手動撤銷,因為逆操作除了功能和原本操作不同,其他方面和原本的操作性質是一樣的.
解決方法
從yml配置檔案讀取資料
加入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
yml配置
這些配置項全部都是自定義的,完全沒有框架的支援.
short:
message:
app-code: 303208760dc14fb994c6dfba5c82e1e6
host: https://feginesms.market.alicloudapi.com
method: GET
path: /codeNotice
sign: 1
skin: 1
在java類中引用方式
@Component @ConfigurationProperties(prefix = "short.message") public class ShortMessageProperties {
意義:以後在SpringBoot環境下開發時,如果某些資訊不能在java程式碼中寫死,就可以使用這樣的機制在yml配置檔案中配置,再使用@value註解讀取.yml或properties檔案都可以.
執行註冊
流程分析
目標
如果針對註冊操作所做的各項驗證能夠通過,則將Member資訊存入資料庫.
思路
1、接受表單資料 a)登陸賬號 b)密碼 c)手機號 d)驗證碼 2、檢查驗證碼是否有效 a)無效:返回失敗資訊,停止執行 b)有效繼續執行 3、檢查手機號是否有效 a)無效:返回失敗資訊,停止執行 b)有效:繼續執行 4、拼接接收到驗證碼的key 5、呼叫redis-provider的api方法獲取對應的驗證碼值 a)沒有查詢到值:返回失敗資訊,停止執行 b)查詢到有效值:繼續執行 6、進行比較 a)表單提交的驗證碼 b)redis取回的驗證碼 7、不一致:返回失敗資訊,停止執行 8、一致 a)從redis中刪除當前key對應的驗證碼 b)繼續執行 9、呼叫資料庫api方法檢查登陸賬號是否被佔用 a)已經被佔用:返回失敗資訊,停止執行 b)沒有被佔用:繼續執行 10、密碼加密 11、呼叫資料庫api方法檢查登陸賬號是否被佔用
封裝MemberVO
public class MemberVO { private String loginAcct; private String userPswd; private String email; private String userName; private String phoneNum; private String code; public MemberVO() { } public MemberVO(String loginAcct, String userPswd, String email, String userName, String phoneNum, String code) { this.loginAcct = loginAcct; this.userPswd = userPswd; this.email = email; this.userName = userName; this.phoneNum = phoneNum; this.code = code; }
呼叫資料庫provider的api
@RequestMapping("/save/member/remote") public ResultEntity<String> saveMember(@RequestBody MemberPO memberPO);
資料庫provider
資料庫儲存
Controller
@RequestMapping("/save/member/remote") public ResultEntity<String> saveMember(@RequestBody MemberPO memberPO) { try { memberService.saveMember(memberPO); return ResultEntity.successWithoutData(); } catch (Exception e) { if (e instanceof DuplicateKeyException) { return ResultEntity.failed(ConstantUtil.MESSAGE_LOGIN_ACCT_ALREADY_IN); } return ResultEntity.failed(e.getMessage()); } }
Service
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class, readOnly = false) public void saveMember(MemberPO memberPO) { memberPOMapper.insertSelective(memberPO); }
Mapper
int insertSelective(MemberPO record);
Sql
<insert id="insertSelective" parameterType="com.example.entity.po.MemberPO" > insert into t_member <trim prefix="(" suffix=")" suffixOverrides="," > <if test="id != null" > id, </if> <if test="loginAcct != null" > login_acct, </if> <if test="userPswd != null" > user_pswd, </if> <if test="userName != null" > user_name, </if> <if test="email != null" > email, </if> <if test="authStatus != null" > auth_status, </if> <if test="userType != null" > user_type, </if> <if test="realName != null" > real_name, </if> <if test="cardNum != null" > card_num, </if> <if test="acctType != null" > acct_type, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides="," > <if test="id != null" > #{id,jdbcType=INTEGER}, </if> <if test="loginAcct != null" > #{loginAcct,jdbcType=VARCHAR}, </if> <if test="userPswd != null" > #{userPswd,jdbcType=CHAR}, </if> <if test="userName != null" > #{userName,jdbcType=VARCHAR}, </if> <if test="email != null" > #{email,jdbcType=VARCHAR}, </if> <if test="authStatus != null" > #{authStatus,jdbcType=INTEGER}, </if> <if test="userType != null" > #{userType,jdbcType=INTEGER}, </if> <if test="realName != null" > #{realName,jdbcType=VARCHAR}, </if> <if test="cardNum != null" > #{cardNum,jdbcType=VARCHAR}, </if> <if test="acctType != null" > #{acctType,jdbcType=INTEGER}, </if> </trim> </insert>
執行註冊
controller
@RequestMapping("/auth/do/member/register") public String register(MemberVO memberVO, ModelMap modelMap) { // 1.獲取使用者輸入的手機號 String phoneNum = memberVO.getPhoneNum(); // 2.拼redis中儲存驗證碼的key String key = ConstantUtil.REDIS_CODE_PREFIX + phoneNum; // 3.從redis讀取key對應的value ResultEntity<String> resultEntity = redisRemoteService.getRedisStringValueByKeyRemote(key); // 4.查詢操作是否有效 String result = resultEntity.getResult(); if (ResultEntity.FAILED.equals(result)) { modelMap.addAttribute(ConstantUtil.ATTR_NANE_MESSAGE, resultEntity.getMessage()); return "member-reg"; } String redisCode = resultEntity.getData(); if (redisCode == null) { modelMap.addAttribute(ConstantUtil.ATTR_NANE_MESSAGE, ConstantUtil.MESSAGE_CODE_NOT_EXISTS); return "member-reg"; } // 5.如果能夠到value則比較表單的驗證碼和redis的驗證碼 String formCode = memberVO.getCode(); if (!Objects.equals(formCode, redisCode)) { modelMap.addAttribute(ConstantUtil.ATTR_NANE_MESSAGE, ConstantUtil.MESSAGE_CODE_INVALID); return "member-reg"; } // 6.如果驗證碼一致,則從redis刪除 redisRemoteService.removeRedisKeyRemote(key); // 7.執行密碼加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String userpswd = memberVO.getUserPswd(); String encode = passwordEncoder.encode(userpswd); memberVO.setUserPswd(encode); // 8.執行儲存 // 建立空的MemberPO物件 MemberPO memberPO = new MemberPO(); // 複製屬性 BeanUtils.copyProperties(memberVO, memberPO); // 呼叫遠端方法 ResultEntity<String> saveMemberResultEntity = mySQLRemoteService.saveMember(memberPO); if (ResultEntity.FAILED.equals(saveMemberResultEntity)) { modelMap.addAttribute(ConstantUtil.ATTR_NANE_MESSAGE, saveMemberResultEntity.getMessage()); return "member-reg"; } return "redirect:/auth/member/to/login/page"; }
一些問題
SpringBoot注入失敗的原因有哪些
SpringIOC找不到Bean物件
1、沒有開啟掃描的包
2、沒有注入Bean物件
3、沒有新增元件註解
4、方法沒有@Bean修飾
@FeignClients需要加入掃描哪些包才可以找到bean物件
mybatis之trim prefix="(" suffix=")"
trim元素的主要功能是可以在自己包含的內容前加上某些字首,也可以在其後加上某些字尾,與之對應的屬性是prefix和suffix;可以把包含內容的首部某些內容覆蓋,即忽略,也可以把尾部的某些內容覆蓋,對應的屬性是prefixOverrides和suffixOverrides;正因為trim有這樣的功能,所以我們也可以非常簡單的利用trim來代替where元素的功能。
1.<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
prefix:在trim標籤內sql語句加上字首。
suffix:在trim標籤內sql語句加上字尾。
suffixOverrides:指定去除多餘的字尾內容,如:suffixOverrides=",",去除trim標籤內sql語句多餘的字尾","。
prefixOverrides:指定去除多餘的字首內容
suffixOverrides=","
執行的sql語句也許是這樣的:insert into cart (id,user_id,deal_id,) values(1,2,1,);顯然是錯誤的
指定之後語句就會變成insert into cart (id,user_id,deal_id) values(1,2,1);這樣就將“,”去掉了。
字首同理。
獲取上述簡訊介面的appcode
阿里雲-》雲市場-》API-》電子商務找到如下:
購買成功後