『高级篇』docker之开发用户服务EdgeService(13)
原创文章,欢迎转载。转载请注明:转载自IT人故事会,谢谢!
原文链接地址:『高级篇』docker之开发用户服务EdgeService(13)
上一节开发了用户服务,即将开发的是用户服务EdgeService,从这个调用关系,可以看到用户的EdgeService是一个服务的服务,首选调用用户服务,对用户信息基本的操作,调用信息服务实现发送短信,发送邮件,还需要实现登录和注册的功能,并且登录是一个单点登录需要支持其他的系统,支持课程的登录的EdgeService,对他的要求是无状态的,还需要集中式的缓存redis。这么多服务集中于一身说明它是一个非常复杂的服务,不过也没关系,我们从头到尾把他开发完成。源码:github.com/limingios/m…
新建maven模块user-edge-service
- 引入user-thrift-service-api 和 message-thrift-service-api的pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.3.RELEASE</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.idig8</groupId><artifactId>user-edge-service</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.apache.thrift</groupId><artifactId>libthrift</artifactId><version>0.10.0</version></dependency><dependency><groupId>com.idig8</groupId><artifactId>user-thrift-service-api</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.idig8</groupId><artifactId>message-thrift-service-api</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot</artifactId><version>RELEASE</version><scope>compile</scope></dependency></dependencies></project>
复制代码
- redis的工具类,用于无状态的存储,token信息 保存用的userInfo
RedisConfig
package com.idig8.user.redis;import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;/*** Redis缓存配置类*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Value("${spring.redis.timeout}")private int timeout;@Value("${spring.redis.password}")private String password;//缓存管理器@Beanpublic CacheManager cacheManager(RedisTemplate redisTemplate) {RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);//设置缓存过期时间cacheManager.setDefaultExpiration(10000);return cacheManager;}@Beanpublic JedisConnectionFactory redisConnectionFactory() {JedisConnectionFactory factory = new JedisConnectionFactory();factory.setHostName(host);factory.setPort(port);factory.setTimeout(timeout);factory.setPassword(password);return factory;}@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){StringRedisTemplate template = new StringRedisTemplate(factory);setSerializer(template);//设置序列化工具template.afterPropertiesSet();return template;}private void setSerializer(StringRedisTemplate template){Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setValueSerializer(jackson2JsonRedisSerializer);}
}复制代码
RedisClient
package com.idig8.user.redis;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/*** Created by liming*/
@Component
public class RedisClient {@Autowiredprivate RedisTemplate redisTemplate;public <T> T get(String key) {return (T)redisTemplate.opsForValue().get(key);}public void set(String key, Object value) {redisTemplate.opsForValue().set(key, value);}public void set(String key, Object value, int timeout) {redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);}public void expire(String key, int timeout) {redisTemplate.expire(key, timeout, TimeUnit.SECONDS);}}
复制代码
- Response 和 LoginResponse 统一返回字符串
Response
package com.idig8.user.response;import java.io.Serializable;/*** Created by liming*/
public class Response implements Serializable {public static final Response USERNAME_PASSWORD_INVALID = new Response("1001", "username or password invalid");public static final Response MOBILE_OR_EMAIL_REQUIRED = new Response("1002", "mobile or email is required");public static final Response SEND_VERIFYCODE_FAILED = new Response("1003", "send verify code failed");public static final Response VERIFY_CODE_INVALID = new Response("1004", "verifyCode invalid");public static final Response SUCCESS = new Response();private String code;private String message;public Response() {this.code = "0";this.message = "success";}public Response(String code, String message) {this.code = code;this.message = message;}public static Response exception(Exception e) {return new Response("9999", e.getMessage());}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}复制代码
LoginResponse
package com.idig8.user.response;/*** Created by liming*/
public class LoginResponse extends Response {private String token;public LoginResponse(String token) {this.token = token;}public String getToken() {return token;}public void setToken(String token) {this.token = token;}
}复制代码
- 客户端访问通过和服务端相同的协议进行通信
package com.idig8.user.thrift;import com.idig8.thrift.message.MessageService;
import com.idig8.thrift.user.UserService;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class ServiceProvider {@Value("${thrift.user.ip}")private String serverIp;@Value("${thrift.user.port}")private int serverPort;@Value("${thrift.message.ip}")private String messageServerIp;@Value("${thrift.message.port}")private int messageServerPort;private enum ServiceType {USER,MESSAGE}public UserService.Client getUserService() {return getService(serverIp, serverPort, ServiceType.USER);}public MessageService.Client getMessasgeService() {return getService(messageServerIp, messageServerPort, ServiceType.MESSAGE);}public <T> T getService(String ip, int port, ServiceType serviceType) {TSocket socket = new TSocket(ip, port, 3000);TTransport transport = new TFramedTransport(socket);try {transport.open();} catch (TTransportException e) {e.printStackTrace();return null;}TProtocol protocol = new TBinaryProtocol(transport);TServiceClient result = null;switch (serviceType) {case USER:result = new UserService.Client(protocol);break;case MESSAGE:result = new MessageService.Client(protocol);break;}return (T)result;}}复制代码
- controller 引入thrift的service方法和redis的操作工具类 用于redis的操作
因为userInfo是通过thrift自动升成的,里面很多方法太过麻烦,不利于开发查看数据内容,最好的方式自己创建一个对象,将自动升成userInfo转换成自定义的UserDTO,USerDTO最好的方式是在thrift的工程中进行的。如果多个项目,比较方便,单独的用户的edgeservice中进行DTO的话,只能他自己用业务不清晰。
package com.idig8.user.controller;import com.idig8.thrift.user.UserInfo;
import com.idig8.thrift.user.dto.UserDTO;
import com.idig8.user.redis.RedisClient;
import com.idig8.user.response.LoginResponse;
import com.idig8.user.response.Response;
import com.idig8.user.thrift.ServiceProvider;
import org.apache.commons.lang.StringUtils;
import org.apache.thrift.TException;
import org.apache.tomcat.util.buf.HexUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;import java.security.MessageDigest;
import java.util.Random;@Controller
public class UserController {@Autowiredprivate ServiceProvider serviceProvider;@Autowiredprivate RedisClient redisClient;@RequestMapping(value = "/login",method = RequestMethod.POST)@ResponseBodypublic Response login(@RequestParam("username")String username,@RequestParam("password")String password){
// 1. 验证用户名密码UserInfo userInfo = null;try {userInfo = serviceProvider.getUserService().getUserByName(username);} catch (TException e) {e.printStackTrace();return Response.USERNAME_PASSWORD_INVALID;}if (userInfo == null){return Response.USERNAME_PASSWORD_INVALID;}if(!userInfo.getPassword().equalsIgnoreCase(md5(password))){return Response.USERNAME_PASSWORD_INVALID;}// 2. 生成tokenString token = genToken();
// 3. 缓存用户//因为userInfo是通过thrift自动升成的,里面很多方法太过麻烦,不利于开发查看数据内容//最好的方式自己创建一个对象,将自动升成userInfo转换成自定义的UserDTOredisClient.set(token,toDTO(userInfo));return new LoginResponse(token);}@RequestMapping(value = "/sendVerifyCode", method = RequestMethod.POST)@ResponseBodypublic Response sendVerifyCode(@RequestParam(value="mobile", required = false) String mobile,@RequestParam(value="email", required = false) String email) {String message = "Verify code is:";String code = randomCode("0123456789", 6);try {boolean result = false;if(StringUtils.isNotBlank(mobile)) {result = serviceProvider.getMessasgeService().sendMobileMessage(mobile, message+code);redisClient.set(mobile, code);} else if(StringUtils.isNotBlank(email)) {result = serviceProvider.getMessasgeService().sendEmailMessage(email, message+code);redisClient.set(email, code);} else {return Response.MOBILE_OR_EMAIL_REQUIRED;}if(!result) {return Response.SEND_VERIFYCODE_FAILED;}} catch (TException e) {e.printStackTrace();return Response.exception(e);}return Response.SUCCESS;}@RequestMapping(value="/register", method = RequestMethod.POST)@ResponseBodypublic Response register(@RequestParam("username") String username,@RequestParam("password") String password,@RequestParam(value="mobile", required = false) String mobile,@RequestParam(value="email", required = false) String email,@RequestParam("verifyCode") String verifyCode) {if(StringUtils.isBlank(mobile) && StringUtils.isBlank(email)) {return Response.MOBILE_OR_EMAIL_REQUIRED;}if(StringUtils.isNotBlank(mobile)) {String redisCode = redisClient.get(mobile);if(!verifyCode.equals(redisCode)) {return Response.VERIFY_CODE_INVALID;}}else {String redisCode = redisClient.get(email);if(!verifyCode.equals(redisCode)) {return Response.VERIFY_CODE_INVALID;}}UserInfo userInfo = new UserInfo();userInfo.setUsername(username);userInfo.setPassword(md5(password));userInfo.setMobile(mobile);userInfo.setEmail(email);try {serviceProvider.getUserService().regiserUser(userInfo);} catch (TException e) {e.printStackTrace();return Response.exception(e);}return Response.SUCCESS;}private UserDTO toDTO(UserInfo userInfo) {UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(userInfo, userDTO);return userDTO;}private String genToken() {return randomCode("0123456789abcdefghijklmnopqrstuvwxyz", 32);}private String randomCode(String s, int size) {StringBuilder result = new StringBuilder(size);Random random = new Random();for(int i=0;i<size;i++) {int loc = random.nextInt(s.length());result.append(s.charAt(loc));}return result.toString();}private String md5(String password) {try {MessageDigest md5 = MessageDigest.getInstance("MD5");byte[] md5Bytes = md5.digest(password.getBytes("utf-8"));return HexUtils.toHexString(md5Bytes);} catch (Exception e) {e.printStackTrace();}return null;}
}复制代码
测试准备工作
- 数据库内添加一条记录(如果不添加记录会报getUsername是null,通过在线网站生成MD5的密码加密)
- 启动user-thrift-service 和 message-thrift-service 2个服务
- 启动user-edge-service
- 工具调用接口访问user-edge-service里面的接口,查看是否正常返回调用服务是否成功
- 登录操作查看是否redis内有存储内容
流程梳理
- 建立2个thrift api项目 user 和 message 通过thrift文件生成对应语言的方法。
- 建立2个服务端项目,各自引入user和message的api项目。
- 多种语言比较特殊,例如message里面需要两边都通过python端需要通过thirft生成对应的python代码方便python制作server端。java端调用需要通过
thirft升成对应的java代码方便其他项目的引用。 - 如果都是单语言的话,都是java的话,只需要生成一个thrift的java代码,和对应的server端服务端代码就可以了。
- 对于使用端只需要引用2个api就可以实现RPC的调用。但是需要记住的是他们之前的协议和传输内容必须一致才可以完成通信。
user-edge-service-client 新建--单点登录的服务
package com.idig8.user.client;import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.idig8.thrift.user.dto.UserDTO;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.codehaus.jackson.map.ObjectMapper;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;/*** Created by Michael on 2017/10/31.*/
public abstract class LoginFilter implements Filter {private static Cache<String, UserDTO> cache =CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(3, TimeUnit.MINUTES).build();public void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;String token = request.getParameter("token");if(StringUtils.isBlank(token)) {Cookie[] cookies = request.getCookies();if(cookies!=null) {for(Cookie c : cookies) {if(c.getName().equals("token")) {token = c.getValue();}}}}UserDTO userDTO = null;if(StringUtils.isNotBlank(token)) {userDTO = cache.getIfPresent(token);if(userDTO==null) {userDTO = requestUserInfo(token);if(userDTO!=null) {cache.put(token, userDTO);}}}if(userDTO==null) {response.sendRedirect("http://www.mooc.com/user/login");return;}login(request, response, userDTO);filterChain.doFilter(request, response);}protected abstract String userEdgeServiceAddr();protected abstract void login(HttpServletRequest request, HttpServletResponse response, UserDTO userDTO);private UserDTO requestUserInfo(String token) {String url = "http://"+userEdgeServiceAddr()+"/user/authentication";HttpClient client = new DefaultHttpClient();HttpPost post = new HttpPost(url);post.addHeader("token", token);InputStream inputStream = null;try {HttpResponse response = client.execute(post);if(response.getStatusLine().getStatusCode()!= HttpStatus.SC_OK) {throw new RuntimeException("request user info failed! StatusLine:"+response.getStatusLine());}inputStream = response.getEntity().getContent();byte[] temp = new byte[1024];StringBuilder sb = new StringBuilder();int len = 0;while((len = inputStream.read(temp))>0) {sb.append(new String(temp,0,len));}UserDTO userDTO = new ObjectMapper().readValue(sb.toString(), UserDTO.class);return userDTO;} catch (IOException e) {e.printStackTrace();} finally {if(inputStream!=null) {try{inputStream.close();}catch(Exception e) {e.printStackTrace();}}}return null;}public void destroy() {}
}复制代码
需要引入单点登录的模块进行实现的功能
PS:其实通过梳理发现这个还是有套路可寻的如何多语言进行通信,先生成对应的语言的代码,然后通过rpc的服务端和客户端,他们之前进行协议话的通信,服务端完成自身的业务逻辑,客户端就获取返回的结果。
『高级篇』docker之开发用户服务EdgeService(13)相关推荐
- 『高级篇』docker之开发课程EdgeService(16)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之开发课程EdgeService(16) 课程的edgeService依赖于课程服务的dubbo服务,对 ...
- 『高级篇』docker容器来说微服务优势和不足(四)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker容器来说微服务优势和不足(四) 来看看微服务有哪些优势和不足. 优势 独立性 从构建部署,扩容收容,容错 ...
- 『高级篇』docker之APIGateway(17)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之APIGateway(17) 这次说最后一个模块APIGateway,他的功能就是将我们客户端的请求统 ...
- 『高级篇』docker容器来说什么是微服务(三)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker容器来说什么是微服务(三) 上一节说了单体架构,单体架构也无法适应我们的服务,来说说微服务,看能否解决单 ...
- 『高级篇』docker之DockerSwarm的集群环境搭建(28)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之DockerSwarm的集群环境搭建(28) 上次了解了docker Swarm,这次一起动手操作,搭 ...
- 『高级篇』docker之安全认证kubernetes命令熟悉(40)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『高级篇』docker之安全认证kubernetes命令熟悉(40) 安全版的kubernetes集群我们部署完成了. 下面我 ...
- C++轻量级微服务_『高级篇』docker容器来说什么是微服务(三)
上一节说了单体架构,单体架构也无法适应我们的服务,来说说微服务,看能否解决单体架构的问题. 什么是微服务 最近两,三年才出现的新名词,虽然时间还不是很长,几乎每个软件从业人员对它有影响,也都通过微服务 ...
- 『中级篇』docker之CI/CD持续集成-项目生成镜像(76)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『中级篇』docker之CI/CD持续集成-项目生成镜像(76) 开始想用docker registry做私有镜像库,后来放弃 ...
- 『中级篇』docker导学(一)
原创文章,欢迎转载.转载请注明:转载自IT人故事会,谢谢! 原文链接地址:『中级篇』docker导学(一) 这两年容器技术及其相关工具,平台异常火爆.在各大技术论坛或云计算峰会议题中,都会占很大比重, ...
最新文章
- 详解深度学习的可解释性研究(上篇)
- 树状数组 poj 2352
- Java小故事(一)
- 生产者,消费者,CDN
- PostgreSQL中表名、字段名大小写问题
- Android开发技巧——ViewPager加View情况封装PagerAdapter的实现类
- 获取apk安装包sha1的值
- linux udhcpc指令,dhcpclient和udhcpc区别和用法
- linux开机桌面出现网格,[转自linux联盟]openfoam 网格类编程
- 新版office365介绍
- Bailian2950 摘花生【贪心】
- 夜间灯光数据下载(DMSP/OLS,NPP/VIIRS、珞珈一号网址)
- Matlab简单教程:条件分支
- 本地win10服务器不能复制文件,解决Win10无法复制文件并提示“0x80070032”错误的方法...
- win8计算机无法安装打印机驱动程序,win8系统安装打印机驱动失败怎么办|win8系统安装打印机驱动失败的解决方法...
- TOM邮箱|国内适合商务人士的邮箱是什么邮箱
- Android P 隐藏状态栏电池图标
- 平台如何实现实人认证?
- 7-2 sdust-Java-学生成绩读取与排序 (20 分)
- MATH1013总结
热门文章
- AngularJS中的方法参数的问题
- (转载)C语言右移运算符的问题(特别当与取反运算符一起时)
- CNN破解简单验证码(Tensorflow实现)
- 注解描述(持续更新)
- VS2013 未找到与約束ContractName
- Centos6.4安装jdk
- 【转】Android android listview的HeadView左右切换图片(仿新浪,网易,百度等切换图片)...
- Jquery,Ready函数.
- poj 3373 Changing Digits
- Unix/Linux 中的 shell 机制