Compare commits

..

1 Commits
main ... cmoc

Author SHA1 Message Date
zzs
ed46709d43 feat:change config settings 2025-02-22 09:22:38 +08:00
11 changed files with 109 additions and 343 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<version>3.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>vin.vio</groupId>
@ -76,12 +76,6 @@
<artifactId>httpmime</artifactId>
<version>4.5.13</version> <!-- 请根据需要使用最新版本 -->
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2023.0.1.2</version>
</dependency>
</dependencies>
<build>

View File

@ -1,33 +0,0 @@
package vin.vio.collaboraonline.config.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
* @Description: 设置需要使用的配置文件
* @Date: 2025/2/26 9:50
* @Created: by ZZSLL
*/
@Configuration
public class ProfileSplitFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String path = request.getRequestURI();
String[] parts = path.split("/");
if (parts.length > 1) {
String tenant = parts[1];
if (!"wopi".equals(tenant)) {
request.setAttribute("tenant", tenant);
} else {
request.setAttribute("tenant", "cmoc");
}
}
chain.doFilter(request, response);
}
}

View File

@ -1,97 +0,0 @@
package vin.vio.collaboraonline.config.tenant;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
* @Description: 获取配置文件中内容保存为map供后续调用支持动态刷新配置文件
* @Date: 2025/2/26 9:57
* @Created: by ZZSLL
*/
@Component
public class TenantConfigManager {
private static final Log log = LogFactory.get();
// 存储各租户配置key租户名称
private final ConcurrentHashMap<String, Map<String, Object>> tenantConfigs = new ConcurrentHashMap<>();
@Autowired
private TenantProperties tenantProperties;
@Value("${spring.cloud.nacos.server-addr}")
private String nacosServerAddr;
@Value("${spring.cloud.nacos.config.group}")
private String group;
@Value("${spring.cloud.nacos.config.namespace}")
private String namespace;
@Value("${spring.cloud.nacos.username}")
private String username;
@Value("${spring.cloud.nacos.password}")
private String password;
@PostConstruct
public void init() throws Exception {
Properties properties = new Properties();
properties.put("serverAddr", nacosServerAddr);
properties.put("namespace", namespace);
properties.put("username", username);
properties.put("password", password);
ConfigService configService = NacosFactory.createConfigService(properties);
for (String tenant : tenantProperties.getList()) {
String dataId = "application-" + tenant + ".yml";
String content = configService.getConfig(dataId, group, 5000);
Map<String, Object> configMap = parseYaml(content);
tenantConfigs.put(tenant, configMap);
configService.addListener(dataId, group, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
Map<String, Object> updatedConfig = parseYaml(configInfo);
tenantConfigs.put(tenant, updatedConfig);
log.info("租户 " + tenant + " 配置已更新!");
}
});
}
}
private Map<String, Object> parseYaml(String yamlContent) {
if (yamlContent == null || yamlContent.trim().isEmpty()) {
return null;
}
Yaml yaml = new Yaml();
// YAML 转换为 Map结果结构和 YAML 文件结构一致
return yaml.load(yamlContent);
}
/**
* 根据租户名称获取对应的配置 Map
*/
public Map<String, Object> getConfigByTenant(String tenant) {
return tenantConfigs.get(tenant);
}
}

View File

@ -1,26 +0,0 @@
package vin.vio.collaboraonline.config.tenant;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description: 配置文件列表
* @Date: 2025/2/26 10:15
* @Created: by ZZSLL
*/
@Component
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
private List<String> list;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}

View File

