feat: 添加ERP服务和系统服务,完善员工门户功能
## 新增服务模块 ### 1. ERP服务 (hzhub-erp) - 新增独立的ERP数据适配服务 - 支持SQL Server 2008 R2数据源 - 提供动态API配置管理系统 - 包含客户管理、销售数据等业务接口 ### 2. 系统服务 (hzhub-system) - 新增独立的系统管理服务 - 用户、角色、权限、部门、菜单管理 - 租户管理、操作日志、在线用户监控 - 工作流引擎(warm-flow)集成 - 企业微信审批同步功能 ### 3. API网关 (hzhub-gateway) - 新增Spring Cloud Gateway网关服务 - JWT认证、路由分发、限流熔断 - XSS防护、请求日志记录 - 统一入口端口8080 ## 后台管理功能增强 ### ERP动态API管理 - 新增动态API配置管理界面 - API测试、文档预览、统计监控 - 错误日志查看、缓存管理 - 从数据库表自动导入API配置 ### 系统管理增强 - 企业微信配置管理 - 企业微信审批同步配置 - 部门和用户管理优化 ## 员工门户功能完善 ### 业务页面 - 审批中心:工作流审批、待办任务 - CRM管理:客户关系管理 - 经销商管理:经销商数据展示 - 供应链管理:采购、库存、销售 - BI报表:数据可视化分析 - ERP数据探索:SQL Server数据查询 ### 个人中心 - 基本设置:个人信息管理 - 安全设置:密码修改、登录日志 - 锁屏功能:自动锁屏、手动锁屏 ### 其他功能 - 标签页管理:多标签页导航 - 页面缓存:keepAlive缓存机制 - 会话超时:自动检测并提示 ## 经销商门户 ### 页面路由 - 新增经销商管理页面路由 - AI聊天界面完善 ## 文档更新 - ERP API数据库初始化指南 - ERP API前端完整实现文档 - ERP API测试和验证指南 - Gateway路由迁移计划 - 项目配置文档更新 ## 部署脚本 - 统一启动/停止/重启脚本 - Docker Compose配置优化 - Nginx配置文件更新 ## 技术栈 - 后端: Spring Boot 3.5.8, Java 17 - 前端: Vue 3, TypeScript, Element Plus, Vben Admin - 工作流: warm-flow 1.8.2 - 网关: Spring Cloud Gateway - 数据库: MySQL 8.0, SQL Server 2008 R2 - 缓存: Redis 7 - 向量库: Weaviate 1.25.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
0
hzhub-ai/hzhub-admin/Dockerfile
Normal file → Executable file
0
hzhub-ai/hzhub-admin/Dockerfile
Normal file → Executable file
19
hzhub-ai/hzhub-admin/pom.xml
Normal file → Executable file
19
hzhub-ai/hzhub-admin/pom.xml
Normal file → Executable file
@@ -23,6 +23,11 @@
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- <!– mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 –>-->
|
||||
<!-- <!– Oracle –>-->
|
||||
<!-- <dependency>-->
|
||||
@@ -67,26 +72,16 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hzhub</groupId>
|
||||
<artifactId>hzhub-system</artifactId>
|
||||
<artifactId>hzhub-common-oss</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成-->
|
||||
<dependency>
|
||||
<groupId>org.hzhub</groupId>
|
||||
<artifactId>hzhub-generator</artifactId>
|
||||
</dependency>
|
||||
<!-- hzhub-system / hzhub-workflow / hzhub-generator 已迁移至独立服务 -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hzhub</groupId>
|
||||
<artifactId>hzhub-chat</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工作流模块 -->
|
||||
<dependency>
|
||||
<groupId>org.hzhub</groupId>
|
||||
<artifactId>hzhub-workflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AI流程编排模块 -->
|
||||
<dependency>
|
||||
<groupId>org.hzhub</groupId>
|
||||
|
||||
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/HZHubAIApplication.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/HZHubAIApplication.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/HZHubAIServletInitializer.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/HZHubAIServletInitializer.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/config/MapperConflictResolver.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/config/MapperConflictResolver.java
Normal file → Executable file
@@ -1,238 +0,0 @@
|
||||
package org.hzhub.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
import org.hzhub.common.core.constant.SystemConstants;
|
||||
import org.hzhub.common.core.domain.R;
|
||||
import org.hzhub.common.core.domain.model.LoginBody;
|
||||
import org.hzhub.common.core.domain.model.RegisterBody;
|
||||
import org.hzhub.common.core.domain.model.SocialLoginBody;
|
||||
import org.hzhub.common.core.utils.*;
|
||||
import org.hzhub.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.hzhub.common.json.utils.JsonUtils;
|
||||
import org.hzhub.common.ratelimiter.annotation.RateLimiter;
|
||||
import org.hzhub.common.ratelimiter.enums.LimitType;
|
||||
import org.hzhub.common.satoken.utils.LoginHelper;
|
||||
import org.hzhub.common.social.config.properties.SocialLoginConfigProperties;
|
||||
import org.hzhub.common.social.config.properties.SocialProperties;
|
||||
import org.hzhub.common.social.utils.SocialUtils;
|
||||
import org.hzhub.common.sse.dto.SseMessageDto;
|
||||
import org.hzhub.common.sse.utils.SseMessageUtils;
|
||||
import org.hzhub.common.tenant.helper.TenantHelper;
|
||||
import org.hzhub.system.domain.bo.SysTenantBo;
|
||||
import org.hzhub.system.domain.vo.*;
|
||||
import org.hzhub.system.service.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 认证
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@SaIgnore
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final SocialProperties socialProperties;
|
||||
private final SysLoginService loginService;
|
||||
private final SysRegisterService registerService;
|
||||
private final ISysConfigService configService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysSocialService socialUserService;
|
||||
private final ISysClientService clientService;
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
* @param body 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
@ApiEncrypt
|
||||
@PostMapping("/login")
|
||||
public R<LoginVo> login(@RequestBody String body) {
|
||||
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
// 授权类型和客户端id
|
||||
String clientId = loginBody.getClientId();
|
||||
String grantType = loginBody.getGrantType();
|
||||
log.info("登录请求 - clientId: {}, grantType: {}", clientId, grantType);
|
||||
SysClientVo client = clientService.queryByClientId(clientId);
|
||||
log.info("查询客户端结果 - client: {}, grantType: {}", client, client != null ? client.getGrantType() : "null");
|
||||
// 查询不到 client 或 client 内不包含 grantType
|
||||
if (ObjectUtil.isNull(client)) {
|
||||
log.info("客户端id: {} 不存在!", clientId);
|
||||
return R.fail(MessageUtils.message("auth.grant.type.error"));
|
||||
}
|
||||
if (!StringUtils.contains(client.getGrantType(), grantType)) {
|
||||
log.info("客户端id: {} 认证类型:{} 不匹配! 数据库grantType: {}", clientId, grantType, client.getGrantType());
|
||||
return R.fail(MessageUtils.message("auth.grant.type.error"));
|
||||
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
||||
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
|
||||
}
|
||||
// 校验租户
|
||||
loginService.checkTenant(loginBody.getTenantId());
|
||||
// 登录
|
||||
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
|
||||
|
||||
Long userId = LoginHelper.getUserId();
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage("欢迎登录hzhub-ai后台管理系统");
|
||||
dto.setUserIds(List.of(userId));
|
||||
SseMessageUtils.publishMessage(dto);
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取跳转URL
|
||||
*
|
||||
* @param source 登录来源
|
||||
* @return 结果
|
||||
*/
|
||||
@GetMapping("/binding/{source}")
|
||||
public R<String> authBinding(@PathVariable("source") String source,
|
||||
@RequestParam String tenantId, @RequestParam String domain) {
|
||||
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return R.fail(source + "平台账号暂不支持");
|
||||
}
|
||||
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("tenantId", tenantId);
|
||||
map.put("domain", domain);
|
||||
map.put("state", AuthStateUtils.createState());
|
||||
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
|
||||
return R.ok("操作成功", authorizeUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端回调绑定授权(需要token)
|
||||
*
|
||||
* @param loginBody 请求体
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/social/callback")
|
||||
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
|
||||
// 校验token
|
||||
StpUtil.checkLogin();
|
||||
// 获取第三方登录信息
|
||||
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
|
||||
loginBody.getSource(), loginBody.getSocialCode(),
|
||||
loginBody.getSocialState(), socialProperties);
|
||||
AuthUser authUserData = response.getData();
|
||||
// 判断授权响应是否成功
|
||||
if (!response.ok()) {
|
||||
return R.fail(response.getMsg());
|
||||
}
|
||||
loginService.socialRegister(authUserData);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 取消授权(需要token)
|
||||
*
|
||||
* @param socialId socialId
|
||||
*/
|
||||
@DeleteMapping(value = "/unlock/{socialId}")
|
||||
public R<Void> unlockSocial(@PathVariable Long socialId) {
|
||||
// 校验token
|
||||
StpUtil.checkLogin();
|
||||
Boolean rows = socialUserService.deleteWithValidById(socialId);
|
||||
return rows ? R.ok() : R.fail("取消授权失败");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public R<Void> logout() {
|
||||
loginService.logout();
|
||||
return R.ok("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@ApiEncrypt
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@Validated @RequestBody RegisterBody user) {
|
||||
if (!configService.selectRegisterEnabled(user.getTenantId())) {
|
||||
return R.fail("当前系统没有开启注册功能!");
|
||||
}
|
||||
registerService.register(user);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面租户下拉框
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
||||
@GetMapping("/tenant/list")
|
||||
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
||||
// 返回对象
|
||||
LoginTenantVo result = new LoginTenantVo();
|
||||
boolean enable = TenantHelper.isEnable();
|
||||
result.setTenantEnabled(enable);
|
||||
// 如果未开启租户这直接返回
|
||||
if (!enable) {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
|
||||
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
|
||||
try {
|
||||
// 如果只超管返回所有租户
|
||||
if (LoginHelper.isSuperAdmin()) {
|
||||
result.setVoList(voList);
|
||||
return R.ok(result);
|
||||
}
|
||||
} catch (NotLoginException ignored) {
|
||||
}
|
||||
|
||||
// 获取域名
|
||||
String host;
|
||||
String referer = request.getHeader("referer");
|
||||
if (StringUtils.isNotBlank(referer)) {
|
||||
// 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
|
||||
host = referer.split("//")[1].split("/")[0];
|
||||
} else {
|
||||
host = new URL(request.getRequestURL().toString()).getHost();
|
||||
}
|
||||
// 根据域名进行筛选
|
||||
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
|
||||
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
|
||||
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
package org.hzhub.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hzhub.common.core.constant.Constants;
|
||||
import org.hzhub.common.core.constant.GlobalConstants;
|
||||
import org.hzhub.common.core.domain.R;
|
||||
import org.hzhub.common.core.exception.ServiceException;
|
||||
import org.hzhub.common.core.utils.SpringUtils;
|
||||
import org.hzhub.common.core.utils.StringUtils;
|
||||
import org.hzhub.common.core.utils.reflect.ReflectUtils;
|
||||
import org.hzhub.common.mail.config.properties.MailProperties;
|
||||
import org.hzhub.common.mail.utils.MailUtils;
|
||||
import org.hzhub.common.ratelimiter.annotation.RateLimiter;
|
||||
import org.hzhub.common.ratelimiter.enums.LimitType;
|
||||
import org.hzhub.common.redis.utils.RedisUtils;
|
||||
import org.hzhub.common.web.config.properties.CaptchaProperties;
|
||||
import org.hzhub.common.web.enums.CaptchaType;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.hzhub.system.domain.vo.CaptchaVo;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 验证码操作处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@SaIgnore
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final MailProperties mailProperties;
|
||||
|
||||
/**
|
||||
* 短信验证码
|
||||
*
|
||||
* @param phonenumber 用户手机号
|
||||
*/
|
||||
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
|
||||
@GetMapping("/resource/sms/code")
|
||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||
String templateId = "";
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
map.put("code", code);
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
|
||||
if (!smsResponse.isSuccess()) {
|
||||
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||
return R.fail(smsResponse.getData().toString());
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱验证码
|
||||
*
|
||||
* @param email 邮箱
|
||||
*/
|
||||
@GetMapping("/resource/email/code")
|
||||
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
||||
if (!mailProperties.getEnabled()) {
|
||||
return R.fail("当前系统没有开启邮箱功能!");
|
||||
}
|
||||
SpringUtils.getAopProxy(this).emailCodeImpl(email);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱验证码
|
||||
* 独立方法避免验证码关闭之后仍然走限流
|
||||
*/
|
||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||
public void emailCodeImpl(String email) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
try {
|
||||
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
|
||||
} catch (Exception e) {
|
||||
log.error("验证码短信发送异常 => {}", e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@GetMapping("/auth/code")
|
||||
public R<CaptchaVo> getCode() {
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
if (!captchaEnabled) {
|
||||
CaptchaVo captchaVo = new CaptchaVo();
|
||||
captchaVo.setCaptchaEnabled(false);
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
* 独立方法避免验证码关闭之后仍然走限流
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||
public CaptchaVo getCodeImpl() {
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
CodeGenerator codeGenerator;
|
||||
if (CaptchaType.MATH == captchaType) {
|
||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
|
||||
} else {
|
||||
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
|
||||
}
|
||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||
String code = captcha.getCode();
|
||||
if (CaptchaType.MATH == captchaType) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
||||
code = exp.getValue(String.class);
|
||||
}
|
||||
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
CaptchaVo captchaVo = new CaptchaVo();
|
||||
captchaVo.setUuid(uuid);
|
||||
captchaVo.setImg(captcha.getImageBase64());
|
||||
return captchaVo;
|
||||
}
|
||||
|
||||
}
|
||||
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/controller/IndexController.java
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/controller/IndexController.java
Normal file → Executable file
117
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/service/OssServiceImpl.java
Executable file
117
hzhub-ai/hzhub-admin/src/main/java/org/hzhub/service/OssServiceImpl.java
Executable file
@@ -0,0 +1,117 @@
|
||||
package org.hzhub.service;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hzhub.common.core.domain.dto.OssDTO;
|
||||
import org.hzhub.common.core.exception.ServiceException;
|
||||
import org.hzhub.common.core.service.OssService;
|
||||
import org.hzhub.common.core.utils.StringUtils;
|
||||
import org.hzhub.common.core.utils.file.ContentTypeUtil;
|
||||
import org.hzhub.common.oss.core.OssClient;
|
||||
import org.hzhub.common.oss.entity.UploadResult;
|
||||
import org.hzhub.common.oss.factory.OssFactory;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 简易 OSS 服务实现(用于 hzhub-ai)
|
||||
* 上传文件至 OSS,文件信息存储至 sys_oss 表
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class OssServiceImpl implements OssService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public OssDTO uploadFile(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new ServiceException("上传文件不能为空");
|
||||
}
|
||||
String originalFileName = file.getOriginalFilename();
|
||||
String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."));
|
||||
OssClient storage = OssFactory.instance();
|
||||
UploadResult uploadResult;
|
||||
try {
|
||||
uploadResult = storage.uploadSuffix(file.getBytes(), suffix, ContentTypeUtil.getContentType(suffix, file.getContentType()));
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("上传失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 保存到 sys_oss 表
|
||||
Long ossId = saveOssRecord(originalFileName, suffix, storage.getConfigKey(), uploadResult, file.getSize(), file.getContentType());
|
||||
OssDTO dto = new OssDTO();
|
||||
dto.setOssId(ossId);
|
||||
dto.setFileName(uploadResult.getFilename());
|
||||
dto.setOriginalName(originalFileName);
|
||||
dto.setFileSuffix(suffix);
|
||||
dto.setUrl(uploadResult.getUrl());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String selectUrlByIds(String ossIds) {
|
||||
if (StringUtils.isBlank(ossIds)) return "";
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (String idStr : ossIds.split(",")) {
|
||||
idStr = idStr.trim();
|
||||
if (StringUtils.isBlank(idStr)) continue;
|
||||
try {
|
||||
String url = jdbcTemplate.queryForObject(
|
||||
"SELECT url FROM sys_oss WHERE oss_id = ?", String.class, Long.parseLong(idStr));
|
||||
if (url != null) {
|
||||
urls.add(url);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询 OSS URL 失败: ossId={}", idStr, e);
|
||||
}
|
||||
}
|
||||
return StringUtils.joinComma(urls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OssDTO> selectByIds(String ossIds) {
|
||||
if (StringUtils.isBlank(ossIds)) return List.of();
|
||||
List<OssDTO> result = new ArrayList<>();
|
||||
for (String idStr : ossIds.split(",")) {
|
||||
idStr = idStr.trim();
|
||||
if (StringUtils.isBlank(idStr)) continue;
|
||||
try {
|
||||
List<OssDTO> rows = jdbcTemplate.query(
|
||||
"SELECT oss_id, file_name, original_name, file_suffix, url FROM sys_oss WHERE oss_id = ?",
|
||||
(rs, rowNum) -> {
|
||||
OssDTO dto = new OssDTO();
|
||||
dto.setOssId(rs.getLong("oss_id"));
|
||||
dto.setFileName(rs.getString("file_name"));
|
||||
dto.setOriginalName(rs.getString("original_name"));
|
||||
dto.setFileSuffix(rs.getString("file_suffix"));
|
||||
dto.setUrl(rs.getString("url"));
|
||||
return dto;
|
||||
},
|
||||
Long.parseLong(idStr));
|
||||
if (!rows.isEmpty()) {
|
||||
result.add(rows.get(0));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询 OSS 记录失败: ossId={}", idStr, e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long saveOssRecord(String originalName, String suffix, String configKey, UploadResult result, long size, String contentType) {
|
||||
Long ossId = RandomUtil.randomLong(Long.MAX_VALUE);
|
||||
String extJson = String.format("{\"fileSize\":%d,\"contentType\":\"%s\"}", size, contentType);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO sys_oss (oss_id, url, file_name, original_name, file_suffix, service, ext1) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
ossId, result.getUrl(), result.getFilename(), originalName, suffix, configKey, extJson);
|
||||
return ossId;
|
||||
}
|
||||
}
|
||||
0
hzhub-ai/hzhub-admin/src/main/resources/application-dev.yml
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/application-dev.yml
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/application-prod.yml
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/application-prod.yml
Normal file → Executable file
12
hzhub-ai/hzhub-admin/src/main/resources/application.yml
Normal file → Executable file
12
hzhub-ai/hzhub-admin/src/main/resources/application.yml
Normal file → Executable file
@@ -108,8 +108,8 @@ sa-token:
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: false
|
||||
# jwt秘钥
|
||||
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
|
||||
# jwt秘钥(必须 >= 32 字节)
|
||||
jwt-secret-key: ${JWT_SECRET:Om1fovSeKIA1oLIoHdDPMF-trbqbrPQoDS3H4u1xoRY}
|
||||
|
||||
# security配置
|
||||
security:
|
||||
@@ -210,13 +210,7 @@ springdoc:
|
||||
packages-to-scan: org.hzhub.demo
|
||||
- group: 2.通用模块
|
||||
packages-to-scan: org.hzhub.web
|
||||
- group: 3.系统模块
|
||||
packages-to-scan: org.hzhub.system
|
||||
- group: 4.代码生成模块
|
||||
packages-to-scan: org.hzhub.generator
|
||||
- group: 5.工作流模块
|
||||
packages-to-scan: org.hzhub.workflow
|
||||
- group: 6.MCP模块
|
||||
- group: 3.MCP模块
|
||||
packages-to-scan: org.hzhub.mcp
|
||||
|
||||
# 防止XSS攻击
|
||||
|
||||
0
hzhub-ai/hzhub-admin/src/main/resources/banner.txt
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/banner.txt
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages_en_US.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages_en_US.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages_zh_CN.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/i18n/messages_zh_CN.properties
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/ip2region.xdb
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/ip2region.xdb
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/logback-plus.xml
Normal file → Executable file
0
hzhub-ai/hzhub-admin/src/main/resources/logback-plus.xml
Normal file → Executable file
Reference in New Issue
Block a user