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