欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于springboot搭建的web系統(tǒng)架構的方法步驟

 更新時間:2019年04月25日 11:30:16   作者:Oven5217  
這篇文章主要介紹了基于springboot搭建的web系統(tǒng)架構的方法步驟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

從接觸springboot開始,便深深的被它的簡潔性深深的折服了,精簡的配置,方便的集成,使我再也不想用傳統(tǒng)的ssm框架來搭建項目,一大堆的配置文件,維護起來很不方便,集成的時候也要費力不少。從第一次使用springboot開始,一個簡單的main方法,甚至一個配置文件也不需要(當然我是指的沒有任何數(shù)據(jù)交互,沒有任何組件集成的情況),就可以把一個web項目啟動起來,下面總結一下自從使用springboot依賴,慢慢完善的自己的一個web系統(tǒng)的架構,肯定不是最好的,但平時自己用著很舒服。

1. 配置信息放到數(shù)據(jù)庫里邊

個人比較不喜歡配置文件,因此有一個原則,配置文件能不用就不用,配置信息能少些就少些,配置內容能用代碼寫堅決不用xml,因此我第一個想到的就是,能不能把springboot的配置信息寫到數(shù)據(jù)庫里,在springboot啟動的時候自動去加載,而在application.properties里邊只寫一個數(shù)據(jù)源。最終找到了方法:

注意圖中箭頭指向的兩行,構造了一個properties對象,然后將這個對象放到了springboot的啟動對象application中,properties是一個類似map的key-value容器,springboot可以將其中的東西當做成原來application.properties中的內容一樣,因此在properties對象的內容也就相當于寫在了application.properties文件中。知道了這個之后就簡單了,我們將原本需要寫在application.properties中的所有配置信息寫在數(shù)據(jù)庫中,在springboot啟動的時候從數(shù)據(jù)庫中讀取出來放到properties對象中,然后再將這個對象set到application中即可。上圖中PropertyConfig.loadProperties()方法就是進行了這樣的操作,代碼如下:

PropertyConfig.java

public class PropertyConfig {

  /**
   * 生成Properties對象
   */
  public static Properties loadProperties() {
    Properties properties = new Properties();
    loadPropertiesFromDb(properties);
    return properties;
  }

