inetAddresses = anInterface.getInetAddresses(); inetAddresses.hasMoreElements();) {
+ InetAddress inetAddr = inetAddresses.nextElement();
+ // 排除loopback类型地址
+ if (!inetAddr.isLoopbackAddress()) {
+ if (inetAddr.isSiteLocalAddress()) {
+ // 如果是site-local地址,就是它了
+ return inetAddr.getHostAddress();
+ } else if (candidateAddress == null) {
+ // site-local类型的地址未被发现,先记录候选地址
+ candidateAddress = inetAddr;
+ }
+ }
+ }
+ }
+ if (candidateAddress != null) {
+ return candidateAddress.getHostAddress();
+ }
+ // 如果没有发现 non-loopback地址.只能用最次选的方案
+ InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
+ if (jdkSuppliedAddress == null) {
+ return "";
+ }
+ return jdkSuppliedAddress.getHostAddress();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+}
diff --git a/backend/platform-system/pom.xml b/backend/platform-system/pom.xml
new file mode 100644
index 0000000..7befed1
--- /dev/null
+++ b/backend/platform-system/pom.xml
@@ -0,0 +1,49 @@
+
+
+ 4.0.0
+
+
+ com.yfd
+ platform
+ 1.0
+ ../pom.xml
+
+
+ platform-system
+ 1.0
+ jar
+
+ Platform System
+ Platform System Module
+
+
+
+
+ com.yfd
+ platform-common
+ 1.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot4-starter
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/PlatformApplication.java b/backend/platform-system/src/main/java/com/yfd/platform/system/PlatformApplication.java
new file mode 100644
index 0000000..d35a8da
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/PlatformApplication.java
@@ -0,0 +1,46 @@
+package com.yfd.platform.system;
+
+import com.yfd.platform.common.utils.SpringContextHolder;
+import com.yfd.platform.system.annotation.rest.AnonymousGetMapping;
+import com.yfd.platform.system.datasource.DynamicDataSourceConfig;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
+import org.springframework.boot.web.server.servlet.context.ServletComponentScan;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.bind.annotation.RestController;
+
+//@SpringBootApplication
+@RestController
+@EnableTransactionManagement
+@ServletComponentScan("com.yfd.platform.config")
+@MapperScan(basePackages = "com.yfd.platform.*.*.mapper")
+@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class, DataRedisAutoConfiguration.class})
+@Import({DynamicDataSourceConfig.class})
+@EnableCaching
+public class PlatformApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(PlatformApplication.class, args);
+ }
+
+ @Bean
+ public SpringContextHolder springContextHolder() {
+ return new SpringContextHolder();
+ }
+
+ /**
+ * 访问首页提示
+ *
+ * @return /
+ */
+ @AnonymousGetMapping("/")
+ public String index() {
+ return "Backend service started successfully";
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/ServletInitializer.java b/backend/platform-system/src/main/java/com/yfd/platform/system/ServletInitializer.java
new file mode 100644
index 0000000..b8e23ec
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/ServletInitializer.java
@@ -0,0 +1,14 @@
+package com.yfd.platform.system;
+
+import com.yfd.platform.env.PlatformApplication;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+public class ServletInitializer extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(PlatformApplication.class);
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/AnonymousAccess.java b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/AnonymousAccess.java
new file mode 100644
index 0000000..697ed99
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/AnonymousAccess.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * @author jacky
+ * 用于标记匿名访问方法
+ */
+@Inherited
+@Documented
+@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnonymousAccess {
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/Log.java b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/Log.java
new file mode 100644
index 0000000..0550f14
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/Log.java
@@ -0,0 +1,20 @@
+package com.yfd.platform.system.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author TangWei
+ * @date 2018-11-24
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Log {
+
+ String value() default "";
+
+ String module() default "";
+}
+
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/rest/AnonymousGetMapping.java b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/rest/AnonymousGetMapping.java
new file mode 100644
index 0000000..b97909b
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/annotation/rest/AnonymousGetMapping.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yfd.platform.system.annotation.rest;
+
+import com.yfd.platform.annotation.AnonymousAccess;
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation for mapping HTTP {@code GET} requests onto specific handler
+ * methods.
+ *
+ * 支持匿名访问 GetMapping
+ *
+ * @author liaojinlong
+ * @see RequestMapping
+ */
+@AnonymousAccess
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@RequestMapping(method = RequestMethod.GET)
+public @interface AnonymousGetMapping {
+
+ /**
+ * Alias for {@link RequestMapping#name}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String name() default "";
+
+ /**
+ * Alias for {@link RequestMapping#value}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] value() default {};
+
+ /**
+ * Alias for {@link RequestMapping#path}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] path() default {};
+
+ /**
+ * Alias for {@link RequestMapping#params}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] params() default {};
+
+ /**
+ * Alias for {@link RequestMapping#headers}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] headers() default {};
+
+ /**
+ * Alias for {@link RequestMapping#consumes}.
+ *
+ * @since 4.3.5
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] consumes() default {};
+
+ /**
+ * Alias for {@link RequestMapping#produces}.
+ */
+ @AliasFor(annotation = RequestMapping.class)
+ String[] produces() default {};
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/aspect/LogAspect.java b/backend/platform-system/src/main/java/com/yfd/platform/system/aspect/LogAspect.java
new file mode 100644
index 0000000..330f78b
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/aspect/LogAspect.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.aspect;
+
+import com.yfd.platform.system.domain.SysLog;
+import com.yfd.platform.system.mapper.SysUserMapper;
+import com.yfd.platform.system.service.ISysLogService;
+import com.yfd.platform.system.service.IUserService;
+import com.yfd.platform.utils.RequestHolder;
+import com.yfd.platform.utils.SecurityUtils;
+import com.yfd.platform.utils.StringUtils;
+import lombok.extern.slf4j.Slf4j;
+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.springframework.stereotype.Component;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/**
+ * @author
+ * @date 2018-11-24
+ */
+@Component
+@Aspect
+@Slf4j
+public class LogAspect {
+
+ @Resource
+ private final ISysLogService sysLogService;
+
+ @Resource
+ private IUserService userService;
+
+ ThreadLocal currentTime = new ThreadLocal<>();
+
+ public LogAspect(ISysLogService sysLogService) {
+ this.sysLogService = sysLogService;
+ }
+
+ /**
+ * 配置切入点
+ */
+ @Pointcut("@annotation(com.yfd.platform.annotation.Log)")
+ public void logPointcut() {
+ // 该方法无方法体,主要为了让同类中其他方法使用此切入点
+ }
+
+ /**
+ * 配置环绕通知,使用在方法logPointcut()上注册的切入点
+ *
+ * @param joinPoint join point for advice
+ */
+ @Around("logPointcut()")
+ public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
+ Object result;
+ currentTime.set(System.currentTimeMillis());
+ result = joinPoint.proceed();
+ SysLog log = new SysLog("INFO");
+ currentTime.remove();
+ HttpServletRequest request = RequestHolder.getHttpServletRequest();
+ Map nameInfo = userService.getNameInfo();
+ String nickname = nameInfo.get("nickname");
+ String username = nameInfo.get("username");
+ sysLogService.save(nickname, username, StringUtils.getBrowser(request),
+ StringUtils.getIp(request), joinPoint, log);
+ return result;
+ }
+
+ public String getUsername() {
+ try {
+ return SecurityUtils.getCurrentUsername();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/component/ServerSendEventServer.java b/backend/platform-system/src/main/java/com/yfd/platform/system/component/ServerSendEventServer.java
new file mode 100644
index 0000000..3f77766
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/component/ServerSendEventServer.java
@@ -0,0 +1,147 @@
+package com.yfd.platform.system.component;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+/**
+ * @author Huhailong
+ * SSE Server send Event 服务器推送服务
+ */
+@Slf4j
+public class ServerSendEventServer {
+
+ /**
+ * 当前连接数
+ */
+ private static AtomicInteger count = new AtomicInteger(0);
+
+ private static Map sseEmitterMap =
+ new ConcurrentHashMap<>();
+
+ public static SseEmitter connect(String userId) {
+ //设置超时时间,0表示不过期,默认是30秒,超过时间未完成会抛出异常
+ SseEmitter sseEmitter = new SseEmitter(0L);
+ //SseEmitter sseEmitter = new SseEmitter();
+ //注册回调
+ sseEmitter.onCompletion(completionCallBack(userId));
+ sseEmitter.onError(errorCallBack(userId));
+ sseEmitter.onTimeout(timeOutCallBack(userId));
+ sseEmitterMap.put(userId, sseEmitter);
+ //数量+1
+ count.getAndIncrement();
+ log.info("create new sse connect ,current user:{}", userId);
+ return sseEmitter;
+ }
+
+ /**
+ * 给指定用户发消息
+ */
+ public static void sendMessage(String userId, String message) {
+ if (sseEmitterMap.containsKey(userId)) {
+ try {
+ sseEmitterMap.get(userId).send(message);
+ } catch (IOException e) {
+ log.error("user id:{}, send message error:{}", userId,
+ e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 给所有用户发消息
+ */
+ public static void sendMessage(String message) {
+ if (sseEmitterMap != null && !sseEmitterMap.isEmpty()) {
+ sseEmitterMap.forEach((k, v) -> {
+ // 发送消息
+ sendMessage(k, message);
+
+ });
+ }
+ }
+
+ /**
+ * 想多人发送消息,组播
+ */
+ public static void groupSendMessage(String groupId, String message) {
+ if (sseEmitterMap != null && !sseEmitterMap.isEmpty()) {
+ sseEmitterMap.forEach((k, v) -> {
+ try {
+ if (k.startsWith(groupId)) {
+ v.send(message, MediaType.APPLICATION_JSON);
+ }
+ } catch (IOException e) {
+ log.error("user id:{}, send message error:{}", groupId,
+ message);
+ removeUser(k);
+ }
+ });
+ }
+ }
+
+ public static void batchSendMessage(String message) {
+ sseEmitterMap.forEach((k, v) -> {
+ try {
+ v.send(message, MediaType.APPLICATION_JSON);
+ } catch (IOException e) {
+ log.error("user id:{}, send message error:{}", k,
+ e.getMessage());
+ removeUser(k);
+ }
+ });
+ }
+
+ /**
+ * 群发消息
+ */
+ public static void batchSendMessage(String message, Set userIds) {
+ userIds.forEach(userId -> sendMessage(userId, message));
+ }
+
+ public static void removeUser(String userId) {
+ sseEmitterMap.remove(userId);
+ //数量-1
+ count.getAndDecrement();
+ log.info("remove user id:{}", userId);
+ }
+
+ public static List getIds() {
+ return new ArrayList<>(sseEmitterMap.keySet());
+ }
+
+ public static int getUserCount() {
+ return count.intValue();
+ }
+
+ private static Runnable completionCallBack(String userId) {
+ return () -> {
+ log.info("结束连接,{}", userId);
+ removeUser(userId);
+ };
+ }
+
+ private static Runnable timeOutCallBack(String userId) {
+ return () -> {
+ log.info("连接超时,{}", userId);
+ removeUser(userId);
+ };
+ }
+
+ private static Consumer errorCallBack(String userId) {
+ return throwable -> {
+ log.error("连接异常,{}", userId);
+ removeUser(userId);
+ };
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/component/WebSocketServer.java b/backend/platform-system/src/main/java/com/yfd/platform/system/component/WebSocketServer.java
new file mode 100644
index 0000000..2f25f40
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/component/WebSocketServer.java
@@ -0,0 +1,106 @@
+package com.yfd.platform.system.component;
+
+import org.springframework.stereotype.Component;
+
+import jakarta.websocket.*;
+import jakarta.websocket.server.PathParam;
+import jakarta.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+@ServerEndpoint("/websocket/{token}")
+@Component
+public class WebSocketServer {
+ private static int onlineCount=0;//在线人数
+ private static CopyOnWriteArrayList webSocketSet=new CopyOnWriteArrayList();//在线用户集合
+ private Session session;//与某个客户端的连接会话
+ private String currentUser;
+
+ @OnOpen
+ public void onOpen(@PathParam("token") String token, Session session){
+ this.currentUser = token;
+ this.session=session;
+ webSocketSet.add(this);//加入set中
+ addOnlineCount();
+ System.out.println("有新连接加入!当前在线人数为"+getOnlineCount());
+ allCurrentOnline();
+ }
+
+ @OnClose
+ public void onClose(){
+ webSocketSet.remove(this);
+ subOnlineCount();
+ System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
+ allCurrentOnline();
+ }
+
+ @OnMessage
+ public void onMessage(String message, Session session){
+ System.out.println("来自客户端的消息:"+message);
+ for (WebSocketServer item:webSocketSet){
+ try {
+ item.sendMessage(message);
+ } catch (IOException e) {
+ e.printStackTrace();
+ continue;
+ }
+ }
+ }
+
+ @OnError
+ public void onError(Session session, Throwable throwable){
+ System.out.println("发生错误!");
+ throwable.printStackTrace();
+ }
+
+ public void sendMessage(String message) throws IOException {
+ this.session.getBasicRemote().sendText(message);
+ }
+
+ /**
+ * 获取当前所有在线用户名
+ */
+ public static void allCurrentOnline(){
+ for (WebSocketServer item : webSocketSet) {
+ System.out.println(item.currentUser);
+ }
+ }
+
+ /**
+ * 发送给指定用户
+ */
+ public static void sendMessageTo(String message,String token) throws IOException {
+ for (WebSocketServer item : webSocketSet) {
+ if(item.currentUser.equals(token)){
+ item.session.getBasicRemote().sendText(message);
+ }
+ }
+ }
+
+ /**
+ * 群发自定义消息
+ */
+ public static void sendInfo(String message) throws IOException {
+ System.out.println(message);
+ for (WebSocketServer item : webSocketSet) {
+ try {
+ item.sendMessage(message);
+ } catch (IOException e) {
+ continue;
+ }
+ }
+ }
+
+ public static synchronized int getOnlineCount(){
+ return onlineCount;
+ }
+ public static synchronized void addOnlineCount(){
+ WebSocketServer.onlineCount++;
+ }
+ public static synchronized void subOnlineCount(){
+ WebSocketServer.onlineCount--;
+ }
+
+}
+
+
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/AppInitProperties.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/AppInitProperties.java
new file mode 100644
index 0000000..6c2af26
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/AppInitProperties.java
@@ -0,0 +1,17 @@
+package com.yfd.platform.system.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "app.init")
+public class AppInitProperties {
+ private boolean enabled = false;
+ private String schema;
+ private String data;
+ // 用于判断是否已初始化:默认检查是否存在核心表
+ private String markerTable = "sys_user";
+ private String markerVersion = "v1.0.0";
+}
\ No newline at end of file
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/DataInitializer.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/DataInitializer.java
new file mode 100644
index 0000000..24f279d
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/DataInitializer.java
@@ -0,0 +1,129 @@
+package com.yfd.platform.system.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
+import org.springframework.jdbc.datasource.init.ScriptException;
+import org.springframework.jdbc.datasource.init.ScriptStatementFailedException;
+import org.springframework.stereotype.Component;
+
+import javax.sql.DataSource;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+import org.springframework.util.StreamUtils;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+
+@Slf4j
+@Component
+@Profile({"dev","server"})
+@RequiredArgsConstructor
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class DataInitializer implements ApplicationRunner {
+
+ private final DataSource dataSource;
+ private final AppInitProperties properties;
+ private final ResourceLoader resourceLoader;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ if (!properties.isEnabled()) {
+ log.info("[DataInit] 自动初始化已关闭 app.init.enabled=false");
+ return;
+ }
+
+ try (Connection conn = dataSource.getConnection()) {
+ boolean initialized = tableExists(conn, properties.getMarkerTable());
+ if (initialized) {
+ log.info("[DataInit] 检测到标记表已存在: {},跳过初始化", properties.getMarkerTable());
+ return;
+ }
+
+ log.info("[DataInit] 未检测到标记表: {},开始导入 schema 与 data", properties.getMarkerTable());
+ executeIfPresent(conn, properties.getSchema());
+ executeIfPresent(conn, properties.getData());
+ log.info("[DataInit] 导入完成。marker={} version={}", properties.getMarkerTable(), properties.getMarkerVersion());
+ } catch (Exception ex) {
+ log.error("[DataInit] 初始化失败: {}", ex.getMessage(), ex);
+ throw ex;
+ }
+ }
+
+ private boolean tableExists(Connection conn, String tableName) {
+ if (tableName == null || tableName.isEmpty()) return false;
+ try {
+ DatabaseMetaData meta = conn.getMetaData();
+ try (ResultSet rs = meta.getTables(conn.getCatalog(), null, tableName, null)) {
+ return rs.next();
+ }
+ } catch (Exception e) {
+ log.warn("[DataInit] 检查表存在异常: {}", e.getMessage());
+ return false;
+ }
+ }
+
+ private void executeIfPresent(Connection conn, String location) throws Exception {
+ if (location == null || location.isEmpty()) return;
+ Resource resource = resourceLoader.getResource(location);
+ if (!resource.exists()) {
+ log.warn("[DataInit] 资源不存在: {}", location);
+ return;
+ }
+ log.info("[DataInit] 执行脚本: {}", location);
+ // 读取并清理脚本首部的 BOM 和危险语句(CREATE DATABASE / USE schema)
+ String sql = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
+ if (sql != null && !sql.isEmpty()) {
+ // 移除 UTF-8 BOM(\uFEFF)
+ if (sql.charAt(0) == '\uFEFF') {
+ sql = sql.substring(1);
+ }
+ // 移除 "USE xxx;" 和 "CREATE DATABASE" 等与连接无关的语句
+ sql = sql.lines()
+ .filter(line -> {
+ String raw = line;
+ String t = raw.trim();
+ String u = t.toUpperCase();
+ // 过滤与连接无关或可能引发解析问题的语句/注释
+ if (u.startsWith("USE ")) return false;
+ if (u.startsWith("CREATE DATABASE")) return false;
+ if (t.startsWith("--")) return false; // 单行注释
+ if (t.startsWith("/*") || t.startsWith("*/")) return false; // 多行注释行
+ if (t.startsWith("/*!")) return false; // MySQL 版本注释
+ if (t.matches("^-+$")) return false; // 分隔线
+ return true;
+ })
+ .collect(Collectors.joining("\n"));
+ }
+ if (sql != null && !sql.trim().isEmpty()) {
+ try {
+ ScriptUtils.executeSqlScript(conn, new EncodedResource(new ByteArrayResource(sql.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
+ } catch (ScriptStatementFailedException e) {
+ String preview = sql.lines()
+ .filter(s -> !s.trim().isEmpty())
+ .limit(10)
+ .collect(Collectors.joining("\n"));
+ log.error("[DataInit] SQL语句执行失败: {}\n前10行预览:\n{}", e.getMessage(), preview);
+ throw e;
+ } catch (ScriptException e) {
+ String preview = sql.lines()
+ .filter(s -> !s.trim().isEmpty())
+ .limit(10)
+ .collect(Collectors.joining("\n"));
+ log.error("[DataInit] 脚本执行失败: {}\n前10行预览:\n{}", e.getMessage(), preview);
+ throw e;
+ }
+ } else {
+ log.warn("[DataInit] 脚本在清理后为空,跳过执行: {}", location);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileProperties.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileProperties.java
new file mode 100644
index 0000000..5e56412
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileProperties.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file")
+public class FileProperties {
+
+ /** 文件大小限制 */
+ private Long maxSize;
+
+ /** 头像大小限制 */
+ private Long avatarMaxSize;
+
+ private ElPath mac;
+
+ private ElPath linux;
+
+ private ElPath windows;
+
+ public ElPath getPath(){
+ String os = System.getProperty("os.name");
+ if(os.toLowerCase().startsWith("win")) {
+ return windows;
+ } else if(os.toLowerCase().startsWith("mac")){
+ return mac;
+ }
+ return linux;
+ }
+
+ @Data
+ public static class ElPath{
+
+ private String path;
+
+ private String avatar;
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileSpaceProperties.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileSpaceProperties.java
new file mode 100644
index 0000000..1b58d6f
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/FileSpaceProperties.java
@@ -0,0 +1,17 @@
+package com.yfd.platform.system.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 文件空间相关配置,替换 @Value("${file-space.system}") 用法
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file-space")
+public class FileSpaceProperties {
+
+ /** 基础目录,例如 D:/data/platform/ */
+ private String system;
+}
\ No newline at end of file
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/GlobalExceptionHandler.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..b4bc8c5
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/GlobalExceptionHandler.java
@@ -0,0 +1,25 @@
+package com.yfd.platform.system.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+/**
+ * @author TangWei
+ * @Date: 2023/3/27 18:07
+ * @Description:
+ */
+@Slf4j
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ResponseBody
+ @ExceptionHandler(value = Throwable.class)
+ public ResponseResult handleException(Throwable e) {
+ log.error("message:{}", e.getMessage());
+ e.printStackTrace();
+ return ResponseResult.error(e.getMessage());
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/JobRunner.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/JobRunner.java
new file mode 100644
index 0000000..b394da0
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/JobRunner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yfd.platform.system.domain.QuartzJob;
+import com.yfd.platform.system.mapper.QuartzJobMapper;
+import com.yfd.platform.utils.QuartzManage;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 2019-01-07
+ */
+@Component
+@RequiredArgsConstructor
+@Order(Ordered.LOWEST_PRECEDENCE)
+public class JobRunner implements ApplicationRunner {
+
+ private static final Logger log = LoggerFactory.getLogger(JobRunner.class);
+ private final QuartzJobMapper quartzJobMapper;
+ private final QuartzManage quartzManage;
+
+ /**
+ * 项目启动时重新激活启用的定时任务
+ *
+ * @param applicationArguments /
+ */
+ @Override
+ public void run(ApplicationArguments applicationArguments) {
+ log.info("--------------------注入定时任务---------------------");
+ List quartzJobs =
+ quartzJobMapper.selectList(new LambdaQueryWrapper().eq(QuartzJob::getStatus, "1"));
+ quartzJobs.forEach(quartzManage::addJob);
+ log.info("--------------------定时任务注入完成---------------------");
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/JwtAuthenticationTokenFilter.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..396f9e7
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,83 @@
+package com.yfd.platform.system.config;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yfd.platform.component.ServerSendEventServer;
+import com.yfd.platform.constant.Constant;
+import com.yfd.platform.system.domain.LoginUser;
+import com.yfd.platform.system.domain.Message;
+import com.yfd.platform.system.service.IMessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private WebConfig webConfig;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest httpServletRequest,
+ HttpServletResponse httpServletResponse,
+ FilterChain filterChain) throws ServletException, IOException {
+ //获取token
+ String uri = httpServletRequest.getRequestURI();
+ String token = httpServletRequest.getHeader("token");
+ if (StrUtil.isEmpty(token) || "/user/login".equals(uri)) {
+ filterChain.doFilter(httpServletRequest, httpServletResponse);
+ return;
+ }
+ //解析token
+ boolean isok = JWTUtil.verify(token, "12345678".getBytes());
+ String userid = "";
+ if (isok) {
+ final JWT jwt = JWTUtil.parseToken(token);
+ userid = jwt.getPayload("userid").toString();
+ //从cachekey中获取用户信息失效时间
+ String cachekey = "expire_time:" + userid;
+ if(StrUtil.isNotEmpty(webConfig.loginuserCache().get(cachekey))){
+ long expire_time =Long.parseLong(webConfig.loginuserCache().get(cachekey));
+ if (System.currentTimeMillis() > expire_time) {
+ httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Token超过期限!");
+ return;
+ }
+ }
+ }
+
+ //从cachekey中获取用户信息
+ String cachekey = "login:" + userid;
+ String jsonstr = webConfig.loginuserCache().get(cachekey);
+ LoginUser loginUser = JSON.parseObject(jsonstr, LoginUser.class);
+ if (ObjectUtil.isEmpty(loginUser)) {
+ httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
+ "登录用户已失效!");
+ return;
+ }
+ //存入SecurityContextHolder
+ UsernamePasswordAuthenticationToken authenticationToken =
+ new UsernamePasswordAuthenticationToken(loginUser, null,
+ loginUser.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+ webConfig.loginuserCache().put(Constant.TOKEN + userid, token);
+ //更新了超期时间
+ long expireTime =System.currentTimeMillis() + ( 30L * 60L * 1000L);
+ webConfig.loginuserCache().put("expire_time:" + userid, String.valueOf(expireTime));
+ //放行过滤器
+ filterChain.doFilter(httpServletRequest, httpServletResponse);
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/MessageConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/MessageConfig.java
new file mode 100644
index 0000000..9535d15
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/MessageConfig.java
@@ -0,0 +1,50 @@
+package com.yfd.platform.system.config;
+
+import cn.hutool.cache.Cache;
+import cn.hutool.cache.impl.CacheObj;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.yfd.platform.component.ServerSendEventServer;
+import com.yfd.platform.constant.Constant;
+import com.yfd.platform.system.domain.Message;
+import com.yfd.platform.system.domain.SysUser;
+import com.yfd.platform.system.service.IMessageService;
+import com.yfd.platform.system.service.IUserService;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+
+import jakarta.annotation.Resource;
+import java.util.Iterator;
+
+/**
+ * @author TangWei
+ * @Date: 2023/3/24 15:56
+ * @Description:
+ */
+@Component
+public class MessageConfig {
+
+ @Resource
+ private IMessageService messageService;
+
+ @Resource
+ private IUserService userService;
+
+ @Resource
+ private WebConfig webConfig;
+
+ public void sendMessage() {
+ long count =
+ messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1"));
+ String userId = userService.getUserInfo().getId();
+ String token = webConfig.loginuserCache().get(Constant.TOKEN + userId);
+ ServerSendEventServer.sendMessage(token, count + "");
+ }
+
+ public void addMessage(Message message) {
+ messageService.save(message);
+ long count =
+ messageService.count(new LambdaQueryWrapper().eq(Message::getStatus, "1"));
+ ServerSendEventServer.sendMessage(count + "");
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/MybitsPlusConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/MybitsPlusConfig.java
new file mode 100644
index 0000000..8ed050a
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/MybitsPlusConfig.java
@@ -0,0 +1,35 @@
+package com.yfd.platform.system.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/******************************
+ * 用途说明:
+ * 作者姓名: pcj
+ * 创建时间: 2022/10/24 10:50
+ ******************************/
+@Configuration
+public class MybitsPlusConfig {
+
+// @Bean
+// public MybatisPlusInterceptor mybatisPlusInterceptor() {
+// MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
+// mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+// return mybatisPlusInterceptor;
+// }
+
+ /**
+ * 分页插件配置(Oracle 兼容)
+ */
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 添加分页拦截器,指定数据库类型为 Oracle
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.ORACLE));
+ return interceptor;
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/ProdApiPrefixFilter.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/ProdApiPrefixFilter.java
new file mode 100644
index 0000000..75aad0e
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/ProdApiPrefixFilter.java
@@ -0,0 +1,44 @@
+package com.yfd.platform.system.config;
+
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.RequestDispatcher;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.annotation.WebFilter;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 将以 /prod-api/ 开头的请求转发到去掉前缀的真实后端接口路径。
+ * 例如:/prod-api/user/code -> /user/code
+ * 这样可以兼容前端生产环境仍使用 /prod-api 作为网关前缀的情况。
+ */
+@WebFilter(urlPatterns = "/prod-api/*", filterName = "prodApiPrefixFilter")
+public class ProdApiPrefixFilter implements Filter {
+
+ private static final String PREFIX = "/prod-api";
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+ if (!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)) {
+ chain.doFilter(req, res);
+ return;
+ }
+
+ HttpServletRequest request = (HttpServletRequest) req;
+ String uri = request.getRequestURI();
+
+ // 仅拦截 /prod-api/* 的接口请求并进行内部 forward
+ if (uri.startsWith(PREFIX + "/")) {
+ String forwardUri = uri.substring(PREFIX.length());
+ RequestDispatcher dispatcher = request.getRequestDispatcher(forwardUri);
+ dispatcher.forward(req, res);
+ return;
+ }
+
+ chain.doFilter(req, res);
+ }
+}
\ No newline at end of file
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/QuartzConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/QuartzConfig.java
new file mode 100644
index 0000000..4ccad1c
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/QuartzConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config;
+
+import org.quartz.Scheduler;
+import org.quartz.spi.TriggerFiredBundle;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.quartz.AdaptableJobFactory;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * 定时任务配置
+ *
+ * @author /
+ * @date 2019-01-07
+ */
+@Configuration
+public class QuartzConfig {
+
+ /**
+ * 解决Job中注入Spring Bean为null的问题
+ */
+ @Component("quartzJobFactory")
+ public static class QuartzJobFactory extends AdaptableJobFactory {
+
+ private final AutowireCapableBeanFactory capableBeanFactory;
+
+ public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
+ this.capableBeanFactory = capableBeanFactory;
+ }
+
+ @Override
+ protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
+
+ //调用父类的方法
+ Object jobInstance = super.createJobInstance(bundle);
+ capableBeanFactory.autowireBean(jobInstance);
+ return jobInstance;
+ }
+ }
+
+ /**
+ * 注入scheduler到spring
+ *
+ * @param quartzJobFactory /
+ * @return Scheduler
+ * @throws Exception /
+ */
+ @Bean(name = "scheduler")
+ public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {
+ SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
+ factoryBean.setJobFactory(quartzJobFactory);
+ factoryBean.afterPropertiesSet();
+ Scheduler scheduler = factoryBean.getScheduler();
+ scheduler.start();
+ return scheduler;
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/ResponseResult.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/ResponseResult.java
new file mode 100644
index 0000000..a4a5c36
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/ResponseResult.java
@@ -0,0 +1,57 @@
+package com.yfd.platform.system.config;
+
+import java.util.HashMap;
+
+public class ResponseResult extends HashMap {
+ private static final long serialVersionUID = 1L;
+
+ public ResponseResult() {
+ }
+
+ public static ResponseResult unlogin() {
+ return message("401", "未登录");
+ }
+
+ public static ResponseResult error() {
+ return error("操作失败");
+ }
+
+ public static ResponseResult success() {
+ return success("操作成功");
+ }
+
+ public static ResponseResult error(String msg) {
+ ResponseResult json = new ResponseResult();
+ json.put((String)"code", "1");//错误
+ json.put((String)"msg", msg);
+ return json;
+ }
+
+ public static ResponseResult message(String code, String msg) {
+ ResponseResult json = new ResponseResult();
+ json.put((String)"code", code);
+ json.put((String)"msg", msg);
+ return json;
+ }
+
+ public static ResponseResult success(String msg) {
+ ResponseResult json = new ResponseResult();
+ json.put((String)"code", "0");//正常
+ json.put((String)"msg", msg);
+ return json;
+ }
+
+ public static ResponseResult successData(Object obj) {
+ ResponseResult json = new ResponseResult();
+ json.put((String)"code", "0");//正常
+ json.put((String)"msg", "操作成功");
+ json.put("data", obj);
+ return json;
+ }
+
+
+ public ResponseResult put(String key, Object value) {
+ super.put(key, value);
+ return this;
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/SecurityConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/SecurityConfig.java
new file mode 100644
index 0000000..abd6062
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/SecurityConfig.java
@@ -0,0 +1,90 @@
+package com.yfd.platform.system.config;
+
+import com.yfd.platform.config.bean.LoginProperties;
+import com.yfd.platform.exception.AccessDeniedHandExcetion;
+import com.yfd.platform.exception.AuthenticationException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+public class SecurityConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ @ConfigurationProperties(prefix = "login", ignoreUnknownFields = true)
+ public LoginProperties loginProperties() {
+ return new LoginProperties();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+
+ @Autowired
+ private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
+
+ @Autowired
+ private AuthenticationException authenticationException;
+
+ @Autowired
+ private AccessDeniedHandExcetion accessDeniedHandExcetion;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(csrf -> csrf.disable())
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/user/login").anonymous()
+ .requestMatchers("/user/code").permitAll()
+ .requestMatchers(HttpMethod.GET, "/").permitAll()
+ .requestMatchers(HttpMethod.GET,
+ "/*.html",
+ "/webSocket/**",
+ "/assets/**",
+ "/icon/**").permitAll()
+ .requestMatchers(
+ "/swagger-ui.html",
+ "/swagger-ui/**",
+ "/v3/api-docs/**",
+ "/v3/api-docs.yaml",
+ "/swagger-resources/**",
+ "/webjars/**",
+ "/*/api-docs").permitAll()
+ .requestMatchers(
+ "/report/**",
+ "/images/**",
+ "/pageimage/**",
+ "/avatar/**",
+ "/systemurl/**",
+ "/api/imageserver/upload").permitAll()
+ .anyRequest().authenticated()
+ )
+ .cors(cors -> {});
+
+ http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+
+ http.exceptionHandling(ex -> ex
+ .authenticationEntryPoint(authenticationException)
+ .accessDeniedHandler(accessDeniedHandExcetion)
+ );
+
+ return http.build();
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/SwaggerConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/SwaggerConfig.java
new file mode 100644
index 0000000..683b4c2
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/SwaggerConfig.java
@@ -0,0 +1,49 @@
+package com.yfd.platform.system.config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springdoc.core.models.GroupedOpenApi;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.Contact;
+
+/**
+ * Springdoc OpenAPI 配置
+ */
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI projectOpenAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("项目API 接口文档")
+ .version("3.0")
+ .description("")
+ .contact(new Contact().name("郑顺利").email("13910913995@163.com"))
+ );
+ }
+
+ @Bean
+ public GroupedOpenApi groupWebsiteApi() {
+ return GroupedOpenApi.builder()
+ .group("1. 平台模块")
+ .packagesToScan("com.yfd.platform.modules.platformdb.controller")
+ .build();
+ }
+
+ @Bean
+ public GroupedOpenApi groupQuartzApi() {
+ return GroupedOpenApi.builder()
+ .group("2. 定时任务")
+ .packagesToScan("com.yfd.platform.modules.quartz.controller")
+ .build();
+ }
+
+ @Bean
+ public GroupedOpenApi groupSystemApi() {
+ return GroupedOpenApi.builder()
+ .group("3. 系统管理")
+ .packagesToScan("com.yfd.platform.system.controller")
+ .build();
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebConfig.java
new file mode 100644
index 0000000..f9b5dd6
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebConfig.java
@@ -0,0 +1,61 @@
+package com.yfd.platform.system.config;
+
+import cn.hutool.cache.Cache;
+import cn.hutool.cache.CacheUtil;
+import lombok.SneakyThrows;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+ @Resource
+ private FileSpaceProperties fileSpaceProperties;
+
+
+
+ @Bean
+ public Cache loginuserCache() {
+ return CacheUtil.newLRUCache(200);//用户登录缓存数 缺省200
+ }
+
+ @Bean
+ public CorsFilter corsFilter() {
+ UrlBasedCorsConfigurationSource source =
+ new UrlBasedCorsConfigurationSource();
+ CorsConfiguration config = new CorsConfiguration();
+ config.setAllowCredentials(true);
+ config.addAllowedOriginPattern("*");
+ config.addAllowedHeader("*");
+ config.addAllowedMethod("*");
+ config.setMaxAge(3600L);
+ source.registerCorsConfiguration("/**", config);
+ return new CorsFilter(source);
+ }
+
+ @SneakyThrows
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/icon/**")
+ .addResourceLocations("classpath:/static/icon/")
+ .setCachePeriod(0);
+
+ registry.addResourceHandler("/assets/**")
+ .addResourceLocations("classpath:/static/assets/")
+ .setCachePeriod(0);
+
+ registry.addResourceHandler("swagger-ui.html").addResourceLocations(
+ "classpath:/META-INF/resources/");
+
+ String systemUrl = "file:" + fileSpaceProperties.getSystem().replace("\\", "/")+"user\\";
+ registry.addResourceHandler("/avatar/**").addResourceLocations(systemUrl).setCachePeriod(0);
+
+
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebSocketConfig.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebSocketConfig.java
new file mode 100644
index 0000000..50b91b9
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/WebSocketConfig.java
@@ -0,0 +1,16 @@
+package com.yfd.platform.system.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+
+ return new ServerEndpointExporter();
+ }
+}
+
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCode.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCode.java
new file mode 100644
index 0000000..3ab9cbe
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCode.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.bean;
+
+import lombok.Data;
+
+/**
+ * 登录验证码配置信息
+ *
+ * @author: liaojinlong
+ * @date: 2020/6/10 18:53
+ */
+@Data
+public class LoginCode {
+
+ /**
+ * 验证码配置
+ */
+ private LoginCodeEnum codeType;
+ /**
+ * 验证码有效期 分钟
+ */
+ private Long expiration = 2L;
+ /**
+ * 验证码内容长度
+ */
+ private int length = 2;
+ /**
+ * 验证码宽度
+ */
+ private int width = 111;
+ /**
+ * 验证码高度
+ */
+ private int height = 36;
+ /**
+ * 验证码字体
+ */
+ private String fontName;
+ /**
+ * 字体大小
+ */
+ private int fontSize = 25;
+
+ public LoginCodeEnum getCodeType() {
+ return codeType;
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCodeEnum.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCodeEnum.java
new file mode 100644
index 0000000..f02266c
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginCodeEnum.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.bean;
+
+/**
+ * 验证码配置枚举
+ *
+ * @author: liaojinlong
+ * @date: 2020/6/10 17:40
+ */
+
+public enum LoginCodeEnum {
+ /**
+ * 算数
+ */
+ arithmetic,
+ /**
+ * 中文
+ */
+ chinese,
+ /**
+ * 中文闪图
+ */
+ chinese_gif,
+ /**
+ * 闪图
+ */
+ gif,
+ spec
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginProperties.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginProperties.java
new file mode 100644
index 0000000..43a75e2
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/bean/LoginProperties.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version loginCode.length.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-loginCode.length.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.bean;
+
+import cn.hutool.core.util.StrUtil;
+import com.wf.captcha.*;
+import com.wf.captcha.base.Captcha;
+import com.yfd.platform.exception.BadConfigurationException;
+import lombok.Data;
+import java.awt.*;
+import java.util.Objects;
+
+/**
+ * 配置文件读取
+ *
+ * @author liaojinlong
+ * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
+ */
+@Data
+public class LoginProperties {
+
+ /**
+ * 账号单用户 登录
+ */
+ private boolean singleLogin = false;
+
+ private LoginCode loginCode;
+ /**
+ * 用户登录信息缓存
+ */
+ private boolean cacheEnable;
+
+ public boolean isSingleLogin() {
+ return singleLogin;
+ }
+
+ public boolean isCacheEnable() {
+ return cacheEnable;
+ }
+
+ /**
+ * 获取验证码生产类
+ *
+ * @return /
+ */
+ public Captcha getCaptcha() {
+ if (Objects.isNull(loginCode)) {
+ loginCode = new LoginCode();
+ if (Objects.isNull(loginCode.getCodeType())) {
+ loginCode.setCodeType(LoginCodeEnum.arithmetic);
+ }
+ }
+ return switchCaptcha(loginCode);
+ }
+
+ /**
+ * 依据配置信息生产验证码
+ *
+ * @param loginCode 验证码配置信息
+ * @return /
+ */
+ private Captcha switchCaptcha(LoginCode loginCode) {
+ Captcha captcha;
+ synchronized (this) {
+ switch (loginCode.getCodeType()) {
+ case arithmetic:
+ // 算术类型 https://gitee.com/whvse/EasyCaptcha
+ captcha = new ArithmeticCaptcha(loginCode.getWidth(), loginCode.getHeight());
+ // 几位数运算,默认是两位
+ captcha.setLen(loginCode.getLength());
+ break;
+ case chinese:
+ captcha = new ChineseCaptcha(loginCode.getWidth(), loginCode.getHeight());
+ captcha.setLen(loginCode.getLength());
+ break;
+ case chinese_gif:
+ captcha = new ChineseGifCaptcha(loginCode.getWidth(), loginCode.getHeight());
+ captcha.setLen(loginCode.getLength());
+ break;
+ case gif:
+ captcha = new GifCaptcha(loginCode.getWidth(), loginCode.getHeight());
+ captcha.setLen(loginCode.getLength());
+ break;
+ case spec:
+ captcha = new SpecCaptcha(loginCode.getWidth(), loginCode.getHeight());
+ captcha.setLen(loginCode.getLength());
+ break;
+ default:
+ throw new BadConfigurationException("验证码配置信息错误!正确配置查看 LoginCodeEnum ");
+ }
+ }
+ if(StrUtil.isNotBlank(loginCode.getFontName())){
+ captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN, loginCode.getFontSize()));
+ }
+ return captcha;
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskExecutePool.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskExecutePool.java
new file mode 100644
index 0000000..afad8a3
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskExecutePool.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.thread;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 异步任务线程池装配类
+ * @author https://juejin.im/entry/5abb8f6951882555677e9da2
+ * @date 2019年10月31日15:06:18
+ */
+@Slf4j
+@Configuration
+public class AsyncTaskExecutePool implements AsyncConfigurer {
+
+ /** 注入配置类 */
+ private final AsyncTaskProperties config;
+
+ public AsyncTaskExecutePool(AsyncTaskProperties config) {
+ this.config = config;
+ }
+
+ @Override
+ public Executor getAsyncExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ //核心线程池大小
+ executor.setCorePoolSize(config.getCorePoolSize());
+ //最大线程数
+ executor.setMaxPoolSize(config.getMaxPoolSize());
+ //队列容量
+ executor.setQueueCapacity(config.getQueueCapacity());
+ //活跃时间
+ executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
+ //线程名字前缀
+ executor.setThreadNamePrefix("el-async-");
+ // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务
+ // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ executor.initialize();
+ return executor;
+ }
+
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return (throwable, method, objects) -> {
+ log.error("===="+throwable.getMessage()+"====", throwable);
+ log.error("exception method:"+method.getName());
+ };
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskProperties.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskProperties.java
new file mode 100644
index 0000000..35adb9e
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/AsyncTaskProperties.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.thread;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 线程池配置属性类
+ * @author https://juejin.im/entry/5abb8f6951882555677e9da2
+ * @date 2019年10月31日14:58:18
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "task.pool")
+public class AsyncTaskProperties {
+
+ private int corePoolSize;
+
+ private int maxPoolSize;
+
+ private int keepAliveSeconds;
+
+ private int queueCapacity;
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/TheadFactoryName.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/TheadFactoryName.java
new file mode 100644
index 0000000..6ac93f2
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/TheadFactoryName.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.thread;
+
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 自定义线程名称
+ * @author
+ * @date 2019年10月31日17:49:55
+ */
+@Component
+public class TheadFactoryName implements ThreadFactory {
+
+ private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
+ private final ThreadGroup group;
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final String namePrefix;
+
+ public TheadFactoryName() {
+ this("el-pool");
+ }
+
+ private TheadFactoryName(String name){
+ // 使用当前线程的线程组,避免依赖已弃用的 SecurityManager
+ group = Thread.currentThread().getThreadGroup();
+ //此时namePrefix就是 name + 第几个用这个工厂创建线程池的
+ this.namePrefix = name +
+ POOL_NUMBER.getAndIncrement();
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ //此时线程的名字 就是 namePrefix + -thread- + 这个线程池中第几个执行的线程
+ Thread t = new Thread(group, r,
+ namePrefix + "-thread-"+threadNumber.getAndIncrement(),
+ 0);
+ if (t.isDaemon()) {
+ t.setDaemon(false);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/ThreadPoolExecutorUtil.java b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/ThreadPoolExecutorUtil.java
new file mode 100644
index 0000000..d4224be
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/config/thread/ThreadPoolExecutorUtil.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019-2020 Zheng Jie
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yfd.platform.system.config.thread;
+
+
+
+import com.yfd.platform.utils.SpringContextHolder;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 用于获取自定义线程池
+ * @author
+ * @date 2019年10月31日18:16:47
+ */
+public class ThreadPoolExecutorUtil {
+
+ public static ThreadPoolExecutor getPoll(){
+ AsyncTaskProperties properties = SpringContextHolder.getBean(AsyncTaskProperties.class);
+ return new ThreadPoolExecutor(
+ properties.getCorePoolSize(),
+ properties.getMaxPoolSize(),
+ properties.getKeepAliveSeconds(),
+ TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(properties.getQueueCapacity()),
+ new TheadFactoryName()
+ );
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/constant/Constant.java b/backend/platform-system/src/main/java/com/yfd/platform/system/constant/Constant.java
new file mode 100644
index 0000000..23a1044
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/constant/Constant.java
@@ -0,0 +1,42 @@
+package com.yfd.platform.system.constant;
+
+/**
+ * @author TangWei
+ * @Date: 2023/3/3 17:40
+ * @Description: 常量类
+ */
+public class Constant {
+
+ public static final String LOGIN = "login:";
+ public static final String TOKEN = "token:";
+ public static final String USER_ID = "userid";
+ public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ public static final String CODE_KEY = "code-key-";
+ public static final long CODE_EXPIRATION_TIME = 1000 * 60;
+ /**
+ * 用于IP定位转换
+ */
+ public static final String REGION = "内网IP|内网IP";
+ /**
+ * win 系统
+ */
+ public static final String WIN = "win";
+
+ /**
+ * mac 系统
+ */
+ public static final String MAC = "mac";
+
+ /**
+ * 常用接口
+ */
+ public static class Url {
+
+ // IP归属地查询
+ // public static final String IP_URL = "http://whois.pconline.com
+ // .cn/ipJson.jsp?ip=%s&json=true";
+ public static final String IP_URL = "http://whois.pconline.com" +
+ ".cn/ipJson.jsp?ip=%s&json=true";
+ }
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSource.java b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSource.java
new file mode 100644
index 0000000..a6cf3ff
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSource.java
@@ -0,0 +1,17 @@
+package com.yfd.platform.system.datasource;
+
+import java.lang.annotation.*;
+
+/******************************
+ * 用途说明:
+ * 作者姓名: wxy
+ * 创建时间: 2022/9/23 17:48
+ ******************************/
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataSource {
+
+ String name() default "";
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSourceAspect.java b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSourceAspect.java
new file mode 100644
index 0000000..56556e2
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DataSourceAspect.java
@@ -0,0 +1,55 @@
+package com.yfd.platform.system.datasource;
+
+import cn.hutool.core.util.StrUtil;
+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.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/******************************
+ * 用途说明:
+ * 作者姓名: wxy
+ * 创建时间: 2022/9/23 17:50
+ ******************************/
+@Aspect
+@Component
+public class DataSourceAspect {
+
+ @Pointcut("@annotation(com.yfd.platform.datasource.DataSource)")
+ public void dataSourcePointCut() {
+
+ }
+
+ private String DataBaseName;
+
+ @Around("dataSourcePointCut()")
+ public Object around(ProceedingJoinPoint point) throws Throwable {
+ MethodSignature signature = (MethodSignature) point.getSignature();
+ Method method = signature.getMethod();
+ if (StrUtil.isNotBlank(DataBaseName)){
+ DynamicDataSource.setDataSource(DataBaseName);
+ }else {
+ DynamicDataSource.setDataSource("master");
+ }
+
+ try {
+ return point.proceed();
+ } finally {
+ DynamicDataSource.clearDataSource();
+ }
+ }
+
+ public String getDataBase(Integer type){
+ if (type == 1){
+ DataBaseName="master";
+ }else {
+ DataBaseName="slave";
+ }
+ return DataBaseName;
+ }
+
+}
diff --git a/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DynamicDataSource.java b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DynamicDataSource.java
new file mode 100644
index 0000000..7dfbf40
--- /dev/null
+++ b/backend/platform-system/src/main/java/com/yfd/platform/system/datasource/DynamicDataSource.java
@@ -0,0 +1,40 @@
+package com.yfd.platform.system.datasource;
+
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+/******************************
+ * 用途说明:
+ * 作者姓名: wxy
+ * 创建时间: 2022/9/23 17:47
+ ******************************/
+public class DynamicDataSource extends AbstractRoutingDataSource {
+ private static final ThreadLocal contextHolder = new ThreadLocal<>();
+
+ public DynamicDataSource(DataSource defaultTargetDataSource, Map