項目初始化+三方接口對接

This commit is contained in:
2026-04-24 17:05:31 +08:00
commit 0440330226
15 changed files with 873 additions and 0 deletions

116
pom.xml Normal file
View File

@@ -0,0 +1,116 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bim</groupId>
<artifactId>api</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>BIM API</name>
<description>BIM项目管理后端接口服务</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.22</version>
</dependency>
<!-- 校验框架核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,12 @@
package com.bim.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BimApiApplication {
public static void main(String[] args) {
SpringApplication.run(BimApiApplication.class, args);
}
}

View File

@@ -0,0 +1,20 @@
package com.bim.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,38 @@
package com.bim.api.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

View File

@@ -0,0 +1,64 @@
package com.bim.api.controller;
import com.bim.api.entity.PjDayListResponse;
import com.bim.api.entity.WbsResponse;
import com.bim.api.query.ProjectProgressParams;
import com.bim.api.util.ThirdPartyAuthUtil;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/bim")
public class BimController {
private final ThirdPartyAuthUtil authUtil;
public BimController(ThirdPartyAuthUtil authUtil) {
this.authUtil = authUtil;
}
@GetMapping("/wbs")
public Map<String, Object> getWbsTree(
@RequestParam String projectId,
@RequestParam(required = false) String parentId) {
Map<String, Object> result = new HashMap<>();
try {
WbsResponse response = authUtil.getWbsTree(projectId, parentId);
if ("0".equals(response.getCode())) {
result.put("code", 200);
result.put("message", "success");
result.put("data", response);
} else {
result.put("code", 500);
result.put("message", response.getMsg());
}
} catch (Exception e) {
result.put("code", 500);
result.put("message", e.getMessage());
}
return result;
}
@PostMapping("/getPjDayListByPositionId")
public Map<String, Object> getPjDayListByPositionId(@Valid @RequestBody ProjectProgressParams progressParams) {
Map<String, Object> result = new HashMap<>();
try {
PjDayListResponse response = authUtil.findPjDayListByPositionId(progressParams);
if ("0".equals(response.getCode())) {
result.put("code", 200);
result.put("message", "success");
result.put("data", response);
} else {
result.put("code", 500);
result.put("message", response.getMsg());
}
} catch (Exception e) {
result.put("code", 500);
result.put("message", e.getMessage());
}
return result;
}
}

View File

@@ -0,0 +1,30 @@
package com.bim.api.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class PjDayListResponse {
private String msg;
private String code;
private List<PjDayData> data;
private Integer count;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class PjDayData {
private Boolean isNewRecord;
private String projectId;
private String positionId;
private String fullName;
private String workCode;
private String workUnit;
private Double meteringAmt;
private Double meteringNotaxAmt;
private Double meteringNum;
private String name;
}
}

View File

@@ -0,0 +1,131 @@
package com.bim.api.entity;
import lombok.Data;
@Data
public class SwitchLoginResponse {
private String rtnFlag;
private String code;
private String rtnMessage;
private String msg;
private SwitchLoginObj rtnObj;
@Data
public static class SwitchLoginObj {
private Principal principal;
private AccessTokenInfo accessToken;
}
@Data
public static class Principal {
private String id;
private String loginName;
private String name;
private String orgId;
private String deptId;
private String orgName;
private String deptName;
private String accountId;
private String loginType;
private CurUser curUser;
private String __sid;
private String sessionid;
}
@Data
public static class CurUser {
private String id;
private Boolean isNewRecord;
private String createDate;
private String updateDate;
private String createById;
private String updateById;
private String loginName;
private String no;
private String name;
private String email;
private String phone;
private String mobile;
private String loginFlag;
private String photo;
private AccountInfo account;
private OrgInfo org;
private DeptInfo dept;
private String orgId;
private String deptId;
private String orgName;
private String deptName;
private String accountId;
private String userId;
private String userNo;
private String userName;
private String wxOpenId;
private String roleNames;
private String roleCodes;
private Boolean admin;
}
@Data
public static class AccountInfo {
private String id;
private Boolean isNewRecord;
private String loginName;
private String acc4aName;
}
@Data
public static class OrgInfo {
private String id;
private Boolean isNewRecord;
private String parentId;
private String innerCode;
private Integer orderNo;
private Boolean isLeaf;
private String treeTable;
private String textField;
private String orgCode;
private String orgName;
private String orgType;
private Boolean extParentData;
private Boolean noUsed;
private Double timeLimit;
private Double contractAmt;
private Double postBudgetAmt;
private String org4aId;
private String org4aName;
private String org4aShortname;
private String dataSource;
private String state;
}
@Data
public static class DeptInfo {
private String id;
private Boolean isNewRecord;
private String parentId;
private String innerCode;
private Integer orderNo;
private Boolean isLeaf;
private String treeTable;
private String textField;
private String orgCode;
private String orgName;
private String orgType;
private Boolean extParentData;
private Boolean noUsed;
private Double timeLimit;
private Double contractAmt;
private Double postBudgetAmt;
private String org4aId;
private String org4aName;
private String org4aShortname;
private String state;
}
@Data
public static class AccessTokenInfo {
private String access_token;
private Long expires_in;
private String refresh_token;
}
}

View File

@@ -0,0 +1,24 @@
package com.bim.api.entity;
import lombok.Data;
@Data
public class TokenResponse {
private String rtnFlag;
private String code;
private String rtnMessage;
private String msg;
private AccessTokenObj rtnObj;
@Data
public static class AccessTokenObj {
private AccessToken accessToken;
}
@Data
public static class AccessToken {
private String access_token;
private Long expires_in;
private String refresh_token;
}
}

View File

@@ -0,0 +1,113 @@
package com.bim.api.entity;
import lombok.Data;
import java.util.List;
@Data
public class UserListResponse {
private String rtnFlag;
private String code;
private String rtnMessage;
private String msg;
private UserListObj rtnObj;
@Data
public static class UserListObj {
private Integer total;
private List<UserData> data;
private Integer pageSize;
private Integer currentPage;
}
@Data
public static class UserData {
private String id;
private Boolean isNewRecord;
private String createDate;
private String updateDate;
private String createById;
private String updateById;
private String loginName;
private String no;
private String name;
private String email;
private String phone;
private String mobile;
private String loginFlag;
private String photo;
private Account account;
private Org org;
private Dept dept;
private String orgId;
private String deptId;
private String orgName;
private String deptName;
private String accountId;
private String userId;
private String userNo;
private String userName;
private String wxOpenId;
private String roleNames;
private String roleCodes;
private Boolean admin;
}
@Data
public static class Account {
private String id;
private Boolean isNewRecord;
private String loginName;
private String acc4aName;
}
@Data
public static class Org {
private String id;
private Boolean isNewRecord;
private String parentId;
private String innerCode;
private Integer orderNo;
private Boolean isLeaf;
private String treeTable;
private String textField;
private String orgCode;
private String orgName;
private String orgType;
private Boolean extParentData;
private Boolean noUsed;
private Double timeLimit;
private Double contractAmt;
private Double postBudgetAmt;
private String org4aId;
private String org4aName;
private String org4aShortname;
private String dataSource;
private String isCloudUp;
private String state;
}
@Data
public static class Dept {
private String id;
private Boolean isNewRecord;
private String parentId;
private String innerCode;
private Integer orderNo;
private Boolean isLeaf;
private String treeTable;
private String textField;
private String orgCode;
private String orgName;
private String orgType;
private Boolean extParentData;
private Boolean noUsed;
private Double timeLimit;
private Double contractAmt;
private Double postBudgetAmt;
private String org4aId;
private String org4aName;
private String org4aShortname;
private String state;
}
}

View File

