博客 / 詳情

返回

《優化接口設計的思路》系列:第四篇—接口的權限控制

前言

大家好!我是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調優啊、高併發系統架構設計啊我一次都沒有接觸到過,遠遠稱不上大神。不過我還是想寫一些文章,不是為了炫技,只是想把我工作中遇到的問題變成後續解決問題的經驗,説真的這些文章已經開始幫到我了,如果它們也能幫助到你,榮幸之至!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.