利用springcloud搭建一个简易的分布式简历展示系统

开发环境

开发工具:idea+MAVEN
数据库:mysql+redis
jdk版本:1.8
web容器:springboot自带的tomcat
预计用到的框架:springboot+spring cloud+mybatis
前端:由于不是很懂前端框架,准备百度找几个对应的模板(基础的jquery我还是会)

准备工作

1.先准备一个能用的idea
2.设计好相应的模块以及表,想好哪些模块可以独立为一个服务
3.还要找找资料写好的代码怎么上传到github.
如何将本地代码上传至github
https://github.com/TangXuesong/sso.git 这是我的github地址
4.项目服务图:
利用springcloud搭建一个简易的分布式简历展示系统_第1张图片

表设计思路以及详细内容

1.每个项目都需要有一个用户登录系统,那么不可或非需要一个用户表(由于只是简单的简历展示,
权限啥的就不弄了)
2.既然是简历展示系统,那么肯定要有与简历相关的表,比如说简历表/简历详细信息表/工作经验表/
项目经验表。(这是从51job添加简历时想到的,一个用户多个简历,每个简历有多个公司,每个公司
有多个项目经验).
每个表其实要弄很多个备用字段防止以后要修改表字段很麻烦,但是由于这是个人做的简易项目就不这
么弄了,还有索引啥的sql优化也就不考虑了,毕竟没那么大的数据量哈哈

TBL_CSM_USER(用户表):
UR_ID						INT(11)			用户ID,主键自增
UR_NICKNAME					VARCHAR(50)		用户昵称
UR_EMAIL					VARCHAR(50)		注册邮箱(长度是自己估算的,没有那么认真去计较)
UR_PASSWORD					VARCHAR(200)	用户密码
UR_SEX						CHAR(1)			用户性别(0表示男/1表示女/默认是男)
UR_AGE						INT(3)			用户年龄
UR_REGISTER_TIME			VARCHAR(30)		用户注册时间(yyyy-mm-dd HH:mm:ss格式)
TBL_RSM_RESUME(用户简历表):
RS_ID						INT(11)			简历表ID,主键自增
RS_URID						INT(11)			对应用户ID
RS_NAME						VARCHAR(50)		简历名称
RS_CREATE_TIME				VARCHAR(30)		简历创建时间(yyyy-mm-dd HH:mm:ss格式)
RS_UPDATE_TIME				VARCHAR(30)		简历修改时间(yyyy-mm-dd HH:mm:ss格式)
RS_DEL_IND					CHAR(1)			删除标志(Y表示是/N表示否)
TBL_RSM_RESDETAIL(简历详细信息表):
RL_ID						INT(11)			简历详细信息表ID,主键自增
RL_RSID						INT(11)		对应简历ID
RL_NAME						VARCHAR(50)		姓名
RL_SEX						CHAR(1)			性别(0表示男/1表示女/默认是男)
RL_BIRTH					VARCHAR(11)		生日(YYYY-MM-DD)
RL_LOCAL					VARCHAR(20)		所在地
RL_EMAIL					VARCHAR(50)		邮箱
RL_PHONE					VARCHAR(20)		联系电话(设置成varchar是因为可能会有021-xxxx这样子)
RL_CURREV					DOUBLE(11,2)	目前月收入
RL_EXPREV					DOUBLE(11,2)	期望月收入			
RL_SLFINR					VARCHAR(500)	自我介绍
RL_OCCUPATION				VARCHAR(20)		职业
RL_SCHOOL					VARCHAR(50)		毕业院校(偷懒,毕业时间,院校类型,专业都用这一个字段)		
RL_CREATE_TIME				VARCHAR(30)		简历详细信息创建时间(yyyy-mm-dd HH:mm:ss格式)
RL_UPDATE_TIME				VARCHAR(30)		简历详细信息修改时间(yyyy-mm-dd HH:mm:ss格式)
TBL_RSM_WORKEXP(工作经验表):
WP_ID						INT(11)			工作经验表ID,主键自增
WP_RLID						INT(11)		对应简历ID
WP_COMPNAME					VARCHAR(100)	公司名称
WP_STARTTIME				VARCHAR(20)		开始工作时间(YYYY-MM)
WP_ENDTIME					VARCHAR(20)		结束工作时间(YYYY-MM)
WP_OCCUPATION				VARCHAR(20)		在该公司的职业
WP_WORKCONTEXT				VARCHAR(300)	工作描述
WP_CREATE_TIME				VARCHAR(30)		工作经验创建时间(yyyy-mm-dd HH:mm:ss格式)
WP_UPDATE_TIME				VARCHAR(30)		工作经验修改时间(yyyy-mm-dd HH:mm:ss格式)
TBL_RSM_PROJEXP(项目经验表):
PP_ID						INT(11)			项目经验表ID,主键自增
PP_WPID						INT(11)			对应工作经验ID
PP_NAME						VARCHAR(20)		项目名称
PP_STARTTIME				VARCHAR(20)		项目开始时间(YYYY-MM)
PP_ENDTIME					VARCHAR(20)		项目结束时间(YYYY-MM)
PP_PROJDESC					VARCHAR(300)	项目描述
PP_WORKDESC					VARCHAR(500)	项目中个人职业描述
PP_CREATE_TIME				VARCHAR(30)		项目经验创建时间(yyyy-mm-dd HH:mm:ss格式)
PP_UPDATE_TIME				VARCHAR(30)		项目经验修改时间(yyyy-mm-dd HH:mm:ss格式)

