diff --git a/ebike-user/pom.xml b/ebike-user/pom.xml
index 9231bb9..1e5d6f7 100644
--- a/ebike-user/pom.xml
+++ b/ebike-user/pom.xml
@@ -154,11 +154,6 @@
spring-boot-starter-test
-
- org.springframework.boot
- spring-boot-starter-data-redis
-
-
jakarta.servlet
jakarta.servlet-api
@@ -175,6 +170,20 @@
jackson-datatype-jsr310
${jackson.version}
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+ ${satoken.version}
+
+
+
+
+ cn.dev33
+ sa-token-redis-jackson
+ ${satoken.version}
+
diff --git a/ebike-user/src/main/java/com/cdzy/user/component/TenantInterceptor.java b/ebike-user/src/main/java/com/cdzy/user/component/TenantInterceptor.java
new file mode 100644
index 0000000..99d1722
--- /dev/null
+++ b/ebike-user/src/main/java/com/cdzy/user/component/TenantInterceptor.java
@@ -0,0 +1,64 @@
+package com.cdzy.user.component;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.cdzy.common.model.response.CommonStaffInfo;
+import com.cdzy.user.config.AuthProperties;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.util.TimeZone;
+
+/**
+ * @author yanglei
+ * @since 2026-02-10 11:10
+ */
+@Component
+public class TenantInterceptor implements HandlerInterceptor {
+
+ @Resource
+ private AuthProperties authProperties;
+
+ private final AntPathMatcher pathMatcher = new AntPathMatcher();
+
+ @Override
+ public boolean preHandle(@NotNull HttpServletRequest request,
+ @NotNull HttpServletResponse response,
+ @NotNull Object handler) {
+
+ String uri = request.getRequestURI();
+ // 判断当前路径是否在配置的路径 "名单" 列表中
+ boolean needAuth = authProperties.getRequiredPaths().stream()
+ .anyMatch(pattern -> pathMatcher.match(pattern, uri));
+
+ if (!needAuth) {
+ return true;
+ }
+ StpUtil.checkLogin();
+
+
+ Long tenantId = getTenantIdByReuqest(request);
+ request.setAttribute("tenantId", tenantId);
+
+ return true;
+ }
+
+ Long getTenantIdByReuqest(HttpServletRequest request) {
+ String token = request.getHeader("Authorization");
+ String id = (String) StpUtil.getLoginIdByToken(token);
+ Object object = StpUtil.getSessionByLoginId(id).get(id);
+ ObjectMapper objectMapper = new ObjectMapper()
+ .registerModule(new JavaTimeModule())
+ .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+ .setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
+ CommonStaffInfo staffInfo = objectMapper.convertValue(object, CommonStaffInfo.class);
+ return staffInfo.getOperatorId();
+ }
+}
\ No newline at end of file
diff --git a/ebike-user/src/main/java/com/cdzy/user/config/AuthProperties.java b/ebike-user/src/main/java/com/cdzy/user/config/AuthProperties.java
new file mode 100644
index 0000000..bf9f491
--- /dev/null
+++ b/ebike-user/src/main/java/com/cdzy/user/config/AuthProperties.java
@@ -0,0 +1,23 @@
+package com.cdzy.user.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * user模块接口拦截列表(从配置文件获取)
+ *
+ * @author yanglei
+ * @since 2026-02-10 11:10
+ */
+
+@ConfigurationProperties(prefix = "cdzy.auth")
+@Component
+@Data
+public class AuthProperties {
+
+ private List requiredPaths = new ArrayList<>();
+}
diff --git a/ebike-user/src/main/java/com/cdzy/user/config/WebConfig.java b/ebike-user/src/main/java/com/cdzy/user/config/WebConfig.java
new file mode 100644
index 0000000..e7c61d2
--- /dev/null
+++ b/ebike-user/src/main/java/com/cdzy/user/config/WebConfig.java
@@ -0,0 +1,21 @@
+package com.cdzy.user.config;
+
+import com.cdzy.user.component.TenantInterceptor;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+ @Resource
+ private TenantInterceptor tenantInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ // 添加拦截器,拦截所有请求(由拦截器内部判断是否需要校验)
+ registry.addInterceptor(tenantInterceptor)
+ .addPathPatterns("/**");
+ }
+}
\ No newline at end of file
diff --git a/ebike-user/src/main/resources/application-dev.yml b/ebike-user/src/main/resources/application-dev.yml
index 1100f6f..23b321e 100644
--- a/ebike-user/src/main/resources/application-dev.yml
+++ b/ebike-user/src/main/resources/application-dev.yml
@@ -63,6 +63,29 @@ spring:
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
+sa-token:
+ # token 名称(同时也是 cookie 名称)
+ token-name: Authorization
+ # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+ timeout: 2592000
+ # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+ active-timeout: -1
+ # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+ is-concurrent: true
+ # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+ is-share: false
+ # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+ token-style: random-32
+ # 是否输出操作日志
+ is-log: true
+
+# 需要被拦截的接口列表
+cdzy:
+ auth:
+ # 需要 token 校验的路径列表
+ required-paths:
+ - /ebikeOrder/api/updateOrderAmount
mybatis-flex:
mapper-locations: classpath:mapper/*.xml
diff --git a/ebike-user/src/main/resources/application-prod.yml b/ebike-user/src/main/resources/application-prod.yml
index d5dcacb..d77490a 100644
--- a/ebike-user/src/main/resources/application-prod.yml
+++ b/ebike-user/src/main/resources/application-prod.yml
@@ -63,6 +63,29 @@ spring:
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
+############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
+sa-token:
+ # token 名称(同时也是 cookie 名称)
+ token-name: Authorization
+ # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+ timeout: 2592000
+ # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+ active-timeout: -1
+ # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+ is-concurrent: true
+ # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+ is-share: false
+ # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+ token-style: random-32
+ # 是否输出操作日志
+ is-log: true
+
+# 需要被拦截的接口列表
+cdzy:
+ auth:
+ # 需要 token 校验的路径列表
+ required-paths:
+ - /ebikeOrder/api/updateOrderAmount
mybatis-flex:
mapper-locations: classpath:mapper/*.xml