docker之开发用户服务EdgeService教程

新建maven模块user-edge-service

引入user-thrift-service-api 和 message-thrift-service-api的pom文件


 

 
    
   
   
    org.springframework.boot
    
   
    spring-boot-starter-parent
    
   
    1.5.3.RELEASE
    
  
    
  
   4.0.0
  

    
  
   com.idig8
  
    
  
   user-edge-service
  
    
  
   1.0-SNAPSHOT
  

    
   
    
    
     org.springframework.boot
     
    
     spring-boot-starter-web
     
    
    
    
     org.apache.thrift
     
    
     libthrift
     
    
     0.10.0
     
    
    
    
     com.idig8
     
    
     user-thrift-service-api
     
    
     1.0-SNAPSHOT
     
    
    
    
     com.idig8
     
    
     message-thrift-service-api
     
    
     1.0-SNAPSHOT
     
    
    
    
     org.springframework.boot
     
    
     spring-boot-starter-data-redis
     
    
    
    
     commons-lang
     
    
     commons-lang
     
    
     2.6
     
    
    
    
     org.springframework.boot
     
    
     spring-boot-starter-thymeleaf
     
    
    
    
     org.springframework.boot
     
    
     spring-boot
     
    
     RELEASE
     
    
     compile
     
    
  


 

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
br/>@Value("${spring.redis.port}")
br/>@Value("${spring.redis.password}")

//缓存管理器@Bean<br "="" rel="nofollow">br/>@Bean
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//设置缓存过期时间
cacheManager.setDefaultExpiration(10000);
return cacheManager;
}

@Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host);
factory.setPort(port);
factory.setTimeout(timeout);
factory.setPassword(password);
return factory;
}

@Bean
public RedisTemplate 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
``` java
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 {

    @Autowired
    private RedisTemplate redisTemplate;

    public 
  
    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 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 { @Autowired private ServiceProvider serviceProvider; @Autowired private RedisClient redisClient; @RequestMapping(value = "/login",method = RequestMethod.POST) @ResponseBody public 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. 生成token String token = genToken(); // 3. 缓存用户 //因为userInfo是通过thrift自动升成的,里面很多方法太过麻烦,不利于开发查看数据内容 //最好的方式自己创建一个对象,将自动升成userInfo转换成自定义的UserDTO redisClient.set(token,toDTO(userInfo)); return new LoginResponse(token); } @RequestMapping(value = "/sendVerifyCode", method = RequestMethod.POST) @ResponseBody public 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) @ResponseBody public 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 

测试准备工作

数据库内添加一条记录(如果不添加记录会报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
                  
                    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的服务端和客户端,他们之前进行协议话的通信,服务端完成自身的业务逻辑,客户端就获取返回的结果。

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注