开始开发

1.用idea先新建一个Maven项目,名称叫resume.

2.在该项目基础上新建一个spring cloud eureka-server工程

	1.建好以后需要配置application.yml文件指定端口,服务名称.在启动类上加上注解@EurekaServer
	Application,表明该服务是一个EurekaServer服务.

3.新建一个resume-user工程(表明该工程提供用户服务)

	1.会用springboot/mybatis/web/eureka/rabbitmq到/
	2.建好以后需要配置application.yml文件指定端口,服务名称,在启动类上加上注解@EnableEurekaClient
	表明该服务是一个EurekaClient服务

4.以相同步骤新建两个工程resume-email/resume-center(邮箱发送和简历中心)

整体结构截图如下:
利用springcloud搭建一个简易的分布式简历展示系统_第2张图片

4.开始开发

1.配置mysql数据源,利用mybatis-generator生成相关mapper.xml和mapper.java文件.
2.配置redis
3.调试html文件做到能用的地步(这个花了我一下午)
4.先做注册功能,注册肯定是需要邮箱发送验证码的,具体代码如下:
Controller层:

    //发送验证码
    @RequestMapping("sendEmail")
    public String sendEmail(HttpSession session, String email) {
        String code = userService.sendEmail(email);
        if (code.equals("false")) {
            return "false";
        }
        HashMap<String, String> map = new HashMap<>();
        logger.info("email------:"+email+"---------code-------:"+code);
        map.put(email, code);
        session.setAttribute("regCode", map);
        return "true";
    }

Service层:

    //发送注册邮件
    @Override
    public String sendEmail(String email) {
        //先校验邮箱格式是否正确
        if (!email.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")) {
            return "false";
        }
        //先查询数据库,校验该邮箱是否已经注册.
        TblCsmUser user = tblCsmUserMapper.selectByEmail(email);
        if(user!=null)
            return "false";
        //生成六位数作为验证码
        String code = CodeUtils.createCode();
        //此处调用rabiitMQ通知邮箱服务发送邮件给对应邮箱.
        return code;
    }

Mapper层:

  <select id="selectByEmail" resultMap="BaseResultMap" parameterType="java.lang.String" >
    select
    <include refid="Base_Column_List" />
    from tbl_csm_user
    where UR_EMAIL = #{email,jdbcType=VARCHAR}
  </select>

以上是发送验证码功能(RABBITMQ部分得等我学会了再加上去,哈哈.目前就大概了解是个消息中间件,用来异步发送消息,节省时间)
接下来是注册功能:
Controller层:

    //注册
    @RequestMapping("register")
    public String register(HttpSession session, String email, String code, String password, String name) {
        //先从session中取出regCodeMap
        HashMap<String, String> regCode = (HashMap<String, String>) session.getAttribute("regCode");
        if (regCode == null)
            return "codeError";
        return userService.register(regCode, email, name, code, password);

    }

