一、验证数据是否可用
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缓存清空即可,这里就不再写了