  /**
   * 從數(shù)據(jù)庫中加載配置信息
   */
  private static void loadPropertiesFromDb(Properties properties) {
    InputStream in = PropertyConfig.class.getClassLoader().getResourceAsStream("application.properties");
    try {
      properties.load(in);
    } catch (Exception e) {
      e.printStackTrace();
    }
    String profile = properties.getProperty("profile");
    String driverClassName = properties.getProperty("spring.datasource.driver-class-name");
    String url = properties.getProperty("spring.datasource.url");
    String userName = properties.getProperty("spring.datasource.username");
    String password = properties.getProperty("spring.datasource.password");

    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      Class.forName(driverClassName);
      String tableName = "t_config_dev";
      if ("pro".equals(profile)) {
        tableName = "t_config_pro";
      }
      String sql = "select * from " + tableName;
      conn = DriverManager.getConnection(url, userName, password);
      pstmt = conn.prepareStatement(sql);
      rs = pstmt.executeQuery();
      while (rs.next()) {
        String key = rs.getString("key");
        String value = rs.getString("value");
        properties.put(key, value);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        if (conn != null) {
          conn.close();
        }
        if (pstmt != null) {
          pstmt.close();
        }
        if (rs != null) {
          rs.close();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

}

代碼中,首先使用古老的jdbc技術,讀取數(shù)據(jù)庫t_config表,將表中的key-value加載到properties中,代碼中profile是為了區(qū)分開發(fā)環(huán)境和生產環(huán)境,以便于確定從那張表中加載配置文件,數(shù)據(jù)庫中的配置信息如下:

這樣以后,application.properties中就不用再寫很多的配置信息,而且,如果將這些配置信息放到數(shù)據(jù)庫中之后,如果起多個應用可是公用這一張表,這樣也可以做到配置信息的公用的效果,這樣修改以后,配置文件中就只有數(shù)據(jù)源的信息了:

profile代表使用哪個環(huán)境,代碼中可以根據(jù)這個信息來從開發(fā)表中加載配置信息還是從生產表中加載配置信息。

2. 統(tǒng)一返回結果

一般web項目中,大多數(shù)都是接口,以返回json數(shù)據(jù)為主,因此統(tǒng)一一個返回格式很必要。在本示例中,建了一個BaseController,所有的Controller都需要繼承這個類,在這個BaseController中定義了成功的返回和失敗的返回,在其他業(yè)務的Controller中,返回的時候,只需要return super.success(xxx)或者return super.fail(xxx, xxx)即可,例:

說到這里,返回給前臺的狀態(tài)碼,建議也是封裝成一個枚舉類型,不建議直接返回200、400之類的,不方便維護也不方便查詢。那么BaseController里做了什么呢?如下:

定義一個ResultInfo類,該類只有兩個屬性,一個是Integer類型的狀態(tài)碼,一個是泛型,用于成功時返回給前臺的數(shù)據(jù),和失敗時返回給前臺的提示信息。

3. 統(tǒng)一異常捕獲

在上一步中的Controller代碼中看到拋出了一個自定義的異常,在Controller中,屬于最外層的代碼了,這個時候如果有異常就不能直接拋出去了,這里再拋出去就沒有人處理了,服務器只能返回給前臺一個錯誤,用戶體驗不好。因此,建議所有的Controller代碼都用try-catch包裹,捕獲到異常后統(tǒng)一進行處理,然后再給前臺一個合理的提示信息。在上一步中拋出了一個自定義異常:

throw new MyException(ResultEnum.DELETE_ERROR.getCode(), "刪除員工出錯,請聯(lián)系網(wǎng)站管理人員。", e);

該自定義異常有三個屬性,分別是異常狀態(tài)碼,異常提示信息,以及捕獲到的異常對象,接下來定義一個全局的異常捕獲,統(tǒng)一對異常進行處理:

@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandle {

  /**
   * 處理捕獲的異常
   */
  @ExceptionHandler(value = Exception.class)
  public Object handleException(Exception e, HttpServletRequest request, HttpServletResponse resp) throws IOException {
    log.error(AppConst.ERROR_LOG_PREFIX + "請求地址:" + request.getRequestURL().toString());
    log.error(AppConst.ERROR_LOG_PREFIX + "請求方法:" + request.getMethod());
    log.error(AppConst.ERROR_LOG_PREFIX + "請求者IP:" + request.getRemoteAddr());
    log.error(AppConst.ERROR_LOG_PREFIX + "請求參數(shù):" + ParametersUtils.getParameters(request));
    if (e instanceof MyException) {
      MyException myException = (MyException) e;
      log.error(AppConst.ERROR_LOG_PREFIX + myException.getMsg(), myException.getE());
      if (myException.getCode().equals(ResultEnum.SEARCH_PAGE_ERROR.getCode())) {
        JSONObject result = new JSONObject();
        result.put("code", myException.getCode());
        result.put("msg", myException.getMsg());
        return result;
      } else if (myException.getCode().equals(ResultEnum.ERROR_PAGE.getCode())) {
        resp.sendRedirect("/err");
        return "";
      } else {
        return new ResultInfo<>(myException.getCode(), myException.getMsg());
      }
    } else if (e instanceof UnauthorizedException) {
      resp.sendRedirect("/noauth");
      return "";
    } else {
      log.error(AppConst.ERROR_LOG_PREFIX + "錯誤信息:", e);
    }
    resp.sendRedirect("/err");
    return "";
  }

}

統(tǒng)一捕獲異常之后,可以進行相應的處理,我這里沒有進行特殊的處理,只是進行了一下區(qū)分,獲取數(shù)據(jù)的接口拋出的異常,前臺肯定是使用的ajax請求,因此返回前臺一個json格式的信息,提示出錯誤內容。如果是跳轉頁面拋出的異常,類似404之類的,直接跳轉到自定義的404頁面。補充一點,springboot項目默認是有/error路由的,返回的就是error頁面,所以,如果你在你的項目中定義一個error.html的頁面,如果報404錯誤,會自動跳轉到該頁面。

補充,統(tǒng)一異常處理類中使用了一個注解@Slf4j,該注解是lombok包中的,項目中加入了該依賴后,再也不用寫繁瑣的get、set等代碼,當然類似的像上邊的聲明log對象的代碼也不用寫了:

4. 日志配置文件區(qū)分環(huán)境

本示例使用的是logback日志框架。需要在resources目錄中添加logback.xml配置文件,這是一個比較頭疼的地方,我本來想一個配置文件也沒有的,奈何我也不知道怎么將這個日志的配置文件放到數(shù)據(jù)庫中,所以暫時先這么著了,好在幾乎沒有需要改動它的時候。

我在項目中添加了兩個日志的配置文件,分別是logback-dev.xml和logback-pro.xml可以根據(jù)不同的環(huán)境決定使用哪個配置文件,在數(shù)據(jù)庫配置表中(相當于寫在了application.properties中)添加一條配置logging.config=classpath:logback-dev.xml來區(qū)分使用哪個文件作為日志的配置文件,配置文件內容如下:

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <property name="LOG_HOME" value="/Users/oven/log/demo"/>
  <!-- INFO日志定義 -->
  <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${LOG_HOME}/demo.info.log</File>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <FileNamePattern>${LOG_HOME}/demo.info.%d{yyyy-MM-dd}.log</FileNamePattern>
      <maxHistory>180</maxHistory>
    </rollingPolicy>
    <encoder>
      <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</Pattern>
      <charset>UTF-8</charset>
    </encoder>
  </appender>

  <!-- ERROR日志定義 -->
  <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${LOG_HOME}/demo.error.log</File>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <FileNamePattern>${LOG_HOME}/demo.error.%d{yyyy-MM-dd}.log</FileNamePattern>
      <maxHistory>180</maxHistory>
    </rollingPolicy>
    <encoder>
      <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</Pattern>
      <charset>UTF-8</charset>
    </encoder>
  </appender>

  <!-- DEBUG日志定義 -->
  <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${LOG_HOME}/demo.debug.log</File>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <FileNamePattern>${LOG_HOME}/demo.debug.%d{yyyy-MM-dd}.log</FileNamePattern>
      <maxHistory>180</maxHistory>
    </rollingPolicy>
    <encoder>
      <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</Pattern>
      <charset>UTF-8</charset>
    </encoder>
  </appender>

  <!-- 定義控制臺日志信息 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="STDOUT"/>
  </root>
  <logger name="com.oven.controller" level="ERROR">
    <appender-ref ref="ERROR"/>
  </logger>
  <logger name="com.oven.exception" level="ERROR">
    <appender-ref ref="ERROR"/>
  </logger>
  <logger name="com.oven.mapper" level="DEBUG">
    <appender-ref ref="DEBUG"/>
  </logger>
  <logger name="com.oven.aop" level="INFO">
    <appender-ref ref="INFO"/>
  </logger>

</configuration>

在配置文件中,定義了三個級別的日志,info、debug和error分別輸出到三個文件中,便于查看。在生成日志文件的時候,進行了按照日志進行拆分的配置,每一個級別的日志每一天都會重新生成一個,根據(jù)日期進行命名,超過180天的日志將自動會刪除。當然你還可以按照日志大小進行拆分,我這里沒有進行這項的配置。

5. 全局接口請求記錄

進行全局的接口請求記錄,可以記錄接口的別調用情況,然后進行一些統(tǒng)計和分析,在本示例中,只是將全局的接口調用情況記錄到了info日志中,沒有進行相應的分析操作:

@Slf4j
@Aspect
@Component
public class WebLogAspect {

  @Pointcut("execution(public * com.oven.controller.*.*(..))")
  public void webLog() {
  }

  @Before("webLog()")
  public void doBefore() {
    // 獲取請求
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    @SuppressWarnings("ConstantConditions") HttpServletRequest request = attributes.getRequest();
    // 記錄請求內容
    log.info(AppConst.INFO_LOG_PREFIX + "請求地址:" + request.getRequestURL().toString());
    log.info(AppConst.INFO_LOG_PREFIX + "請求方法:" + request.getMethod());
    log.info(AppConst.INFO_LOG_PREFIX + "請求者IP:" + request.getRemoteAddr());
    log.info(AppConst.INFO_LOG_PREFIX + "請求參數(shù):" + ParametersUtils.getParameters(request));
  }

  @AfterReturning(returning = "ret", pointcut = "webLog()")
  public void doAfterReturning(Object ret) {
    // 請求返回的內容
    if (ret instanceof ResultInfo) {
      log.info(AppConst.INFO_LOG_PREFIX + "返回結果:" + ((ResultInfo) ret).getCode().toString());
    }
  }

}

6. 集成shiro實現(xiàn)權限校驗

集成shirl,輕松的實現(xiàn)了權限的管理,如果對shiro不熟悉朋友,還需要先把shiro入門一下才好,shiro的集成一般都需要自定義一個realm,來進行身份認證和授權,因此先來一個自定義realm:

MyShiroRealm.java

public class MyShiroRealm extends AuthorizingRealm {

  @Resource
  private MenuService menuService;
  @Resource
  private UserService userService;

  /**
   * 授權
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    User user = (User) principals.getPrimaryPrincipal();
    List<String> permissions = menuService.getAllMenuCodeByUserId(user.getId());
    authorizationInfo.addStringPermissions(permissions);
    return authorizationInfo;
  }

  /**
   * 身份認證
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String userName = String.valueOf(token.getUsername());
    // 從數(shù)據(jù)庫獲取對應用戶名的用戶
    User user = userService.getByUserName(userName);
    // 賬號不存在
    if (user == null) {
      throw new UnknownAccountException(ResultEnum.NO_THIS_USER.getValue());
    }

    Md5Hash md5 = new Md5Hash(token.getPassword(), AppConst.MD5_SALT, 2);
    // 密碼錯誤
    if (!md5.toString().equals(user.getPassword())) {
      throw new IncorrectCredentialsException(ResultEnum.PASSWORD_WRONG.getValue());
    }

    // 賬號鎖定
    if (user.getStatus().equals(1)) {
      throw new LockedAccountException(ResultEnum.USER_DISABLE.getValue());
    }
    ByteSource salt = ByteSource.Util.bytes(AppConst.MD5_SALT);
    return new SimpleAuthenticationInfo(user, user.getPassword(), salt, getName());
  }

}

自定義完realm后需要一個配置文件但自定義的realm配置到shiro里:

ShiroConfig.java

@Configuration
public class ShiroConfig {

  @Bean
  public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/static/**", "anon");
    filterChainDefinitionMap.put("/css/**", "anon");
    filterChainDefinitionMap.put("/font/**", "anon");
    filterChainDefinitionMap.put("/js/**", "anon");
    filterChainDefinitionMap.put("/*.js", "anon");
    filterChainDefinitionMap.put("/login", "anon");
    filterChainDefinitionMap.put("/doLogin", "anon");
    filterChainDefinitionMap.put("/**", "authc");
    shiroFilterFactoryBean.setLoginUrl("/login");
    shiroFilterFactoryBean.setSuccessUrl("/");
    shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
  }

  /**
   * 憑證匹配器
   */
  @Bean
  public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("MD5");
    hashedCredentialsMatcher.setHashIterations(2);
    return hashedCredentialsMatcher;
  }

  @Bean
  public MyShiroRealm myShiroRealm() {
    MyShiroRealm myShiroRealm = new MyShiroRealm();
    myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    return myShiroRealm;
  }


  @Bean
  public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myShiroRealm());
    return securityManager;
  }

