《最佳化介面設計的思路》系列:第四篇—介面的許可權控制

sum墨發表於2023-09-25

前言

大家好!我是sum墨,一個一線的底層碼農,平時喜歡研究和思考一些技術相關的問題並整理成文,限於本人水平,如果文章和程式碼有表述不當之處,還請不吝賜教。

作為一名從業已達六年的老碼農,我的工作主要是開發後端Java業務系統,包括各種管理後臺和小程式等。在這些專案中,我設計過單/多租戶體系系統,對接過許多開放平臺,也搞過訊息中心這類較為複雜的應用,但幸運的是,我至今還沒有遇到過線上系統由於程式碼崩潰導致資損的情況。這其中的原因有三點:一是業務系統本身並不複雜;二是我一直遵循某大廠程式碼規約,在開發過程中儘可能按規約編寫程式碼;三是經過多年的開發經驗積累,我成為了一名熟練工,掌握了一些實用的技巧。

我們在做系統的時候,只要這個系統裡面存在角色和許可權相關的業務需求,那麼介面的許可權控制肯定必不可少。但是大家一搜介面許可權相關的資料,出來的就是整合Shrio、Spring Security等各種框架,然後下面一頓貼配置和程式碼,看得人云裡霧裡。實際上介面的許可權控制是整個系統許可權控制裡面很小的一環,沒有設計好底層資料結構,是無法做好介面的許可權控制的。那麼怎麼做一個系統的許可權控制呢?我認為有以下幾步:

那麼接下來我就按這個流程一一給大家說明許可權是怎麼做出來的。(注:只需要SpringBoot和Redis,不需要額外許可權框架。)

本文參考專案原始碼地址:summo-springboot-interface-demo

一、許可權底層表結構設計

第一,只要一個系統是給人用的,那麼這個系統就一定會有一張使用者表;第二,只要有人的地方,就一定會有角色許可權的劃分,最簡單的就是超級管理員、普通使用者;第三,如此常見的設計,會有一套相對規範的設計標準。
許可權底層表結構設計的標準就是:RBAC模型

1. RBAC模型簡介

RBAC(Role-Based Access Control)許可權模型的概念,即:基於角色的許可權控制。透過角色關聯使用者,角色關聯許可權的方式間接賦予使用者許可權。

回到業務需求上來,應該是下面這樣的要求:

上圖可以看出,使用者 多對多 角色 多對多 許可權

用表結構展示的話就是這樣,一共5張表,3張實體表,2張關聯表

2. 建表語句