Service层:

 //注册账号
    @Override
    public String register(HashMap<String, String> regCode, String email, String name, String code, String password) {
        //先校验验证码和邮箱是否正确
        String sendCode = regCode.get(email);
        if(!code.equals(sendCode))
            return "codeError";

        //正确的情况下查询数据库该邮箱是否已经注册过
        TblCsmUser dbUser = tblCsmUserMapper.selectByEmail(email);
        if(dbUser!=null)
            return "hasError";
        //没有注册过则添加该用户
        TblCsmUser user=new TblCsmUser();
        user.setUrAge(20); //默认20
        user.setUrEmail(email);//注册邮箱
        user.setUrNickname(name);//用户昵称
        user.setUrRegisterTime(DateUtils.getCurrDatetoHHmmss()); //当前系统时间戳
        user.setUrSex("0"); //默认是男
        user.setUrPassword(MD5Utils.inputPassToFormPass(password));
        tblCsmUserMapper.insertSelective(user);
        return "true";
    }

5.注册功能写完以后该做登录了,登录实现逻辑挺简单,但是考虑到要做单点登录,所以注释较多,我想了很久差点没绕出来:
Controller层:

    //登录
    @RequestMapping("login")
    public String login(HttpServletResponse response, HttpSession session, String email, String password) {
        String result= userService.login(email, password);
        if(result.equals("false"))
            return "false";
        //写入redis数据库,将该token以及用户信息
        redisTemplate.opsForValue().set("user"+session.getId(),result,1800,TimeUnit.MINUTES);
        //写入session以示登录成功
        session.setAttribute("user",result);
        return session.getId();
    }

Service层:

    //登录
    @Override
    public String login(String email, String password) {
        //1.从数据库中获取该邮箱对应的用户
        TblCsmUser dbUser = tblCsmUserMapper.selectByEmail(email);
        if(dbUser==null)
            return "false";
        if(!dbUser.getUrPassword().equals(MD5Utils.inputPassToFormPass(password)))
            return "false";
        return JSON.toJSONString(dbUser);
    }

以上完成后其实用户就可以正常登录了(Cookie写入方法就不贴了,redis的使用推荐一个博客,简单易懂)
Springboot使用redis详解
接下来首先要考虑的是做简历中心(展示修改简历)到登录中心的一个登录过程,目前思路如下:

1.用户首次访问简历中心,简历中心拦截器校验会先调用登录中心的一个接口查看用户是否登录(这个接口的任务
应该是:获取cookie->没有cookie返回false->获取token,拿token去redis中查找->redis没有数据返回
false->redis有数据返回token给简历中心)
2.简历中心如果接收到返回的是false,则跳转到登录中心的登录界面去登录,如果接受到的是token,就拿该token
去redis中查找用户信息,如果没有找到就重复1步骤,找到的话就将该token存入session.(想到这里第1步
之前应该还有个步骤,那就是从session中获取token,如果没有token就照第1步,如果有token那就去redis
中查一下校验该token是否过期,没有过期的话就不会被拦截器拦截了)

以上涉及微服务之间调用接口,又得找资料学习一下

单点登录实现方式:
简历中心写一个拦截器,代码如下:

package com.txs.resume.resumecenter.interceptor;

import com.alibaba.fastjson.JSON;
import com.txs.resume.resumecenter.pojo.TblCsmUser;
import com.txs.resume.resumecenter.utils.RestCallUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

/*
*
* 使用拦截器校验该用户是否登录
*
* */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    private Logger logger= LogManager.getLogger(LoginInterceptor.class.getName());

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("进入到简历中心拦截器");
        Object user = request.getSession().getAttribute("resUser");
        logger.info(user);
        //先检查是否有用户登录信息,有就让其通过.
        if(user!=null){
            return true;
        }
        //检查登录中心返回的token是否有效,
        String token=request.getParameter("token");
        if(token!=null){
            //用该token去redis数据库查询取到对应的用户信息,存放在session中表示局部变量
            String userJson = stringRedisTemplate.opsForValue().get("user" + token);
            TblCsmUser csmUser = JSON.parseObject(userJson,TblCsmUser.class);
            request.getSession().setAttribute("resUser",csmUser);
            return true;
        }
        //获取登录中心的登录地址跳转过去进行登录并带上返回地址
        ServiceInstance serviceInstance = loadBalancerClient.choose(RestCallUtils.USERSERVICE);
        String redirect_uri=request.getRequestURL().toString();
        logger.info(redirect_uri);
        response.sendRedirect("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/login.html?redirect_uri="+redirect_uri);
        return false;

    }
}

登录中心也写一个拦截器:

package com.txs.resume.resumeuser.interceptor;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
*
* 使用拦截器校验该用户是否登录
*
* */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    private Logger logger= LogManager.getLogger(LoginInterceptor.class.getName());

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("进入到登录中心拦截器");
        Object user = request.getSession().getAttribute("user");
        //判断是否已经登录过,如果没有登录就放行去登录界面.
        logger.info(user);
        if(user==null){
            return true;
        }
        //登陆过就直接获取token并返回给简历中心
        String token = request.getSession().getId();
        logger.info(token);
        String redirect_url = request.getParameter("redirect_url");
        logger.info(redirect_url);
        response.sendRedirect(redirect_url+"?token="+token);
        return false;
    }
}

因为现在找工作第一,这个项目的简历模块就先搁置(毕竟是简单的增删改查),把注册模块的发送邮件集成为RABBITMQ再弄

1.邮箱服务代码如下:

package com.txs.resume.resumeemail.utils;

import jdk.nashorn.internal.runtime.logging.Logger;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;

@Component
@RabbitListener(queues = "email")
public class EmailListener {

    @Autowired
    JavaMailSender javaMailSender;

    @RabbitHandler
    public void sendEmail(HashMap<String, String> map) {
        System.out.println("发送验证码");
        String email = map.get("email");
        String code = map.get("code");
        SimpleMailMessage msg=new SimpleMailMessage();
        msg.setSubject("注册松松松简历系统验证码");
        msg.setFrom("[email protected]");
        msg.setTo(email);
        msg.setText("您的验证码为:"+code);
        msg.setSentDate(new Date());
        javaMailSender.send(msg);
    }


}

2.application.yml配置如下:

spring:
  application:
    name: resume-email
  mail:
    host: smtp.163.com
    username: t943044085@163.com
    password: RIBQYMASMCALNPAR
    default-encoding: UTF-8
    protocol: smtp
    port: 25


  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /test

3.用户中心配置rabbitMq代码如下:

package com.txs.resume.resumeuser.rabbit;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class RabbitmqConfiguration {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void initRabbitTemplate() {
        // 设置生产者消息确认
        rabbitTemplate.setConfirmCallback(new RabbitConfirmCallback());
    }

    /**
     * 申明队列
     *
     * @return
     */
    @Bean
    public Queue queue() {
        Map<String, Object> arguments = new HashMap<>(4);
        // 申明死信交换器
        arguments.put("x-dead-letter-exchange", "exchange-dlx");
        return new Queue("email", true, false, false, arguments);
    }

    /**
     * 没有路由到的消息将进入此队列
     *
     * @return
     */
    @Bean
    public Queue unRouteQueue() {
        return new Queue("queue-unroute");
    }

    /**
     * 死信队列
     *
     * @return
     */
    @Bean
    public Queue dlxQueue() {
        return new Queue("dlx-queue");
    }

    /**
     * 申明交换器
     *
     * @return
     */
    @Bean
    public Exchange exchange() {
        Map<String, Object> arguments = new HashMap<>(4);
        // 当发往exchange-rabbit-monitor的消息,routingKey和bindingKey没有匹配上时,将会由exchange-unroute交换器进行处理
        arguments.put("alternate-exchange", "exchange-unroute");
        return new DirectExchange("exchange-email", true, false, arguments);
    }

    @Bean
    public FanoutExchange unRouteExchange() {
        // 此处的交换器的名字要和 exchange() 方法中 alternate-exchange 参数的值一致
        return new FanoutExchange("exchange-unroute");
    }

    /**
     * 申明死信交换器
     *
     * @return
     */
    @Bean
    public FanoutExchange dlxExchange() {
        return new FanoutExchange("exchange-dlx");
    }

    /**
     * 申明绑定
     *
     * @return
     */
    @Bean
    public Binding binding() {
        return BindingBuilder.bind(queue()).to(exchange()).with("email").noargs();
    }

    @Bean
    public Binding unRouteBinding() {
        return BindingBuilder.bind(unRouteQueue()).to(unRouteExchange());
    }

    @Bean
    public Binding dlxBinding() {
        return BindingBuilder.bind(dlxQueue()).to(dlxExchange());
    }
}

上面是申明相应的rabbitmq队列与交换机,向队列发送消息很简单,一句话:

        //此处调用rabiitMQ通知邮箱服务发送邮件给对应邮箱.
        Map<String,String> map = new HashMap<>();
        map.put("email", email);
        map.put("code", code);
        rabbitTemplate.convertAndSend("email", map);

你可能感兴趣的:(利用springcloud搭建一个简易的分布式简历展示系统)