  /**
   * 開啟shiro aop注解
   */
  @Bean
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
  }

  @Bean(name = "simpleMappingExceptionResolver")
  public SimpleMappingExceptionResolver
  createSimpleMappingExceptionResolver() {
    SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
    Properties mappings = new Properties();
    mappings.setProperty("DatabaseException", "databaseError");
    mappings.setProperty("UnauthorizedException", "403");
    r.setExceptionMappings(mappings);
    r.setDefaultErrorView("error");
    r.setExceptionAttribute("ex");
    return r;
  }

}

身份認證如果簡單的理解的話,你可以理解為登錄的過程。授權就是授予你權利,代表你在這個系統(tǒng)中有權限做什么動作,具體shiro的內容小伙伴們自行去學習吧。

7. 登錄校驗,安全攔截

在集成了shiro之后,登錄操作就需要使用到自定義的realm了,具體的登錄代碼如下:

/**
   * 登錄操作
   *
   * @param userName 用戶名
   * @param pwd   密碼
   */
  @RequestMapping("/doLogin")
  @ResponseBody
  public Object doLogin(String userName, String pwd, HttpServletRequest req) throws MyException {
    try {
      Subject subject = SecurityUtils.getSubject();
      UsernamePasswordToken token = new UsernamePasswordToken(userName, pwd);
      subject.login(token);

      User userInDb = userService.getByUserName(userName);
      // 登錄成功后放入application,防止同一個賬戶多人登錄
      ServletContext application = req.getServletContext();
      @SuppressWarnings("unchecked")
      Map<String, String> loginedMap = (Map<String, String>) application.getAttribute(AppConst.LOGINEDUSERS);
      if (loginedMap == null) {
        loginedMap = new HashMap<>();
        application.setAttribute(AppConst.LOGINEDUSERS, loginedMap);
      }
      loginedMap.put(userInDb.getUserName(), req.getSession().getId());

      // 登錄成功后放入session中
      req.getSession().setAttribute(AppConst.CURRENT_USER, userInDb);
      logService.addLog("登錄系統(tǒng)!", "成功!", userInDb.getId(), userInDb.getNickName(), IPUtils.getClientIPAddr(req));
      return super.success("登錄成功!");
    } catch (Exception e) {
      User userInDb = userService.getByUserName(userName);
      if (e instanceof UnknownAccountException) {
        logService.addLog("登錄系統(tǒng)!", "失敗[" + ResultEnum.NO_THIS_USER.getValue() + "]", 0, "", IPUtils.getClientIPAddr(req));
        return super.fail(ResultEnum.NO_THIS_USER.getCode(), ResultEnum.NO_THIS_USER.getValue());
      } else if (e instanceof IncorrectCredentialsException) {
        logService.addLog("登錄系統(tǒng)!", "失敗[" + ResultEnum.PASSWORD_WRONG.getValue() + "]", userInDb.getId(), userInDb.getNickName(), IPUtils.getClientIPAddr(req));
        return super.fail(ResultEnum.PASSWORD_WRONG.getCode(), ResultEnum.PASSWORD_WRONG.getValue());
      } else if (e instanceof LockedAccountException) {
        logService.addLog("登錄系統(tǒng)!", "失敗[" + ResultEnum.USER_DISABLE.getValue() + "]", userInDb.getId(), userInDb.getNickName(), IPUtils.getClientIPAddr(req));
        return super.fail(ResultEnum.USER_DISABLE.getCode(), ResultEnum.USER_DISABLE.getValue());
      } else {
        throw new MyException(ResultEnum.UNKNOW_ERROR.getCode(), "登錄操作出錯,請聯(lián)系網(wǎng)站管理人員。", e);
      }
    }
  }

身份認證的操作交給了shiro,利用用戶名和密碼構造一個身份的令牌,調用shiro的login方法,這個時候就會進入自定義reaml的身份認證方法中,也就是上一步中的doGetAuthenticationInfo方法,具體的認證操作看上一步的代碼,無非就是賬號密碼的校驗等。身份認證的時候,通過拋出異常的方式給登錄操作返回信息,從而在登錄方法中判斷身份認證失敗后的信息,從而返回給前臺進行提示。

在身份認證通過后,拿到當前登錄用戶的信息,首先放到session中,便于后續(xù)的使用。其次在放到application對象中,防止同一個賬號的多次登錄。

有了身份任何和授權自然就少不了安全校驗,在本示例中使用了一個攔截器來實現(xiàn)安全校驗的工作:

SecurityInterceptor.java

@Component
public class SecurityInterceptor extends HandlerInterceptorAdapter {

  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
    resp.setContentType("text/plain;charset=UTF-8");
    String servletPath = req.getServletPath();
    // 放行的請求
    if (servletPath.startsWith("/login") || servletPath.startsWith("/doLogin") || servletPath.equals("/err")) {
      return true;
    }
    if (servletPath.startsWith("/error")) {
      resp.sendRedirect("/err");
      return true;
    }

    // 獲取當前登錄用戶
    User user = (User) req.getSession().getAttribute(AppConst.CURRENT_USER);

    // 沒有登錄狀態(tài)下訪問系統(tǒng)主頁面,都跳轉到登錄頁,不提示任何信息
    if (servletPath.startsWith("/")) {
      if (user == null) {
        resp.sendRedirect(getDomain(req) + "/login");
        return false;
      }
    }

    // 未登錄或會話超時
    if (user == null) {
      String requestType = req.getHeader("X-Requested-With");
      if ("XMLHttpRequest".equals(requestType)) { // ajax請求
        ResultInfo<Object> resultInfo = new ResultInfo<>();
        resultInfo.setCode(ResultEnum.SESSION_TIMEOUT.getCode());
        resultInfo.setData(ResultEnum.SESSION_TIMEOUT.getValue());
        resp.getWriter().write(JSONObject.toJSONString(resultInfo));
        return false;
      }
      String param = URLEncoder.encode(ResultEnum.SESSION_TIMEOUT.getValue(), "UTF-8");
      resp.sendRedirect(getDomain(req) + "/login?errorMsg=" + param);
      return false;
    }

    // 檢查是否被其他人擠出去
    ServletContext application = req.getServletContext();
    @SuppressWarnings("unchecked")
    Map<String, String> loginedMap = (Map<String, String>) application.getAttribute(AppConst.LOGINEDUSERS);
    if (loginedMap == null) { // 可能是掉線了
      String requestType = req.getHeader("X-Requested-With");
      if ("XMLHttpRequest".equals(requestType)) { // ajax請求
        ResultInfo<Object> resultInfo = new ResultInfo<>();
        resultInfo.setCode(ResultEnum.LOSE_LOGIN.getCode());
        resultInfo.setData(ResultEnum.LOSE_LOGIN.getValue());
        resp.getWriter().write(JSONObject.toJSONString(resultInfo));
        return false;
      }
      String param = URLEncoder.encode(ResultEnum.LOSE_LOGIN.getValue(), "UTF-8");
      resp.sendRedirect(getDomain(req) + "/login?errorMsg=" + param);
      return false;
    }
    String loginedUserSessionId = loginedMap.get(user.getUserName());
    String mySessionId = req.getSession().getId();

    if (!mySessionId.equals(loginedUserSessionId)) {
      String requestType = req.getHeader("X-Requested-With");
      if ("XMLHttpRequest".equals(requestType)) { // ajax請求
        ResultInfo<Object> resultInfo = new ResultInfo<>();
        resultInfo.setCode(ResultEnum.OTHER_LOGINED.getCode());
        resultInfo.setData(ResultEnum.OTHER_LOGINED.getValue());
        resp.getWriter().write(JSONObject.toJSONString(resultInfo));
        return false;
      }
      String param = URLEncoder.encode(ResultEnum.OTHER_LOGINED.getValue(), "UTF-8");
      resp.sendRedirect(getDomain(req) + "/login?errorMsg=" + param);
      return false;
    }
    return true;
  }

  /**
   * 獲得域名
   */
  private String getDomain(HttpServletRequest request) {
    String path = request.getContextPath();
    return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path;
  }

}

在攔截器中,首先對一些不需要校驗的請求進行放行,例如登錄動作、登錄頁面請求以及錯誤頁面等。然后獲取當前登錄的用戶,如果沒有登錄則自動跳轉到登錄頁面。在返回前臺的時候,判斷請求屬于同步請求還是異步請求,如果是同步請求,直接進行頁面的跳轉,跳轉到登錄頁面。如果是異步請求,則返回前臺一個json數(shù)據(jù),提示前臺登錄信息失效。這里補充一點,前臺可以使用ajaxhook進行異步請求的捕獲,相當于一個前端的全局攔截器,攔截所有的異步請求,可以監(jiān)視所有異步請求的返回結果,如果返回的是登錄失效,則進行跳轉到登錄頁面的操作。具體ajaxhook的使用方法請自行學習,本示例中暫時沒有使用。

下面是判斷同一個賬號有沒有多次登錄,具體方法就是使用當前的sessionId,將當前登錄用戶和請求sissionId作為一個key-value放到了application中,如果該用戶的sessionId發(fā)生了變化,說明又有一個人登錄了該賬號,然后就進行相應的提示操作。

8. 配置虛擬路徑

web項目中免不了并上傳的操作,圖片或者文件,如果上傳的是圖片,一般還要進行回顯的操作,我們不想將上傳的文件直接存放在項目的目錄中,而是放在一個自定義的目錄,同時項目還可以訪問:

這樣在進行上傳操作的時候,就可以將上傳的文件放到項目以外的目錄中,然后外部訪問的時候,通過虛擬路徑進行映射訪問。

9. 集成redis緩存

springboot的強悍就是集成一個東西太方便了,如果你不想做任何配置,只需要加入redis的依賴,然后在配置文件(本示例中配置是在數(shù)據(jù)庫中)中添加redis的鏈接信息,就可以在項目中使用redis了。

本示例中使用redis做緩存,首先寫了一個緩存的類,代碼有些長不做展示。然后在service層進行緩存的操作:

代碼中使用了double check的騷操作,防止高并發(fā)下緩存失效的問題(雖然我的示例不可能有高并發(fā),哈哈)。另外就是緩存更新的問題,網(wǎng)上說的有很多,先更新數(shù)據(jù)再更新緩存,先更新緩存再更新數(shù)據(jù)庫等等,具體要看你是做什么,本示例中沒有什么需要特殊注意的地方,因此就先更新數(shù)據(jù)庫,然后再移除緩存:

10. 項目代碼和依賴以及靜態(tài)資源分別打包

之前遇到一個問題,springboot打包之后是一個jar文件,如果將所有依賴也打到這個jar包中的話,那么這個jar包動輒幾十兆,來回傳輸不說,如果想改動其中的一個配置內容,還異常的繁瑣,因此,將項目代碼,就是自己寫的代碼打成一個jar包(一般只有幾百k),然后將所有的依賴打包到一個lib目錄,然后再將所有的配置信息以及靜態(tài)文件打包到resources目錄,這樣,靜態(tài)文件可以直接進行修改,瀏覽器清理緩存刷新即可出現(xiàn)改動效果,而且打包出來的項目代碼也小了很多,至于依賴,一般都是不變的,所以也沒必要每次都打包它。具體操作就是在pom.xml中增加一個插件即可,代碼如下:

代碼太長,不做展示

11. 項目啟動