(1) t_user

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `user_id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '使用者ID',
  `user_name` varchar(32) DEFAULT NULL COMMENT '使用者名稱稱',
  `gmt_create` datetime DEFAULT NULL COMMENT '建立時間',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
  `creator_id` bigint DEFAULT NULL COMMENT '建立人ID',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人ID',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;

(2) t_role

DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
  `role_id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `role_name` varchar(32) CHARACTER SET utf8mb4   DEFAULT NULL COMMENT '角色名稱',
  `role_code` varchar(32) CHARACTER SET utf8mb4   DEFAULT NULL COMMENT '角色code',
  `gmt_create` datetime DEFAULT NULL COMMENT '建立時間',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
  `creator_id` bigint DEFAULT NULL COMMENT '建立人ID',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人ID',
  PRIMARY KEY (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

(3) t_auth

DROP TABLE IF EXISTS `t_auth`;
CREATE TABLE `t_auth` (
  `auth_id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '許可權ID',
  `auth_code` varchar(32) DEFAULT NULL COMMENT '許可權code',
  `auth_name` varchar(32) CHARACTER SET utf8mb4  DEFAULT NULL COMMENT '許可權名稱',
  `gmt_create` datetime DEFAULT NULL COMMENT '建立時間',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
  `creator_id` bigint DEFAULT NULL COMMENT '建立人ID',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人ID',
  PRIMARY KEY (`auth_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

(4) t_user_role

DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
  `id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '物理ID',
  `user_id` bigint NOT NULL COMMENT '使用者ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `gmt_create` datetime DEFAULT NULL COMMENT '建立時間',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
  `creator_id` bigint DEFAULT NULL COMMENT '建立人ID',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人ID',
   PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

(5) t_role_auth

DROP TABLE IF EXISTS `t_role_auth`;
CREATE TABLE `t_role_auth` (
 `id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '物理ID',
  `role_id` bigint DEFAULT NULL COMMENT '角色ID',
  `auth_id` bigint DEFAULT NULL COMMENT '許可權ID',
  `gmt_create` datetime DEFAULT NULL COMMENT '建立時間',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
  `creator_id` bigint DEFAULT NULL COMMENT '建立人ID',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人ID',
   PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二、使用者身份認證和授權

上面已經把表設計好了,接下來就是程式碼開發了。不過,在開發之前我們要搞清楚認證授權這兩個詞是啥意思。

  • 什麼是認證?
    認證是確認一個使用者的身份,確保使用者是其所聲稱的人。它透過驗證使用者的身份資訊,例如使用者名稱和密碼,來確認使用者的身份。
  • 什麼是授權?
    授權是根據使用者的身份和許可權,給予使用者特定的訪問許可權或使用某些資源的權力。它確定使用者可以執行的操作,並限制他們不能執行的操作。授權確保使用者只能訪問他們被允許的內容和功能。

光看定義也很難懂,這裡我舉個例子配合說明。

現有兩個使用者:小A和小B;兩個角色:管理員和普通使用者;4個操作:新增/刪除/修改/查詢。圖例如下:

那麼,對於小A來說,認證就是小A登入系統後,會授予管理員的角色,授權就是授予小A新增/刪除/修改/查詢的許可權;
同理,對於小B來說,認證就是小B登入系統後,會授予普通使用者的角色,授權就是授予小B查詢的許可權。

接下來且看如何實現

1. 初始化資料

t_user表資料

INSERT INTO `t_user` (`user_id`, `user_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, '小A', '2023-09-21 09:48:14', '2023-09-21 09:48:19', -1, -1);
INSERT INTO `t_user` (`user_id`, `user_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (2, '小B', '2023-09-21 09:48:14', '2023-09-21 09:48:19', -1, -1);

t_role表資料

INSERT INTO `t_role` (`role_id`, `role_name`, `role_code`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, '管理員', 'admin', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_role` (`role_id`, `role_name`, `role_code`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (2, '普通使用者', 'normal', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);

t_auth表資料

INSERT INTO `t_auth` (`auth_id`, `auth_code`, `auth_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 'add', '新增', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_auth` (`auth_id`, `auth_code`, `auth_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (2, 'delete', '刪除', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_auth` (`auth_id`, `auth_code`, `auth_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (3, 'query', '查詢', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_auth` (`auth_id`, `auth_code`, `auth_name`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (4, 'update', '更新', '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);

t_user_role表資料

INSERT INTO `t_user_role` (`user_id`, `role_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 1, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_user_role` (`user_id`, `role_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (2, 2, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);

t_role_auth表資料

INSERT INTO `t_role_auth` (`role_id`, `auth_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 2, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_role_auth` (`role_id`, `auth_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 1, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_role_auth` (`role_id`, `auth_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 3, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_role_auth` (`role_id`, `auth_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (1, 4, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);
INSERT INTO `t_role_auth` (`role_id`, `auth_id`, `gmt_create`, `gmt_modified`, `creator_id`, `modifier_id`) VALUES (2, 3, '2023-09-21 09:52:45', '2023-09-21 09:52:47', -1, -1);

2、新增/user/login介面模擬登入

介面程式碼如下

@GetMapping("/login")
public ResponseEntity<String> userLogin(@RequestParam(required = true) String userName,
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) {
  return userService.login(userName, httpServletRequest, httpServletResponse);
}

業務程式碼如下

@Override
public ResponseEntity<String> login(String userName, HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) {
  //根據名稱查詢使用者資訊
  UserDO userDO = userMapper.selectOne(new QueryWrapper<UserDO>().lambda().eq(UserDO::getUserName, userName));
  if (Objects.isNull(userDO)) {
    return ResponseEntity.ok("未查詢到使用者");
  }
  //查詢當前使用者的角色資訊
  List<UserRoleDO> userRoleDOList = userRoleMapper.selectList(
            new QueryWrapper<UserRoleDO>().lambda().eq(UserRoleDO::getUserId, userDO.getUserId()));
  if (CollectionUtils.isEmpty(userRoleDOList)) {
    return ResponseEntity.ok("當前使用者沒有角色");
  }
  //查詢當前使用者的許可權
  List<RoleAuthDO> roleAuthDOS = roleAuthMapper.selectList(new QueryWrapper<RoleAuthDO>().lambda()
            .in(RoleAuthDO::getRoleId, userRoleDOList.stream().map(UserRoleDO::getRoleId).collect(
                Collectors.toList())));
  if (CollectionUtils.isEmpty(roleAuthDOS)) {
    return ResponseEntity.ok("當前角色沒有對應許可權");
  }
  //查詢許可權code
  List<AuthDO> authDOS = authMapper.selectList(new QueryWrapper<AuthDO>().lambda()
            .in(AuthDO::getAuthId, roleAuthDOS.stream().map(RoleAuthDO::getAuthId).collect(
                Collectors.toList())));

  //生成唯一token
  String token = UUID.randomUUID().toString();
  //快取使用者資訊
  redisUtil.set(token, JSONObject.toJSONString(userDO), tokenTimeout);
  //快取使用者許可權資訊
  redisUtil.set("auth_" + userDO.getUserId(),
            JSONObject.toJSONString(authDOS.stream().map(AuthDO::getAuthCode).collect(Collectors.toList())),
            tokenTimeout);
  //向localhost中新增Cookie
  Cookie cookie = new Cookie("token", token);
  cookie.setDomain("localhost");
  cookie.setPath("/");
  cookie.setMaxAge(tokenTimeout.intValue());
  httpServletResponse.addCookie(cookie);
  //返回登入成功
  return ResponseEntity.ok(JSONObject.toJSONString(userDO));
}

上面程式碼用流程圖表示如下

3. 呼叫登入介面

小A登入:http://localhost:8080/user/login?userName=小A
小B登入:http://localhost:8080/user/login?userName=小B

(沒畫前端介面,大家將就看下哈)

小A登入呼叫返回如下

小B登入呼叫返回如下

三、使用者許可權驗證邏輯

透過第二步,使用者已經進行了認證、授權的操作,那麼接下來就是使用者驗權:即驗證使用者是否有呼叫介面的許可權。

1. 定義介面許可權註解

前面定義了4個許可權:新增/刪除/修改/查詢,分別對應著4個介面。這裡我們使用註解進行一一對應。
註解定義如下:
RequiresPermissions.java

package com.summo.demo.config.permissions;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
    /**
     * 許可權列表
     * @return
     */
    String[] value();

    /**
     * 許可權控制方式,且或者和
     * @return
     */
    Logical logical() default Logical.AND;

}

該註解有兩個屬性,value和logical。value是一個陣列,代表當前介面擁有哪些許可權;logical有兩個值AND和OR,AND的意思是當前使用者必須要有value中所有的許可權才可以呼叫該介面,OR的意思是當前使用者只需要有value中任意一個許可權就可以呼叫該介面。

註解處理程式碼邏輯如下:
RequiresPermissionsHandler.java

package com.summo.demo.config.permissions;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONObject;

import com.summo.demo.config.context.GlobalUserContext;
import com.summo.demo.config.context.UserContext;
import com.summo.demo.config.manager.UserManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RequiresPermissionsHandler {

    @Autowired
    private UserManager userManager;

    @Pointcut("@annotation(com.summo.demo.config.permissions.RequiresPermissions)")
    public void pointcut() {
        // do nothing
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //獲取使用者上下文
        UserContext userContext = GlobalUserContext.getUserContext();
        if (Objects.isNull(userContext)) {
            throw new RuntimeException("使用者認證失敗,請檢查是否登入");
        }
        //獲取註解
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
        //獲取當前介面上資料許可權
        String[] permissions = requiresPermissions.value();
        if (Objects.isNull(permissions) && permissions.length == 0) {
            throw new RuntimeException("使用者認證失敗,請檢查該介面是否新增了資料許可權");
        }
        //判斷當前是and還是or
        String[] notHasPermissions;
        switch (requiresPermissions.logical()) {
            case AND:
                //當邏輯為and時,所有的資料許可權必須存在
                notHasPermissions = checkPermissionsByAnd(userContext.getUserId(), permissions);
                if (Objects.nonNull(notHasPermissions) && notHasPermissions.length > 0) {
                    throw new RuntimeException(
                        MessageFormat.format("使用者許可權不足,缺失以下許可權:[{0}]", JSONObject.toJSONString(notHasPermissions)));
                }
                break;
            case OR:
                //當邏輯為and時,所有的資料許可權必須存在
                notHasPermissions = checkPermissionsByOr(userContext.getUserId(), permissions);
                if (Objects.nonNull(notHasPermissions) && notHasPermissions.length > 0) {
                    throw new RuntimeException(
                        MessageFormat.format("使用者許可權不足,缺失以下許可權:[{0}]", JSONObject.toJSONString(notHasPermissions)));
                }
                break;
            default:
                //預設為and
        }
        return joinPoint.proceed();
    }

    /**
     * 當資料許可權為or時,進行判斷
     *
     * @param userId      使用者ID
     * @param permissions 許可權組
     * @return 沒有授予的許可權
     */
    private String[] checkPermissionsByOr(Long userId, String[] permissions) {
        // 獲取使用者許可權集
        Set<String> permissionSet = userManager.queryAuthByUserId(userId);
        if (permissionSet.isEmpty()) {
            return permissions;
        }
        //一一比對
        List<String> tempPermissions = new ArrayList<>();
        for (String permission1 : permissions) {
            permissionSet.forEach(permission -> {
                if (permission1.equals(permission)) {
                    tempPermissions.add(permission);
                }
            });
        }
        if (Objects.nonNull(tempPermissions) && tempPermissions.size() > 0) {
            return null;
        }
        return permissions;
    }

    /**
     * 當資料許可權為and時,進行判斷
     *
     * @param userId      使用者ID
     * @param permissions 許可權組
     * @return 沒有授予的許可權
     */
    private String[] checkPermissionsByAnd(Long userId, String[] permissions) {
        // 獲取使用者許可權集
        Set<String> permissionSet = userManager.queryAuthByUserId(userId);
        if (permissionSet.isEmpty()) {
            return permissions;
        }
        //如果permissions大小為1,可以單獨處理一下
        if (permissionSet.size() == 1 && permissionSet.contains(permissions[0])) {
            return null;
        }
        if (permissionSet.size() == 1 && !permissionSet.contains(permissions[0])) {
            return permissions;
        }
        //一一比對
        List<String> tempPermissions = new ArrayList<>();
        for (String permission1 : permissions) {
            permissionSet.forEach(permission -> {
                if (permission1.equals(permission)) {
                    tempPermissions.add(permission);
                }
            });
        }
        //如果tempPermissions的長度與permissions相同,那麼說明許可權吻合
        if (permissions.length == tempPermissions.size()) {
            return null;
        }
        //否則取出當前使用者沒有的許可權,並返回用作提示
        List<String> notHasPermissions = Arrays.stream(permissions).filter(
            permission -> !tempPermissions.contains(permission)).collect(Collectors.toList());
        return notHasPermissions.toArray(new String[notHasPermissions.size()]);
    }

}

2. 註解使用方式

使用比較簡單,直接放到介面的方法上

@GetMapping("/add")
@RequiresPermissions(value = "add", logical = Logical.OR)
public ResponseEntity<String> add(@RequestBody AddReq addReq) {
  return userService.add(addReq);
}

@GetMapping("/delete")
@RequiresPermissions(value = "delete", logical = Logical.OR)
public ResponseEntity<String> delete(@RequestParam Long userId) {
  return userService.delete(userId);
}

@GetMapping("/query")
@RequiresPermissions(value = "query", logical = Logical.OR)
public ResponseEntity<String> query(@RequestParam String userName) {
  return userService.query(userName);
}

@GetMapping("/update")
@RequiresPermissions(value = "update", logical = Logical.OR)
public ResponseEntity<String> update(@RequestBody UpdateReq updateReq) {
  return userService.update(updateReq);
}

3. 介面驗權的流程

四、使用者許可權變動後的狀態重新整理

其實前面三步完成後,正向流已經完成了,但使用者的許可權是變化的,比如:

小B的許可權從查詢變為了查詢更新

但小B的token還未過期,這時應該怎麼辦呢?

還記得登入的時候,我有快取兩個資訊嗎

對應程式碼中的

//快取使用者資訊
redisUtil.set(token, JSONObject.toJSONString(userDO), tokenTimeout);
//快取使用者許可權資訊
redisUtil.set("auth_" + userDO.getUserId(),JSONObject.toJSONString(authDOS.stream().map(AuthDO::getAuthCode).collect(Collectors.toList())),tokenTimeout);

在這裡我其實將token和許可權是分開儲存的,token只存使用者資訊,而許可權資訊用auth_userId為key進行儲存的,這樣就可以做到即使token還在,我也能動態修改當前使用者的許可權資訊了,且許可權實時變更不會影響使用者體驗。

不過,這個地方有一個爭議的點
使用者許可權發生變更的時候,是更新許可權快取呢?還是直接刪除使用者的許可權快取呢?

我的建議是:刪除許可權快取。原因有三

  • 使用者許可權快取並不是一直存在,存在連快取都沒有的情況。
  • 快取更新只適用於單個使用者許可權的更新,但是我要把角色和許可權的關聯變動了呢?
  • 直接把許可權快取刪除,使用者會不會報錯?我查詢許可權快取的方式是:先查詢快取,快取沒有在查詢資料庫,所以並不會出現快取被刪除就報錯的情況。

tips:如何優雅的實現“先查詢快取再查詢資料庫?”請看我這篇文章:https://juejin.cn/post/7124885941117779998

五、認證失敗或無許可權等異常情況處理

出現由於許可權不足或認證失敗的問題,常見的做法有重定向到登入頁、通知使用者重新整理介面等,具體怎麼處理還要看產品是怎麼要求的。
關於網站的異常有很多,許可權相關的狀態碼是401、伺服器錯誤的狀態碼是500,除此之外還會有自定義的錯誤碼,我打算放在介面最佳化系列的後面用專篇說明,敬請期待哦~

寫在最後

《最佳化介面設計的思路》系列已結寫到第四篇了,前面幾篇都沒有總結,在這篇總結一下吧。

從我開始寫部落格到現在已經6年了,差不多也寫了將近60篇左右的文章。剛開始的時候就是寫SpringBoot,寫SpringBoot如何整合Vue,那是2017年。

得益於老大的要求(或者是公司想省錢),剛工作的時候就是前後端程式碼都寫,但是寫的一塌糊塗,甚至連最基礎的專案環境都搭不好。那時候在網上找個pom.xml配置,依賴死活下載不下來,後來才知道maven倉庫預設國外的源,要把它換成國內的才能提高下載速度。那時候上班就是下午把專案跑起來了,第二天上午專案又啟動不了了,如此迴圈往復,我的筆記裡面存了非常多的配置檔案。再後來技術水平提高了點,單專案終於會玩了,微服務又火起來了,瞭解過SpringCloud的小夥伴應該知道SpringCloud的版本更復雜,搭建環境更難。在這可能有人會疑惑,你不會不能去問人嗎?我也很無奈,一則是社恐不敢問,二則是我們部門全是菜鳥,都等著我學會教他們呢...

後來我老大說,既然用不來人家的,那就自己寫一套,想起來那時真單純,我就真的自己開始寫微服務架構。最開始我對微服務的唯一印象就是一個服務提供者、一個服務消費者,肯定是兩個應用,至於為啥是這樣,查的百度都是這樣寫的。然後我就建了兩個應用,一個閘道器應用、一個業務應用,自己寫HttpUtil進行服務間呼叫,也不知道啥是註冊中心,我只知道閘道器應用那裡要有業務應用的IP地址,否則閘道器調不了業務程式碼。當時的呼叫程式碼我已經找不了,只記得當時程式碼的形狀很像一個“>”,用了太多的if...else...了!!!

那時候雖然程式碼寫的很爛、bug一堆,但我們老大也沒罵我們,每週四還會給我們上夜校,跟我們講一些大廠的框架和技術棧。他跟我們說,現在多用用人家的技術,到時候出去面試大廠也容易一些。寫博文也是老大讓我們做的,他說現在一點點的積累,等到過幾年就會變成文庫了。現在想來,真是一個不錯的老大!

現在2023年了,我還在寫程式碼,但也不僅僅只是寫程式碼,還帶一些專案,獨立負責的也有。要說我現在的程式碼水平嘛,屬於那種工廠熟練工水平,八股裡面的什麼JVM調優啊、高併發系統架構設計啊我一次都沒有接觸到過,遠遠稱不上大神。不過我還是想寫一些文章,不是為了炫技,只是想把我工作中遇到的問題變成後續解決問題的經驗,說真的這些文章已經開始幫到我了,如果它們也能幫助到你,榮幸之至!

相關文章