Commit 51c8d319 by fanjiaxin

代码初始化

parent 39bd3699
Pipeline #71427 passed with stages
in 1 minute 29 seconds
-- 推客信息表
CREATE TABLE SHARER_INFO
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_id` varchar(64) NOT NULL DEFAULT '' COMMENT '用户开放ID',
`sharer_appid` varchar(64) NOT NULL DEFAULT '' COMMENT '推客应用ID',
`bind_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '绑定时间',
`commission_ratio` decimal(5, 2) NOT NULL DEFAULT 0.00 COMMENT '佣金比例',
`commission_type` varchar(32) NOT NULL DEFAULT '' COMMENT '佣金类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX idx_open_id (open_id),
INDEX idx_sharer_appid (sharer_appid)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='推客信息表';
-- 推客商品分佣表
CREATE TABLE SHARER_PRODUCT_COMMISSION
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_id` varchar(64) NOT NULL DEFAULT '' COMMENT '用户开放ID',
`sharer_appid` varchar(64) NOT NULL DEFAULT '' COMMENT '推客应用ID',
`product_id` varchar(64) NOT NULL DEFAULT '' COMMENT '商品ID',
`commission_ratio` decimal(5, 2) NOT NULL DEFAULT 0.00 COMMENT '佣金比例',
`commission_type` varchar(32) NOT NULL DEFAULT '' COMMENT '佣金类型',
`unset` int(11) NOT NULL DEFAULT 1 COMMENT '是否未配置,1:未配置,取默认佣金比例,0:已配置',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX idx_open_id (open_id),
INDEX idx_sharer_appid (sharer_appid)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='推客商品分佣表';
-- 推客商品分佣配置操作记录表
CREATE TABLE SHARER_PRODUCT_COMMISSION_RECORD
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`open_id` varchar(64) NOT NULL DEFAULT '' COMMENT '用户开放ID',
`sharer_appid` varchar(64) NOT NULL DEFAULT '' COMMENT '推客应用ID',
`product_id` varchar(64) NOT NULL DEFAULT '' COMMENT '商品ID',
`opt_type` varchar(32) NOT NULL DEFAULT '' COMMENT '操作类型',
`opt_info` varchar(256) NOT NULL DEFAULT '' COMMENT '操作信息,记录操作前比例 & 操作后比例',
`opt_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
`opt_status` varchar(1) NOT NULL DEFAULT '1' COMMENT '状态,1 操作成功 2 操作失败',
`ext_info` varchar(1024) NOT NULL DEFAULT '' COMMENT '拓展信息',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
INDEX idx_open_id (open_id),
INDEX idx_sharer_appid (sharer_appid),
INDEX idx_product_id (product_id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='推客商品分佣配置操作记录表';
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.assembly;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.netease.yanxuan.missa.client.annotation.EnableMissaClients;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import com.netease.yanxuan.missa.client.annotation.EnableMissaClients;
/**
* 项目启动类
*/
@ComponentScan(basePackages = "com.netease.yanxuan.wx.store.sharer")
@EnableApolloConfig
@EnableMissaClients(basePackages = "com.netease.yanxuan.wx.store.sharer")
@MapperScan(basePackages = "com.netease.yanxuan.wx.store.sharer.dal.mapper")
@MapperScan(basePackages = "com.netease.yanxuan.wx.store.sharer.dal")
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
......
package com.netease.yanxuan.wx.store.sharer.biz.config;
import com.alibaba.fastjson.JSON;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import com.ctrip.framework.apollo.spring.annotation.EnableAutoUpdateApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ValueMapping;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserContextHolder;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserInfo;
import com.netease.yanxuan.wx.store.sharer.biz.meta.enums.CommissionChangeOptTypeEnum;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo.ProductCommissionSharerBO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo.ProductCommissionSharerProductBO;
import com.netease.yanxuan.wx.store.sharer.dal.mapper.SharerProductCommissionRecordMapper;
import com.netease.yanxuan.wx.store.sharer.dal.meta.model.po.SharerProductCommissionRecord;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Description 商品分佣金配置
* @Author fanjiaxin
* @Date 2025/3/10 17:26
*/
@Slf4j
@Data
@Component
@RequiredArgsConstructor
@EnableAutoUpdateApolloConfig
public class ProductCommissionConfig {
private final SharerProductCommissionRecordMapper sharerProdutcCommissionRecordMapper;
/**
* 默认分佣比例
*/
@Value("${application.commissionratio.default:10}")
private BigDecimal defaultCommissionRatio;
/**
* 用户分佣比例
*/
@ValueMapping("${application.commissionratio.sharer:}")
private ProductCommissionSharerBO productCommissionSharerBO;
/**
* 用户商品分佣比例
*/
@ValueMapping("${application.commissionratio.sharerproduct:}")
private ProductCommissionSharerProductBO productCommissionSharerProductBO;
/**
* 监听
*/
@ApolloConfigChangeListener("sv-channel.config")
public void onChange(ConfigChangeEvent changeEvent) {
log.info("ProductCommissionConfig:onChange, changeEvent = {}", JSON.toJSONString(changeEvent));
// 默认分佣比例
String defaultCommissionratio = "application.commissionratio.default";
if (changeEvent.isChanged(defaultCommissionratio)) {
ConfigChange change = changeEvent.getChange(defaultCommissionratio);
defaultCommissionratioChange(change);
}
// 用户分佣比例
String sharerCommissionratio = "application.commissionratio.sharer";
if (changeEvent.isChanged(sharerCommissionratio)) {
ConfigChange change = changeEvent.getChange(sharerCommissionratio);
sharerCommissionratioChange(change);
}
// 用户商品分佣比例
String sharerproductCommissionratio = "application.commissionratio.sharerproduct";
if (changeEvent.isChanged(sharerproductCommissionratio)) {
ConfigChange change = changeEvent.getChange(sharerproductCommissionratio);
sharerproductCommissionratioChange(change);
}
}
/**
* 默认分佣比例变更
*/
private void defaultCommissionratioChange(ConfigChange change){
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
log.error("用户登录状态过期");
return;
}
SharerProductCommissionRecord record = new SharerProductCommissionRecord();
record.setOpenId(loginUserInfo.getOpenId());
record.setSharerAppid(loginUserInfo.getSharerAppId());
record.setProductId("");
record.setOptType(CommissionChangeOptTypeEnum.DEFAULT_CHANGE.getCode());
record.setOptInfo(change.getOldValue() + "&" + change.getNewValue());
record.setOptTime(new Date());
record.setOptStatus("1");
record.setExtInfo("");
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
sharerProdutcCommissionRecordMapper.insert(record);
}
/**
* 用户分佣比例变更
*/
private void sharerCommissionratioChange(ConfigChange change){
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
log.error("用户登录状态过期");
return;
}
SharerProductCommissionRecord record = new SharerProductCommissionRecord();
record.setOpenId(loginUserInfo.getOpenId());
record.setSharerAppid(loginUserInfo.getSharerAppId());
record.setProductId("");
record.setOptType(CommissionChangeOptTypeEnum.SET.getCode());
record.setOptInfo(change.getOldValue() + "&" + change.getNewValue());
record.setOptTime(new Date());
record.setOptStatus("1");
record.setExtInfo("");
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
sharerProdutcCommissionRecordMapper.insert(record);
}
/**
* 用户商品分佣比例变更
*/
private void sharerproductCommissionratioChange(ConfigChange change){
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
log.error("用户登录状态过期");
return;
}
SharerProductCommissionRecord record = new SharerProductCommissionRecord();
record.setOpenId(loginUserInfo.getOpenId());
record.setSharerAppid(loginUserInfo.getSharerAppId());
record.setProductId("");
record.setOptType(CommissionChangeOptTypeEnum.SET.getCode());
record.setOptInfo(change.getOldValue() + "&" + change.getNewValue());
record.setOptTime(new Date());
record.setOptStatus("1");
record.setExtInfo("");
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
sharerProdutcCommissionRecordMapper.insert(record);
}
}
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.biz.config;
import com.netease.yanxuan.wx.store.sharer.biz.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.Collections;
/**
* @Description 配置
* @Author fanjiaxin
* @Date 2025/3/10 10:54
*/
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
private static final String[] EXCLUDE_URLS = {"/open/**", "/product/page/list", "/user/login"};
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns(getIncludePathPatterns())
.excludePathPatterns(getExcludePathPatterns());
}
/**
* 需要用户和服务认证判断的路径
*/
private ArrayList<String> getIncludePathPatterns() {
ArrayList<String> list = new ArrayList<>();
String[] urls = {"/**"};
Collections.addAll(list, urls);
return list;
}
/**
* 白名单
*/
private ArrayList<String> getExcludePathPatterns() {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, EXCLUDE_URLS);
return list;
}
}
package com.netease.yanxuan.wx.store.sharer.biz.core;
/**
* @Description 授权上下文
* @Author fanjiaxin
* @Date 2025/3/10 11:10
*/
public class LoginUserContextHolder {
public static ThreadLocal<LoginUserInfo> HOLDER = new ThreadLocal<>();
public static void set(LoginUserInfo loginUserInfo) {
HOLDER.set(loginUserInfo);
}
public static LoginUserInfo get() {
return HOLDER.get();
}
public static void remove() {
HOLDER.remove();
}
}
package com.netease.yanxuan.wx.store.sharer.biz.core;
import com.alibaba.fastjson.JSON;
import com.netease.yanxuan.wx.store.sharer.common.constant.CoreConstant;
import com.netease.yanxuan.wx.store.sharer.common.handler.RedisClient;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
/**
* @Description 微信认证信息上下文持有器
* @Author fanjiaxin
* @Date 2025/2/24 11:53
*/
@Service
@RequiredArgsConstructor
public class LoginUserHelper {
private final RedisClient redisClient;
/**
* 获取用户认证信息
*/
public LoginUserInfo getLoginUserInfo(String token) {
String loginUserInfoJson = redisClient.getStr(CoreConstant.REDIS_TOKEN_USER_KEY + token);
if(StringUtils.isNotBlank(loginUserInfoJson)){
return JSON.parseObject(loginUserInfoJson, LoginUserInfo.class);
}
return null;
}
/**
* 保存用户认证信息
*/
public void setLoginUserInfo(String token, LoginUserInfo loginUserInfo) {
LoginUserContextHolder.set(loginUserInfo);
redisClient.setStr(CoreConstant.REDIS_TOKEN_USER_KEY + token,
JSON.toJSONString(loginUserInfo), CoreConstant.REDIS_TOKEN_EXPIRE_SECONDS);
}
}
package com.netease.yanxuan.wx.store.sharer.biz.core;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 认证信息
* @Author fanjiaxin
* @Date 2025/3/10 11:04
*/
@Data
public class LoginUserInfo implements Serializable{
private static final long serialVersionUID = 3375070827863778256L;
/**
* 推客认证token
*/
private String accessToken;
/**
* 推客在小程序中的 openid
*/
private String openId;
/**
* 推客在微信电商平台注册的身份标识
*/
private String sharerAppId;
}
package com.netease.yanxuan.wx.store.sharer.biz.interceptor;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserInfo;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserContextHolder;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserHelper;
import com.netease.yanxuan.wx.store.sharer.common.constant.CoreConstant;
import com.netease.yanxuan.wx.store.sharer.common.exception.NoAuthException;
import com.netease.yanxuan.wx.store.sharer.common.handler.RedisClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description 鉴权拦截器
* @Author fanjiaxin
* @Date 2025/3/10 10:20
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final RedisClient redisClient;
private final LoginUserHelper jwtHelper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader(CoreConstant.AUTH_HEADER_TOKEN_KEY);
if (StringUtils.isEmpty(token)) {
throw new NoAuthException("用户认证失败");
}
LoginUserInfo loginUserInfo = jwtHelper.getLoginUserInfo(token);
if (null == loginUserInfo) {
log.info("用户登录状态过期,token缓存失效,path:{}", request.getServletPath());
throw new NoAuthException("用户登录状态过期");
}
// 刷新缓存
jwtHelper.setLoginUserInfo(token, loginUserInfo);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
LoginUserContextHolder.remove();
}
/**
* 刷新token缓存过期时间
*/
private void resetCacheExpire(String token, String userInfo) {
redisClient.setStr(CoreConstant.REDIS_TOKEN_USER_KEY + token, userInfo, CoreConstant.REDIS_TOKEN_EXPIRE_SECONDS);
}
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @Description 操作记录类型
* @Author fanjiaxin
* @Date 2025/3/3 13:58
*/
@Getter
@RequiredArgsConstructor
public enum CommissionChangeOptTypeEnum {
SET("1", "设置分佣比例"),
UNSET("2", "取消设置分佣比例"),
DEFAULT_CHANGE("3", "默认分佣比例变更");
private final String code;
private final String desc;
}
\ No newline at end of file
/**
* 枚举对象
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.biz.meta.enums;
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @Description 用户授权-业务对象
* @Author fanjiaxin
* @Date 2025/3/9 13:44
*/
@Data
public class LoginBO {
/**
* 登录时获取的code
*/
@NotBlank(message = "授权码不能为空")
private String code;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* @Description 用户分佣比例-业务对象
* @Author fanjiaxin
* @Date 2025/3/9 13:44
*/
@Data
public class ProductCommissionSharerBO implements Serializable {
private static final long serialVersionUID = -1482990976577986435L;
/**
* 推客ID
*/
private String sharerId;
/**
* 分佣比例
*/
private BigDecimal CommissionRatio;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* @Description 用户商品分佣比例-业务对象
* @Author fanjiaxin
* @Date 2025/3/9 13:44
*/
@Data
public class ProductCommissionSharerProductBO implements Serializable {
private static final long serialVersionUID = -1482990976577986435L;
/**
* 推客ID
*/
private String sharerId;
/**
* 商品ID
*/
private Integer productId;
/**
* 分佣比例
*/
private BigDecimal CommissionRatio;
}
/**
* bo对象
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo;
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @Description 商品列表
* @Author fanjiaxin
* @Date 2025/3/11 11:05
*/
@Data
public class ProductListVO {
/**
* 商品标题
*/
private String title;
/**
* 商品主图
*/
private String headImg;
/**
* 商品价格
*/
private BigDecimal price;
/**
* 商品佣金
*/
private BigDecimal commission;
/**
* 商品店铺ID
*/
private String shopAppid;
/**
* 商品ID
*/
private Integer productId;
/**
* 商品推广链接
*/
private String productUrl;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo;
import lombok.Data;
/**
* @Description 用户信息
* @Author fanjiaxin
* @Date 2025/3/11 10:59
*/
@Data
public class SharerInfoVO {
/**
* 推客在小程序中的 openid
*/
private String openId;
/**
* 推客在微信电商平台注册的身份标识
*/
private String sharerAppId;
/**
* 和机构的绑定状态
* 0:未绑定 1:已绑定
*/
private Integer bindStatus;
/**
* 当前推客的注册状态
* 0:未注册
* 1:注册中,还未完成
* 2:已完成注册
* 3:用户未支付实名,需要把微信先支付实名才能继续注册
*/
private Integer registerStatus;
/**
* register_status等于 0 或者 1时
* 调用注册流程时,openBusinessView需要的businessType
*/
private String registerBusinessType;
/**
* 注册时需要的queryString参数
*/
private String registerQueryString;
/**
* bind_status等于0时,调用绑定流程时,openBusinessView需要的businessType
*/
private String bindBusinessType;
/**
* 绑定时需要的queryString参数
*/
private String bindQueryString;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
/**
* @Description 用户分佣比例信息
* @Author fanjiaxin
* @Date 2025/3/11 10:59
*/
@Data
@Builder
public class UserCommissionRatioVO {
/**
* 默认分佣比例
*/
private BigDecimal commissionRatio;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo;
import lombok.Builder;
import lombok.Data;
/**
* @Description 用户token信息
* @Author fanjiaxin
* @Date 2025/3/11 10:59
*/
@Data
@Builder
public class UserTokenVO {
/**
* token
*/
private String token;
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.page;
import lombok.Data;
import java.io.Serializable;
/**
* 分页查询实体类
*/
@Data
public class PageQuery implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 页数
*/
private Integer pageSize;
/**
* 翻页的上下文,用于请求下一页
*/
private String nextKey;
public Integer getPageSize() {
return null == pageSize || pageSize <= 0 ? 10 : pageSize;
}
}
package com.netease.yanxuan.wx.store.sharer.biz.meta.page;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页数据对象
*/
@Data
public class PageVO<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 是否有下一页
*/
private Boolean hasMore;
/**
* 翻页的上下文,用于请求下一页
*/
private String nextKey;
/**
* 列表数据
*/
private List<T> list;
}
/**
* <p>
* 核心业务逻辑,接口定义以Service结尾,例如:AbcService。
* 接口定义可以根据业务场景,定义业务分包。对应实现放到[impl]子包中,
* 接口实现以ServiceImpl结尾,例如:AbcServiceImpl
* </p>
*
* @author hzwangliyuan.
* @date 2019/11/28.
*/
package com.netease.yanxuan.wx.store.sharer.biz;
package com.netease.yanxuan.wx.store.sharer.biz.service;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.ProductListVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.page.PageQuery;
import com.netease.yanxuan.wx.store.sharer.biz.meta.page.PageVO;
/**
* @Description 商品-业务层
* @Author fanjiaxin
* @Date 2025/3/10 12:28
*/
public interface IProductService {
/**
* 获取商品分页列表
*/
PageVO<ProductListVO> getPageList(PageQuery pageQuery, String keyword);
}
package com.netease.yanxuan.wx.store.sharer.biz.service;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo.LoginBO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.UserCommissionRatioVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.UserTokenVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.SharerInfoVO;
/**
* @Description 用户-业务层
* @Author fanjiaxin
* @Date 2025/3/10 12:28
*/
public interface IUserService {
/**
* 登录
*/
UserTokenVO login(LoginBO bo);
/**
* 刷新用户信息
*/
SharerInfoVO refreshUserInfo();
/**
* 获取用户信息
*/
SharerInfoVO getUserInfo();
/**
* 查询平台默认的分佣比例
*/
UserCommissionRatioVO getCommissionRatioDefault();
}
package com.netease.yanxuan.wx.store.sharer.biz.service.impl;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserContextHolder;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserInfo;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.ProductListVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.page.PageQuery;
import com.netease.yanxuan.wx.store.sharer.biz.meta.page.PageVO;
import com.netease.yanxuan.wx.store.sharer.biz.service.IProductService;
import com.netease.yanxuan.wx.store.sharer.common.exception.NoAuthException;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatPromoteProductDetailRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatPromoteProductLinkRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatPromoteProductListRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatSharerListRequest;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @Description 商品-业务实现类
* @Author fanjiaxin
* @Date 2025/3/10 12:31
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ProductServiceImpl implements IProductService {
private final WeChatPromoteProductListRequest weChatPromoteProductListRequest;
private final WeChatPromoteProductDetailRequest weChatPromoteProductDetailRequest;
private final WeChatPromoteProductLinkRequest weChatPromoteProductLinkRequest;
private final WeChatSharerListRequest weChatSharerListRequest;
@Override
public PageVO<ProductListVO> getPageList(PageQuery pageQuery, String keyword) {
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
throw new NoAuthException("用户登录状态过期");
}
PageVO<ProductListVO> pageVO = new PageVO<>();
WeChatPromoteProductListVO productListVO = weChatPromoteProductListRequest.handle(keyword,
pageQuery.getNextKey(), pageQuery.getPageSize());
pageVO.setHasMore(!CollectionUtils.isEmpty(productListVO.getProduct_list())
&& productListVO.getProduct_list().size() >= pageQuery.getPageSize());
pageVO.setNextKey(productListVO.getNext_key());
// 商品列表
List<ProductListVO> resultList = Optional.ofNullable(productListVO.getProduct_list()).orElseGet(ArrayList::new)
.stream()
.map(item -> {
WeChatPromoteProductDetailVO detailVO = weChatPromoteProductDetailRequest.handle(item.getShop_appid(),
item.getProduct_id());
if(null == detailVO){
return null;
}
WeChatPromoteProductDetailVO.WeChatPromoteProductVO product = detailVO.getProduct();
if(null == product){
return null;
}
WeChatPromoteProductDetailVO.WeChatPromoteProductVO.WeChatPromoteProductInfoVO productInfo = product.getProduct_info();
if(null == productInfo){
return null;
}
ProductListVO listVO = new ProductListVO();
listVO.setTitle(productInfo.getTitle());
listVO.setHeadImg(CollectionUtils.isEmpty(productInfo.getHead_imgs()) ? null : productInfo.getHead_imgs().get(0));
// 取参数SkuInfo中sale_price的最小
BigDecimal price = Optional.ofNullable(productInfo.getSkus()).orElseGet(ArrayList::new)
.stream()
.map(WeChatPromoteProductDetailVO.WeChatPromoteProductVO.WeChatPromoteProductInfoVO.WeChatPromoteProductSkuInfoVO::getSale_price)
.min(BigDecimal::compareTo).orElse(null);
listVO.setPrice(price);
// 推客分佣比例
BigDecimal commissionRatio = null;
WeChatSharerListVO sharerListVO = weChatSharerListRequest.handle(loginUserInfo.getOpenId(), "", 1);
if (null != sharerListVO && !CollectionUtils.isEmpty(sharerListVO.getSharer_info_list())) {
WeChatSharerInfoVO sharerInfoVO = sharerListVO.getSharer_info_list().get(0);
if (null != sharerInfoVO && null != sharerInfoVO.getCommission_ratio()) {
commissionRatio = sharerInfoVO.getCommission_ratio();
}
}
WeChatPromoteProductDetailVO.WeChatPromoteProductVO.WeChatPromoteProductCommissionInfoVO commissionInfo = product.getCommission_info();
// 服务费率
Integer serviceRatio = null != commissionInfo ? commissionInfo.getService_ratio() : null;
// 佣金=商品价格*服务费率*推客分佣比例,四舍五入保留小数点后两位
BigDecimal commission = null;
if(null != price && null != commissionRatio && null != serviceRatio){
commission = price.multiply(new BigDecimal(serviceRatio.toString()))
.multiply(commissionRatio).setScale(2, RoundingMode.HALF_UP);
}
listVO.setCommission(commission);
listVO.setShopAppid(item.getShop_appid());
listVO.setProductId(item.getProduct_id());
// 获取推广链接
WeChatPromoteProductLinkVO promoteProductLinkVO = weChatPromoteProductLinkRequest.handle(item.getShop_appid(), item.getProduct_id());
listVO.setProductUrl(null != promoteProductLinkVO ? promoteProductLinkVO.getShort_link() : null);
return listVO;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
pageVO.setList(resultList);
return pageVO;
}
}
package com.netease.yanxuan.wx.store.sharer.biz.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.netease.yanxuan.wx.store.sharer.biz.config.ProductCommissionConfig;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserContextHolder;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserHelper;
import com.netease.yanxuan.wx.store.sharer.biz.core.LoginUserInfo;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.bo.LoginBO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.SharerInfoVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.UserCommissionRatioVO;
import com.netease.yanxuan.wx.store.sharer.biz.meta.model.vo.UserTokenVO;
import com.netease.yanxuan.wx.store.sharer.biz.service.IUserService;
import com.netease.yanxuan.wx.store.sharer.common.exception.NoAuthException;
import com.netease.yanxuan.wx.store.sharer.dal.mapper.SharerInfoMapper;
import com.netease.yanxuan.wx.store.sharer.dal.meta.model.po.SharerInfo;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatSharerListRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatSharerRegisterBindRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatUserInfoRequest;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatSharerInfoVO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatSharerListVO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatSharerRegisterBindVO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatUserInfoVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.Date;
/**
* @Description 用户-业务实现类
* @Author fanjiaxin
* @Date 2025/3/10 12:31
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class UserServiceImpl implements IUserService {
private final SharerInfoMapper sharerInfoMapper;
private final ProductCommissionConfig productCommissionConfig;
private final LoginUserHelper jwtHelper;
private final WeChatUserInfoRequest weChatUserRequest;
private final WeChatSharerRegisterBindRequest weChatSharerRegisterBindRequest;
private final WeChatSharerListRequest weChatSharerListRequest;
@Override
public UserTokenVO login(LoginBO bo) {
Date now = new Date();
// 获取用户信息
WeChatUserInfoVO userInfoVO = weChatUserRequest.handle(bo.getCode());
// 获取本地用户信息
LambdaQueryWrapper<SharerInfo> sharerInfoLqw = Wrappers.lambdaQuery();
sharerInfoLqw.eq(SharerInfo::getOpenId, userInfoVO.getOpenid());
sharerInfoLqw.last("LIMIT 1");
SharerInfo sharerInfo = sharerInfoMapper.selectOne(sharerInfoLqw);
if (null == sharerInfo) {
sharerInfo = new SharerInfo();
sharerInfo.setOpenId(userInfoVO.getOpenid());
// 默认佣金比例
sharerInfo.setCommissionRatio(new BigDecimal(10));
sharerInfo.setCommissionType("1");
sharerInfo.setCreateTime(now);
sharerInfo.setUpdateTime(now);
sharerInfoMapper.insert(sharerInfo);
}
// 生成token
String token = StpUtil.getTokenValue();
LoginUserInfo loginUserInfo = new LoginUserInfo();
loginUserInfo.setAccessToken(token);
loginUserInfo.setOpenId(sharerInfo.getOpenId());
loginUserInfo.setSharerAppId(null);
jwtHelper.setLoginUserInfo(token, loginUserInfo);
return UserTokenVO.builder().token(token).build();
}
@Override
public SharerInfoVO refreshUserInfo() {
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
throw new NoAuthException("用户登录状态过期");
}
SharerInfoVO result = getSharerRegisterBindInfo(loginUserInfo.getOpenId());
// 获取本地用户信息
LambdaQueryWrapper<SharerInfo> sharerInfoLqw = Wrappers.lambdaQuery();
sharerInfoLqw.eq(SharerInfo::getOpenId, loginUserInfo.getOpenId());
sharerInfoLqw.last("LIMIT 1");
SharerInfo sharerInfo = sharerInfoMapper.selectOne(sharerInfoLqw);
Date now = new Date();
// 初始化保存
if (null == sharerInfo) {
sharerInfo = new SharerInfo();
sharerInfo.setOpenId(loginUserInfo.getOpenId());
// 默认佣金比例
sharerInfo.setCommissionRatio(new BigDecimal(10));
sharerInfo.setCommissionType("1");
sharerInfo.setCreateTime(now);
sharerInfo.setUpdateTime(now);
sharerInfoMapper.insert(sharerInfo);
}
// 已经绑定
if (StringUtils.isNotBlank(sharerInfo.getSharerAppid())) {
result.setSharerAppId(sharerInfo.getSharerAppid());
}
WeChatSharerListVO sharerListVO = weChatSharerListRequest.handle(loginUserInfo.getOpenId(), "", 1);
if (null != sharerListVO && !CollectionUtils.isEmpty(sharerListVO.getSharer_info_list())) {
WeChatSharerInfoVO sharerInfoVO = sharerListVO.getSharer_info_list().get(0);
if (null != sharerInfoVO && StringUtils.isNotBlank(sharerInfoVO.getSharer_appid())) {
result.setSharerAppId(sharerInfoVO.getSharer_appid());
sharerInfo.setSharerAppid(sharerInfoVO.getSharer_appid());
sharerInfo.setBindTime(new Date(sharerInfoVO.getBind_time()));
sharerInfo.setCommissionRatio(new BigDecimal(10));
sharerInfo.setCommissionType("1");
sharerInfo.setUpdateTime(now);
sharerInfoMapper.updateById(sharerInfo);
}
}
return result;
}
@Override
public SharerInfoVO getUserInfo() {
LoginUserInfo loginUserInfo = LoginUserContextHolder.get();
if (null == loginUserInfo) {
throw new NoAuthException("用户登录状态过期");
}
SharerInfoVO result = getSharerRegisterBindInfo(loginUserInfo.getOpenId());
// 获取本地用户信息
LambdaQueryWrapper<SharerInfo> sharerInfoLqw = Wrappers.lambdaQuery();
sharerInfoLqw.eq(SharerInfo::getOpenId, loginUserInfo.getOpenId());
sharerInfoLqw.last("LIMIT 1");
SharerInfo sharerInfo = sharerInfoMapper.selectOne(sharerInfoLqw);
// 已经绑定
if (null != sharerInfo && StringUtils.isNotBlank(sharerInfo.getSharerAppid())) {
result.setSharerAppId(sharerInfo.getSharerAppid());
}
return result;
}
@Override
public UserCommissionRatioVO getCommissionRatioDefault() {
BigDecimal defaultCommissionRatio = productCommissionConfig.getDefaultCommissionRatio();
return UserCommissionRatioVO.builder().commissionRatio(defaultCommissionRatio).build();
}
private SharerInfoVO getSharerRegisterBindInfo(String openId) {
// 获取推客注册与绑定信息
WeChatSharerRegisterBindVO sharerRegisterBindVO = weChatSharerRegisterBindRequest.handle(openId);
SharerInfoVO result = new SharerInfoVO();
result.setOpenId(openId);
result.setBindStatus(sharerRegisterBindVO.getBind_status());
result.setRegisterStatus(sharerRegisterBindVO.getRegister_status());
result.setRegisterBusinessType(sharerRegisterBindVO.getRegister_business_type());
result.setRegisterQueryString(sharerRegisterBindVO.getRegister_query_string());
result.setBindBusinessType(sharerRegisterBindVO.getBind_business_type());
result.setBindQueryString(sharerRegisterBindVO.getBind_query_string());
return result;
}
}
/**
* 服务实现
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.biz.service.impl;
/**
* 服务接口
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.biz.service;
dev.meta=http://dev.yx.localhost:8550/proxy/dev.apolloy-configservice.service.mailsaas
test.meta=http://127.0.0.1:8550/proxy/test.apolloy-configservice.service.mailsaas/
release.meta=http://127.0.0.1:8550/proxy/release.apolloy-configservice.service.mailsaas/
pressure.meta=http://10.200.168.231:8080
regression.meta=http://127.0.0.1:8550/proxy/regression.apolloy-configservice.service.mailsaas/
online.meta=http://127.0.0.1:8550/proxy/online.apolloy-configservice.service.mailsaas/
\ No newline at end of file
/**
* dto对象
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.client.meta.model.dto;
/**
* 本层的定义意在实现本系统的能力暴露,以便于依赖方可以通过这个jar包直接访问我们web层的接口
*
* @author hzwangliyuan.
* @date 2019/11/28.
*/
package com.netease.yanxuan.wx.store.sharer.client;
package com.netease.yanxuan.wx.store.sharer.common.config;
import lombok.Data;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplateProperties getRestTemplateProperties() {
return new RestTemplateProperties();
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(httpRequestFactory());
return restTemplate;
}
/**
* httpclient 实现的ClientHttpRequestFactory
*/
@Bean
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient(getRestTemplateProperties()));
}
/**
* 使用连接池的 httpclient
*/
@Bean
public HttpClient httpClient(RestTemplateProperties restTemplateProperties) {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
// 最大链接数
connectionManager.setMaxTotal(restTemplateProperties.getMaxTotal());
// 同路由并发数20
connectionManager.setDefaultMaxPerRoute(restTemplateProperties.getMaxPerRoute());
// 增加空闲连接校验间隔‌
connectionManager.setValidateAfterInactivity(restTemplateProperties.getValidateAfterInactivity());
RequestConfig requestConfig = RequestConfig.custom()
// 读超时
.setSocketTimeout(restTemplateProperties.getReadTimeout())
// 链接超时
.setConnectTimeout(restTemplateProperties.getConnectTimeout())
// 链接不够用的等待时间
.setConnectionRequestTimeout(restTemplateProperties.getReadTimeout())
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, true))
.build();
}
@Data
@Component
@ConfigurationProperties(prefix = "httpclient")
public class RestTemplateProperties {
/**
* 最大链接数
*/
private int maxTotal = 2000;
/**
* 同路由最大并发数
*/
private int maxPerRoute = 100;
/**
* 读取超时时间 ms
*/
private int readTimeout = 35000;
/**
* 链接超时时间 ms
*/
private int connectTimeout = 10000;
/**
* 空闲连接校验间隔‌
*/
private int validateAfterInactivity = 2000;
}
}
package com.netease.yanxuan.wx.store.sharer.common.constant;
/**
* @Description 通用常量信息
* @Author fanjiaxin
* @Date 2025/3/9 13:15
*/
public interface CoreConstant {
/**
* 请求头认证Token
*/
String AUTH_HEADER_TOKEN_KEY = "Authorization";
/**
* Redis用户认证Token
*/
String REDIS_TOKEN_USER_KEY = "TOKEN:USER:";
/**
* Redis认证Token失效时间,秒
*/
int REDIS_TOKEN_EXPIRE_SECONDS = 30 * 24 * 60 * 60;
/**
* Redis微信认证Token
*/
String REDIS_TOKEN_WECHAT_KEY = "TOKEN:WECHAT";
/**
* Redis微信认证Token锁
*/
String REDIS_TOKEN_WECHAT_LOCK_KEY = "TOKEN:WECHAT:LOCK";
}
package com.netease.yanxuan.wx.store.sharer.common.constant;
/**
* 返回状态码
*/
public interface HttpStatusConstant {
/**
* 成功标记
*/
int SUCCESS = 200;
/**
* 未授权
*/
int UNAUTHORIZED = 401;
/**
* 失败标记
*/
int FAIL = 500;
}
package com.netease.yanxuan.wx.store.sharer.common.core;
import com.netease.yanxuan.wx.store.sharer.common.constant.HttpStatusConstant;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.StringUtils;
import java.io.Serializable;
/**
* @Description 响应信息主体
* @Author fanjiaxin
* @Date 2025/3/9 13:13
*/
@Data
@NoArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功
*/
public static final int SUCCESS = HttpStatusConstant.SUCCESS;
/**
* 失败
*/
public static final int FAIL = HttpStatusConstant.FAIL;
/**
* 消息状态码
*/
private Integer code;
/**
* 消息内容
*/
private String msg;
/**
* 数据对象
*/
private T data;
public static <T> Result<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> Result<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> Result<T> ok(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> Result<T> ok(String msg, T data) {
return restResult(data, SUCCESS, msg);
}
public static <T> Result<T> fail() {
return restResult(null, FAIL, "操作失败");
}
public static <T> Result<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> Result<T> fail(T data) {
return restResult(data, FAIL, "操作失败");
}
public static <T> Result<T> fail(String msg, T data) {
return restResult(data, FAIL, msg);
}
public static <T> Result<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
public static <T> Result<T> fail(Throwable throwable) {
String msg = StringUtils.substring(throwable.getMessage(), 0, 2000);
return restResult(null, FAIL, msg);
}
private static <T> Result<T> restResult(T data, int code, String msg) {
Result<T> r = new Result<>();
r.setCode(code);
r.setData(data);
r.setMsg(msg);
return r;
}
public static <T> Boolean isError(Result<T> ret) {
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(Result<T> ret) {
return Result.SUCCESS == ret.getCode();
}
}
package com.netease.yanxuan.wx.store.sharer.common.exception;
/**
* 业务-异常类
*/
public class BizException extends RuntimeException {
private static final long serialVersionUID = -7960867947542194198L;
public BizException() {
super();
}
public BizException(String message) {
super(message);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(Throwable cause) {
super(cause);
}
public BizException(Object obj) {
super();
}
public BizException(String message, Object obj) {
super(message);
}
public BizException(String message, Throwable cause, Object obj) {
super(message, cause);
}
public BizException(Throwable cause, Object obj) {
super(cause);
}
}
package com.netease.yanxuan.wx.store.sharer.common.exception;
/**
* 授权未通过-异常类
*/
public class NoAuthException extends BizException {
private static final long serialVersionUID = 6300030459749920168L;
public NoAuthException() {
super();
}
public NoAuthException(String message) {
super(message);
}
public NoAuthException(String message, Throwable cause) {
super(message, cause);
}
public NoAuthException(Throwable cause) {
super(cause);
}
public NoAuthException(Object obj) {
super();
}
public NoAuthException(String message, Object obj) {
super(message);
}
public NoAuthException(String message, Throwable cause, Object obj) {
super(message, cause);
}
public NoAuthException(Throwable cause, Object obj) {
super(cause);
}
}
package com.netease.yanxuan.wx.store.sharer.common.handler;
import com.netease.yanxuan.wx.store.sharer.common.constant.HttpStatusConstant;
import com.netease.yanxuan.wx.store.sharer.common.core.Result;
import com.netease.yanxuan.wx.store.sharer.common.exception.BizException;
import com.netease.yanxuan.wx.store.sharer.common.exception.NoAuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 认证失败
*/
@ExceptionHandler(NoAuthException.class)
public Result<Void> handleNoAuthException(NoAuthException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return Result.fail(HttpStatusConstant.UNAUTHORIZED, "认证失败,无法访问系统资源");
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return Result.fail(e);
}
/**
* 业务异常
*/
@ExceptionHandler(BizException.class)
public Result<Void> handleYiCaiServiceException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生业务异常.", requestURI, e);
return Result.fail(e);
}
}
package com.netease.yanxuan.wx.store.sharer.common.handler;
import org.springframework.http.HttpMethod;
/**
* @Description HTTP请求
* @Author fanjiaxin
* @Date 2025/3/11 17:27
*/
public interface HttpRequestClient {
/**
* 执行 HTTP 请求
*
* @param url 请求地址
* @param method 请求类型 (GET/POST)
* @param params 请求参数 (GET 对应查询参数,POST 对应表单/JSON 参数)
* @param resType 返回数据类型
* @return 响应结果
*/
<T, R> R execute(String url, HttpMethod method, T params, Class<R> resType);
}
package com.netease.yanxuan.wx.store.sharer.common.handler;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @Description redis客户端
* @Author fanjiaxin
* @Date 2025/3/10 10:32
*/
@Service
@RequiredArgsConstructor
public class RedisClient {
private final RedisTemplate<String, Object> redisTemplate;
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public void set(String key, Object value, int expireSeconds) {
redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
}
public String getStr(String key) {
Object value = redisTemplate.opsForValue().get(key);
return null != value ? value.toString() : null;
}
public void setStr(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public void setStr(String key, String value, int expireSeconds) {
redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
}
public Boolean setIfAbsent(String key, Object value, int expireSeconds) {
return redisTemplate.opsForValue().setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
}
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
}
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.common.handler;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Map;
/**
* @Description RestTemplateClient
* @Author fanjiaxin
* @Date 2025/3/11 17:27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RestTemplateClient implements HttpRequestClient {
private final RestTemplate restTemplate;
@Override
public <T, R> R execute(String url, HttpMethod method, T params, Class<R> resType) {
log.info("RestTemplateClient execute url:{},method:{},params:{}", url, method, JSON.toJSONString(params));
if (method == HttpMethod.GET) {
return handleGetRequest(url, params, resType);
} else {
return handlePostRequest(url, params, resType);
}
}
private <T, R> R handleGetRequest(String url, T params, Class<R> resType) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
Map<String, Object> paramsMap = JSON.parseObject(JSON.toJSONString(params), Map.class);
paramsMap.forEach(builder::queryParam);
URI uri = builder.build().toUri();
log.info("RestTemplateClient handleGetRequest execute uri:{}", uri);
ResponseEntity<R> response = restTemplate.getForEntity(url, resType, paramsMap);
log.info("RestTemplateClient handleGetRequest execute response:{}", JSON.toJSONString(response));
return response.getBody();
}
private <T, R> R handlePostRequest(String url, T params, Class<R> resType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<T> requestEntity = new HttpEntity<>(params, headers);
ResponseEntity<R> response = restTemplate.postForEntity(url, requestEntity, resType);
log.info("RestTemplateClient handlePostRequest execute response:{}", JSON.toJSONString(response));
return response.getBody();
}
}
/**
* @(#)FastJsonRedisSerializer.java, 2022/7/8.
* <p/>
* Copyright 2022 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.yanxuan.wx.store.sharer.common.json;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* @author 柳敦盛 (liudunsheng@corp.netease.com)
*/
public class FastJsonRedisSerializer implements RedisSerializer {
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
ParserConfig.getGlobalInstance().addAccept("com.netease.");
}
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
@Override
public byte[] serialize(Object t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return FastJsonSerializer.serialize(t).getBytes(DEFAULT_CHARSET);
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return FastJsonSerializer.deserialize(str);
}
}
......@@ -16,46 +16,5 @@
<groupId>com.netease.yanxuan</groupId>
<artifactId>yanxuan-wx-store-sharer-common</artifactId>
</dependency>
<!-- store-db
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>netease.ddb</groupId>
<artifactId>db</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.dal.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.netease.yanxuan.shadow.common.IDGenerator;
import com.netease.yanxuan.shadow.conf.DrmShadowConfig;
import com.netease.yanxuan.shadow.conf.ShadowConfig;
import com.netease.yanxuan.shadow.ddb.IDGeneratorDipperDDB;
import com.netease.yanxuan.shadow.druid.ShadowTableFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Collections;
@Slf4j
@Configuration
@EnableTransactionManagement
public class DataSourceConfiguration {
@Autowired
private DrmDBConfig drmDBConfig;
@Bean("mysqlDataSource")
public DataSource genDataSource(@Qualifier("shadowTableFilter") ShadowTableFilter shadowTableFilter) {
DruidDataSource dataSource = new DruidDataSource();
//url
dataSource.setUrl(drmDBConfig.getMysqlUrl());
dataSource.setUsername(drmDBConfig.getMysqlUserName());
dataSource.setPassword(drmDBConfig.getMysqlPassWord());
dataSource.setDriverClassName(drmDBConfig.getMysqlDriverClassName());
//pool
dataSource.setDbType(drmDBConfig.getMysqlDbType());
dataSource.setInitialSize(drmDBConfig.getMysqlInitialSize());
dataSource.setMinIdle(drmDBConfig.getMysqlMinIdle());
dataSource.setMaxActive(drmDBConfig.getMysqlMaxActive());
dataSource.setMaxWait(drmDBConfig.getMysqlMaxWait());
dataSource.setMinEvictableIdleTimeMillis(drmDBConfig.getMysqlMinEvictableTimeMillis());
dataSource.setRemoveAbandoned(drmDBConfig.isMysqlRemoveAbandoned());
dataSource.setRemoveAbandonedTimeoutMillis(drmDBConfig.getMysqlRemoveAbandonedTimeMills());
dataSource.setUseUnfairLock(drmDBConfig.isMysqlUseUnfairLock());
dataSource.setTestWhileIdle(drmDBConfig.isMysqlTestWhileIdle());
dataSource.setValidationQuery(drmDBConfig.getMysqlValidationQuery());
dataSource.setValidationQueryTimeout(drmDBConfig.getMysqlValidationQueryTimeout());
dataSource.setTimeBetweenEvictionRunsMillis(drmDBConfig.getMysqlTimeBetweenEvictionRunsMillis());
dataSource.setTestOnBorrow(drmDBConfig.isMysqlTestOnBorrow());
dataSource.setTestOnReturn(drmDBConfig.isMysqlTestOnReturn());
dataSource.setPoolPreparedStatements(drmDBConfig.isMysqlPoolPreparedStatements());
dataSource.setMaxPoolPreparedStatementPerConnectionSize(
drmDBConfig.getMysqlMaxPoolPreparedStatementPerConnectionSize());
dataSource.setProxyFilters(Collections.singletonList(shadowTableFilter));
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setDbType("mysql");
return dataSource;
}
/**
* session factory
*/
@Primary
@Bean(name = "sqlSessionFactoryBean")
public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
// 配置类型别名
sqlSessionFactoryBean.setTypeAliasesPackage("com.netease.yanxuan.marketing.mall.reward.dal.meta.model.po");
// 配置mapper的扫描,找到所有的mapper.xml映射文件
PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = patternResolver.getResources("classpath:mapper/*.xml");
sqlSessionFactoryBean.setMapperLocations(resources);
// 加载全局的配置文件
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
Resource resource = defaultResourceLoader.getResource("classpath:mybatis/mybatis-config.xml");
sqlSessionFactoryBean.setConfigLocation(resource);
sqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor());
return sqlSessionFactoryBean;
}
/**
* sqlSessionTemplate
*/
@Primary
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 用于事务管理
*/
@Bean(name = "transactionManager")
@Primary
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ShadowConfig shadowConfig() {
return new DrmShadowConfig();
}
@Bean
public IDGenerator ddbIDGenerator(ShadowConfig shadowConfig) {
return new IDGeneratorDipperDDB(shadowConfig);
}
@Bean
public ShadowTableFilter shadowTableFilter(@Qualifier("shadowConfig") ShadowConfig shadowConfig) {
ShadowTableFilter shadowTableFilter = new ShadowTableFilter();
shadowTableFilter.setShadowConfig(shadowConfig);
return shadowTableFilter;
}
}
package com.netease.yanxuan.wx.store.sharer.dal.config;
import com.ctrip.framework.apollo.spring.annotation.EnableAutoUpdateApolloConfig;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 数据库配置 不会动态生效,需重启生效
*/
@Getter
@Component
@EnableAutoUpdateApolloConfig("db.config")
public class DrmDBConfig {
@Value("${mysql.url:}")
private String mysqlUrl;
@Value("${mysql.username:}")
private String mysqlUserName;
@Value("${mysql.password:}")
private String mysqlPassWord;
@Value("${mysql.driverClassName:com.mysql.jdbc.Driver}")
private String mysqlDriverClassName;
@Value("${mysql.type:mysql}")
private String mysqlDbType;
@Value("${mysql.initial.size:5}")
private int mysqlInitialSize;
@Value("${mysql.minIdle:5}")
private int mysqlMinIdle;
@Value("${mysql.maxActive:50}")
private int mysqlMaxActive;
@Value("${mysql.maxWait:1000}")
private long mysqlMaxWait;
@Value("${mysql.minEvictableIdleTimeMillis:35000}")
private long mysqlMinEvictableTimeMillis;
@Value("${mysql.removeAbandoned:false}")
private boolean mysqlRemoveAbandoned;
@Value("${mysql.removeAbandonedTimeoutMillis:180000}")
private long mysqlRemoveAbandonedTimeMills;
@Value("${mysql.connectProperties:socketTimeout=3000;connectTimeout=1200}")
private String mysqlConnectionProperties;
@Value("${mysql.useUnfairLock:false}")
private boolean mysqlUseUnfairLock;
@Value("${mysql.testWhileIdle:true}")
private boolean mysqlTestWhileIdle;
@Value("${mysql.validationQuery:SELECT 1}")
private String mysqlValidationQuery;
@Value("${mysql.validationQueryTimeout:1}")
private int mysqlValidationQueryTimeout;
@Value("${mysql.timeBetweenEvictionRunsMillis:30000}")
private long mysqlTimeBetweenEvictionRunsMillis;
@Value("${mysql.testOnBorrow:false}")
private boolean mysqlTestOnBorrow;
@Value("${mysql.testOnReturn:false}")
private boolean mysqlTestOnReturn;
@Value("${mysql.poolPreparedStatements:false}")
private boolean mysqlPoolPreparedStatements;
@Value("${mysql.maxPoolPreparedStatementPerConnectionSize:0}")
private int mysqlMaxPoolPreparedStatementPerConnectionSize;
/**
* redis
*/
private List<String> nodes;
@Value("${redis.cluster.nodes:10.104.0.129:16380,10.104.0.129:16381,10.104.0.129:16382}")
private String clusterNodes;
@Value("${redis.cluster.password:}")
private String password;
@Value("${redis.cluster.max-redirects:3}")
private Integer maxRedirects;
@Value("${redis.maxTotal:100}")
private Integer maxTotal;
@Value("${redis.maxIdle:100}")
private Integer maxIdle;
@Value("${redis.minIdle:20}")
private Integer minIdle;
@Value("${redis.numTestsPerEvictionRun:1024}")
private Integer numTestsPerEvictionRun;
@Value("${redis.timeBetweenEvictionRunsMillis:30000}")
private Integer timeBetweenEvictionRunsMillis;
@Value("${redis.minEvictableIdleTimeMillis:1800000}")
private Integer minEvictableIdleTimeMillis;
@Value("${redis.softMinEvictableIdleTimeMillis:10000}")
private Integer softMinEvictableIdleTimeMillis;
@Value("${redis.maxWaitMillis:1500}")
private Integer maxWaitMillis;
@Value("${redis.testOnBorrow:false}")
private Boolean testOnBorrow;
@Value("${redis.testWhileIdle:true}")
private Boolean testWhileIdle;
@Value("${redis.blockWhenExhausted:false}")
private Boolean blockWhenExhausted;
public List<String> getClusterNodes() {
String[] split = this.clusterNodes.split(",");
nodes = new ArrayList<>(Arrays.asList(split));
return nodes;
}
}
/**
* @(#)MybatisConfig.java, 2024/9/2.
* <p/>
* Copyright 2024 Netease, Inc. All rights reserved. NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license
* terms.
*/
package com.netease.yanxuan.wx.store.sharer.dal.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
/**
* 配置分页插件
* @author 刘运星 (liuyunxing01@corp.netease.com)
*/
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
/**
* @(#)RedisConfiguration.java, 2022/7/8.
* <p/>
* Copyright 2022 Netease, Inc. All rights reserved.
* NETEASE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package com.netease.yanxuan.wx.store.sharer.dal.config;
import com.netease.yanxuan.wx.store.sharer.common.json.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
/**
* @author 柳敦盛 (liudunsheng@corp.netease.com)
*/
@Configuration
public class RedisConfiguration {
private static final long CONNECT_TIMEOUT = 500L;
private static final long READ_TIMEOUT = 500L;
@Autowired
private DrmDBConfig drmDBConfig;
@Bean
public RedisClusterConfiguration redisClusterConfiguration() {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(
drmDBConfig.getClusterNodes());
redisClusterConfiguration.setMaxRedirects(drmDBConfig.getMaxRedirects());
redisClusterConfiguration.setPassword(RedisPassword.of(drmDBConfig.getPassword()));
return redisClusterConfiguration;
}
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数
jedisPoolConfig.setMaxTotal(drmDBConfig.getMaxTotal());
// 最大空闲连接数
jedisPoolConfig.setMaxIdle(drmDBConfig.getMaxIdle());
jedisPoolConfig.setMinIdle(drmDBConfig.getMinIdle());
// 每次释放连接的最大数目
jedisPoolConfig.setNumTestsPerEvictionRun(drmDBConfig.getNumTestsPerEvictionRun());
// 释放连接的扫描间隔(毫秒)
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(drmDBConfig.getTimeBetweenEvictionRunsMillis());
// 连接最小空闲时间
jedisPoolConfig.setMinEvictableIdleTimeMillis(drmDBConfig.getMinEvictableIdleTimeMillis());
// 连接空闲多久后释放, 当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放
jedisPoolConfig.setSoftMinEvictableIdleTimeMillis(drmDBConfig.getSoftMinEvictableIdleTimeMillis());
// 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1
jedisPoolConfig.setMaxWaitMillis(drmDBConfig.getMaxWaitMillis());
// 在获取连接的时候检查有效性, 默认false
jedisPoolConfig.setTestOnBorrow(drmDBConfig.getTestOnBorrow());
// 在空闲时检查有效性, 默认false
jedisPoolConfig.setTestWhileIdle(drmDBConfig.getTestWhileIdle());
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
jedisPoolConfig.setBlockWhenExhausted(drmDBConfig.getBlockWhenExhausted());
return jedisPoolConfig;
}
@Bean
public JedisClientConfiguration jedisClientConfiguration() {
JedisClientConfiguration.JedisClientConfigurationBuilder builder = JedisClientConfiguration.builder();
return builder.connectTimeout(Duration.ofMillis(CONNECT_TIMEOUT)).readTimeout(Duration.ofMillis(READ_TIMEOUT))
.usePooling().poolConfig(jedisPoolConfig()).build();
}
@Bean(name = "connectionFactory")
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory(redisClusterConfiguration(), jedisClientConfiguration());
}
@Bean("redisTemplate")
@Primary
public RedisTemplate<String, Object> getRedisTemplate(
@Qualifier("connectionFactory") JedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);
redisTemplate.setValueSerializer(fastJsonRedisSerializer);
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
return redisTemplate;
}
@Bean("redisStringTemplate")
public RedisTemplate<Object, Object> getRedisStringTemplate(
@Qualifier("connectionFactory") JedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
return redisTemplate;
}
@Bean("openapiCacheManager")
public CacheManager cacheManager(@Qualifier("connectionFactory") JedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}
}
package com.netease.yanxuan.wx.store.sharer.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.netease.yanxuan.wx.store.sharer.dal.meta.model.po.SharerInfo;
/**
* @Description 推客信息表
* @Author fanjiaxin
* @Date 2025/3/11 22:08
*/
public interface SharerInfoMapper extends BaseMapper<SharerInfo> {
}
package com.netease.yanxuan.wx.store.sharer.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.netease.yanxuan.wx.store.sharer.dal.meta.model.po.SharerProductCommission;
/**
* @Description 推客商品分佣表
* @Author fanjiaxin
* @Date 2025/3/11 21:54
*/
public interface SharerProductCommissionMapper extends BaseMapper<SharerProductCommission> {
}
package com.netease.yanxuan.wx.store.sharer.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.netease.yanxuan.wx.store.sharer.dal.meta.model.po.SharerProductCommissionRecord;
/**
* @Description 推客商品分佣配置操作记录表
* @Author fanjiaxin
* @Date 2025/3/11 21:54
*/
public interface SharerProductCommissionRecordMapper extends BaseMapper<SharerProductCommissionRecord> {
}
/**
* mapper对象
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.dal.mapper;
package com.netease.yanxuan.wx.store.sharer.dal.meta.model.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
/**
* @Description 推客信息表
* @Author fanjiaxin
* @Date 2025/3/11 21:54
*/
@Data
@TableName("SHARER_INFO")
public class SharerInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID,自动生成。
*/
@TableId(type = IdType.AUTO)
private BigInteger id;
/**
* 用户开放ID
*/
private String openId;
/**
* 推客应用ID
*/
private String sharerAppid;
/**
* 绑定时间
*/
private Date bindTime;
/**
* 佣金比例
*/
private BigDecimal commissionRatio;
/**
* 分佣类型【0:平台分佣 1:机构分佣】
*/
private String commissionType;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
package com.netease.yanxuan.wx.store.sharer.dal.meta.model.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
/**
* @Description 推客商品分佣表
* @Author fanjiaxin
* @Date 2025/3/11 21:54
*/
@Data
@TableName("SHARER_PRODUCT_COMMISSION")
public class SharerProductCommission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID,自动生成。
*/
@TableId(type = IdType.AUTO)
private BigInteger id;
/**
* 用户开放ID
*/
private String openId;
/**
* 推客应用ID
*/
private String sharerAppid;
/**
* 商品ID
*/
private String productId;
/**
* 佣金比例
*/
private BigDecimal commissionRatio;
/**
* 佣金类型
*/
private String commissionType;
/**
* 是否未配置,1:未配置,取默认佣金比例,0:已配置
*/
private String unset;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
package com.netease.yanxuan.wx.store.sharer.dal.meta.model.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
/**
* @Description 推客商品分佣配置操作记录表
* @Author fanjiaxin
* @Date 2025/3/11 21:54
*/
@Data
@TableName("SHARER_PRODUCT_COMMISSION_RECORD")
public class SharerProductCommissionRecord implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID,自动生成。
*/
@TableId(type = IdType.AUTO)
private BigInteger id;
/**
* 用户开放ID
*/
private String openId;
/**
* 推客应用ID
*/
private String sharerAppid;
/**
* 商品ID
*/
private String productId;
/**
* 操作类型
* 1- 设置商品分佣比例
* 2- 取消设置商品分佣比例
* 3- 默认分佣比例变更
*/
private String optType;
/**
* 操作信息,记录操作前比例 & 操作后比例
*/
private String optInfo;
/**
* 操作时间
*/
private Date optTime;
/**
* 状态,1 操作成功 2 操作失败
*/
private String optStatus;
/**
* 拓展信息
*/
private String extInfo;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
/**
* po对象
*
* @author 莫闲.
* @date 2021/1/29.
*/
package com.netease.yanxuan.wx.store.sharer.dal.meta.model.po;
/**
* dao包作为可选包,用于融合值得复用的mapper层的统一处理,例如参数的调整、适配,
* 多个mapper关联的情况等,当然这些也可以考虑在service实现。
* dao=data access object,所以这里把NoSQL,例如mongo的访问封装也放到这里。
*
* @author hzwangliyuan.
* @date 2019/11/28.
*/
package com.netease.yanxuan.wx.store.sharer.dal;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 这个配置使全局的映射器启用或禁用缓存 -->
<setting name="cacheEnabled" value="false"/>
<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载 -->
<setting name="lazyLoadingEnabled" value="false"/>
<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 允许JDBC支持生成的键。需要适合的驱动。如果设置为true则这个设置强制生成的键被使用,尽管一些驱动拒绝兼容但仍然有效(比如Derby) -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 允许或不允许多种结果集从一个单独的语句中返回(需要适合的驱动) -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 使用列标签代替列名。不同的驱动在这方便表现不同。参考驱动文档或充分测试两种方法来决定所使用的驱动 -->
<setting name="useColumnLabel" value="true"/>
<!-- 指定MyBatis如何自动映射列到字段/属性。PARTIAL只会自动映射简单,没有嵌套的结果。FULL会自动映射任意复杂的结果(嵌套的或其他情况) -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 配置默认的执行器。SIMPLE执行器没有什么特别之处。REUSE执行器重用预处理语句。BATCH执行器重用语句和批量更新 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 设置超时时间,它决定驱动等待一个数据库响应的时间 -->
<setting name="defaultStatementTimeout" value="25000"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeHandlers>
</typeHandlers>
</configuration>
package com.netease.yanxuan.wx.store.sharer.integration.config;
import com.ctrip.framework.apollo.spring.annotation.EnableAutoUpdateApolloConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Description 应用配置
* @Author fanjiaxin
* @Date 2025/3/10 17:26
*/
@Data
@Component
@EnableAutoUpdateApolloConfig
public class WeChatConfig {
/**
* 微信接口基地址
*/
@Value("${wechat.url:https://api.weixin.qq.com}")
private String url;
/**
* 微信开放接口appId
*/
@Value("${wechat.appid:wx57ee288d2823d5e3}")
private String appid;
/**
* 微信开放接口appSecret
*/
@Value("${wechat.appsecret:0bf536fe0b0a20542dd97a490f8e89b7}")
private String appsecret;
}
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.integration.constant;
/**
* @Description 微信API
* @Author fanjiaxin
* @Date 2025/3/11 11:59
*/
public class WeChatApi {
/**
* 获取用户认证信息
*/
public static final String GET_TOKEN = "/cgi-bin/token";
// -------------------------------------- 用户 --------------------------------------
/**
* 获取小程序用户信息
*/
public static final String GET_USER_INFO = "/sns/jscode2session";
// -------------------------------------- 推客 --------------------------------------
/**
* 获取推客的注册状态,以及和机构的绑定状态
*/
public static final String GET_PROMOTER_REGISTER_AND_BIND_STATUS = "/channels/ec/promoter/get_promoter_register_and_bind_status";
/**
* 获取机构绑定的推客信息
*/
public static final String GET_BIND_SHARER_LIST = "/channels/ec/promoter/get_bind_sharer_list";
/**
* 设置推客的的分佣类型和比例信息
*/
public static final String SET_SHARER_COMMISSION_INFO = "/channels/ec/promoter/set_sharer_commission_info";
// -------------------------------------- 商品 --------------------------------------
/**
* 获取可推广的商品id列表
*/
public static final String GET_PROMOTE_PRODUCT_LIST = "/channels/ec/promoter/get_promote_product_list";
/**
* 获取可以推广的商品详情
*/
public static final String GET_PROMOTE_PRODUCT_DETAIL = "/channels/ec/promoter/get_promote_product_detail";
/**
* 获取推客对某个商品的推广短链
*/
public static final String GET_PRODUCT_PROMOTION_LINK_INFO = "/channels/ec/promoter/get_product_promotion_link_info";
/**
* 获取推客的某个商品的推广分佣比例
*/
public static final String GET_SHARER_PRODUCT_COMMISSION_INFO = "/channels/ec/promoter/get_sharer_product_commission_info";
/**
* 设置推客的单个商品的分佣比例信息
*/
public static final String SET_SHARER_PRODUCT_COMMISSION_INFO = "/channels/ec/promoter/set_sharer_product_commission_info";
}
package com.netease.yanxuan.wx.store.sharer.integration.core;
import com.netease.yanxuan.wx.store.sharer.common.constant.CoreConstant;
import com.netease.yanxuan.wx.store.sharer.common.exception.BizException;
import com.netease.yanxuan.wx.store.sharer.common.handler.RedisClient;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatAccessTokenRequest;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatAccessTokenVO;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @Description 微信认证信息上下文持有器
* @Author fanjiaxin
* @Date 2025/2/24 11:53
*/
@Service
@RequiredArgsConstructor
public class WeChatContextHolder {
private final RedisClient redisClient;
private final WeChatAccessTokenRequest weChatAccessTokenRequest;
private static final long RETRY_DELAY_MS = 200; // 每次重试之间的延迟时间(毫秒)
private static final int EXPIRE_SECONDS = 5 * 60;
/**
* 获取微信认证信息
*/
public String getAccessToken() {
// 双重检查锁定
String accessToken = redisClient.getStr(CoreConstant.REDIS_TOKEN_WECHAT_KEY);
if (StringUtils.isNotBlank(accessToken)) {
return accessToken;
}
try {
return createAccessToken();
} catch (InterruptedException e) {
throw new BizException("微信认证获取AccessToken异常", e);
}
}
/**
* 创建微信认证信息
*/
public String createAccessToken() throws InterruptedException {
// 设置10秒的锁
boolean locked = redisClient.setIfAbsent(CoreConstant.REDIS_TOKEN_WECHAT_LOCK_KEY, "locked", 10);
if (!locked) {
// 未获取到锁,等待一段时间后重试
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
return createAccessToken();
}
// 双重检查锁定
// 双重检查锁定
String accessToken = redisClient.getStr(CoreConstant.REDIS_TOKEN_WECHAT_KEY);
if (StringUtils.isNotBlank(accessToken)) {
return accessToken;
}
WeChatAccessTokenVO handle = weChatAccessTokenRequest.handle();
redisClient.setStr(CoreConstant.REDIS_TOKEN_WECHAT_KEY, handle.getAccess_token(),
handle.getExpires_in() - EXPIRE_SECONDS);
return handle.getAccess_token();
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler;
import org.springframework.http.HttpMethod;
/**
* @Description IWeChatRequest
* @Author fanjiaxin
* @Date 2025/3/11 17:12
*/
public interface IWeChatRequest {
/**
* 请求类型
*/
HttpMethod getRequestMethod();
/**
* 请求地址
*/
String getRequestUrl();
}
package com.netease.yanxuan.wx.store.sharer.integration.handler;
import com.netease.yanxuan.wx.store.sharer.common.exception.BizException;
import com.netease.yanxuan.wx.store.sharer.common.handler.RestTemplateClient;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatCoreVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description RestTemplateClient
* @Author fanjiaxin
* @Date 2025/3/11 17:27
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatRestTemplateHandler {
private final RestTemplateClient restTemplateClient;
public <T, R extends WeChatCoreVO> R execute(String url, HttpMethod method, T params, Class<R> resType) {
R result = restTemplateClient.execute(url, method, params, resType);
if (!"0".equals(result.getErrcode())) {
throw new BizException("调用微信接口失败," + result.getErrmsg());
}
return result;
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatAccessTokenBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatAccessTokenVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 微信凭证请求
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatAccessTokenRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
private static final String GRANT_TYPE = "client_credential";
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.GET;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_TOKEN;
}
/**
* 处理
*/
public WeChatAccessTokenVO handle() {
WeChatAccessTokenBO params = WeChatAccessTokenBO.builder()
.grant_type(GRANT_TYPE)
.appid(weChatConfig.getAppid())
.secret(weChatConfig.getAppsecret())
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatAccessTokenVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatGetSharerProductCommissionBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatGetSharerProductCommissionVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 设置推客的单个商品的分佣比例信息
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatGetSharerProductCommissionRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_SHARER_PRODUCT_COMMISSION_INFO;
}
/**
* 处理
*/
public WeChatGetSharerProductCommissionVO handle(String sharerAppid, Integer productId, String commissionRatio) {
WeChatGetSharerProductCommissionBO params = WeChatGetSharerProductCommissionBO.builder()
.sharer_appid(sharerAppid)
.product_id(productId)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatGetSharerProductCommissionVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.enums.ProductPlanTypeEnum;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatPromoteProductDetailBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatPromoteProductDetailVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 商品详情
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatPromoteProductDetailRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_PROMOTE_PRODUCT_DETAIL;
}
/**
* 处理
*/
public WeChatPromoteProductDetailVO handle(String shopAppid, Integer productId) {
WeChatPromoteProductDetailBO params = WeChatPromoteProductDetailBO.builder()
.plan_type(ProductPlanTypeEnum.OPEN.getCode())
.shop_appid(shopAppid)
.product_id(productId)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatPromoteProductDetailVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatPromoteProductLinkBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatPromoteProductLinkVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 商品推广链接
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatPromoteProductLinkRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_PRODUCT_PROMOTION_LINK_INFO;
}
/**
* 处理
*/
public WeChatPromoteProductLinkVO handle(String sharerAppid, Integer productId) {
WeChatPromoteProductLinkBO params = WeChatPromoteProductLinkBO.builder()
.sharer_appid(sharerAppid)
.product_id(productId)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatPromoteProductLinkVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.enums.ProductPlanTypeEnum;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatPromoteProductListBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatPromoteProductListVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 商品列表
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatPromoteProductListRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_PROMOTE_PRODUCT_LIST;
}
/**
* 处理
*/
public WeChatPromoteProductListVO handle(String keyword, String nextKey, Integer pageSize) {
WeChatPromoteProductListBO params = WeChatPromoteProductListBO.builder()
.plan_type(ProductPlanTypeEnum.OPEN.getCode())
.keyword(keyword)
.next_key(nextKey)
.page_size(pageSize)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatPromoteProductListVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.enums.CommissionTypeEnum;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatSetSharerCommissionBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatCoreVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 设置推客的分佣比例信息
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatSetSharerCommissionRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.SET_SHARER_COMMISSION_INFO;
}
/**
* 处理
*/
public WeChatCoreVO handle(String sharerAppid, String commissionRatio) {
WeChatSetSharerCommissionBO params = WeChatSetSharerCommissionBO.builder()
.sharer_appid(sharerAppid)
.commission_type(CommissionTypeEnum.PLATFORM.getCode())
.commission_ratio(commissionRatio)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatCoreVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatSetSharerProductCommissionBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatCoreVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 设置推客的单个商品的分佣比例信息
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatSetSharerProductCommissionRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.SET_SHARER_PRODUCT_COMMISSION_INFO;
}
/**
* 处理
*/
public WeChatCoreVO handle(String sharerAppid, Integer productId, String commissionRatio) {
WeChatSetSharerProductCommissionBO params = WeChatSetSharerProductCommissionBO.builder()
.sharer_appid(sharerAppid)
.product_id(productId)
.commission_ratio(commissionRatio)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatCoreVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatSharerListBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatSharerListVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 微信凭证请求
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatSharerListRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_BIND_SHARER_LIST;
}
/**
* 处理
*/
public WeChatSharerListVO handle(String sharerOpenid, String nextKey, Integer pageSize) {
WeChatSharerListBO params = WeChatSharerListBO.builder()
.sharer_openid(sharerOpenid)
.next_key(nextKey)
.page_size(pageSize)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatSharerListVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatSharerRegisterBindBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatSharerRegisterBindVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 微信凭证请求
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatSharerRegisterBindRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.POST;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_USER_INFO;
}
/**
* 处理
*/
public WeChatSharerRegisterBindVO handle(String sharerOpenid) {
WeChatSharerRegisterBindBO params = WeChatSharerRegisterBindBO.builder()
.is_simple_register(true)
.sharer_openid(sharerOpenid)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatSharerRegisterBindVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.handler.impl;
import com.netease.yanxuan.wx.store.sharer.integration.config.WeChatConfig;
import com.netease.yanxuan.wx.store.sharer.integration.constant.WeChatApi;
import com.netease.yanxuan.wx.store.sharer.integration.handler.IWeChatRequest;
import com.netease.yanxuan.wx.store.sharer.integration.handler.WeChatRestTemplateHandler;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo.WeChatUserInfoBO;
import com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo.WeChatUserInfoVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
/**
* @Description 微信凭证请求
* @Author fanjiaxin
* @Date 2025/3/11 17:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatUserInfoRequest implements IWeChatRequest {
private final WeChatRestTemplateHandler weChatRestTemplateHandler;
private final WeChatConfig weChatConfig;
private static final String GRANT_TYPE = "authorization_code";
@Override
public HttpMethod getRequestMethod() {
return HttpMethod.GET;
}
@Override
public String getRequestUrl() {
return weChatConfig.getUrl() + WeChatApi.GET_USER_INFO;
}
/**
* 处理
*/
public WeChatUserInfoVO handle(String code) {
WeChatUserInfoBO params = WeChatUserInfoBO.builder()
.appid(weChatConfig.getAppid())
.secret(weChatConfig.getAppsecret())
.js_code(code)
.grant_type(GRANT_TYPE)
.build();
return weChatRestTemplateHandler.execute(getRequestUrl(), getRequestMethod(), params, WeChatUserInfoVO.class);
}
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @Description 分佣类型
* @Author fanjiaxin
* @Date 2025/3/3 13:58
*/
@Getter
@RequiredArgsConstructor
public enum CommissionTypeEnum {
PLATFORM("0", "平台分佣"),
AGENCY("1", "机构分佣");
private final String code;
private final String desc;
}
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.integration.meta.enums;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* @Description 商品的计划类型
* @Author fanjiaxin
* @Date 2025/3/3 13:58
*/
@Getter
@RequiredArgsConstructor
public enum ProductPlanTypeEnum {
TARGET(1, "定向计划"),
OPEN(2, "公开计划");
private final Integer code;
private final String desc;
}
\ No newline at end of file
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 用户认证-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatAccessTokenBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 小店唯一凭证
*/
private String appid;
/**
* 小店唯一凭证密钥
*/
private String secret;
/**
* 授权类型
*/
private String grant_type;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 获取推客的单个商品的分佣比例信息-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
@Builder
public class WeChatGetSharerProductCommissionBO implements Serializable {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 推客在微信电商平台注册的身份标识
*/
private String sharer_appid;
/**
* 商品列表
*/
private Integer product_id;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 微信分页参数-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatPageBO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分页参数,第一页为空,后面返回前面一页返回的数据
*/
private String next_key;
/**
* 一页获取多少个推客,最大 20
*/
private Integer page_size;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 商品详情-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatPromoteProductDetailBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 所属小店appid
*/
private String shop_appid;
/**
* 商品id
*/
private Integer product_id;
/**
* 商品的计划类型 1:定向计划 2:公开计划
*/
private Integer plan_type;
/**
* 填true返回商品可用的机构券
*/
private Boolean get_available_coupon;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 商品推广链接-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatPromoteProductLinkBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 推客 appid,和sharer_openid二选一
*/
private String sharer_appid;
/**
* 推客在小程序中的openid,和sharer_appid二选一
*/
private String sharer_openid;
/**
* 商品 id,如果使用该参数,需要传入shop_appid
*/
private Integer product_id;
/**
* 商品所属店铺 appid
*/
private String shop_appid;
/**
* 商品短链,和 product_id 二选一
*/
private String product_short_link;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 商品列表-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatPromoteProductListBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 商品的计划类型 1:定向计划 2:公开计划
*/
private Integer plan_type;
/**
* 搜索关键词
*/
private String keyword;
/**
* 分页参数,第一页为空,后面返回前面一页返回的数据
*/
private String next_key;
/**
* 一页获取多少个推客,最大 20
*/
private Integer page_size;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 设置推客的的分佣类型和比例信息-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
@Builder
public class WeChatSetSharerCommissionBO implements Serializable {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 推客在微信电商平台注册的身份标识
*/
private String sharer_appid;
/**
* 分佣类型【 0:平台分佣, 1:机构自己分佣】
*/
private String commission_type;
/**
* 平台分佣时的分佣比例,范围为【100000 - 900000】,代表【10%-90%】
*/
private String commission_ratio;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 设置推客的单个商品的分佣比例信息-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
@Builder
public class WeChatSetSharerProductCommissionBO implements Serializable {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 推客在微信电商平台注册的身份标识
*/
private String sharer_appid;
/**
* 商品列表
*/
private Integer product_id;
/**
* 平台分佣时的分佣比例,范围为【100000 - 900000】,代表【10%-90%】
*/
private String commission_ratio;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 推客列表-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatSharerListBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 查询某个推客,传入推客的小程序openid
*/
private String sharer_openid;
/**
* 查询某个推客,传入推客的appid
*/
private String sharer_appid;
/**
* 分页参数,第一页为空,后面返回前面一页返回的数据
*/
private String next_key;
/**
* 一页获取多少个推客,最大 20
*/
private Integer page_size;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 推客注册绑定信息-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatSharerRegisterBindBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 推客在小程序中的 openid,和 sharer_appid 二选一
*/
private String sharer_openid;
/**
* 推客在微信电商平台注册的身份标识,和 sharer_openid 二选一
*/
private String sharer_appid;
/**
* 是否走简易版本注册【当走简易版本注册时,可以不要求推客开通商户号,但分佣只能走机构自己分佣;如果不走简易注册流程时,要求推客开通商户号作为收款账户,可以平台分佣】
*/
private Boolean is_simple_register;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.bo;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 微信用户信息-业务对象
* @Author fanjiaxin
* @Date 2025/3/11 18:43
*/
@Data
@Builder
public class WeChatUserInfoBO implements Serializable {
private static final long serialVersionUID = 1996751915518651231L;
/**
* 小店唯一凭证
*/
private String appid;
/**
* 小店唯一凭证密钥
*/
private String secret;
/**
* 登录时获取的 code
*/
private String js_code;
/**
* 授权类型
*/
private String grant_type;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
/**
* @Description 微信凭证-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatAccessTokenVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 获取到的凭证
*/
private String access_token;
/**
* 凭证有效时间,单位:秒。目前是7200秒之内的值
*/
private Integer expires_in;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 微信凭证-核心对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatCoreVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 错误编码
*/
private String errcode;
/**
* 错误信息
*/
private String errmsg;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
/**
* @Description 设置推客的单个商品的分佣比例信息-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatGetSharerProductCommissionVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 平台分佣时的分佣比例,范围为【100000 - 900000】,代表【10%-90%】
*/
private String commission_ratio;
/**
* 是否设置了这个商品的佣金率
*/
private Boolean is_set;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* @Description 商品详情-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatPromoteProductDetailVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 商品信息
*/
private WeChatPromoteProductVO product;
@Data
public static class WeChatPromoteProductVO {
/**
* 所属小店appid
*/
private String shop_appid;
/**
* 商品id
*/
private Integer product_id;
/**
* 商品卡片透传参数【注意:内嵌商品卡片时一定要传,不然会归因失败】
*/
private String product_promotion_link;
/**
* 商品信息,结构体详情请参考ProductInfo
*/
private WeChatPromoteProductInfoVO product_info;
/**
* 跟佣信息
*/
private WeChatPromoteProductCommissionInfoVO commission_info;
@Data
public static class WeChatPromoteProductInfoVO {
/**
* 标题
*/
private String title;
/**
* 副标题。如果添加时没录入,回包可能不包含该字段
*/
private String sub_title;
/**
* 主图,多张,列表,最多9张,每张不超过2MB
*/
private List<String> head_imgs;
/**
* 用于在小程序跳转小店场景添加商品时传递跟佣信息
*/
private String product_promotion_link;
/**
* sku信息
*/
private List<WeChatPromoteProductSkuInfoVO> skus;
@Data
public static class WeChatPromoteProductSkuInfoVO {
/**
* skuID
*/
private String sku_id;
/**
* 售卖价格,以分为单位
*/
private BigDecimal sale_price;
/**
* 库存
*/
private Integer stock_num;
}
}
@Data
public static class WeChatPromoteProductCommissionInfoVO {
/**
* 商品带货状态 ,枚举值详情请参考ItemStatus
*/
private Integer status;
/**
* 服务费率[0, 1000000]
*/
private Integer service_ratio;
/**
* 时间戳,合作开始时间
*/
private Long start_time;
/**
* 时间戳,合作结束时间
*/
private Long end_time;
}
}
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
/**
* @Description 商品列表-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatPromoteProductLinkVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 推广短链
*/
private String short_link;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import java.util.List;
/**
* @Description 商品列表-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatPromoteProductListVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 分页参数,第一页为空,后面返回前面一页返回的数据
*/
private String next_key;
/**
* 商品列表
*/
private List<WeChatPromoteProductListItemVO> product_list;
@Data
public static class WeChatPromoteProductListItemVO {
/**
* 商品ID
*/
private Integer product_id;
/**
* 商品所属店铺 appid
*/
private String shop_appid;
}
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* @Description 微信凭证-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatSharerInfoVO implements Serializable {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 推客的 appid
*/
private String sharer_appid;
/**
* 绑定时间戳
*/
private Long bind_time;
/**
* 分佣比例
*/
private BigDecimal commission_ratio;
/**
* 分佣类型【0:平台分佣 1:机构分佣】
*/
private Integer commission_type;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import java.util.List;
/**
* @Description 微信凭证-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatSharerListVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 分页参数,第一页为空,后面返回前面一页返回的数据
*/
private String next_key;
/**
* 推客集合
*/
private List<WeChatSharerInfoVO> sharer_info_list;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
/**
* @Description 微信凭证-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
public class WeChatSharerRegisterBindVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 和机构的绑定状态,0:未绑定 1:已绑定
*/
private Integer bind_status;
/**
* 当前推客的注册状态
* 0:未注册 1:注册中,还未完成 2:已完成注册 3:用户未支付实名,需要把微信先支付实名才能继续注册
*/
private Integer register_status;
/**
* register_status等于 0 或者 1时
* 调用注册流程时,openBusinessView需要的businessType
*/
private String register_business_type;
/**
* 注册时需要的queryString参数
*/
private String register_query_string;
/**
* bind_status等于0时,调用绑定流程时,openBusinessView需要的businessType
*/
private String bind_business_type;
/**
* 绑定时需要的queryString参数
*/
private String bind_query_string;
}
package com.netease.yanxuan.wx.store.sharer.integration.meta.model.vo;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @Description 微信凭证-视图对象
* @Author fanjiaxin
* @Date 2025/3/11 19:05
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class WeChatUserInfoVO extends WeChatCoreVO {
private static final long serialVersionUID = 8529644543147459802L;
/**
* 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回
*/
private String unionid;
/**
* 用户唯一标识
*/
private String openid;
/**
* 会话密钥
*/
private String session_key;
}
/**
* 分包规则按照依赖源(比如系统名)来定义,内部的类命名以Client结尾,例如AbcClient。
* 为了兼容spring的通用模式,也建议在外部定义接口类
*
* @author hzwangliyuan.
* @date 2019/11/28.
*/
package com.netease.yanxuan.wx.store.sharer.integration;
package com.netease.yanxuan.wx.store.sharer.integration.task;
import com.netease.yanxuan.wx.store.sharer.integration.core.WeChatContextHolder;
import com.netease.yanxuan.wx.store.sharer.integration.handler.impl.WeChatAccessTokenRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* @Description 华能e购认证信息初始化
* @Date 2025/2/24 11:57
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class WeChatAuthTask implements CommandLineRunner {
private final WeChatAccessTokenRequest weChatAccessTokenRequest;
private final WeChatContextHolder weChatContextHolder;
@Override
public void run(String... args) {
log.info("微信认证信息初始化开始...");
try {
weChatContextHolder.getAccessToken();
log.info("微信认证信息初始化完成");
} catch (Exception e) {
log.error("微信认证信息初始化失败", e);
}
}
}
package com.netease.yanxuan.wx.store.sharer.web.controller;
import lombok.extern.slf4j.Slf4j;
/**
* @Description web层通用数据处理
* @Author fanjiaxin
* @Date 2025/3/9 13:17
*/
@Slf4j
public class BaseController {
}
package com.netease.yanxuan.wx.store.sharer.web.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description 开放-控制器
* @Author fanjiaxin
* @Date 2025/3/9 13:10
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/open")
public class OpenController extends BaseController {
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment