Merge branch 'master' into cc_20251028_decoration
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
package com.cool.store.enums.wechat;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/16 15:19
|
||||
* @Version 1.0
|
||||
*/
|
||||
public enum WechatTemplateDetailEnum {
|
||||
|
||||
CHARACTER_STRING2("编号","character_string2"),
|
||||
THING10("项目名称","thing10"),
|
||||
TIME14("完成时间","time14"),
|
||||
THING25("客户名称","thing25"),
|
||||
THING60("位置","thing60"),
|
||||
|
||||
|
||||
THING6("工单标题","thing6"),
|
||||
TIME33("创建时间","time33"),
|
||||
CHARACTER_STRING14("工单编号","character_string14"),
|
||||
|
||||
|
||||
;
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
||||
private String code;
|
||||
|
||||
WechatTemplateDetailEnum(String name, String code) {
|
||||
this.name = name;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.cool.store.enums.wechat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.cool.store.enums.wechat.WechatTemplateDetailEnum.*;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:39
|
||||
* @Version 1.0
|
||||
*/
|
||||
public enum WechatTemplateEnum {
|
||||
|
||||
QUESTION_NOTICE("QUESTION_NOTICE", "T3sp5gBItHKD8oCeEiQMjn7JXpngFiz3dDcaArk84xY", "收到工单通知",
|
||||
Arrays.asList(CHARACTER_STRING2,THING10,TIME14,THING25,THING60)),
|
||||
|
||||
NEW_QUESTION_NOTICE("new_question_notice", "35rmoQ5wEHvUgCgSIJ2CpOb1DOJgWOiy8J7DXT_e4_Y", "新工单提醒",
|
||||
Arrays.asList(THING6,TIME33,CHARACTER_STRING14)),
|
||||
|
||||
;
|
||||
|
||||
|
||||
private final String code;
|
||||
private final String templateId;
|
||||
private final String title;
|
||||
private final List<WechatTemplateDetailEnum> contentList;
|
||||
|
||||
WechatTemplateEnum(String code, String templateId, String title, List<WechatTemplateDetailEnum> contentList) {
|
||||
this.code = code;
|
||||
this.templateId = templateId;
|
||||
this.title = title;
|
||||
this.contentList = contentList;
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getTemplateId() {
|
||||
return templateId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public List<WechatTemplateDetailEnum> getContentList() {
|
||||
return contentList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取枚举
|
||||
*/
|
||||
public static WechatTemplateEnum getByCode(String code) {
|
||||
for (WechatTemplateEnum template : values()) {
|
||||
if (template.getCode().equals(code)) {
|
||||
return template;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板ID获取枚举
|
||||
*/
|
||||
public static WechatTemplateEnum getByTemplateId(String templateId) {
|
||||
for (WechatTemplateEnum template : values()) {
|
||||
if (template.getTemplateId().equals(templateId)) {
|
||||
return template;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.cool.store.utils;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:21
|
||||
* @Version 1.0
|
||||
* OkHttp工具类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class OkHttpUtil {
|
||||
|
||||
@Autowired
|
||||
private OkHttpClient okHttpClient;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
/**
|
||||
* GET请求
|
||||
*/
|
||||
public String doGet(String url) throws IOException {
|
||||
return doGet(url, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET请求 - 带请求头
|
||||
*/
|
||||
public String doGet(String url, Map<String, String> headers) throws IOException {
|
||||
Request.Builder builder = new Request.Builder().url(url);
|
||||
|
||||
// 添加请求头
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
Request request = builder.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Unexpected code: " + response);
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
return body != null ? body.string() : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求 - JSON数据
|
||||
*/
|
||||
public String doPostJson(String url, Object data) throws IOException {
|
||||
return doPostJson(url, data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST请求 - JSON数据,带请求头
|
||||
*/
|
||||
public String doPostJson(String url, Object data, Map<String, String> headers) throws IOException {
|
||||
//打印日志
|
||||
logRequest(url, data);
|
||||
|
||||
String json = objectMapper.writeValueAsString(data);
|
||||
RequestBody requestBody = RequestBody.create( JSON,json);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(url)
|
||||
.post(requestBody);
|
||||
|
||||
// 添加请求头
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
Request request = builder.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
|
||||
"HTTP请求失败,状态码: " + response.code());
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
|
||||
String responseBody = body != null ? body.string() : null;
|
||||
|
||||
logResponse(url, response.code(), responseBody);
|
||||
return responseBody;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步GET请求
|
||||
*/
|
||||
public void doGetAsync(String url, Callback callback) {
|
||||
doGetAsync(url, null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步GET请求 - 带请求头
|
||||
*/
|
||||
public void doGetAsync(String url, Map<String, String> headers, Callback callback) {
|
||||
Request.Builder builder = new Request.Builder().url(url);
|
||||
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
Request request = builder.build();
|
||||
okHttpClient.newCall(request).enqueue(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步POST请求
|
||||
*/
|
||||
public void doPostAsync(String url, Object data, Callback callback) {
|
||||
doPostAsync(url, data, null, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步POST请求 - 带请求头
|
||||
*/
|
||||
public void doPostAsync(String url, Object data, Map<String, String> headers, Callback callback) {
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(data);
|
||||
RequestBody requestBody = RequestBody.create( JSON,json);
|
||||
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(url)
|
||||
.post(requestBody);
|
||||
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
Request request = builder.build();
|
||||
okHttpClient.newCall(request).enqueue(callback);
|
||||
} catch (IOException e) {
|
||||
callback.onFailure(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logRequest(String url, Object requestBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
log.info("\n======= 请求开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"请求参数: {}\n" +
|
||||
"======= 请求结束 =======",
|
||||
url,
|
||||
objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(requestBody));
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("日志JSON序列化失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logResponse(String url, int statusCode, String responseBody) {
|
||||
if (log.isInfoEnabled()) {
|
||||
try {
|
||||
// 尝试美化JSON输出
|
||||
Object json = objectMapper.readValue(responseBody, Object.class);
|
||||
String prettyResponse = objectMapper.writerWithDefaultPrettyPrinter()
|
||||
.writeValueAsString(json);
|
||||
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"响应内容: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
prettyResponse);
|
||||
} catch (Exception e) {
|
||||
// 非JSON响应或解析失败时直接输出原始内容
|
||||
log.info("\n======= 响应开始 =======\n" +
|
||||
"API地址: {}\n" +
|
||||
"HTTP状态码: {}\n" +
|
||||
"原始响应: {}\n" +
|
||||
"======= 响应结束 =======",
|
||||
url,
|
||||
statusCode,
|
||||
responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cool.store.dao;
|
||||
|
||||
import com.cool.store.dto.wechat.ServiceAccountOpenIdDTO;
|
||||
import com.cool.store.entity.HyPartnerUserInfoDO;
|
||||
import com.cool.store.mapper.HyPartnerUserInfoMapper;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -112,4 +113,11 @@ public class HyPartnerUserInfoDAO {
|
||||
|
||||
return hyPartnerUserInfoMapper.selectPasswordIsNull();
|
||||
}
|
||||
|
||||
public List<ServiceAccountOpenIdDTO> selectLastBindRecord(List<String> mobileList){
|
||||
if (CollectionUtils.isEmpty(mobileList)){
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
return hyPartnerUserInfoMapper.selectLastBindRecord(mobileList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,27 @@ public class PartnerUserWechatBindDAO {
|
||||
return partnerUserWechatBindMapper.insert(partnerUserWechatBindDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param partnerUserWechatBindDO
|
||||
* @return
|
||||
*/
|
||||
public Integer update(PartnerUserWechatBindDO partnerUserWechatBindDO) {
|
||||
if (partnerUserWechatBindDO == null) {
|
||||
return 0;
|
||||
}
|
||||
return partnerUserWechatBindMapper.update(partnerUserWechatBindDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有的unionId对应的服务号ID
|
||||
* @param unionId 开发平台的用户ID
|
||||
* @param serviceAccountOpenId 服务号ID
|
||||
* @return
|
||||
*/
|
||||
public Integer updateByUnionId(String unionId,String serviceAccountOpenId) {
|
||||
return partnerUserWechatBindMapper.updateByUnionId(unionId,serviceAccountOpenId);
|
||||
}
|
||||
|
||||
public PartnerUserWechatBindDO getByOpenIdAndPartnerId(String partnerId, String openId) {
|
||||
if (StringUtil.isEmpty(partnerId)|| StringUtil.isEmpty(openId)){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cool.store.mapper;
|
||||
|
||||
import com.cool.store.dto.wechat.ServiceAccountOpenIdDTO;
|
||||
import com.cool.store.entity.HyPartnerUserInfoDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -55,4 +56,6 @@ public interface HyPartnerUserInfoMapper extends tk.mybatis.mapper.common.Mappe
|
||||
|
||||
List<HyPartnerUserInfoDO> selectPasswordIsNull();
|
||||
|
||||
List<ServiceAccountOpenIdDTO> selectLastBindRecord(@Param("mobileList") List<String> mobileList);
|
||||
|
||||
}
|
||||
@@ -18,6 +18,22 @@ public interface PartnerUserWechatBindMapper {
|
||||
*/
|
||||
Integer insert(PartnerUserWechatBindDO partnerUserWechatBindDO);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
* @param partnerUserWechatBindDO
|
||||
* @return
|
||||
*/
|
||||
Integer update(PartnerUserWechatBindDO partnerUserWechatBindDO);
|
||||
|
||||
|
||||
/**
|
||||
* 更新所有的unionId对应的服务号ID
|
||||
* @param unionId 开发平台的用户ID
|
||||
* @param serviceAccountOpenId 服务号ID
|
||||
* @return
|
||||
*/
|
||||
Integer updateByUnionId(String unionId,String serviceAccountOpenId);
|
||||
|
||||
/**
|
||||
* 根据partnerId与openId查询
|
||||
* @param partnerId
|
||||
|
||||
@@ -195,4 +195,23 @@
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<select id="selectLastBindRecord" resultType="com.cool.store.dto.wechat.ServiceAccountOpenIdDTO">
|
||||
select
|
||||
b.partner_id as partnerId,
|
||||
b.union_id as unionId,
|
||||
b.service_account_open_id as serviceAccountOpenId,
|
||||
MAX(b.update_time) as lastUpdateTime
|
||||
from xfsg_partner_user_info a
|
||||
left join xfsg_partner_user_wechat_bind b
|
||||
on a.partner_id = b.partner_id
|
||||
where b.partner_id is not null
|
||||
and service_account_open_id is not null
|
||||
<if test="mobileList !=null and mobileList.size>0">
|
||||
<foreach collection="mobileList" open="and mobile in (" close=")" separator="," item="mobile">
|
||||
#{mobile}
|
||||
</foreach>
|
||||
</if>
|
||||
GROUP BY b.partner_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -8,6 +8,8 @@
|
||||
<result column="open_id" property="openId" jdbcType="VARCHAR" />
|
||||
<result column="bind_time" property="bindTime" jdbcType="TIMESTAMP" />
|
||||
<result column="partner_id" property="partnerId" jdbcType="VARCHAR" />
|
||||
<result column="union_id" property="unionId" jdbcType="VARCHAR" />
|
||||
<result column="service_account_open_id" property="serviceAccountOpenId" jdbcType="VARCHAR" />
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
|
||||
</resultMap>
|
||||
@@ -17,15 +19,33 @@
|
||||
open_id,
|
||||
bind_time,
|
||||
partner_id,
|
||||
union_id,
|
||||
create_time
|
||||
) VALUES (
|
||||
#{openId, jdbcType=VARCHAR},
|
||||
#{bindTime, jdbcType=TIMESTAMP},
|
||||
#{partnerId, jdbcType=VARCHAR},
|
||||
#{unionId, jdbcType=VARCHAR},
|
||||
#{createTime, jdbcType=TIMESTAMP}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="com.cool.store.entity.PartnerUserWechatBindDO">
|
||||
UPDATE xfsg_partner_user_wechat_bind
|
||||
<set>
|
||||
<if test="unionId != null">
|
||||
union_id = #{unionId, jdbcType=VARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateByUnionId" >
|
||||
UPDATE xfsg_partner_user_wechat_bind
|
||||
set service_account_open_id = #{serviceAccountOpenId}
|
||||
where union_id = #{unionId}
|
||||
</update>
|
||||
|
||||
<select id="selectByPartnerAndOpenId" resultMap="BaseResultMap">
|
||||
select * from xfsg_partner_user_wechat_bind where partner_id = #{partnerId} and open_id = #{openId}
|
||||
</select>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.cool.store.dto.wechat;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 15:01
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class AccessTokenDTO {
|
||||
|
||||
private String access_token;
|
||||
|
||||
private Integer expires_in;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.cool.store.dto.wechat;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/14 14:39
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class CallbackMessageDTO {
|
||||
|
||||
private String toUserName;
|
||||
|
||||
private String fromUserName;
|
||||
|
||||
private Long createTime;
|
||||
|
||||
private String msgType;
|
||||
|
||||
private String event;
|
||||
|
||||
private String eventKey;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.cool.store.dto.wechat;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/16 14:13
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class ServiceAccountOpenIdDTO {
|
||||
|
||||
private String partnerId;
|
||||
|
||||
private String unionId;
|
||||
|
||||
private String serviceAccountOpenId;
|
||||
|
||||
private Date lastUpdateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.cool.store.dto.wechat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:36
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class WechatTemplateMessageDTO {
|
||||
|
||||
/**
|
||||
* 接收者openid
|
||||
*/
|
||||
@JsonProperty("touser")
|
||||
private String toUser;
|
||||
|
||||
/**
|
||||
* 模板ID
|
||||
*/
|
||||
@JsonProperty("template_id")
|
||||
private String templateId;
|
||||
|
||||
/**
|
||||
* 模板跳转链接(非必须)
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 跳小程序所需数据,不需跳小程序可不用传该数据
|
||||
*/
|
||||
private MiniprogramDTO miniprogram;
|
||||
|
||||
/**
|
||||
* 模板数据
|
||||
*/
|
||||
private Map<String, TemplateDataItemDTO> data;
|
||||
|
||||
/**
|
||||
* 小程序跳转DTO
|
||||
*/
|
||||
@Data
|
||||
public static class MiniprogramDTO {
|
||||
|
||||
/**
|
||||
* 所需跳转到的小程序appid
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 所需跳转到小程序的具体页面路径,支持带参数
|
||||
*/
|
||||
private String pagepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板数据项DTO
|
||||
*/
|
||||
@Data
|
||||
public static class TemplateDataItemDTO {
|
||||
|
||||
/**
|
||||
* 模板内容
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 模板内容字体颜色,不填默认为黑色
|
||||
*/
|
||||
private String color;
|
||||
|
||||
public TemplateDataItemDTO() {
|
||||
}
|
||||
|
||||
public TemplateDataItemDTO(String value) {
|
||||
this.value = value;
|
||||
this.color = "#333333";
|
||||
}
|
||||
|
||||
public TemplateDataItemDTO(String value, String color) {
|
||||
this.value = value;
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.cool.store.dto.wechat;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/15 9:56
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
public class WechatUserInfoDTO {
|
||||
/**
|
||||
* 用户是否订阅该公众号标识
|
||||
* 0代表未关注,1代表关注
|
||||
*/
|
||||
private Integer subscribe;
|
||||
|
||||
/**
|
||||
* 用户的标识,对当前公众号唯一
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
/**
|
||||
* 用户的昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 用户的性别
|
||||
* 1为男性,2为女性,0为未知
|
||||
*/
|
||||
private Integer sex;
|
||||
|
||||
/**
|
||||
* 用户所在城市
|
||||
*/
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 用户所在国家
|
||||
*/
|
||||
private String country;
|
||||
|
||||
/**
|
||||
* 用户所在省份
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 用户的语言
|
||||
* 简体中文为zh_CN
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String headimgurl;
|
||||
|
||||
/**
|
||||
* 用户关注时间,为时间戳
|
||||
*/
|
||||
@JsonProperty("subscribe_time")
|
||||
private Long subscribeTime;
|
||||
|
||||
/**
|
||||
* 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段
|
||||
*/
|
||||
private String unionid;
|
||||
|
||||
/**
|
||||
* 公众号运营者对粉丝的备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 用户所在的分组ID(兼容旧的用户分组接口)
|
||||
*/
|
||||
private Integer groupid;
|
||||
|
||||
/**
|
||||
* 用户被打上的标签ID列表
|
||||
*/
|
||||
@JsonProperty("tagid_list")
|
||||
private List<Integer> tagidList;
|
||||
|
||||
/**
|
||||
* 返回用户关注的渠道来源
|
||||
*/
|
||||
@JsonProperty("subscribe_scene")
|
||||
private String subscribeScene;
|
||||
|
||||
/**
|
||||
* 二维码扫码场景(开发者自定义)
|
||||
*/
|
||||
@JsonProperty("qr_scene")
|
||||
private Long qrScene;
|
||||
|
||||
/**
|
||||
* 二维码扫码场景描述(开发者自定义)
|
||||
*/
|
||||
@JsonProperty("qr_scene_str")
|
||||
private String qrSceneStr;
|
||||
|
||||
/**
|
||||
* 是否已关注
|
||||
*/
|
||||
public boolean isSubscribed() {
|
||||
return subscribe != null && subscribe == 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.persistence.*;
|
||||
@@ -18,6 +21,9 @@ import javax.validation.constraints.NotBlank;
|
||||
*/
|
||||
@Table(name = "xfsg_build_information")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
public class BuildInformationDO {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -36,6 +36,10 @@ public class PartnerUserWechatBindDO implements Serializable {
|
||||
*/
|
||||
private String partnerId;
|
||||
|
||||
private String unionId;
|
||||
|
||||
private String serviceAccountOpenId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.cool.store.builder;
|
||||
|
||||
import com.cool.store.config.weixin.WechatMiniappProperties;
|
||||
import com.cool.store.dto.wechat.WechatTemplateMessageDTO;
|
||||
import com.cool.store.enums.wechat.WechatTemplateEnum;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:34
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Component
|
||||
public class TemplateMessageBuilder {
|
||||
|
||||
@Autowired
|
||||
private WechatMiniappProperties wechatMiniappProperties;
|
||||
|
||||
/**
|
||||
* 构建普通模板消息
|
||||
*/
|
||||
public WechatTemplateMessageDTO buildNormalTemplate(String openId,
|
||||
WechatTemplateEnum template,
|
||||
Map<String, Object> data) {
|
||||
WechatTemplateMessageDTO messageDTO = new WechatTemplateMessageDTO();
|
||||
messageDTO.setToUser(openId);
|
||||
messageDTO.setTemplateId(template.getTemplateId());
|
||||
|
||||
// 设置URL(如果data中包含url)
|
||||
if (data.containsKey("url")) {
|
||||
messageDTO.setUrl((String) data.get("url"));
|
||||
}
|
||||
|
||||
// 构建模板数据
|
||||
messageDTO.setData(buildTemplateData(data));
|
||||
|
||||
return messageDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建小程序跳转模板消息
|
||||
*/
|
||||
public WechatTemplateMessageDTO buildMiniappTemplate(String openId,
|
||||
WechatTemplateEnum template,
|
||||
Map<String, Object> data,
|
||||
String miniAppPagePath) {
|
||||
WechatTemplateMessageDTO messageDTO = new WechatTemplateMessageDTO();
|
||||
messageDTO.setToUser(openId);
|
||||
messageDTO.setTemplateId(template.getTemplateId());
|
||||
|
||||
// 设置小程序跳转
|
||||
WechatTemplateMessageDTO.MiniprogramDTO miniProgram = new WechatTemplateMessageDTO.MiniprogramDTO();
|
||||
miniProgram.setAppid(wechatMiniappProperties.getAppId());
|
||||
miniProgram.setPagepath(miniAppPagePath != null ? miniAppPagePath : wechatMiniappProperties.getDefaultPagePath());
|
||||
messageDTO.setMiniprogram(miniProgram);
|
||||
|
||||
// 设置备用URL(如果data中包含url)
|
||||
if (data.containsKey("url")) {
|
||||
messageDTO.setUrl((String) data.get("url"));
|
||||
}
|
||||
|
||||
// 构建模板数据
|
||||
messageDTO.setData(buildTemplateData(data));
|
||||
|
||||
return messageDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建小程序跳转模板消息(带备用URL)
|
||||
*/
|
||||
public WechatTemplateMessageDTO buildMiniAppTemplateWithUrl(String openId,
|
||||
WechatTemplateEnum template,
|
||||
Map<String, Object> data,
|
||||
String miniAppPagePath,
|
||||
String backupUrl) {
|
||||
WechatTemplateMessageDTO messageDTO = buildMiniappTemplate(openId, template, data, miniAppPagePath);
|
||||
|
||||
// 设置备用URL
|
||||
if (backupUrl != null && !backupUrl.trim().isEmpty()) {
|
||||
messageDTO.setUrl(backupUrl);
|
||||
}
|
||||
|
||||
return messageDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模板数据
|
||||
*/
|
||||
private Map<String, WechatTemplateMessageDTO.TemplateDataItemDTO> buildTemplateData(Map<String, Object> data) {
|
||||
Map<String, WechatTemplateMessageDTO.TemplateDataItemDTO> templateData = new HashMap<>();
|
||||
|
||||
data.forEach((key, value) -> {
|
||||
if (!"url".equals(key) && value != null) {
|
||||
WechatTemplateMessageDTO.TemplateDataItemDTO item =
|
||||
new WechatTemplateMessageDTO.TemplateDataItemDTO(
|
||||
value.toString(),
|
||||
getColorByField(key)
|
||||
);
|
||||
templateData.put(key, item);
|
||||
}
|
||||
});
|
||||
|
||||
return templateData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名获取颜色
|
||||
*/
|
||||
private String getColorByField(String fieldName) {
|
||||
switch (fieldName) {
|
||||
case "amount":
|
||||
case "refundAmount":
|
||||
case "couponValue":
|
||||
case "character_string2":
|
||||
return "#FF0000"; // 金额类字段用红色
|
||||
case "orderNo":
|
||||
case "expressNo":
|
||||
return "#173177"; // 编号类字段用蓝色
|
||||
default:
|
||||
return "#333333"; // 默认用深灰色
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.cool.store.config.weixin;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:41
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "wechat.miniapp")
|
||||
public class WechatMiniappProperties {
|
||||
|
||||
/**
|
||||
* 小程序appId
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 小程序页面路径
|
||||
*/
|
||||
private String defaultPagePath ;
|
||||
|
||||
/**
|
||||
* 是否使用小程序跳转
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.cool.store.config.weixin;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:29
|
||||
* @Version 1.0
|
||||
* 微信服务号配置
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "wechat.mp")
|
||||
public class WechatMpProperties {
|
||||
|
||||
/**
|
||||
* 公众号appId
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 公众号appSecret
|
||||
*/
|
||||
private String appSecret;
|
||||
|
||||
/**
|
||||
* 获取access_token的URL
|
||||
*/
|
||||
private String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
|
||||
|
||||
/**
|
||||
* 发送模板消息的URL
|
||||
*/
|
||||
private String sendTemplateMessageUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send";
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.cool.store.handler;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.dao.PartnerUserWechatBindDAO;
|
||||
import com.cool.store.dto.wechat.WechatUserInfoDTO;
|
||||
import com.cool.store.service.wechat.WechatTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/14 14:56
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class WeChatHandler {
|
||||
|
||||
@Resource
|
||||
PartnerUserWechatBindDAO partnerUserWechatBindDAO;
|
||||
@Resource
|
||||
WechatTemplateService wechatTemplateService;
|
||||
|
||||
public Map<String, Object> parseXmlToMap(String xmlContent) throws Exception {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(new StringReader(xmlContent)));
|
||||
|
||||
NodeList nodes = document.getDocumentElement().getChildNodes();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node node = nodes.item(i);
|
||||
if (node.getNodeType() == Node.ELEMENT_NODE) {
|
||||
String tagName = node.getNodeName();
|
||||
String textContent = node.getTextContent();
|
||||
result.put(tagName, textContent);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public String processMessage(Map<String, Object> messageMap) {
|
||||
String msgType = (String) messageMap.get("MsgType");
|
||||
String event = (String) messageMap.get("Event");
|
||||
|
||||
switch (msgType) {
|
||||
case "event":
|
||||
return handleEvent(messageMap);
|
||||
|
||||
// case "text":
|
||||
// return handleTextMessage(message);
|
||||
//
|
||||
// case "image":
|
||||
// return handleImageMessage(message);
|
||||
|
||||
default:
|
||||
// 其他类型的消息直接回复success
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
|
||||
private String handleEvent(Map<String, Object> messageMap) {
|
||||
String event = (String) messageMap.get("Event");
|
||||
String fromUserName = (String) messageMap.get("FromUserName");
|
||||
String toUserName = (String) messageMap.get("ToUserName");
|
||||
|
||||
switch (event) {
|
||||
case "subscribe":
|
||||
// 关注事件 - 绑定用户
|
||||
return handleSubscribeEvent(fromUserName,toUserName);
|
||||
|
||||
case "unsubscribe":
|
||||
// 取消关注事件 - 解绑用户
|
||||
return handleUnsubscribeEvent(fromUserName,toUserName);
|
||||
|
||||
default:
|
||||
return buildWelcomeReply(fromUserName, toUserName);
|
||||
}
|
||||
}
|
||||
|
||||
private String handleSubscribeEvent(String fromUserName,String toUserName) {
|
||||
try {
|
||||
|
||||
//根据openId 获取用户信息
|
||||
WechatUserInfoDTO userInfo = wechatTemplateService.getUserInfo(fromUserName, null);
|
||||
|
||||
log.info("handleSubscribeEvent: {}", JSONObject.toJSONString(userInfo));
|
||||
|
||||
//根据unionId 更新服务号ID
|
||||
if (userInfo != null) {
|
||||
partnerUserWechatBindDAO.updateByUnionId(userInfo.getUnionid(),fromUserName);
|
||||
}
|
||||
|
||||
// 立即回复欢迎消息
|
||||
return buildWelcomeReply(fromUserName, toUserName);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 即使处理失败也要返回success
|
||||
return buildWelcomeReply(fromUserName, toUserName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理取消关注事件
|
||||
*/
|
||||
private String handleUnsubscribeEvent(String fromUserName,String toUserName) {
|
||||
|
||||
// 异步处理用户解绑
|
||||
//userBindingService.unbindOfficialAccountUser(openId);
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
private String buildSuccessReply(String fromUser, String toUser) {
|
||||
return String.format(
|
||||
"<xml>" +
|
||||
"<ToUserName><![CDATA[%s]]></ToUserName>" +
|
||||
"<FromUserName><![CDATA[%s]]></FromUserName>" +
|
||||
"<CreateTime>%d</CreateTime>" +
|
||||
"<MsgType><![CDATA[text]]></MsgType>" +
|
||||
"<Content><![CDATA[success]]></Content>" +
|
||||
"</xml>",
|
||||
fromUser, toUser, System.currentTimeMillis() / 1000
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private String buildWelcomeReply(String fromUser, String toUser) {
|
||||
return String.format(
|
||||
"<xml>" +
|
||||
"<ToUserName><![CDATA[%s]]></ToUserName>" +
|
||||
"<FromUserName><![CDATA[%s]]></FromUserName>" +
|
||||
"<CreateTime>%d</CreateTime>" +
|
||||
"<MsgType><![CDATA[text]]></MsgType>" +
|
||||
"<Content><![CDATA[欢迎关注!您已成功绑定通知服务,可以接收小程序的重要消息通知。]]></Content>" +
|
||||
"</xml>",
|
||||
fromUser, toUser, System.currentTimeMillis() / 1000
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,8 @@ public class BuildInformationServiceImpl implements BuildInformationService {
|
||||
private OrderSysInfoDAO orderSysInfoDAO;
|
||||
@Autowired
|
||||
private BigRegionDAO bigRegionDAO;
|
||||
@Resource
|
||||
private AcceptanceInfoDAO acceptanceInfoDAO;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -252,7 +254,14 @@ public class BuildInformationServiceImpl implements BuildInformationService {
|
||||
if (StringUtils.isBlank(response.getAddresseeAddress())) {
|
||||
response.setAddresseeAddress(shopInfo.getDetailAddress());
|
||||
}
|
||||
|
||||
// 不存在的情况下从装修验收中获取
|
||||
if (StringUtils.isBlank(response.getDoorPhoto()) || StringUtils.isBlank(response.getInStorePhoto())) {
|
||||
AcceptanceInfoDO acceptanceInfoDO = acceptanceInfoDAO.selectByShopId(shopId);
|
||||
if (Objects.nonNull(acceptanceInfoDO)) {
|
||||
response.setDoorPhoto(StringUtils.isNotBlank(response.getDoorPhoto()) ? response.getDoorPhoto() : acceptanceInfoDO.getShopDoorwayPhoto());
|
||||
response.setInStorePhoto(StringUtils.isNotBlank(response.getInStorePhoto()) ? response.getInStorePhoto() : acceptanceInfoDO.getShopInteriorPhoto());
|
||||
}
|
||||
}
|
||||
return response;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cool.store.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.dao.*;
|
||||
import com.cool.store.entity.*;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
@@ -10,14 +12,12 @@ import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.request.*;
|
||||
import com.cool.store.service.DecorationDesignInfoService;
|
||||
import com.cool.store.utils.poi.StringUtils;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -40,6 +40,8 @@ public class DecorationDesignInfoServiceImpl implements DecorationDesignInfoServ
|
||||
private ShopStageInfoDAO shopStageInfoDAO;
|
||||
@Resource
|
||||
private LineInfoDAO lineInfoDAO;
|
||||
@Resource
|
||||
private BuildInformationDAO buildInformationDAO;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -277,8 +279,28 @@ public class DecorationDesignInfoServiceImpl implements DecorationDesignInfoServ
|
||||
if (shopSubStageInfo.getShopSubStageStatus().equals(ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_122.getShopSubStageStatus())) {
|
||||
shopStageInfoDAO.updateShopStageInfo(shopInfoDO.getId(), ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_123);
|
||||
}
|
||||
// 覆盖建店资料中的门头照和内景照
|
||||
BuildInformationDO buildInformation = BuildInformationDO.builder().shopId(request.getShopId()).doorPhoto(buildJson(request.getShopDoorwayPhotoUrl()))
|
||||
.inStorePhoto(buildJson(request.getShopInteriorPhotoUrl())).build();
|
||||
buildInformationDAO.updateByShopIdSelective(buildInformation);
|
||||
return true;
|
||||
}
|
||||
|
||||
public String buildJson(List<String> urls) {
|
||||
if (CollectionUtils.isEmpty(urls)) {
|
||||
return null;
|
||||
}
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for (String url : urls) {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("url", url);
|
||||
jsonObject.put("type", extractImageSuffix(url));
|
||||
jsonArray.add(jsonObject);
|
||||
}
|
||||
return jsonArray.toJSONString();
|
||||
}
|
||||
|
||||
public String extractImageSuffix(String url) {
|
||||
return url.substring(url.lastIndexOf(".") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import com.cool.store.dto.notice.MessageTemplateCountDTO;
|
||||
import com.cool.store.dto.notice.NoticeDTO;
|
||||
import com.cool.store.dto.store.AuthStoreUserDTO;
|
||||
import com.cool.store.dto.store.StoreAreaDTO;
|
||||
import com.cool.store.dto.wechat.ServiceAccountOpenIdDTO;
|
||||
import com.cool.store.entity.*;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.enums.notice.*;
|
||||
import com.cool.store.enums.wechat.WechatTemplateDetailEnum;
|
||||
import com.cool.store.enums.wechat.WechatTemplateEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.mapper.StoreGroupMappingMapper;
|
||||
import com.cool.store.mapper.StoreMapper;
|
||||
@@ -19,12 +22,16 @@ import com.cool.store.request.notice.*;
|
||||
import com.cool.store.response.bigdata.ApiResponse;
|
||||
import com.cool.store.service.MessageTemplateService;
|
||||
import com.cool.store.service.StoreService;
|
||||
import com.cool.store.service.wechat.WechatTemplateService;
|
||||
import com.cool.store.utils.CoolDateUtils;
|
||||
import com.cool.store.utils.RedisUtilPool;
|
||||
import com.cool.store.utils.poi.DateUtils;
|
||||
import com.cool.store.vo.PartnerUserInfoVO;
|
||||
import com.cool.store.vo.notice.*;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -67,10 +74,14 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
|
||||
@Resource
|
||||
MatterConfigDAO matterConfigDAO;
|
||||
@Resource
|
||||
WechatTemplateService wechatTemplateService;
|
||||
@Resource
|
||||
RedisUtilPool redisUtilPool;
|
||||
@Resource
|
||||
TaskExecutor noticeThreadPool;
|
||||
@Resource
|
||||
HyPartnerUserInfoDAO hyPartnerUserInfoDAO;
|
||||
@Resource
|
||||
MessageIssueService messageIssueService;
|
||||
|
||||
|
||||
@@ -210,10 +221,38 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
|
||||
JSONObject.toJSONString(request.getStoreInfoList()),
|
||||
JSONObject.toJSONString(request.getUserInfoList()),
|
||||
userId);
|
||||
|
||||
//发送通知
|
||||
Set<String> userIds = authUser.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
|
||||
|
||||
//分批 查询用户信息
|
||||
List<String> openIdList = new ArrayList<>();
|
||||
Lists.partition(new ArrayList<>(userIds), 100).forEach(x->{
|
||||
List<EnterpriseUserDO> userInfoByUserIds = enterpriseUserDAO.getUserInfoByUserIds(x);
|
||||
//取出用户的手机号,过滤掉空的手机号
|
||||
List<String> mobileList = userInfoByUserIds.stream().filter(user -> StringUtils.isNotBlank(user.getMobile())).map(EnterpriseUserDO::getMobile).collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(mobileList)){
|
||||
List<ServiceAccountOpenIdDTO> serviceAccountOpenIdDTOS = hyPartnerUserInfoDAO.selectLastBindRecord(mobileList);
|
||||
if (CollectionUtils.isNotEmpty(serviceAccountOpenIdDTOS)){
|
||||
//服务号ID
|
||||
openIdList.addAll(serviceAccountOpenIdDTOS.stream().map(ServiceAccountOpenIdDTO::getServiceAccountOpenId).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
MessageTemplateDO messageTemplateDO = list.get(0);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put(WechatTemplateDetailEnum.THING6.getCode(), messageTemplateDO.getMessageTitle());
|
||||
data.put(WechatTemplateDetailEnum.TIME33.getCode(), DateUtils.parseDateToStr(DateUtils.SPECIAL_DATE_START, new Date()));
|
||||
data.put(WechatTemplateDetailEnum.CHARACTER_STRING14.getCode(), messageTemplateDO.getMessageCode());
|
||||
openIdList.forEach(x->{
|
||||
wechatTemplateService.sendMiniAppTemplate(x, WechatTemplateEnum.NEW_QUESTION_NOTICE,data,"pages/notification/index");
|
||||
});
|
||||
// 即时消息下发
|
||||
messageIssueService.issueMessage(realtimeMessageList);
|
||||
} catch (Exception e) {
|
||||
log.info("发布流程异常,已取消发布");
|
||||
log.info("发布流程异常 e:{}",e.getMessage());
|
||||
} finally {
|
||||
releaseLocks(lockKeys);
|
||||
log.info("发布流程结束,已释放Redis锁");
|
||||
|
||||
@@ -273,6 +273,18 @@ public class SyncDataServiceImpl implements SyncDataService {
|
||||
} catch (Exception e) {
|
||||
log.info("getUrl error:{},JSON:{}", e.getMessage(), json);
|
||||
}
|
||||
return getUrlListByComma(json);
|
||||
}
|
||||
|
||||
private static List<String> getUrlListByComma(String str) {
|
||||
if (StringUtils.isBlank(str)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Arrays.asList(str.split(","));
|
||||
} catch (Exception e) {
|
||||
log.info("getUrlListByComma error:{},str:{}", e.getMessage(), str);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,16 @@ public class WechatMiniAppServiceImpl implements WechatMiniAppService {
|
||||
PartnerUserWechatBindDO bindDO = new PartnerUserWechatBindDO();
|
||||
bindDO.setBindTime(new Date());
|
||||
bindDO.setOpenId(openid);
|
||||
bindDO.setUnionId(unionId);
|
||||
bindDO.setPartnerId(hyPartnerUserInfoDO.getPartnerId());
|
||||
bindDO.setCreateTime(new Date());
|
||||
partnerUserWechatBindDAO.insertSelective(bindDO);
|
||||
}else {
|
||||
//维护unionId 针对老数据没有unionId
|
||||
if (zlPartnerUserBindDO.getUnionId()==null){
|
||||
zlPartnerUserBindDO.setUnionId(unionId);
|
||||
partnerUserWechatBindDAO.update(zlPartnerUserBindDO);
|
||||
}
|
||||
}
|
||||
BeanUtil.copyProperties(hyPartnerUserInfoDO, userInfoVO);
|
||||
fillLineInfo(userInfoVO, hyPartnerUserInfoDO.getPartnerId());
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.cool.store.service.wechat;
|
||||
|
||||
import com.cool.store.builder.TemplateMessageBuilder;
|
||||
import com.cool.store.config.weixin.WechatMpProperties;
|
||||
import com.cool.store.dto.wechat.AccessTokenDTO;
|
||||
import com.cool.store.dto.wechat.WechatTemplateMessageDTO;
|
||||
import com.cool.store.dto.wechat.WechatUserInfoDTO;
|
||||
import com.cool.store.enums.wechat.WechatTemplateEnum;
|
||||
import com.cool.store.utils.OkHttpUtil;
|
||||
import com.cool.store.utils.poi.StringUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author suzhuhong
|
||||
* @Date 2025/10/10 14:15
|
||||
* @Version 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WechatTemplateService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private WechatMpProperties wechatMpProperties;
|
||||
|
||||
@Autowired
|
||||
private TemplateMessageBuilder templateMessageBuilder;
|
||||
|
||||
@Autowired
|
||||
private OkHttpUtil okHttpUtil;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
|
||||
public String getAccessToken() {
|
||||
String url = String.format("%s?grant_type=client_credential&appid=%s&secret=%s",
|
||||
wechatMpProperties.getAccessTokenUrl(),
|
||||
wechatMpProperties.getAppId(),
|
||||
wechatMpProperties.getAppSecret());
|
||||
try {
|
||||
String result = okHttpUtil.doGet(url);
|
||||
log.info("获取access_token响应: {}", result);
|
||||
|
||||
if (StringUtils.isNotEmpty( result)){
|
||||
AccessTokenDTO responseDTO = objectMapper.readValue(result, AccessTokenDTO.class);
|
||||
return responseDTO.getAccess_token();
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
log.error("获取access_token失败", e);
|
||||
} catch (Exception e) {
|
||||
log.error("解析access_token响应失败", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public WechatUserInfoDTO getUserInfo(String openId, String lang) {
|
||||
String accessToken = getAccessToken();
|
||||
//默认中国
|
||||
lang = StringUtils.isEmpty(lang)?"zh_CN":lang;
|
||||
if (accessToken == null) {
|
||||
log.error("获取access_token失败");
|
||||
return null;
|
||||
}
|
||||
String url = String.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s",
|
||||
accessToken, openId);
|
||||
|
||||
if (lang != null && !lang.trim().isEmpty()) {
|
||||
url += "&lang=" + lang;
|
||||
}
|
||||
try {
|
||||
String result = okHttpUtil.doGet(url);
|
||||
log.debug("获取用户信息响应: {}", result);
|
||||
WechatUserInfoDTO userInfo = objectMapper.readValue(result, WechatUserInfoDTO.class);
|
||||
return userInfo;
|
||||
} catch (IOException e) {
|
||||
log.error("获取用户信息失败", e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("解析用户信息响应失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean sendNormalTemplate(String openId, WechatTemplateEnum template, Map<String, Object> data) {
|
||||
WechatTemplateMessageDTO messageDTO = templateMessageBuilder.buildNormalTemplate(openId, template, data);
|
||||
return sendTemplateMessage(messageDTO);
|
||||
}
|
||||
|
||||
public boolean sendMiniAppTemplate(String openId, WechatTemplateEnum template,
|
||||
Map<String, Object> data, String miniAppPagePath) {
|
||||
WechatTemplateMessageDTO messageDTO = templateMessageBuilder.buildMiniappTemplate(
|
||||
openId, template, data, miniAppPagePath);
|
||||
return sendTemplateMessage(messageDTO);
|
||||
}
|
||||
|
||||
public boolean sendMiniAppTemplateWithUrl(String openId, WechatTemplateEnum template,
|
||||
Map<String, Object> data, String miniAppPagePath, String backupUrl) {
|
||||
WechatTemplateMessageDTO messageDTO = templateMessageBuilder.buildMiniAppTemplateWithUrl(
|
||||
openId, template, data, miniAppPagePath, backupUrl);
|
||||
return sendTemplateMessage(messageDTO);
|
||||
}
|
||||
|
||||
private boolean sendTemplateMessage(WechatTemplateMessageDTO messageDTO) {
|
||||
String accessToken = getAccessToken();
|
||||
if (accessToken == null) {
|
||||
log.error("获取access_token失败,无法发送模板消息");
|
||||
return false;
|
||||
}
|
||||
|
||||
String url = String.format("%s?access_token=%s",
|
||||
wechatMpProperties.getSendTemplateMessageUrl(), accessToken);
|
||||
|
||||
log.info("发送模板消息: {}", messageDTO);
|
||||
try {
|
||||
String result = okHttpUtil.doPostJson(url, messageDTO);
|
||||
log.info("发送模板消息响应: {}", result);
|
||||
|
||||
return Boolean.TRUE;
|
||||
} catch (Exception e) {
|
||||
log.error("解析模板消息响应失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,11 @@ public class OpenApiValidateFilter implements Filter {
|
||||
private String coolAppKey;
|
||||
@Value("${cool.api.secret}")
|
||||
private String coolAppSecret;
|
||||
private static final Set<String> WHITELIST_URIS = new HashSet<>(Arrays.asList(
|
||||
"/zxjp/open/v1/statusRefresh",
|
||||
"/zxjp/open/v1/getStoreUser",
|
||||
"/zxjp/open/v1/callback"
|
||||
));
|
||||
private static final List<String> oldUrlMapping = new ArrayList<>(Arrays.asList(
|
||||
"/zxjp/open/v1/statusRefresh","/zxjp/open/v1/changePaymentStatus",
|
||||
"/zxjp/open/v1/getYlsToken", "/zxjp/open/v1/getStoreList",
|
||||
@@ -61,7 +66,7 @@ public class OpenApiValidateFilter implements Filter {
|
||||
}
|
||||
MDC.put(CommonConstants.REQUEST_ID, UUIDUtils.get32UUID());
|
||||
//statusRefresh 放开不需要验签
|
||||
if (uri.startsWith("/zxjp/open/v1/statusRefresh") || uri.startsWith("/zxjp/open/v1/getStoreUser")) {
|
||||
if (isWhitelistUri(uri)) {
|
||||
filterChain.doFilter(servletRequest, response);
|
||||
return;
|
||||
}
|
||||
@@ -174,6 +179,9 @@ public class OpenApiValidateFilter implements Filter {
|
||||
return OpenSignatureUtil.generateOldSign(params, coolAppSecret);
|
||||
}
|
||||
|
||||
private boolean isWhitelistUri(String uri) {
|
||||
return WHITELIST_URIS.stream().anyMatch(uri::startsWith);
|
||||
}
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.cool.store.context.PartnerUserHolder;
|
||||
import com.cool.store.dto.*;
|
||||
import com.cool.store.dto.store.StoreUserPositionDTO;
|
||||
import com.cool.store.dto.wx.MiniProgramFreeLoginDTO;
|
||||
import com.cool.store.handler.WeChatHandler;
|
||||
import com.cool.store.request.OpenApiStoreRequest;
|
||||
import com.cool.store.request.StoreCodeDTO;
|
||||
import com.cool.store.request.*;
|
||||
@@ -15,10 +16,12 @@ import com.cool.store.request.xgj.ReceiptCallBackRequest;
|
||||
import com.cool.store.response.ResponseResult;
|
||||
import com.cool.store.response.bigdata.ApiResponse;
|
||||
import com.cool.store.service.*;
|
||||
import com.cool.store.utils.poi.StringUtils;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -52,6 +55,8 @@ public class OpenApiController {
|
||||
MessageTemplateService messageTemplateService;
|
||||
@Resource
|
||||
WechatMiniAppService wechatMiniAppService;
|
||||
@Autowired
|
||||
WeChatHandler weChatHandler;
|
||||
|
||||
@PostMapping("/statusRefresh")
|
||||
public ApiResponse<Boolean> statusRefresh(@RequestBody StatusRefreshDTO statusRefreshDTO){
|
||||
@@ -185,4 +190,28 @@ public class OpenApiController {
|
||||
public ApiResponse<String> getTokenByMobile(@RequestBody @Validated MiniProgramFreeLoginDTO param) {
|
||||
return ApiResponse.success(wechatMiniAppService.getShortTermTokenByMobile(param));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/callback", produces = "application/xml;charset=UTF-8")
|
||||
@ApiOperation("callback")
|
||||
public String handleWechatMessage(
|
||||
@RequestParam("signature") String signature,
|
||||
@RequestParam("timestamp") String timestamp,
|
||||
@RequestParam("nonce") String nonce,
|
||||
@RequestParam(value = "echostr", required = false) String echostr,
|
||||
@RequestBody(required = false) String requestBody) {
|
||||
|
||||
log.info("url:{}","/open/v1/");
|
||||
log.info("callback_signature:{},timestamp:{},nonce:{}",signature,timestamp,nonce);
|
||||
log.info("requestBody:{}",requestBody);
|
||||
|
||||
if (StringUtils.isNotEmpty(requestBody)) {
|
||||
try {
|
||||
return weChatHandler.processMessage(weChatHandler.parseXmlToMap(requestBody));
|
||||
} catch (Exception e) {
|
||||
log.info("回调处理失败 e:{}",e);
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
return echostr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,15 @@ import com.cool.store.dto.FoodTokenDTO;
|
||||
import com.cool.store.dto.GetAccessTokenDTO;
|
||||
import com.cool.store.dto.HqtTokenDTO;
|
||||
import com.cool.store.dto.ModifyPasswordDTO;
|
||||
import com.cool.store.dto.wechat.CallbackMessageDTO;
|
||||
import com.cool.store.dto.wechat.WechatTemplateMessageDTO;
|
||||
import com.cool.store.entity.*;
|
||||
import com.cool.store.enums.DownSystemTypeEnum;
|
||||
import com.cool.store.enums.MessageEnum;
|
||||
import com.cool.store.enums.SMSMsgEnum;
|
||||
import com.cool.store.enums.point.ShopSubStageStatusEnum;
|
||||
import com.cool.store.enums.wechat.WechatTemplateEnum;
|
||||
import com.cool.store.handler.WeChatHandler;
|
||||
import com.cool.store.job.XxlJobHandler;
|
||||
import com.cool.store.mapper.FranchiseFeeMapper;
|
||||
import com.cool.store.mapper.LineInfoMapper;
|
||||
@@ -36,10 +40,12 @@ import com.cool.store.service.*;
|
||||
import com.cool.store.service.impl.CommonService;
|
||||
import com.cool.store.service.impl.OrderSysInfoServiceImpl;
|
||||
import com.cool.store.service.impl.UserAuthMappingServiceImpl;
|
||||
import com.cool.store.service.wechat.WechatTemplateService;
|
||||
import com.cool.store.utils.CoolDateUtils;
|
||||
import com.cool.store.utils.RedisConstantUtil;
|
||||
import com.cool.store.utils.RedisUtilPool;
|
||||
import com.cool.store.utils.poi.StringUtils;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -92,14 +98,16 @@ public class PCTestController {
|
||||
private HttpRestTemplateService httpRestTemplateService;
|
||||
@Resource
|
||||
FranchiseFeeMapper franchiseFeeMapper;
|
||||
|
||||
@Resource
|
||||
WechatTemplateService wechatTemplateService;
|
||||
@Resource
|
||||
ShopInfoMapper shopInfoMapper;
|
||||
@Resource
|
||||
LinePayService linePayService;
|
||||
@Resource
|
||||
LinePayDAO linePayDAO;
|
||||
|
||||
@Autowired
|
||||
WeChatHandler weChatHandler;
|
||||
@GetMapping("/syncStore")
|
||||
public ResponseResult<Boolean> syncStore(@RequestParam("shopId")Long shopId){
|
||||
syncMainSysServer.syncStore(shopId);
|
||||
@@ -556,4 +564,46 @@ public class PCTestController {
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping(value = "/testWxNotice", produces = "application/xml;charset=UTF-8")
|
||||
@ApiOperation("testWxNotice")
|
||||
public String handleWechatMessage(
|
||||
@RequestParam("signature") String signature,
|
||||
@RequestParam("timestamp") String timestamp,
|
||||
@RequestParam("nonce") String nonce,
|
||||
@RequestParam(value = "echostr", required = false) String echostr,
|
||||
@RequestBody(required = false) String requestBody) {
|
||||
|
||||
System.out.println("收到微信消息:");
|
||||
System.out.println("signature: " + signature);
|
||||
System.out.println("timestamp: " + timestamp);
|
||||
System.out.println("nonce: " + nonce);
|
||||
System.out.println("echostr: " + echostr);
|
||||
System.out.println("requestBody: " + requestBody);
|
||||
|
||||
if (StringUtils.isNotEmpty(requestBody)) {
|
||||
try {
|
||||
return weChatHandler.processMessage(weChatHandler.parseXmlToMap(requestBody));
|
||||
} catch (Exception e) {
|
||||
log.info("回调处理失败 e:{}",e.getMessage());
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
|
||||
@ApiOperation("测试小程序模板消息")
|
||||
@PostMapping("/testMiniAppTemplate")
|
||||
public ApiResponse<Boolean> testMiniAppTemplate() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("character_string2", "ceshi002");
|
||||
data.put("thing10", "测试通知功能");
|
||||
data.put("time14", "2025-10-01 12:00:00");
|
||||
data.put("thing25", "正新管理有限公司");
|
||||
data.put("thing60", "上海市-松江区");
|
||||
wechatTemplateService.sendNormalTemplate("o9_unvpJy1SGdnkeun7igRBSLuB0", WechatTemplateEnum.QUESTION_NOTICE, data);
|
||||
return ApiResponse.success(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -137,3 +137,8 @@ hqt.token.grant_type=client_credentials
|
||||
hqt.token.client.id=WrPffdGpcWkcPsbN
|
||||
hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
|
||||
|
||||
#fuwuhao appId
|
||||
wechat.mp.appId=wx4a18ef8bb41aa55c
|
||||
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
|
||||
#xiaochengxu appid
|
||||
wechat.miniapp.appId=wxd77a2761c1911ee1
|
||||
@@ -139,3 +139,9 @@ hqt.token.username=18161486722
|
||||
hqt.token.grant_type=client_credentials
|
||||
hqt.token.client.id=WrPffdGpcWkcPsbN
|
||||
hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
|
||||
|
||||
#fuwuhao appId
|
||||
wechat.mp.appId=wx4a18ef8bb41aa55c
|
||||
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
|
||||
#xiaochengxu appid
|
||||
wechat.miniapp.appId=wxd77a2761c1911ee1
|
||||
@@ -145,3 +145,12 @@ hqt.token.grant_type=client_credentials
|
||||
hqt.token.client.id=WrPffdGpcWkcPsbN
|
||||
hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD
|
||||
|
||||
|
||||
#fuwuhao appId
|
||||
wechat.mp.appId=wx4a18ef8bb41aa55c
|
||||
wechat.mp.appSecret=793904b58f4ecdead3bbe4312c5f5c45
|
||||
#xiaochengxu appid
|
||||
wechat.miniapp.appId=wxd77a2761c1911ee1
|
||||
|
||||
zx.iot.appId=p-ts3PhNyf
|
||||
zx.iot.appSecret=X4cVmfxM+
|
||||
|
||||
Reference in New Issue
Block a user