添加封装远程调用jsxq

This commit is contained in:
zk
2026-07-03 18:05:16 +08:00
parent a5853f8342
commit ea1f71e010
13 changed files with 749 additions and 3 deletions
@@ -0,0 +1,61 @@
package org.jiayunet.controller;
import lombok.AllArgsConstructor;
import org.jiayunet.remote.jsxq.JsxqClient;
import org.jiayunet.remote.jsxq.pojo.JsxqPage;
import org.jiayunet.remote.jsxq.pojo.dto.ChatEvaluateDto;
import org.jiayunet.remote.jsxq.pojo.dto.MentorFullInfoDto;
import org.jiayunet.remote.jsxq.pojo.dto.MentorItemDto;
import org.jiayunet.remote.jsxq.pojo.dto.TeacherTalkDto;
import org.jiayunet.remote.jsxq.pojo.param.MentorPageParam;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 导师接口(对接简史星球开放平台,公开免鉴权)
* <p>依赖:JsxqClient(远程调用简史星球平台)</p>
*
* @author zk
*/
@RestController
@RequestMapping("/public/mentor")
@Validated
@AllArgsConstructor
public class MentorController {
private final JsxqClient jsxqClient;
/**
* 分页查询导师列表
*/
@PostMapping("/page")
public JsxqPage<MentorItemDto> pageSelectMentor(@RequestBody MentorPageParam param) {
return jsxqClient.pageSelectMentor(param);
}
/**
* 查询导师完整信息
*/
@GetMapping("/info")
public MentorFullInfoDto selectMentorFullInfo(@RequestParam Long mentorId) {
return jsxqClient.selectMentorFullInfo(mentorId);
}
/**
* 查询老师话题列表
*/
@GetMapping("/talk")
public List<TeacherTalkDto> selectTalk(@RequestParam Long teacherId) {
return jsxqClient.selectTalk(teacherId);
}
/**
* 查询老师评价列表
*/
@GetMapping("/evaluate")
public List<ChatEvaluateDto> selectChatEvaluate(@RequestParam Long teacherId) {
return jsxqClient.selectChatEvaluate(teacherId);
}
}
@@ -102,3 +102,10 @@ app:
base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3} base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3}
api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02} api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02}
model: ${AI_MODEL:doubao-1-5-lite-32k-250115} model: ${AI_MODEL:doubao-1-5-lite-32k-250115}
# 简史星球开放平台配置
jsxq:
base-url: ${JSXQ_BASE_URL:https://jsxq.jianshixingqiu.com/api/admin}
token: ${JSXQ_TOKEN:f69b5086-97b0-4e5e-b172-453e444d448c}
tenant-id: ${JSXQ_TENANT_ID:1}
client-toc: ${JSXQ_CLIENT_TOC:Y}
@@ -102,3 +102,10 @@ app:
base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3} base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3}
api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02} api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02}
model: ${AI_MODEL:doubao-1-5-lite-32k-250115} model: ${AI_MODEL:doubao-1-5-lite-32k-250115}
# 简史星球开放平台配置
jsxq:
base-url: ${JSXQ_BASE_URL:https://jsxq.jianshixingqiu.com/api/admin}
token: ${JSXQ_TOKEN:f69b5086-97b0-4e5e-b172-453e444d448c}
tenant-id: ${JSXQ_TENANT_ID:1}
client-toc: ${JSXQ_CLIENT_TOC:Y}
@@ -103,3 +103,10 @@ app:
base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3} base-url: ${AI_BASE_URL:https://ark.cn-beijing.volces.com/api/v3}
api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02} api-key: ${AI_API_KEY:fd065993-bee2-4f31-8bf2-56d5d3012c02}
model: ${AI_MODEL:doubao-1-5-lite-32k-250115} model: ${AI_MODEL:doubao-1-5-lite-32k-250115}
# 简史星球开放平台配置
jsxq:
base-url: ${JSXQ_BASE_URL:https://jsxq.jianshixingqiu.com/api/admin}
token: ${JSXQ_TOKEN:f69b5086-97b0-4e5e-b172-453e444d448c}
tenant-id: ${JSXQ_TENANT_ID:1}
client-toc: ${JSXQ_CLIENT_TOC:Y}
@@ -0,0 +1,154 @@
package org.jiayunet.remote.jsxq;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jiayunet.remote.jsxq.pojo.JsxqPage;
import org.jiayunet.remote.jsxq.pojo.JsxqResponse;
import org.jiayunet.remote.jsxq.pojo.dto.ChatEvaluateDto;
import org.jiayunet.remote.jsxq.pojo.dto.MentorFullInfoDto;
import org.jiayunet.remote.jsxq.pojo.dto.MentorItemDto;
import org.jiayunet.remote.jsxq.pojo.dto.TeacherTalkDto;
import org.jiayunet.remote.jsxq.pojo.param.MentorPageParam;
import org.jiayunet.tool.HttpTool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 简史星球(jianshixingqiu)开放平台客户端
* <p>依赖:JsxqProperties(域名+令牌+租户)、HttpTool(POST 复用其能力,GET 内部实现以支持请求头)</p>
* <p>底层 post/get 统一拼公共请求头、拆外层 {code,msg,data,ok} 包装、按类型反序列化 data;业务接口为本类 public 方法。</p>
*
* @author zk
*/
@Slf4j
@Component
public class JsxqClient {
@Autowired
private JsxqProperties properties;
// ============================ 业务接口 ============================
/**
* 分页查询导师列表
* <p>POST /public/website/pageSelectMentor</p>
*/
public JsxqPage<MentorItemDto> pageSelectMentor(MentorPageParam param) {
JavaType type = HttpTool.objectMapper.getTypeFactory()
.constructParametricType(JsxqPage.class, MentorItemDto.class);
return post("/public/website/pageSelectMentor", param, type);
}
/**
* 查询导师完整信息
* <p>GET /public/website/selectMentorFullInfo?mentorId=</p>
*/
public MentorFullInfoDto selectMentorFullInfo(Long mentorId) {
JavaType type = HttpTool.objectMapper.getTypeFactory().constructType(MentorFullInfoDto.class);
return get("/public/website/selectMentorFullInfo", Map.of("mentorId", mentorId), type);
}
/**
* 查询老师话题列表
* <p>GET /public/website/selectTalk?teacherId=</p>
*/
public List<TeacherTalkDto> selectTalk(Long teacherId) {
JavaType type = HttpTool.objectMapper.getTypeFactory()
.constructCollectionType(List.class, TeacherTalkDto.class);
return get("/public/website/selectTalk", Map.of("teacherId", teacherId), type);
}
/**
* 查询老师评价列表
* <p>GET /public/website/selectChatEvaluate?teacherId=</p>
*/
public List<ChatEvaluateDto> selectChatEvaluate(Long teacherId) {
JavaType type = HttpTool.objectMapper.getTypeFactory()
.constructCollectionType(List.class, ChatEvaluateDto.class);
return get("/public/website/selectChatEvaluate", Map.of("teacherId", teacherId), type);
}
// ============================ 底层通用调用 ============================
/**
* 发送 JSON POST 并解析 data 为指定类型
*/
private <T> T post(String path, Object body, JavaType dataType) {
try {
String raw = HttpTool.sendJsonPost(body, properties.getBaseUrl() + path, buildHeaders());
return parse(raw, dataType, path);
} catch (Exception e) {
log.error("简史星球接口调用失败 POST {}", path, e);
throw new RuntimeException("简史星球接口调用失败: " + e.getMessage(), e);
}
}
/**
* 发送 GET(带公共请求头)并解析 data 为指定类型
*/
private <T> T get(String path, Map<String, ?> params, JavaType dataType) {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(properties.getBaseUrl() + path);
URIBuilder uriBuilder = new URIBuilder(httpGet.getURI());
for (Map.Entry<String, ?> entry : params.entrySet()) {
if (Objects.nonNull(entry.getValue())) {
uriBuilder.addParameter(entry.getKey(), entry.getValue().toString());
}
}
httpGet.setURI(uriBuilder.build());
buildHeaders().forEach(httpGet::setHeader);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
String raw = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (response.getStatusLine().getStatusCode() != 200) {
throw new RuntimeException("HTTP 响应码:" + response.getStatusLine().getStatusCode() + ",响应体:" + raw);
}
return parse(raw, dataType, path);
}
} catch (Exception e) {
log.error("简史星球接口调用失败 GET {}", path, e);
throw new RuntimeException("简史星球接口调用失败: " + e.getMessage(), e);
}
}
/**
* 拆外层 {code,msg,data,ok} 包装并把 data 反序列化为目标类型
*/
private <T> T parse(String raw, JavaType dataType, String path) throws Exception {
JsxqResponse response = HttpTool.objectMapper.readValue(raw, JsxqResponse.class);
boolean success = Boolean.TRUE.equals(response.getOk())
|| (response.getCode() != null && response.getCode() == 0);
if (!success) {
throw new RuntimeException("简史星球接口返回失败 " + path + "code=" + response.getCode() + "msg=" + response.getMsg());
}
JsonNode data = response.getData();
if (data == null || data.isNull()) {
return null;
}
return HttpTool.objectMapper.convertValue(data, dataType);
}
/**
* 构建公共请求头:Authorization / TENANT-ID / CLIENT-TOC
*/
private Map<String, String> buildHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + properties.getToken());
headers.put("TENANT-ID", properties.getTenantId());
headers.put("CLIENT-TOC", properties.getClientToc());
return headers;
}
}
@@ -0,0 +1,29 @@
package org.jiayunet.remote.jsxq;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 简史星球(jianshixingqiu)开放平台配置
* <p>读取 app.jsxq,供 JsxqClient 拼接请求地址与公共请求头</p>
*
* @author zk
*/
@Data
@Component
@ConfigurationProperties(prefix = "app.jsxq")
public class JsxqProperties {
/** 平台基础地址(含网关前缀),如 https://jsxq.jianshixingqiu.com/api/admin */
private String baseUrl;
/** 访问令牌,作为 Authorization: Bearer {token} */
private String token;
/** 租户ID,作为 TENANT-ID 请求头 */
private String tenantId = "1";
/** C端标识,作为 CLIENT-TOC 请求头 */
private String clientToc = "Y";
}
@@ -0,0 +1,30 @@
package org.jiayunet.remote.jsxq.pojo;
import lombok.Data;
import java.util.List;
/**
* 简史星球接口通用分页体(对应 MyBatis-Plus IPage 序列化结构)
*
* @param <T> 记录类型
* @author zk
*/
@Data
public class JsxqPage<T> {
/** 当前页记录 */
private List<T> records;
/** 总记录数 */
private Long total;
/** 每页条数 */
private Long size;
/** 当前页码 */
private Long current;
/** 总页数 */
private Long pages;
}
@@ -0,0 +1,26 @@
package org.jiayunet.remote.jsxq.pojo;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
/**
* 简史星球接口通用外层响应
* <p>结构:{code, msg, data, ok}code=0 且 ok=true 视为成功</p>
*
* @author zk
*/
@Data
public class JsxqResponse {
/** 业务码,0=成功 */
private Integer code;
/** 错误信息,成功时为 null */
private String msg;
/** 业务数据,结构随接口而定,由调用方按类型解析 */
private JsonNode data;
/** 是否成功 */
private Boolean ok;
}
@@ -0,0 +1,25 @@
package org.jiayunet.remote.jsxq.pojo.dto;
import lombok.Data;
/**
* 简史星球-老师评价项
* <p>对应接口 /public/website/selectChatEvaluate 的元素</p>
*
* @author zk
*/
@Data
public class ChatEvaluateDto {
/** 评价标题 */
private String title;
/** 评价内容 */
private String content;
/** 评价人昵称 */
private String nickname;
/** 评价人头像 */
private String avatar;
}
@@ -0,0 +1,228 @@
package org.jiayunet.remote.jsxq.pojo.dto;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
* 简史星球-导师完整信息
* <p>对应接口 /public/website/selectMentorFullInfo</p>
*
* @author zk
*/
@Data
public class MentorFullInfoDto {
/** 导师id */
private Long id;
/** 基本信息 */
private MentorInfo mentorInfo;
/** 认证信息 */
private Verification verification;
/** 支持列表 */
private List<Support> supports;
/** 标签 */
private List<LabelRelation> labelRelations;
/**
* 导师基本信息
*/
@Data
public static class MentorInfo {
/** 平台名 */
private String platformName;
/** 真实姓名 */
private String realName;
/** 昵称 */
private String nickName;
/** 头像 */
private String picture;
/** 身份类型 0:企业在职人士 1:生涯规划师 2:心理咨询师 3:教职工 4:在校学生 */
private Integer identityType;
/** 所在国家 */
private String country;
/** 所在省份 */
private String province;
/** 曾任公司 */
private String everCompany;
/** 所在城市 */
private String city;
/** 银行卡id */
private Long bankCardId;
/** 对接联系人 */
private String contactPerson;
/** 手机号 */
private String phone;
/** 邮箱 */
private String mail;
/** 导师介绍 */
private String introduce;
/** 经历图片 [图片1,图片2] */
private List<String> introduceImages;
/** 微信号 */
private String wechat;
/** 合作次数 */
private Integer cooperationCount;
/** 星级 */
private Integer starLevel;
/** 导师备注 */
private String remark;
/** 排序 */
private Integer sort;
/** 状态 0:待入驻 1:入驻审核中 2:已入驻 3:禁用 -1:拒绝 */
private Integer status;
/** 拒绝原因 */
private String refusal;
}
/**
* 认证信息(按身份类型区分)
*/
@Data
public static class Verification {
/** 生涯规划师 */
private Career career;
/** 心理咨询师 */
private Psychology psychology;
/** 教职工 */
private Staff staff;
/** 在校学生 */
private Student student;
/** 在职人士 */
private Working working;
}
/**
* 生涯规划师认证
*/
@Data
public static class Career {
/** 机构名 */
private String agencyName;
/** 职位 */
private String position;
/** 资质类型 0:GCDF 1:NCDA 2:中级社工师 3:高级社工师 4:初级社工师 5:其他 */
private Integer type;
/** 从业年限 */
private Integer term;
/** 机构logo */
private String agencyLogo;
/** 认证材料照片 [图片1,图片2] */
private List<String> images;
}
/**
* 心理咨询师认证
*/
@Data
public static class Psychology {
/** 机构名 */
private String agencyName;
/** 职位 */
private String position;
/** 资质类型 详见平台字典 0~11 */
private Integer type;
/** 机构logo */
private String agencyLogo;
/** 从业年限 */
private Integer term;
/** 认证材料照片 [图片1,图片2] */
private List<String> images;
}
/**
* 教职工认证
*/
@Data
public static class Staff {
/** 院校名 */
private String schoolName;
/** 院校类型 0:QS前10 1:QS前50 2:985 3:QS前100 4:双一流 5:QS前200 6:211 7:其他 */
private Integer type;
/** 学校logo */
private String schoolLogo;
/** 职位 */
private String position;
/** 从业年限 */
private Integer term;
/** 认证材料照片 [图片1,图片2] */
private List<String> images;
}
/**
* 在校学生认证
*/
@Data
public static class Student {
/** 院校名 */
private String schoolName;
/** 院校类型 0:QS前10 1:QS前50 2:985 3:QS前100 4:双一流 5:QS前200 6:211 7:其他 */
private Integer type;
/** 学校logo */
private String schoolLogo;
/** 专业 */
private String major;
/** 入学时间 */
private LocalDate startTime;
/** 毕业时间 */
private LocalDate endTime;
/** 学历 1:本科 2:硕士 3:博士 */
private Integer degree;
/** 认证材料照片 [图片1,图片2] */
private List<String> images;
}
/**
* 在职人士认证
*/
@Data
public static class Working {
/** 公司名 */
private String companyName;
/** 公司类型 0:世界500强 1:央国企 2:中国500强 3:知名上市 4:独角兽 5:四大 6:红圈所 7:国家机关/事业单位 8:其他 */
private Integer type;
/** 公司logo */
private String companyLogo;
/** 行业Id */
private Long industryId;
/** 职位 */
private String position;
/** 当前职位年限 */
private Integer currentPositionTerm;
/** 从业年限 */
private Integer term;
/** 认证材料照片 [图片1,图片2] */
private List<String> images;
/** 作品链接 */
private String link;
}
/**
* 关联标签
*/
@Data
public static class LabelRelation {
/** 标签id */
private Long labelId;
/** 标签名 */
private String labelName;
/** 别名(仅 label_type=3 时有效) */
private String alias;
}
/**
* 关联支持项
*/
@Data
public static class Support {
/** 支持类型 0:远程实习 1:简历内推 2:定向内推辅导 */
private Integer type;
/** 服务介绍描述 */
private String introduce;
}
}
@@ -0,0 +1,60 @@
package org.jiayunet.remote.jsxq.pojo.dto;
import lombok.Data;
import java.util.List;
/**
* 简史星球-导师列表项
* <p>对应接口 /public/website/pageSelectMentor 的 records 元素</p>
*
* @author zk
*/
@Data
public class MentorItemDto {
/** 导师id */
private Long id;
/** 用户id */
private Long userId;
/** 平台名 */
private String platformName;
/** 头像 */
private String picture;
/** 话题标题(逗号拼接) */
private String titleStr;
/** 话题标题列表 */
private List<String> titles;
/** 标签(逗号拼接) */
private String labelStr;
/** 标签列表 */
private List<String> labels;
/** 话题最小金额 */
private Double price;
/** 机构名 */
private String agencyName;
/** 岗位 */
private String position;
/** 机构logo */
private String agencyLogo;
/** 排序 */
private Integer sort;
/** 认证类型 */
private String type;
/** 身份类型 */
private Integer identityType;
}
@@ -0,0 +1,58 @@
package org.jiayunet.remote.jsxq.pojo.dto;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* 简史星球-老师话题项
* <p>对应接口 /public/website/selectTalk 的元素(仅保留展示所需字段)</p>
*
* @author zk
*/
@Data
public class TeacherTalkDto {
/** 话题id */
private Long id;
/** 咨询师用户ID */
private Long teacherUserId;
/** 标题 */
private String title;
/** 内容 */
private String content;
/** 约聊方式 0:语音电话 1:线下约见 */
private String chatType;
/** 线下咨询单价 */
private BigDecimal seePrice;
/** 线下平台销售价格 */
private BigDecimal seeSalePrice;
/** 语音咨询单价 */
private BigDecimal chatPrice;
/** 语音平台销售价格 */
private BigDecimal chatSalePrice;
/** 标签 */
private String tags;
/** 排序 */
private Integer sort;
/** 咨询时长 */
private Integer talkTimes;
/** 求职业务分类 1:简历优化 2:面试辅导 3:职业规划 */
private String jobBusinessType;
/** 案例图片 [图片1,图片2] */
private List<String> caseImages;
}
@@ -0,0 +1,54 @@
package org.jiayunet.remote.jsxq.pojo.param;
import lombok.Data;
import java.util.List;
/**
* 简史星球-分页查询导师列表入参
* <p>对应接口 /public/website/pageSelectMentor</p>
*
* @author zk
*/
@Data
public class MentorPageParam {
/** 页码,从1开始 */
private Integer page = 1;
/** 每页条数,1~100 */
private Integer pageSize = 20;
/** 关键字 */
private String keyword;
/** 行业ids */
private List<Long> industryIds;
/** 导师ids */
private List<Long> mentorIds;
/** 身份类型 0:企业在职人士 1:生涯规划师 2:心理咨询师 3:教职工 4:在校学生 */
private Integer identityType;
/** 标签ids */
private List<Long> labelIds;
/** 约聊方式 0:语音电话 1:线下约见 */
private Integer chatType;
/** 销售价格最大值 */
private Double maxChatPrice;
/** 销售价格最小值 */
private Double minChatPrice;
/** 公司名 */
private String agencyName;
/** 机构类型 */
private String type;
/** 排序方式 0:默认 1:金牌导师优先 2:好评优先 3:价格升序 4:价格降序 */
private Integer sort = 0;
}