@@ -0,0 +1,55 @@
package com.bim.api.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class WbsResponse {
private String code;
private String msg;
private List<WbsData> data;
private String trace_id;
private String span_id;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class WbsData {
private String id;
private Boolean isNewRecord;
private String createDate;
private String updateDate;
private String auditStatus;
private String auditStatusName;
private String createById;
private String updateById;
private String parentId;
private String innerCode;
private Integer orderNo;
private Boolean isLeaf;
private String treeTable;
private String busiField;
private String name;
private String projectId;
private String fullName;
private String startNo;
private String endNo;
private String figureNo;
private String auditDate;
private String auditBy;
private String auditByName;
private Double reviewAmt;
private Double reviewNotaxAmt;
private Double afterAmt;
private Double afterNotaxAmt;
private Double meteringAmt;
private Double meteringNotaxAmt;
private Integer changeNum;
private String sourceId;
private String staWbsId;
private String staWbsCode;
private String state;
}
}

View File

@@ -0,0 +1,17 @@
package com.bim.api.query;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class ProjectProgressParams {
@NotBlank(message = "projectId不能为空")
private String projectId;
@NotBlank(message = "positionIds不能为空")
private String positionIds;
@NotBlank(message = "period不能为空")
private String period;
}

View File

@@ -0,0 +1,193 @@
package com.bim.api.util;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.bim.api.entity.*;
import com.bim.api.query.ProjectProgressParams;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.JSONPObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class ThirdPartyAuthUtil {
private static final Logger log = LoggerFactory.getLogger(ThirdPartyAuthUtil.class);
private static final String ACCESS_TOKEN_KEY = "bim:access_token";
private static final ObjectMapper objectMapper = new ObjectMapper();
@Value("${third-party.api.base-url}")
private String baseUrl;
private final RedisTemplate<String, Object> redisTemplate;
public ThirdPartyAuthUtil(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void init() {
log.info("三方校验工具类初始化baseUrl: {}", baseUrl);
}
private String getAccessToken() throws Exception {
String cachedToken = (String) redisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
if (cachedToken != null) {
log.info("从Redis获取access_token: {}", cachedToken);
return cachedToken;
}
return refreshAccessToken();
}
private String refreshAccessToken() throws Exception {
String url = baseUrl + "/oauth2/token";
String body = "client_id=pmbim&client_secret=6bf1c07fbf8a42678a08224a328b8c87&grant_type=client_credentials";
HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.header("User-Agent", "Mozilla/5.0")
.body(body)
.execute();
String responseBody = response.body();
log.info("获取token响应: {}", responseBody);
TokenResponse tokenResponse = objectMapper.readValue(responseBody, TokenResponse.class);
if ("0".equals(tokenResponse.getCode()) && tokenResponse.getRtnObj() != null) {
String accessToken = tokenResponse.getRtnObj().getAccessToken().getAccess_token();
Long expiresIn = tokenResponse.getRtnObj().getAccessToken().getExpires_in();
redisTemplate.opsForValue().set(ACCESS_TOKEN_KEY, accessToken, expiresIn, TimeUnit.MILLISECONDS);
log.info("获取新access_token并缓存: {}", accessToken);
return accessToken;
}
throw new RuntimeException("获取access_token失败: " + tokenResponse.getRtnMessage());
}
private void autoLogin() throws Exception {
log.info("开始自动登录流程...");
String accessToken = refreshAccessToken();
String userListUrl = baseUrl + "/sys/user/getUserIdentityPageData";
HttpResponse userListResponse = HttpRequest.post(userListUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.body("access_token=" + accessToken)
.execute();
String responseBody = userListResponse.body();
log.info("获取用户列表响应: {}", responseBody);
UserListResponse userListResp = objectMapper.readValue(responseBody, UserListResponse.class);
if (!"0".equals(userListResp.getCode())) {
throw new RuntimeException("获取用户列表失败: " + userListResp.getRtnMessage());
}
var userList = userListResp.getRtnObj().getData();
if (userList == null || userList.isEmpty()) {
throw new RuntimeException("用户列表为空");
}
String userId = userList.get(0).getId();
log.info("自动选择用户: {}", userId);
String switchLoginUrl = baseUrl + "/oauth2/switchLogin";
HttpResponse switchResponse = HttpRequest.post(switchLoginUrl)
.header("Content-Type", "application/x-www-form-urlencoded")
.body("access_token=" + accessToken + "&userId=" + userId)
.execute();
String switchResponseBody = switchResponse.body();
log.info("切换登录响应: {}", switchResponseBody);
SwitchLoginResponse switchLoginResp = objectMapper.readValue(switchResponseBody, SwitchLoginResponse.class);
if (!"0".equals(switchLoginResp.getCode())) {
throw new RuntimeException("切换登录失败: " + switchLoginResp.getRtnMessage());
}
log.info("自动登录成功");
}
public WbsResponse getWbsTree(String projectId, String parentId) throws Exception {
String accessToken = getAccessToken();
if (accessToken == null) {
autoLogin();
accessToken = getAccessToken();
}
String url = baseUrl + "/pj/pjPosition/zTreeDataBim?access_token=" + accessToken;
String jsonBody = parentId != null && !parentId.isEmpty()
? "{\"projectId\":\"" + projectId + "\",\"parentId\":\"" + parentId + "\"}"
: "{\"projectId\":\"" + projectId + "\"}";
HttpResponse response = HttpRequest.post(url)
.header("Content-Type", "application/json")
.body(jsonBody)
.execute();
String responseBody = response.body();
log.info("获取WBS树响应: {}", responseBody);
WbsResponse wbsResponse = objectMapper.readValue(responseBody, WbsResponse.class);
if (!"0".equals(wbsResponse.getCode()) && "access token invalid".equals(wbsResponse.getMsg())) {
log.info("access_token失效尝试重新登录...");
redisTemplate.delete(ACCESS_TOKEN_KEY);
autoLogin();
accessToken = getAccessToken();
String retryUrl = baseUrl + "/pj/pjPosition/zTreeDataBim?access_token=" + accessToken;
HttpResponse retryResponse = HttpRequest.post(retryUrl)
.header("Content-Type", "application/json")
.body(jsonBody)
.execute();
String retryResponseBody = retryResponse.body();
log.info("重试获取WBS树响应: {}", retryResponseBody);
return objectMapper.readValue(retryResponseBody, WbsResponse.class);
}
return wbsResponse;
}
public PjDayListResponse findPjDayListByPositionId(ProjectProgressParams progressParams) throws Exception {
String accessToken = getAccessToken();
String url = baseUrl + "/bim/bimPjData/findPjDayListByPositionId?access_token=" + accessToken;
Map<String, Object> bodyMap = new HashMap<>();
bodyMap.put("projectId", progressParams.getProjectId());
bodyMap.put("positionIds", progressParams.getPositionIds());
String period = progressParams.getPeriod();
if (period != null) {
period = period.replaceAll("^(\\d{4}-\\d{2}-\\d{2}).*", "$1");
}
bodyMap.put("period", period);
String body = objectMapper.writeValueAsString(bodyMap);
HttpResponse httpResponse = HttpRequest.post(url)
// .header("Content-Type", "application/json")
.body(body)
.execute();
String responseBody = httpResponse.body();
log.info("获取进度日报响应: {}", responseBody);
return objectMapper.readValue(responseBody, PjDayListResponse.class);
}
}

View File

@@ -0,0 +1,14 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/bim_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
data:
redis:
host: localhost
port: 6379
database: 0
third-party:
api:
base-url: https://uat-xmgl.ccccltd.cn/a

View File

@@ -0,0 +1,14 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/bim_prod?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
data:
redis:
host: localhost
port: 6379
database: 0
third-party:
api:
base-url: https://uat-xmgl.ccccltd.cn/a

View File

@@ -0,0 +1,32 @@
server:
port: 8099
servlet:
encoding:
charset: UTF-8
enabled: true
force: true
spring:
profiles:
active: dev
application:
name: bim-api
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
format_sql: true
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
logging:
level:
com.bim.api: debug
org.hibernate.SQL: debug