淘淘商城26_單點登入SSO_02程式碼的實現,雪崩演算法
一、驗證資料是否可用
1. 介面文件
2. 需求分析
URL:請求的路徑
引數:param type param:username/phone/email type:1/2/3
返回型別:TaotaoResult
資料庫表結構:
3. 程式碼編寫
3.1 UserMapper
3.2 UserMapper.xml
<select id="getUserParam" parameterType="com.taotao.pojo.TbUser" resultMap="BaseResultMap"> select <include refid="Base_Column_List" /> from tb_user <where> <if test='username != null and username != ""'> and username = #{username} </if> <if test='phone != null and phone != ""'> and phone = #{phone} </if> <if test='email != null and email != ""'> and email = #{email} </if> </where> </select>
3.3 UserService
3.4 UserServiceImpl
package com.taotao.sso.service.impl; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.taotao.mapper.UserMapper; import com.taotao.pojo.TbUser; import com.taotao.sso.service.UserService; import com.taotao.utils.TaotaoResult; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; /** * 檢查資料是否可用 */ @Override public TaotaoResult checkData(String param, Integer type) { TbUser tbUser = new TbUser(); if (StringUtils.isEmpty(param)) { return TaotaoResult.build(400, "驗證資料不可以為空"); } if (type == 1) {//驗證username tbUser.setUsername(param); } else if (type ==2) {//驗證phone tbUser.setPhone(param); } else if (type == 3) {//驗證email tbUser.setEmail(param); } else { return TaotaoResult.build(400, "資料型別檢驗有誤"); } List<TbUser> list = userMapper.getUserParam(tbUser); if (list ==null || list.size() ==0) { return TaotaoResult.ok(true); } else { return TaotaoResult.ok(false); } } }
3.5 UserController
package com.taotao.sso.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.taotao.sso.service.UserService; import com.taotao.utils.TaotaoResult; @Controller @RequestMapping("/user") public class UserController { /** * 檢驗資料的可用性 */ @Autowired private UserService userService; @RequestMapping("/check/{param}/{type}") @ResponseBody public TaotaoResult checkData(@PathVariable String param, @PathVariable Integer type){ TaotaoResult result = userService.checkData(param, type); return result; } }
3.6 Dubbo配置
applicationContext-service.xml
springmvc.xml
3.7 測試
二、註冊
1. 介面文件
請求方法
POST
URL
http://sso.taotao.com/user/register
引數
username //使用者名稱
password //密碼
phone //手機號
email //郵箱
引數說明
示例
http://sso.taotao.com/user/register
返回值
{
status: 400
msg: "註冊失敗. 請校驗資料後請再提交資料."
data: null
}
2. 需求分析
URL: /user/register
引數:
username //使用者名稱
password //密碼
phone //手機號
email //郵箱
請求方式: post請求方式
3. 程式碼編寫
3.1 在tb_user表中新增欄位:salt
3.2 mapper
3.3 Service
3.4 在common工程中新增工具類:雪崩演算法工具類SnowflakeIdWorker.java
package com.taotao.utils;
/**
* id生成器
*
*/
public class SnowflakeIdWorker {
// ==============================Fields===========================================
/** 開始時間截 (2017-01-01) */
private final long twepoch = 1483200000000L;
/** 機器id所佔的位數 */
private final long workerIdBits = 5L;
/** 資料標識id所佔的位數 */
private final long datacenterIdBits = 5L;
/** 支援的最大機器id,結果是31 (這個移位演算法可以很快的計算出幾位二進位制數所能表示的最大十進位制數) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支援的最大資料標識id,結果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中佔的位數 */
private final long sequenceBits = 12L;
/** 機器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 資料標識id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;
/** 時間截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩碼,這裡為4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作機器ID(0~31) */
private long workerId;
/** 資料中心ID(0~31) */
private long datacenterId;
/** 毫秒內序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的時間截 */
private long lastTimestamp = -1L;
private static final SnowflakeIdWorker idGenerate = new SnowflakeIdWorker(1, 1);
// ==============================Constructors=====================================
/**
* 建構函式
*
* @param workerId
* 工作ID (0~31)
* @param datacenterId
* 資料中心ID (0~31)
*/
private SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 獲取類的例項
*
* @return
*/
public static SnowflakeIdWorker getInstance() {
return idGenerate;
}
// ==============================Methods==========================================
/**
* 獲得下一個ID (該方法是執行緒安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
// 如果當前時間小於上一次ID生成的時間戳,說明系統時鐘回退過這個時候應當丟擲異常
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果是同一時間生成的,則進行毫秒內序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
// 毫秒內序列溢位
if (sequence == 0) {
// 阻塞到下一個毫秒,獲得新的時間戳
timestamp = tilNextMillis(lastTimestamp);
}
}
// 時間戳改變,毫秒內序列重置
else {
sequence = 0L;
}
// 上次生成ID的時間截
lastTimestamp = timestamp;
// 移位並通過或運算拼到一起組成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一個毫秒,直到獲得新的時間戳
*
* @param lastTimestamp
* 上次生成ID的時間截
* @return 當前時間戳
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒為單位的當前時間
*
* @return 當前時間(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
// ==============================Test=============================================
/** 測試 */
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 1000; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
}
}
}
3.5 ServiceImpl
/**
* 註冊
* 檢查資料的合法性,呼叫checkData方法
*/
@Override
public TaotaoResult userRegister(TbUser user) {
//校驗新增的資訊
if (user.getUsername()==null || user.getUsername()=="") {
return TaotaoResult.build(400, "使用者名稱不可以為空!");
}
if (user.getPassword()==null || user.getPassword()=="") {
return TaotaoResult.build(400, "密碼不可以為空!");
}
if (user.getEmail()==null || user.getEmail()=="") {
return TaotaoResult.build(400, "郵箱不可以為空!");
}
if (user.getPhone()==null || user.getPhone()=="") {
return TaotaoResult.build(400, "手機號不可以為空!");
}
//判斷資訊的合法性
TaotaoResult result = null;
result = checkData(user.getUsername(), 1);
if (! (boolean) result.getData()) {//不為空
return TaotaoResult.build(400, "該使用者名稱已存在");
}
result = checkData(user.getPhone(), 2);
if (! (boolean) result.getData()) {//不為空
return TaotaoResult.build(400, "該手機號已存在");
}
result = checkData(user.getEmail(), 3);
if (! (boolean) result.getData()) {//不為空
return TaotaoResult.build(400, "該郵箱已存在");
}
//1.資料補全
user.setCreated(new Date());
user.setUpdated(new Date());
//使用雪崩演算法生成隨機數
long nextId = SnowflakeIdWorker.getInstance().nextId();
//新增鹽值,轉換為字串
user.setSalt(nextId+"");
//密碼加密Md5
String pwd_db = DigestUtils.md5DigestAsHex((user.getPassword()+nextId).getBytes());
user.setPassword(pwd_db);//把加密後的密碼儲存到資料庫
userMapper.userRegister(user);
return TaotaoResult.ok();
}
3.4 Controller
/**
* 註冊
* @param user
* @return
*/
@RequestMapping(value="/register",method=RequestMethod.POST)
@ResponseBody
public TaotaoResult userRegister(TbUser user){
TaotaoResult result = userService.userRegister(user);
return result;
}
3.5 測試
三、登入
1. 介面文件
2. 需求分析
Post請求
Url: user/login
引數,Username password
返回:token碼令牌
把token碼儲存到cookie裡,
Cookie key value
根據cookie裡的key值,就可以查詢到這個token碼令牌
把使用者資訊儲存到redis裡 把 token碼 存到cookie
用的時候,
- 取cookie 取出token令牌
- 根據令牌:去redis裡取出使用者資訊,
- 需求分析: redis的key值,設定生命週期, 2小時,一般為24小時
- 使用者下次登入的時候直接進行登入 首先去cookie裡取令牌,用令牌去redis裡取使用者資訊, (1.)取到的話使用者自動登入重新設定redis的生命週期 (2)沒有取到,直接跳轉登入頁面進行登入.
3. 程式碼編寫
3.1 Mapper
3.2 Service
3.3 ServiceImpl
3.3.1 resource.properties
#使用者資訊
REDIS_USER_BASE_KEY=REDIS_USER_BASE_KEY
#生命週期60*60*24 24小時
REDIS_USER_EXPIRE=86400
3.3.2
@Autowired
private JedisClient jedisClient;
@Value("${REDIS_USER_BASE_KEY}")
private String REDIS_USER_BASE_KEY;//使用者資訊
@Value("${REDIS_USER_EXPIRE}")
private Integer REDIS_USER_EXPIRE;//生命週期
/**
* 登入
*/
@Override
public TaotaoResult userLogin(String username, String password) {
//1.根據使用者名稱查詢使用者資訊
List<TbUser> userList = userMapper.userLogin(username);
//2.判斷,若查詢不到,返回使用者不存在
if (userList ==null ||userList.size() ==0) {
return TaotaoResult.build(400, "該使用者不存在!");
}
//3.若查詢到,則獲取該使用者註冊時的密碼和鹽值
String pwd_db = userList.get(0).getPassword();
String salt = userList.get(0).getSalt();
//4.將頁面輸入的密碼+鹽值,之後Md5加密
String pwd_input = DigestUtils.md5DigestAsHex((password+salt).getBytes());
//5.比較資料庫中的密碼和頁面頁面加密後的密碼
String token = UUID.randomUUID().toString();
if (pwd_input.equals(pwd_db)) {//6.如果相同,則將使用者資訊儲存到redis中,不儲存密碼
//獲取使用者資訊
TbUser user = userList.get(0);
user.setPassword(null);//redis當中不儲存密碼
jedisClient.set(REDIS_USER_BASE_KEY+":"+token, JsonUtils.objectToJson(user));
//設定生命週期
jedisClient.expire(REDIS_USER_BASE_KEY+":"+token, REDIS_USER_EXPIRE);
return TaotaoResult.ok(token);
} else {
return TaotaoResult.build(400, "使用者名稱或密碼不正確!");
}
}
3.4 在common工程zhong新增工具類CookieUtils.java
package com.taotao.utils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* Cookie 工具類
*
*/
public final class CookieUtils {
/**
* 得到Cookie的值, 不編碼
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
if (isDecoder) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
} else {
retValue = cookieList[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
* 設定Cookie的值 不設定生效時間預設瀏覽器關閉即失效,也不編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
* 設定Cookie的值 在指定時間內生效,但不編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
* 設定Cookie的值 不設定生效時間,但編碼
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
* 設定Cookie的值 在指定時間內生效, 編碼引數
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
* 設定Cookie的值 在指定時間內生效, 編碼引數(指定編碼)
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
/**
* 刪除Cookie帶cookie域名
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName) {
doSetCookie(request, response, cookieName, "", -1, false);
}
/**
* 設定Cookie的值,並使其在指定時間內生效
*
* @param cookieMaxage cookie生效的最大秒數
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
try {
if (cookieValue == null) {
cookieValue = "";
} else if (isEncode) {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 設定域名的cookie
String domainName = getDomainName(request);
System.out.println(domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 設定Cookie的值,並使其在指定時間內生效
*
* @param cookieMaxage cookie生效的最大秒數
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
try {
if (cookieValue == null) {
cookieValue = "";
} else {
cookieValue = URLEncoder.encode(cookieValue, encodeString);
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 設定域名的cookie
String domainName = getDomainName(request);
System.out.println(domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 得到cookie的域名
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals("")) {
domainName = "";
} else {
serverName = serverName.toLowerCase();
serverName = serverName.substring(7);
final int end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3) {
// www.xxx.com.cn
domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
} else if (len <= 3 && len > 1) {
// xxx.com or xxx.cn
domainName = "." + domains[len - 2] + "." + domains[len - 1];
} else {
domainName = serverName;
}
}
if (domainName != null && domainName.indexOf(":") > 0) {
String[] ary = domainName.split("\\:");
domainName = ary[0];
}
return domainName;
}
}
在common工程中新增依賴
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
3.5 Controller
/**
* 登入
* @param username
* @param password
* @param request
* @param response
* @return
*/
@RequestMapping(value="/login",method=RequestMethod.POST)
@ResponseBody
public TaotaoResult userLogin(String username, String password, HttpServletRequest request, HttpServletResponse response){
try {
TaotaoResult result = userService.userLogin(username, password);
if (result.getData() != null) {
//將token碼令牌儲存到cookie中
CookieUtils.setCookie(request, response, "TT_TOKEN", result.getData().toString());
}
return result;
} catch (Exception e) {
e.printStackTrace();
return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
}
}
四、通過token查詢使用者資訊
1. 介面文件
2. 需求分析
通過token去redis快取中查詢
Url:/user/token/{token}
引數: token 從路徑中獲取
返回資料:TaotaoResult返回使用者資訊
3. 程式碼編寫
3.1 dao
3.2 Service
3.3 ServiceImpl
/**
* 根據token碼查詢使用者資訊
*/
@Override
public TaotaoResult getUserByToken(String token) {
//1.從redis快取中取出使用者的資訊
String result = jedisClient.get(REDIS_USER_BASE_KEY+":"+token);
//2.判斷取出的結果是否存在
if (StringUtils.isNotEmpty(result)) {
//3.重新設定生命週期
jedisClient.expire(REDIS_USER_BASE_KEY+":"+token, REDIS_USER_EXPIRE);
//返回物件
TbUser user = JsonUtils.jsonToPojo(result, TbUser.class);
return TaotaoResult.ok(user);
} else {
return TaotaoResult.build(400, "該使用者登入已過期");
}
}
3.4 Controller
/**
* 根據token查詢使用者資訊
* @param token
* @return
*/
@RequestMapping("/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token){
TaotaoResult result = userService.getUserByToken(token);
return result;
}
3.5 加上回調函式,用jsonp跨域請求
/**
* 根據token查詢使用者資訊
* @param token
* @return
*/
@RequestMapping("/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token, String callback){
TaotaoResult result = userService.getUserByToken(token);
//判斷是否為json呼叫
if (StringUtils.isEmpty(callback)) {
return result;
} else {
MappingJacksonValue jsonValue = new MappingJacksonValue(result);
jsonValue.setJsonpFunction(callback);
return jsonValue;
}
}
五、安全退出
1. 介面文件
2. 需求分析
這個介面,其實就是將redis快取清空即可,這裡就不再寫了
六、靜態頁面
百度網盤:
連結:https://pan.baidu.com/s/1OJAbvJp71DT2_1m0yJLBlQ
提取碼:yaom
1.
2. 在springmvc.xml中新增靜態資源
3. 登入功能解釋