到現(xiàn)在都沒有貼一個項目的目錄結構,先來一張。目錄中項目跟目錄下的demo.sh就是啟動腳本,當時從網(wǎng)上抄襲改裝過來的,源代碼出自那位大師之手我就不知道了,先行謝過。在部署到服務器的時候,如果服務器上安裝好了jdk、maven、git,每次修改完代碼,直接git pull下來,然后mvn package打包,然后直接./demo.sh start就可以啟動項目,方便快速。慢著,忘記了,如果你提交到github中的application.properties中的數(shù)據(jù)源配置信息是開發(fā)環(huán)境的話,那么你在打包之后,target/resources中的application.properties中的數(shù)據(jù)源需要改成開發(fā)環(huán)境才可以啟動。當然如果你嫌麻煩,可以直接將開發(fā)環(huán)境的數(shù)據(jù)源配置push到github中,安不安全就要你自己考慮了。

12. 總結

示例中可能還有一些細節(jié)沒有說到,總之這個項目是慢慢的添磚添瓦弄出來的,自己在寫很多其他的項目的時候,都是以此項目為模板進行改造出來的,個人感覺很實用很方便,用著也很舒服。github地址:https://github.com/503612012/demo歡迎收藏。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • IntelliJ IDEA本地代碼覆蓋后恢復原來的代碼圖解

    IntelliJ IDEA本地代碼覆蓋后恢復原來的代碼圖解

    今天小編就為大家分享一篇關于IntelliJ IDEA本地代碼覆蓋后恢復原來的代碼圖解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • 在Java中將List轉換為String輸出過程解析

    在Java中將List轉換為String輸出過程解析

    這篇文章主要介紹了在Java中將List轉換為String輸出過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-09-09
  • Java對文件進行基本操作案例講解

    Java對文件進行基本操作案例講解

    這篇文章主要介紹了Java對文件進行基本操作案例講解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是本文的詳細內容,需要的朋友可以參考下
    2021-07-07
  • 簡單總結Java IO中stream流的使用方法

    簡單總結Java IO中stream流的使用方法

    這篇文章主要介紹了Java IO中stream流的使用方法的簡單總結,包括數(shù)據(jù)流和打印流等Java入門學習中的基礎知識,需要的朋友可以參考下
    2016-03-03
  • spring-boot整合ehcache實現(xiàn)緩存機制的方法

    spring-boot整合ehcache實現(xiàn)緩存機制的方法

    spring-boot是一個快速的集成框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發(fā)過程。這篇文章主要介紹了spring-boot整合ehcache實現(xiàn)緩存機制,需要的朋友可以參考下
    2018-01-01
  • Java集合之LinkedHashSet集合詳解

    Java集合之LinkedHashSet集合詳解

    這篇文章主要介紹了Java集合之LinkedHashSet集合詳解,具有可預知迭代順序的Set接口的哈希表和鏈表列表實現(xiàn),此實現(xiàn)與HashSet不同的是,后者維護著一個運行于所有條目的雙重鏈表列表,此鏈表定義了迭代順序,需要的朋友可以參考下
    2023-09-09
  • Java8中Stream流式操作指南之入門篇

    Java8中Stream流式操作指南之入門篇

    Java8中有兩大最為重要的改變,第一個是Lambda?表達式,另外一個則是Stream?API(java.util.stream.*),下面這篇文章主要給大家介紹了Java8中Stream流式操作指南之入門篇的相關資料,需要的朋友可以參考下
    2022-02-02
  • SpringBoot如何使用自定義注解實現(xiàn)接口限流

    SpringBoot如何使用自定義注解實現(xiàn)接口限流

    這篇文章主要介紹了SpringBoot如何使用自定義注解實現(xiàn)接口限流,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 詳解如何將JAR包發(fā)布到Maven中央倉庫

    詳解如何將JAR包發(fā)布到Maven中央倉庫

    這篇文章主要介紹了詳解如何將JAR包發(fā)布到Maven中央倉庫,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • Java設計模式以虹貓藍兔的故事講解代理模式

    Java設計模式以虹貓藍兔的故事講解代理模式

    代理模式是Java常見的設計模式之一。所謂代理模式是指客戶端并不直接調用實際的對象,而是通過調用代理,來間接的調用實際的對象
    2022-04-04

最新評論