@ -1,20 +1,17 @@
package vin.vio.collaboraonline.controller;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import vin.vio.collaboraonline.config.tenant.TenantConfigManager;
import vin.vio.collaboraonline.model.WopiFile;
import vin.vio.collaboraonline.service.FileService;
import java.io.File;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
@ -25,27 +22,21 @@ import java.util.Map;
* @Created: by ZZSLL
*/
@Slf4j
@RestController
@RequestMapping("/{tenant}/wopi")
@RequestMapping("/wopi")
public class WopiController {
@Autowired
private FileService fileService;
@Autowired
private TenantConfigManager manager;
private static final Log log = LogFactory.get();
@Value("${collabora.host-server-addr}")
private String host;
// 检查文件信息
@GetMapping("/files/{fileId}")
public ResponseEntity<?> checkFileInfo(
@PathVariable String fileId,
@RequestParam(required = false) String access_token,
HttpServletRequest request) {
log.info("=========checkFileInfo===========start");
log.info("{} {}", request.getPathInfo(), request.getMethod());
log.info("{}", fileId);
public ResponseEntity<?> checkFileInfo(@PathVariable String fileId, @RequestParam(required = false) String access_token) {
WopiFile file = fileService.getFileInfo(fileId);
if (file == null) {
return ResponseEntity.notFound().build();
@ -53,17 +44,13 @@ public class WopiController {
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(createCheckFileInfoResponse(file, fileId, request));
.body(createCheckFileInfoResponse(file));
}
// 获取文件内容
@GetMapping("/files/{fileId}/contents")
public ResponseEntity<Resource> getFile(
@PathVariable String fileId,
HttpServletRequest request) {
log.info("=========getFile===========start");
log.info("{} {}", request.getPathInfo(), request.getMethod());
log.info("{}", fileId);
public ResponseEntity<Resource> getFile(@PathVariable String fileId) {
Resource fileResource = fileService.loadFileAsResource(fileId);
if (fileResource == null) {
return ResponseEntity.notFound().build();
@ -76,30 +63,16 @@ public class WopiController {
// 保存文件
@RequestMapping(value = "/files/{fileId}/contents", method = {RequestMethod.POST, RequestMethod.PUT})
public ResponseEntity<Void> putFile(@PathVariable String fileId,
InputStream contentStream,
HttpServletRequest request) {
log.info("=========putFile===========start");
log.info("{} {}", request.getPathInfo(), request.getMethod());
log.info("{}", fileId);
public ResponseEntity<Void> putFile(
@PathVariable String fileId, InputStream contentStream) {
boolean success = fileService.saveFile(fileId, contentStream);
return success ?
ResponseEntity.ok().build() :
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
private Map<String, Object> createCheckFileInfoResponse(WopiFile file, String fileId, HttpServletRequest request) {
String tenant = (String) request.getAttribute("tenant");
Map<String, Object> config = manager.getConfigByTenant(tenant);
String host = null;
if (config.containsKey("collabora") && config.get("collabora") instanceof Map<?, ?>) {
Map<String, Object> collabora = (Map<String, Object>) config.get("collabora");
host = (String) collabora.get("host");
}
log.info("=========createCheckFileInfoResponse===========start");
private Map<String, Object> createCheckFileInfoResponse(WopiFile file) {
Map<String, Object> response = new LinkedHashMap<>();
// 强制开启所有编辑权限
@ -123,7 +96,7 @@ public class WopiController {
response.put("DisableCopy", false);
// 添加必要协议字段
response.put("HostEditUrl", host + "/wopi/files/" + fileId + "?access_token=asdasd");
response.put("HostEditUrl", host + "/wopi/files/"+file.getFileName() + "?access_token=asdasd");
response.put("PostMessageOrigin", host); // 本地测试用
// 测试环境特殊配置
@ -131,11 +104,10 @@ public class WopiController {
response.put("BreadcrumbBrandUrl", host);
response.put("BreadcrumbDocName", file.getFileName());
response.put("UserLogo", "https://web.wmyun.com/favicon.ico");
// 绕过版本检查
// response.put("LastModifiedTime", "2020-01-01T00:00:00Z");
// response.put("Version", "test_version");
log.info("{}", response);
return response;
}
}

View File

@ -4,7 +4,6 @@ package vin.vio.collaboraonline.kingdee;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
@ -19,10 +18,8 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import vin.vio.collaboraonline.config.tenant.TenantConfigManager;
import vin.vio.collaboraonline.exception.AllKingdeeException;
import java.io.IOException;
@ -38,35 +35,48 @@ import java.util.concurrent.TimeUnit;
@Service
public class AuthService {
@Autowired
private TenantConfigManager manager;
@Autowired
private HttpServletRequest request;
@Value("${ensign.kingdee.is-public}")
private Boolean isPublic;
@Value("${ensign.kingdee.test-public-end-point}")
private String testPublicEndPoint;
@Value("${ensign.kingdee.test-inner-end-point}")
private String testInnerEndPoint;
@Value("${ensign.kingdee.prod-public-end-point}")
private String prodPublicEndPoint;
@Value("${ensign.kingdee.prod-inner-end-point}")
private String prodInnerEndPoint;
@Value("${ensign.kingdee.env}")
private String env;
@Value("${ensign.kingdee.appToken.appId}")
private String appId;
@Value("${ensign.kingdee.appToken.appSecuret}")
private String appSecuret;
@Value("${ensign.kingdee.appToken.tenantid}")
private String tenantid;
@Value("${ensign.kingdee.appToken.accountId}")
private String accountId;
@Value("${ensign.kingdee.appToken.language}")
private String language;
@Value("${ensign.kingdee.accessToken.user}")
private String user;
@Value("${ensign.kingdee.accessToken.usertype}")
private String usertype;
public String initBasePath() {
Boolean isPublic = null;
String testPublicEndPoint = null;
String testInnerEndPoint = null;
String prodPublicEndPoint = null;
String prodInnerEndPoint = null;
String env = null;
String tenant = (String) request.getAttribute("tenant");
Map<String, Object> config = manager.getConfigByTenant(tenant);
if (config.containsKey("kingdee") && config.get("kingdee") instanceof Map<?,?>) {
Map<String, Object> kingdee = (Map<String, Object>) config.get("kingdee");
env = (String) kingdee.get("env");
testPublicEndPoint = (String) kingdee.get("test-public-end-point");
testInnerEndPoint = (String) kingdee.get("test-inner-end-point");
prodPublicEndPoint = (String) kingdee.get("prod-public-end-point");
prodInnerEndPoint = (String) kingdee.get("prod-inner-end-point");
isPublic = (Boolean) kingdee.get("is-public");
}
if ("test".equals(env)) {
if (isPublic) {
return testPublicEndPoint;
@ -87,30 +97,9 @@ public class AuthService {
}
public String initAccessToken() throws IOException, AllKingdeeException {
String tenant = (String) request.getAttribute("tenant");
Map<String, Object> config = manager.getConfigByTenant(tenant);
String appId = null;
String appSecuret = null;
String tenantid = null;
String accountId = null;
String language = null;
if (config.containsKey("kingdee") && config.get("kingdee") instanceof Map<?,?>) {
Map<String, Object> kingdee = (Map<String, Object>) config.get("kingdee");
if (kingdee.containsKey("appToken") && kingdee.get("appToken") instanceof Map<?,?>) {
Map<String, Object> appToken = (Map<String, Object>) kingdee.get("appToken");
appId = (String) appToken.get("appId");
appSecuret = (String) appToken.get("appSecuret");
tenantid = (String) appToken.get("tenantid");
accountId = (String) appToken.get("accountId");
language = (String) appToken.get("language");
}
}
final String END_POINT = initBasePath() + "/ierp/api/getAppToken.do";
HttpClient client = new DefaultHttpClient();
@ -155,32 +144,6 @@ public class AuthService {
Map<String, String> body = new HashMap<>();
String user = null;
String usertype = null;
String tenantid = null;
String accountId = null;
String language = null;
String tenant = (String) request.getAttribute("tenant");
Map<String, Object> config = manager.getConfigByTenant(tenant);
if (config.containsKey("kingdee") && config.get("kingdee") instanceof Map<?,?>) {
Map<String, Object> kingdee = (Map<String, Object>) config.get("kingdee");
if (kingdee.containsKey("accessToken") && kingdee.get("accessToken") instanceof Map<?,?>) {
Map<String, Object> accessToken = (Map<String, Object>) kingdee.get("accessToken");
user = (String) accessToken.get("user");
usertype = (String) accessToken.get("usertype");
}
if (kingdee.containsKey("appToken") && kingdee.get("appToken") instanceof Map<?,?>) {
Map<String, Object> appTokenParams = (Map<String, Object>) kingdee.get("appToken");
tenantid = (String) appTokenParams.get("tenantid");
accountId = (String) appTokenParams.get("accountId");
language = (String) appTokenParams.get("language");
}
}
body.put("user", user);
body.put("apptoken", appToken);
body.put("tenantid", tenantid);

View File

@ -1,8 +1,11 @@
package vin.vio.collaboraonline.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import vin.vio.collaboraonline.model.WopiFile;
@ -10,6 +13,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.nio.file.*;
/**
@ -22,6 +26,9 @@ import java.nio.file.*;
@Service
public class FileService {
@Value("${collabora.file-storage-path}")
private String BASE_DIR;
@Autowired
private KingdeeService kingdeeService;

View File

@ -1,10 +1,8 @@
package vin.vio.collaboraonline.service;
import cn.hutool.log.Log;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
@ -12,11 +10,13 @@ import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import vin.vio.collaboraonline.config.tenant.TenantConfigManager;
import vin.vio.collaboraonline.exception.AllKingdeeException;
import vin.vio.collaboraonline.kingdee.AuthService;
import java.io.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
@ -34,53 +34,24 @@ import static cn.hutool.http.HttpUtil.downloadFile;
@Service
public class KingdeeService {
@Autowired
private TenantConfigManager manager;
@Value("${collabora.file-storage-path}")
private String BASE_DIR;
@Autowired
private AuthService authService;
@Autowired
private HttpServletRequest request;
private final static Log log = Log.get();
public String requestFile(String attPkId) {
String BASE_DIR = null;
String attachmentQueryURI = null;
String fromId = null;
String attachKey = null;
String tenant = (String) request.getAttribute("tenant");
Map<String, Object> config = manager.getConfigByTenant(tenant);
if (config.containsKey("collabora") && config.get("collabora") instanceof Map<?,?>) {
Map<String, Object> collabora = (Map<String, Object>) config.get("collabora");
BASE_DIR = (String) collabora.get("file-storage-path");
}
if (config.containsKey("kingdee") && config.get("kingdee") instanceof Map<?,?>) {
Map<String, Object> kingdee = (Map<String, Object>) config.get("kingdee");
attachmentQueryURI = (String) kingdee.get("attachment-query-uri");
if (kingdee.containsKey("bill") && kingdee.get("bill") instanceof Map<?,?>) {
Map<String, Object> bill = (Map<String, Object>) kingdee.get("bill");
fromId = (String) bill.get("formId");
attachKey = (String) bill.get("attachKey");
}
}
try {
String accessToken = authService.initAccessToken();
URI newUri = new URI(authService.initBasePath() + attachmentQueryURI);
URI newUri = new URI(authService.initBasePath() + "/ierp/kapi/v2/yem/yem_base/AttachmentsQueryAPI/Query");
ClientHttpRequest delegate = new SimpleClientHttpRequestFactory().createRequest(newUri, HttpMethod.POST);
delegate.getHeaders().add("access_token", accessToken);
delegate.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Map<String, String> requestBody = new HashMap<>();
requestBody.put("formId", fromId);
requestBody.put("formId", "yem_webofficemodel2");
requestBody.put("pkId", attPkId);
requestBody.put("attachKey", attachKey);
requestBody.put("attachKey", "yem_attachmentpanelap");
String jsonBody = JSON.toJSONString(requestBody);
// 设置请求体
try (OutputStream os = delegate.getBody()) {
@ -95,7 +66,6 @@ public class KingdeeService {
}
String savePath = "";
JSONObject result = JSON.parseObject(responseBody.toString());
log.info(JSON.toJSONString(result));
if (result.containsKey("status") && result.getBooleanValue("status", false)) {
JSONArray data = result.getJSONArray("data");
if (data != null && !data.isEmpty()) {
@ -103,10 +73,6 @@ public class KingdeeService {
String url = data0.getString("url");
String name = data0.getString("name");
savePath = BASE_DIR + "/" + name;
File existFile = new File(savePath);
if (existFile.exists()) {
existFile.delete();
}
downloadFile(url, savePath);
}
} else {

View File

@ -0,0 +1,7 @@
collabora:
# 本机局域网ip注意 CollaboraOnline容器需要访问该地址最后不要加/
host-server-addr: http://47.99.175.155:59981
# 文件保存路径
file-storage-path: /work/projects/collabora-api/file

View File

@ -0,0 +1,7 @@
collabora:
# 本机局域网ip注意 CollaboraOnline容器需要访问该地址最后不要加/
host-server-addr: http://192.168.3.104:8080
# 文件保存路径
file-storage-path: E:\Code\CollaboraOnline\files

View File

@ -1,25 +1,31 @@
spring:
application:
name: CollaboraOnline
cloud:
nacos:
config:
namespace: office
group: ATTACHMENT_VIA_GROUP
# server-addr: 127.0.0.1:8848
server-addr: 192.168.3.100:8848
username: nacos
password: nacos
config:
import: nacos:application-tenant.yml
profiles:
active: dev
# http://192.168.3.211:9980/browser/dist/cool.html?WOPISrc=http://192.168.3.104:8080/wopi/files/2087131858671270912&lang=zh-cn
# http://192.168.3.211:9980/browser/dist/cool.html?WOPISrc=http://192.168.3.104:8080/wopi/files/test.docx&lang=zh-cn
# http://47.99.175.155:59980/browser/dist/cool.html?lang=zh-cn&WOPISrc=http://47.99.175.155:59981/wopi/files/test.docx
# https://web.wmyun.com/browser/dist/cool.html?lang=zh-cn&WOPISrc=https://web-api.wmyun.com/wopi/files/2127702662332843008
# http://<collabora容器IP>:<端口>/browser/dist/cool.html?WOPISrc=http://<本机IP>:<服务端口>/wopi/files/<文件名>&lang=zh-cn
# http://192.168.3.211:9980/browser/dist/cool.html?lang=zh-cn&WOPISrc=http://192.168.3.104:8080/wopi/files/2087131858671270912
ensign:
kingdee:
env: test
test-public-end-point: 'https://srm-dev.cmoc.cloud:8022'
test-inner-end-point: 'http://10.64.112.152:8022'
prod-public-end-point: 'http://122.4.221.130:8022'
prod-inner-end-point: 'http://10.64.111.134:8022'
is-public: true
appToken:
appId: 'OA'
appSecuret: 'Cmoc@OA2024!#@..'
tenantid: 'cmocdev'
accountId: '2117884374501299200'
language: 'zh_CN'
accessToken:
user: '18791337254'
usertype: 'Mobile'