commit:升级到vue3,更新最近工作流技术栈,支持sa-token

This commit is contained in:
Jerry
2024-07-05 22:42:33 +08:00
parent bbcc608584
commit 565ecb6371
1751 changed files with 236790 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.orangeforms</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-ext</artifactId>
<dependencies>
<dependency>
<groupId>com.orangeforms</groupId>
<artifactId>common-redis</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
package com.orangeforms.common.ext.base;
import com.orangeforms.common.core.object.MyOrderParam;
import com.orangeforms.common.core.object.MyPageData;
import com.orangeforms.common.core.object.MyPageParam;
import java.util.List;
import java.util.Map;
/**
* 业务组件获取数据的数据源接口。
* 如果业务服务集成了common-ext组件可以通过实现该接口的方式为BizWidgetController访问提供数据。
* 对于没有集成common-ext组件的服务可以通过http方式为BizWidgetController访问提供数据。
*
* @author Jerry
* @date 2024-07-02
*/
public interface BizWidgetDatasource {
/**
* 获取指定通用业务组件的数据。
*
* @param widgetType 业务组件类型。
* @param filter 过滤参数。不同的数据源参数不同。这里我们以键值对的方式传递。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 查询后的分页数据列表。
*/
MyPageData<Map<String, Object>> getDataList(
String widgetType, Map<String, Object> filter, MyOrderParam orderParam, MyPageParam pageParam);
/**
* 获取指定主键Id的数据对象。
*
* @param widgetType 业务组件类型。
* @param fieldName 字段名,如果为空,则使用主键字段名。
* @param fieldValues 字段值集合。
* @return 指定主键Id的数据对象。
*/
List<Map<String, Object>> getDataListWithInList(String widgetType, String fieldName, List<String> fieldValues);
}

View File

@@ -0,0 +1,13 @@
package com.orangeforms.common.ext.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* common-ext通用扩展模块的自动配置引导类。
*
* @author Jerry
* @date 2024-07-02
*/
@EnableConfigurationProperties({CommonExtProperties.class})
public class CommonExtAutoConfig {
}

View File

@@ -0,0 +1,76 @@
package com.orangeforms.common.ext.config;
import cn.hutool.core.collection.CollUtil;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* common-ext配置属性类。
*
* @author Jerry
* @date 2024-07-02
*/
@Data
@ConfigurationProperties(prefix = "common-ext")
public class CommonExtProperties implements InitializingBean {
/**
* 上传存储类型。具体值可参考枚举 UploadStoreTypeEnum。默认0为本地存储。
*/
@Value("${common-ext.uploadStoreType:0}")
private Integer uploadStoreType;
/**
* 仅当uploadStoreType等于0的时候该配置值生效。
*/
@Value("${common-ext.uploadFileBaseDir:./zz-resource/upload-files/commonext}")
private String uploadFileBaseDir;
private List<AppProperties> apps;
private Map<String, AppProperties> applicationMap;
@Override
public void afterPropertiesSet() throws Exception {
if (CollUtil.isEmpty(apps)) {
applicationMap = new HashMap<>(1);
} else {
applicationMap = apps.stream().collect(Collectors.toMap(AppProperties::getAppCode, c -> c));
}
}
@Data
public static class AppProperties {
/**
* 应用编码。
*/
private String appCode;
/**
* 通用业务组件数据源属性列表。
*/
private List<BizWidgetDatasourceProperties> bizWidgetDatasources;
}
@Data
public static class BizWidgetDatasourceProperties {
/**
* 通用业务组件的数据源类型。多个类型之间逗号分隔upms_user,upms_dept。
*/
private String types;
/**
* 列表数据接口地址。格式为完整的urlhttp://xxxxx
*/
private String listUrl;
/**
* 详情数据接口地址。格式为完整的urlhttp://xxxxx
*/
private String viewUrl;
}
}

View File

@@ -0,0 +1,41 @@
package com.orangeforms.common.ext.constant;
/**
* 业务组件数据源类型常量类。
*
* @author Jerry
* @date 2024-07-02
*/
public class BizWidgetDatasourceType {
/**
* 通用用户组件数据源类型。
*/
public static final String UPMS_USER_TYPE = "upms_user";
/**
* 通用部门组件数据源类型。
*/
public static final String UPMS_DEPT_TYPE = "upms_dept";
/**
* 通用角色组件数据源类型。
*/
public static final String UPMS_ROLE_TYPE = "upms_role";
/**
* 通用岗位组件数据源类型。
*/
public static final String UPMS_POST_TYPE = "upms_post";
/**
* 通用部门岗位组件数据源类型。
*/
public static final String UPMS_DEPT_POST_TYPE = "upms_dept_post";
/**
* 私有构造函数,明确标识该常量类的作用。
*/
private BizWidgetDatasourceType() {
}
}

View File

@@ -0,0 +1,58 @@
package com.orangeforms.common.ext.controller;
import com.alibaba.fastjson.JSONObject;
import com.orangeforms.common.core.object.*;
import com.orangeforms.common.ext.util.BizWidgetDatasourceExtHelper;
import com.orangeforms.common.core.annotation.MyRequestBody;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 业务组件获取数据的访问接口。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@RestController
@RequestMapping("${common-ext.urlPrefix}/bizwidget")
public class BizWidgetController {
@Autowired
private BizWidgetDatasourceExtHelper bizWidgetDatasourceExtHelper;
@PostMapping("/list")
public ResponseResult<MyPageData<Map<String, Object>>> list(
@MyRequestBody(required = true) String widgetType,
@MyRequestBody JSONObject filter,
@MyRequestBody MyOrderParam orderParam,
@MyRequestBody MyPageParam pageParam) {
String appCode = TokenData.takeFromRequest().getAppCode();
MyPageData<Map<String, Object>> pageData =
bizWidgetDatasourceExtHelper.getDataList(appCode, widgetType, filter, orderParam, pageParam);
return ResponseResult.success(pageData);
}
/**
* 查看指定多条数据的详情。
*
* @param widgetType 组件类型。
* @param fieldName 字段名,如果为空则默认为主键过滤。
* @param fieldValues 字段值。多个值之间逗号分割。
* @return 详情数据。
*/
@PostMapping("/view")
public ResponseResult<List<Map<String, Object>>> view(
@MyRequestBody(required = true) String widgetType,
@MyRequestBody String fieldName,
@MyRequestBody(required = true) String fieldValues) {
String appCode = TokenData.takeFromRequest().getAppCode();
List<Map<String, Object>> dataMapList =
bizWidgetDatasourceExtHelper.getDataListWithInList(appCode, widgetType, fieldName, fieldValues);
return ResponseResult.success(dataMapList);
}
}

View File

@@ -0,0 +1,112 @@
package com.orangeforms.common.ext.controller;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.StrUtil;
import com.orangeforms.common.core.constant.ErrorCodeEnum;
import com.orangeforms.common.core.object.ResponseResult;
import com.orangeforms.common.core.object.TokenData;
import com.orangeforms.common.core.upload.BaseUpDownloader;
import com.orangeforms.common.core.upload.UpDownloaderFactory;
import com.orangeforms.common.core.upload.UploadResponseInfo;
import com.orangeforms.common.core.upload.UploadStoreTypeEnum;
import com.orangeforms.common.core.util.ContextUtil;
import com.orangeforms.common.ext.config.CommonExtProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBinaryStream;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
/**
* 扩展工具接口类。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@RestController
@RequestMapping("${common-ext.urlPrefix}/util")
public class UtilController {
@Autowired
private UpDownloaderFactory upDownloaderFactory;
@Autowired
private CommonExtProperties properties;
@Autowired
private RedissonClient redissonClient;
private static final String IMAGE_DATA_FIELD = "imageData";
/**
* 上传图片数据。
*
* @param uploadFile 上传图片文件。
*/
@PostMapping("/uploadImage")
public void uploadImage(@RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {
BaseUpDownloader upDownloader =
upDownloaderFactory.get(EnumUtil.getEnumAt(UploadStoreTypeEnum.class, properties.getUploadStoreType()));
UploadResponseInfo responseInfo = upDownloader.doUpload(null,
properties.getUploadFileBaseDir(), "CommonExt", IMAGE_DATA_FIELD, true, uploadFile);
if (BooleanUtil.isTrue(responseInfo.getUploadFailed())) {
ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
ResponseResult.error(ErrorCodeEnum.UPLOAD_FAILED, responseInfo.getErrorMessage()));
return;
}
String uploadUri = ContextUtil.getHttpRequest().getRequestURI();
uploadUri = StrUtil.removeSuffix(uploadUri, "/");
uploadUri = StrUtil.removeSuffix(uploadUri, "/uploadImage");
responseInfo.setDownloadUri(uploadUri + "/downloadImage");
ResponseResult.output(ResponseResult.success(responseInfo));
}
/**
* 下载图片数据。
*
* @param filename 文件名。
* @param response Http 应答对象。
*/
@GetMapping("/downloadImage")
public void downloadImage(@RequestParam String filename, HttpServletResponse response) {
try {
BaseUpDownloader upDownloader =
upDownloaderFactory.get(EnumUtil.getEnumAt(UploadStoreTypeEnum.class, properties.getUploadStoreType()));
upDownloader.doDownload(properties.getUploadFileBaseDir(),
"CommonExt", IMAGE_DATA_FIELD, filename, true, response);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
log.error(e.getMessage(), e);
}
}
/**
* 下载缓存的会话图片数据。
*
* @param filename 文件名。
* @param response Http 应答对象。
*/
@GetMapping("/downloadSessionImage")
public void downloadSessionImage(@RequestParam String filename, HttpServletResponse response) throws IOException {
TokenData tokenData = TokenData.takeFromRequest();
String key = tokenData.getSessionId() + filename;
RBinaryStream stream = redissonClient.getBinaryStream(key);
if (!stream.isExists()) {
ResponseResult.output(HttpServletResponse.SC_FORBIDDEN,
ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, "无效的会话缓存图片!"));
}
response.setHeader("content-type", "application/octet-stream");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
try (OutputStream os = response.getOutputStream()) {
os.write(stream.getAndDelete());
} catch (IOException e) {
log.error("Failed to call LocalUpDownloader.doDownload", e);
}
}
}

View File

@@ -0,0 +1,209 @@
package com.orangeforms.common.ext.util;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.orangeforms.common.core.exception.MyRuntimeException;
import com.orangeforms.common.core.object.*;
import com.orangeforms.common.ext.base.BizWidgetDatasource;
import com.orangeforms.common.ext.config.CommonExtProperties;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 高级通用业务组件的扩展帮助实现类。
*
* @author Jerry
* @date 2024-07-02
*/
@Slf4j
@Component
public class BizWidgetDatasourceExtHelper {
@Autowired
private CommonExtProperties properties;
/**
* 全部框架使用橙单框架,同时组件所在模块,如在线表单,报表等和业务服务位于同一服务内是使用。
*/
private static final String DEFAULT_ORANGE_APP = "__DEFAULT_ORANGE_APP__";
/**
* Map的数据结构为Map<AppCode, Map<widgetDatasourceType, DatasourceWrapper>>
*/
private Map<String, Map<String, DatasourceWrapper>> dataExtractorMap = MapUtil.newHashMap();
@PostConstruct
private void laodThirdPartyAppConfig() {
Map<String, CommonExtProperties.AppProperties> appPropertiesMap = properties.getApplicationMap();
if (MapUtil.isEmpty(appPropertiesMap)) {
return;
}
for (Map.Entry<String, CommonExtProperties.AppProperties> entry : appPropertiesMap.entrySet()) {
String appCode = entry.getKey();
List<CommonExtProperties.BizWidgetDatasourceProperties> datasources = entry.getValue().getBizWidgetDatasources();
Map<String, DatasourceWrapper> m = new HashMap<>(datasources.size());
for (CommonExtProperties.BizWidgetDatasourceProperties datasource : datasources) {
List<String> types = StrUtil.split(datasource.getTypes(), ",");
DatasourceWrapper w = new DatasourceWrapper();
w.setListUrl(datasource.getListUrl());
w.setViewUrl(datasource.getViewUrl());
for (String type : types) {
m.put(type, w);
}
}
dataExtractorMap.put(appCode, m);
}
}
/**
* 为默认APP注册基础组件数据源对象。
*
* @param type 数据源类型。
* @param datasource 业务通用组件的数据源接口。
*/
public void registerDatasource(String type, BizWidgetDatasource datasource) {
Assert.notBlank(type);
Assert.notNull(datasource);
Map<String, DatasourceWrapper> datasourceWrapperMap =
dataExtractorMap.computeIfAbsent(DEFAULT_ORANGE_APP, k -> new HashMap<>(2));
datasourceWrapperMap.put(type, new DatasourceWrapper(datasource));
}
/**
* 根据过滤条件获取指定通用业务组件的数据列表。
*
* @param appCode 接入应用编码。如果为空,则使用默认的 DEFAULT_ORANGE_APP。
* @param type 组件数据源类型。
* @param filter 过滤参数。不同的数据源参数不同。这里我们以键值对的方式传递。
* @param orderParam 排序参数。
* @param pageParam 分页参数。
* @return 查询后的分页数据列表。
*/
public MyPageData<Map<String, Object>> getDataList(
String appCode, String type, Map<String, Object> filter, MyOrderParam orderParam, MyPageParam pageParam) {
if (StrUtil.isBlank(type)) {
throw new MyRuntimeException("Argument [types] can't be BLANK");
}
if (StrUtil.isBlank(appCode)) {
return this.getDataList(type, filter, orderParam, pageParam);
}
DatasourceWrapper wrapper = this.getDatasourceWrapper(appCode, type);
JSONObject body = new JSONObject();
body.put("type", type);
if (MapUtil.isNotEmpty(filter)) {
body.put("filter", filter);
}
if (orderParam != null) {
body.put("orderParam", orderParam);
}
if (pageParam != null) {
body.put("pageParam", pageParam);
}
String response = this.invokeThirdPartyUrlWithPost(wrapper.getListUrl(), body.toJSONString());
ResponseResult<MyPageData<Map<String, Object>>> responseResult =
JSON.parseObject(response, new TypeReference<ResponseResult<MyPageData<Map<String, Object>>>>() {
});
if (!responseResult.isSuccess()) {
throw new MyRuntimeException(responseResult.getErrorMessage());
}
return responseResult.getData();
}
/**
* 根据指定字段的集合获取指定通用业务组件的数据对象列表。
*
* @param appCode 接入应用Id。如果为空则使用默认的 DEFAULT_ORANGE_APP。
* @param type 组件数据源类型。
* @param fieldName 字段名称。
* @param fieldValues 字段值结合。
* @return 指定字段数据集合的数据对象列表。
*/
public List<Map<String, Object>> getDataListWithInList(
String appCode, String type, String fieldName, String fieldValues) {
if (StrUtil.isBlank(fieldValues)) {
throw new MyRuntimeException("Argument [fieldValues] can't be BLANK");
}
if (StrUtil.isBlank(type)) {
throw new MyRuntimeException("Argument [types] can't be BLANK");
}
if (StrUtil.isBlank(appCode)) {
return this.getDataListWithInList(type, fieldName, fieldValues);
}
DatasourceWrapper wrapper = this.getDatasourceWrapper(appCode, type);
JSONObject body = new JSONObject();
body.put("type", type);
if (StrUtil.isNotBlank(fieldName)) {
body.put("fieldName", fieldName);
}
body.put("fieldValues", fieldValues);
String response = this.invokeThirdPartyUrlWithPost(wrapper.getViewUrl(), body.toJSONString());
ResponseResult<List<Map<String, Object>>> responseResult =
JSON.parseObject(response, new TypeReference<ResponseResult<List<Map<String, Object>>>>() {
});
if (!responseResult.isSuccess()) {
throw new MyRuntimeException(responseResult.getErrorMessage());
}
return responseResult.getData();
}
private MyPageData<Map<String, Object>> getDataList(
String type, Map<String, Object> filter, MyOrderParam orderParam, MyPageParam pageParam) {
DatasourceWrapper wrapper = this.getDatasourceWrapper(DEFAULT_ORANGE_APP, type);
return wrapper.getBizWidgetDataSource().getDataList(type, filter, orderParam, pageParam);
}
private List<Map<String, Object>> getDataListWithInList(String type, String fieldName, String fieldValues) {
DatasourceWrapper wrapper = this.getDatasourceWrapper(DEFAULT_ORANGE_APP, type);
return wrapper.getBizWidgetDataSource().getDataListWithInList(type, fieldName, StrUtil.split(fieldValues, ","));
}
private String invokeThirdPartyUrlWithPost(String url, String body) {
String token = TokenData.takeFromRequest().getToken();
Map<String, String> headerMap = new HashMap<>(1);
headerMap.put("Authorization", token);
StringBuilder fullUrl = new StringBuilder(128);
fullUrl.append(url).append("?token=").append(token);
HttpResponse httpResponse = HttpUtil.createPost(fullUrl.toString()).body(body).addHeaders(headerMap).execute();
if (!httpResponse.isOk()) {
String msg = StrFormatter.format(
"Failed to call [{}] with ERROR HTTP Status [{}] and [{}].",
url, httpResponse.getStatus(), httpResponse.body());
log.error(msg);
throw new MyRuntimeException(msg);
}
return httpResponse.body();
}
private DatasourceWrapper getDatasourceWrapper(String appCode, String type) {
Map<String, DatasourceWrapper> datasourceWrapperMap = dataExtractorMap.get(appCode);
Assert.notNull(datasourceWrapperMap);
DatasourceWrapper wrapper = datasourceWrapperMap.get(type);
Assert.notNull(wrapper);
return wrapper;
}
@NoArgsConstructor
@Data
public static class DatasourceWrapper {
private BizWidgetDatasource bizWidgetDataSource;
private String listUrl;
private String viewUrl;
public DatasourceWrapper(BizWidgetDatasource bizWidgetDataSource) {
this.bizWidgetDataSource = bizWidgetDataSource;
}
}
}