Merge branch 'master' into cc_20251016_async

# Conflicts:
#	coolstore-partner-common/src/main/java/com/cool/store/executor/MdcTaskExecutor.java
#	coolstore-partner-service/src/main/java/com/cool/store/service/impl/MessageTemplateServiceImpl.java
This commit is contained in:
苏竹红
2025-10-16 17:29:07 +08:00
47 changed files with 1084 additions and 18 deletions

View File

@@ -0,0 +1,14 @@
package com.cool.store.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 平台库数据源
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PlatformDB {
}

View File

@@ -211,5 +211,23 @@ public class CommonConstants {
public static final String WX_SELF_AUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=1#wechat_redirect";
/**
* 密码最大错误次数
*/
public static final int MAX_ERROR_PASSWORD_COUNT = 5;
/**
* 用户密码
*/
public static final String USER_AUTH_KEY = "user_auth_key";
/**
* accessToken有效期单位秒
*/
public static final int ACTION_TOKEN_EXPIRE = 24 * 60 * 60;
/**
* refreshToken有效期单位秒
*/
public static final int REFRESH_TOKEN_EXPIRE = 30 * 24 * 60 * 60;
}

View File

@@ -0,0 +1,25 @@
package com.cool.store.datasource;
/**
* <p>
* 数据源上下文
* </p>
*
* @author wangff
* @since 2025/9/4
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}

View File

@@ -0,0 +1,53 @@
package com.cool.store.datasource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* <p>
* 动态数据源
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Component
@Primary
public class DynamicDataSource extends AbstractDataSource {
@Autowired
private DataSource defaultDataSource;
@Autowired
private DataSource platformDataSource;
@Override
public Connection getConnection() throws SQLException {
DataSource currentDB = getCurrentDB();
return currentDB.getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
DataSource currentDB = getCurrentDB();
Connection connection = currentDB.getConnection(username, password);
connection.setCatalog(DataSourceContextHolder.getDataSourceType());
return connection;
}
protected DataSource getCurrentDB() {
String dbName = DataSourceContextHolder.getDataSourceType();
if (StringUtils.isBlank(dbName)) {
return defaultDataSource;
}
return platformDataSource;
}
}

View File

@@ -40,6 +40,7 @@ public enum ErrorCodeEnum {
LOGIN_ERROR(400004, "登录失败", null),
ENTERPRISE_INIT(400006, "企业正在初始化,请稍后访问!",null),
NOT_AUTH(400007, "暂无权限,请联系管理员!", null),
REFRESH_TOKEN_INVALID(400008, "refresh token invalid", null),
USER_FREEZE(1021019,"账号被冻结,请联系管理员",null),
ENTERPRISE_NOT_EXIST(1021020,"企业不存在",null),
USER_NOT_EXIST(1021021,"用户不存在",null),
@@ -62,6 +63,11 @@ public enum ErrorCodeEnum {
DATA_CONVERT_ERROR(400002, "日期转换异常!", null),
PARENT_NODE_NOT_EXIST(400002, "父节点不存在", null),
LOGIN_ERROR_MOBILE_ERROR(418, "登录失败 获取手机号失败!!", null),
PASSWORD_ERROR_MAX_COUNT(1021084, "密码错误{0}次,今日账号已锁定",null),
PASSWORD_MISSING(1021085, "密码不能为空!",null),
IMPROVE_USER_INFO(1021086,"请联系管理员,完善用户信息!",null),
PASSWORD_ERROR(1021087, "密码输入错误",null),
PASSWORD_ERROR_MULTI(1021088, "密码错误{0}次,请使用验证码登录",null),
//红圈通
HQT_SHOP_DECORATION_ATTRIBUTES(1022000, "获取红圈通装修属性错误", null),
HQT_PARAMS_ERROR(1022001, "构建红圈通请求参数错误", null),

View File

@@ -0,0 +1,25 @@
package com.cool.store.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 登录类型 枚举类
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Getter
@AllArgsConstructor
public enum LoginTypeEnum {
PASSWORD("账号密码", "passwordLoginServiceImpl"),
;
private final String message;
private final String clazzName;
}

View File

@@ -14,6 +14,7 @@ public enum MatterTypeEnum {
SERVICE_PACKAGE(4,"服务包"),
RESTOCK(5,"补货"),
INVENTORY(6,"盘点"),
REALTIME(7, "即时消息"),
;
MatterTypeEnum(Integer code, String message) {

View File

@@ -16,7 +16,7 @@ public enum ModuleCodeEnum {
DISH(3,"菜品",Arrays.asList(MatterTypeEnum.NOTICE)),
FRANCHISE(4,"加盟",Arrays.asList(MatterTypeEnum.NOTICE)),
//其他(投诉与客户服务、临时通知)
OTHER(5,"其他",Arrays.asList(MatterTypeEnum.NOTICE)),
OTHER(5,"其他",Arrays.asList(MatterTypeEnum.NOTICE, MatterTypeEnum.REALTIME)),
;
ModuleCodeEnum(Integer code, String message,List<MatterTypeEnum> matterTypeEnums) {

View File

@@ -18,6 +18,7 @@ public enum SceneEnum {
RESTOCK(35, "补货", "", MatterTypeEnum.LOGISTICS),
INVENTORY(40, "盘点", "", MatterTypeEnum.LOGISTICS),
REALTIME(45, "即时消息", "", MatterTypeEnum.REALTIME),
;
private Integer sceneCode;

View File

@@ -0,0 +1,45 @@
package com.cool.store.executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* <p>
* 线程池配置类
* </p>
*
* @author wangff
* @since 2025/9/5
*/
@Configuration
public class ThreadPoolTaskConfig {
/**
* 通用线程池
*/
@Bean
public TaskExecutor generalThreadPool() {
int cores = Runtime.getRuntime().availableProcessors();
ThreadPoolTaskExecutor executor = new MdcTaskExecutor();
// 核心线程数目
executor.setCorePoolSize(cores*2);
// 指定最大线程数
executor.setMaxPoolSize(200);
// 队列中最大的数目
executor.setQueueCapacity(5000);
// 线程名称前缀
executor.setThreadNamePrefix("generalThreadPool_");
// 对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程空闲后的最大存活时间
executor.setKeepAliveSeconds(60);
// 加载
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,41 @@
package com.cool.store.utils;
import com.github.pagehelper.PageInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* <p>
* bean转换工具
* </p>
*
* @author wangff
* @since 2025/3/6
*/
public class BeanUtil extends cn.hutool.core.bean.BeanUtil {
public static <T, R> List<R> toList(List<T> list, Class<R> clazz) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<R> result = new ArrayList<>(list.size());
for (T t : list) {
R r = toBean(t, clazz);
result.add(r);
}
return result;
}
public static <T, R> PageInfo<R> toPage(PageInfo<T> page, Class<R> clazz) {
PageInfo<R> newPage = new PageInfo<>();
newPage.setPages(page.getPages());
newPage.setTotal(page.getTotal());
newPage.setPageNum(page.getPageNum());
newPage.setPageSize(page.getPageSize());
List<R> list = toList(page.getList(), clazz);
newPage.setList(list);
return newPage;
}
}

View File

@@ -0,0 +1,34 @@
package com.cool.store.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* <p>
* Spring上下文工具
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 获取bean
* @param name beanName
* @param clazz bean类型
* @return bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}

View File

@@ -4,8 +4,8 @@ import com.cool.store.constants.CommonConstants;
import com.cool.store.dto.UserDTO;
import com.cool.store.dto.openPreparation.UserNameDTO;
import com.cool.store.entity.EnterpriseUserDO;
import com.cool.store.entity.login.UserLoginDO;
import com.cool.store.mapper.EnterpriseUserMapper;
import com.cool.store.response.oppty.UserResponse;
import com.cool.store.utils.StringUtil;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
@@ -175,4 +175,13 @@ public class EnterpriseUserDAO {
}
return enterpriseUserMapper.searchUserByUserIdsAndKeyword(userIdList, keyword);
}
/**
* 从平台库根据唯一id获取用户登录信息
* @param unionid 唯一id
* @return 用户登录信息
*/
public UserLoginDO getUserLoginByUnionid(String unionid) {
return enterpriseUserMapper.getUserLoginByUnionid(unionid);
}
}

View File

@@ -1,8 +1,10 @@
package com.cool.store.mapper;
import com.cool.store.annotation.PlatformDB;
import com.cool.store.dto.UserDTO;
import com.cool.store.dto.openPreparation.UserNameDTO;
import com.cool.store.entity.EnterpriseUserDO;
import com.cool.store.entity.login.UserLoginDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -103,4 +105,12 @@ public interface EnterpriseUserMapper {
List<EnterpriseUserDO> searchUserByUserIdsAndKeyword( @Param("userIdList") List<String> userIdList, @Param("keyword") String keyword);
List<String> getUserIdsByRegionIdList( @Param("regionIdList") List<String> regionIdList);
/**
* 从平台库根据唯一id获取用户登录信息
* @param unionid 唯一id
* @return 用户登录信息
*/
@PlatformDB
UserLoginDO getUserLoginByUnionid(@Param("unionid") String unionid);
}

View File

@@ -235,4 +235,10 @@
</foreach>
</if>
</select>
<select id="getUserLoginByUnionid" resultType="com.cool.store.entity.login.UserLoginDO">
SELECT user_id, mobile, password
FROM enterprise_user
WHERE unionid = #{unionid} AND active = true
</select>
</mapper>

View File

@@ -25,7 +25,7 @@
<result column="operator_list" property="operatorList" />
</resultMap>
<insert id="insertBatch" parameterType="java.util.List">
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
INSERT INTO zxjp_store_message (
store_id,
store_code,

View File

@@ -0,0 +1,27 @@
package com.cool.store.dto.login;
import com.cool.store.enums.LoginTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* <p>
* 登录DTO
* </p>
*
* @author wangff
* @since 2025/9/3
*/
@Data
public class UserLoginDTO {
@ApiModelProperty("手机号")
private String mobile;
@ApiModelProperty("密码")
private String password;
@NotNull(message = "登录类型不能为空")
private LoginTypeEnum loginType;
}

View File

@@ -0,0 +1,18 @@
package com.cool.store.dto.login;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* <p>
* RefreshToken登录DTO
* </p>
*
* @author wangff
* @since 2025/9/5
*/
@Data
public class UserRefreshLoginDTO {
@ApiModelProperty("RefreshToken")
private String refreshToken;
}

View File

@@ -0,0 +1,33 @@
package com.cool.store.entity.login;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 用户登录信息
* </p>
*
* @author wangff
* @since 2025/9/3
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDO {
/**
* 用户id
*/
private String userId;
/**
* 手机号
*/
private String mobile;
/**
* 密码
*/
private String password;
}

View File

@@ -49,6 +49,12 @@ public class StoreMasterDTO {
@ApiModelProperty("省市区")
private String area;
@ApiModelProperty("")
private String province;
@ApiModelProperty("")
private String city;
@ApiModelProperty("区/县")
private String district;
@ApiModelProperty("乡镇")
private String town;
@ApiModelProperty("门店地址")

View File

@@ -22,4 +22,6 @@ public class BatchPublishRequest {
@ApiModelProperty( "默认处理人信息 type[person position userGroup organization]")
List<CommonDTO> userInfoList;
@ApiModelProperty("事项类型")
private Integer matterType;
}

View File

@@ -0,0 +1,33 @@
package com.cool.store.userholder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* RefreshToken用户信息
* </p>
*
* @author wangff
* @since 2025/9/5
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RefreshUser {
/**
* 用户Id
*/
private String userId;
/**
* RefreshToken
*/
private String refreshToken;
/**
* 手机号
*/
private String mobile;
}

View File

@@ -0,0 +1,35 @@
package com.cool.store.vo.login;
import com.cool.store.entity.SysRoleDO;
import lombok.Data;
/**
* <p>
* 登录用户基本信息VO
* </p>
*
* @author wangff
* @since 2025/9/5
*/
@Data
public class UserBaseInfoVO {
private String id;
private String userId;
private String name;
private Boolean isAdmin;
private String mobile;
private String email;
private String avatar;
private String roles;
private String language;
private SysRoleDO sysRoleDO;
}

View File

@@ -0,0 +1,38 @@
package com.cool.store.vo.login;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 用户登录VO
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginVO {
/**
* 登录token
*/
private String accessToken;
/**
* 刷新token
*/
private String refreshToken;
/**
* accessToken过期时间
*/
private Integer expire;
/**
* 用户信息
*/
private UserBaseInfoVO user;
}

View File

@@ -1,5 +1,8 @@
package com.cool.store.service;
import com.cool.store.userholder.CurrentUser;
import com.cool.store.userholder.RefreshUser;
/**
* @Author suzhuhong
* @Date 2025/5/29 16:34
@@ -13,7 +16,10 @@ public interface EnterpriseService {
* @param mobile
* @return
*/
String getAccessToken(String mobile);
CurrentUser getLoginInfo(String mobile);
/**
* 获取并缓存refreshToken
*/
RefreshUser getRefreshUser(String userId, String mobile);
}

View File

@@ -1,6 +1,7 @@
package com.cool.store.service.impl;
import com.alibaba.fastjson.JSON;
import com.cool.store.constants.CommonConstants;
import com.cool.store.constants.RedisConstant;
import com.cool.store.dao.EnterpriseUserDAO;
import com.cool.store.entity.EnterpriseUserDO;
@@ -12,8 +13,10 @@ import com.cool.store.exception.ServiceException;
import com.cool.store.mapper.SysRoleMapper;
import com.cool.store.service.EnterpriseService;
import com.cool.store.userholder.CurrentUser;
import com.cool.store.userholder.RefreshUser;
import com.cool.store.utils.RedisUtilPool;
import com.cool.store.utils.poi.DateUtils;
import com.cool.store.utils.poi.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.RandomNumberGenerator;
@@ -48,7 +51,7 @@ public class EnterpriseServiceImpl implements EnterpriseService {
private String eid;
@Override
public String getAccessToken(String mobile) {
public CurrentUser getLoginInfo(String mobile) {
CurrentUser currentUser = new CurrentUser();
EnterpriseUserDO enterpriseUser = enterpriseUserDAO.selectByMobile(mobile);
if (Objects.isNull(enterpriseUser)){
@@ -107,8 +110,20 @@ public class EnterpriseServiceImpl implements EnterpriseService {
currentUser.setAppType("qw_self_dkf");
currentUser.setUnionid(enterpriseUser.getUnionid());
currentUser.setUserType(enterpriseUser.getUserType());
redisUtilPool.setString(RedisConstant.ACCESS_TOKEN_PREFIX + currentUser.getAccessToken(), JSON.toJSONString(currentUser), 24 * 60 * 60);
return currentUser.getAccessToken();
redisUtilPool.setString(RedisConstant.ACCESS_TOKEN_PREFIX + currentUser.getAccessToken(), JSON.toJSONString(currentUser), CommonConstants.ACTION_TOKEN_EXPIRE);
return currentUser;
}
@Override
public RefreshUser getRefreshUser(String userId, String mobile) {
if (StringUtils.isBlank(mobile)) {
EnterpriseUserDO userInfo = enterpriseUserDAO.getUserInfoById(userId);
mobile = userInfo.getMobile();
}
String refreshToken = getToken();
RefreshUser refreshUser = new RefreshUser(userId, refreshToken, mobile);
redisUtilPool.setString(RedisConstant.REFRESH_TOKEN_PREFIX + refreshToken, JSON.toJSONString(refreshUser), CommonConstants.REFRESH_TOKEN_EXPIRE);
return refreshUser;
}
public static void main(String[] args) {

View File

@@ -0,0 +1,78 @@
package com.cool.store.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.util.ArrayUtil;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.dao.MessageTemplateDAO;
import com.cool.store.entity.MessageTemplateDO;
import com.cool.store.entity.StoreMessageDO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.vo.notice.StoreMessageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* <p>
* 消息下发 服务类
* </p>
*
* @author wangff
* @since 2025/9/5
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MessageIssueService {
private final MessageTemplateDAO messageTemplateDAO;
private final SimpMessagingTemplate simpMessagingTemplate;
/**
* 下发即时通知消息
*
* @param list 门店消息列表
*/
@Async("generalThreadPool")
public void issueMessage(List<StoreMessageDO> list) {
if (CollectionUtils.isEmpty(list)) return;
log.info("下发即时通知, messageList:{}", JSONObject.toJSONString(list));
try {
Set<Long> messageTemplateIds = CollStreamUtil.toSet(list, StoreMessageDO::getMessageTemplateId);
List<MessageTemplateDO> messageTemplateList = messageTemplateDAO.getByIds(new ArrayList<>(messageTemplateIds));
Map<Long, MessageTemplateDO> messageTemplateMap = CollStreamUtil.toMap(messageTemplateList, MessageTemplateDO::getId, v -> v);
for (StoreMessageDO storeMessageDO : list) {
if (StringUtils.isNotBlank(storeMessageDO.getOperatorList())) {
String[] userIds = storeMessageDO.getOperatorList().split(",");
if (ArrayUtil.isNotEmpty(userIds)) {
MessageTemplateDO messageTemplateDO = messageTemplateMap.get(storeMessageDO.getMessageTemplateId());
if (Objects.isNull(messageTemplateDO)) {
throw new ServiceException(ErrorCodeEnum.MESSAGE_NOT_EXIST);
}
StoreMessageVO storeMessageVO = BeanUtil.toBean(storeMessageDO, StoreMessageVO.class);
BeanUtil.copyProperties(messageTemplateDO, storeMessageVO, "id");
String message = JSONObject.toJSONString(storeMessageVO);
for (String userId : userIds) {
try {
simpMessagingTemplate.convertAndSend("/queue/message/" + userId, message);
} catch (MessagingException e) {
log.info("即时通知下发异常, userId:{}, error:{}", userId, e.getMessage());
}
}
}
}
}
} catch (Exception e) {
log.info("即时通知下发异常", e);
}
}
}

View File

@@ -3,7 +3,6 @@ package com.cool.store.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.context.LoginUserInfo;
import com.cool.store.context.PartnerUserHolder;
import com.cool.store.dao.*;
import com.cool.store.dto.notice.CommonDTO;
import com.cool.store.dto.notice.MessageTemplateCountDTO;
@@ -26,11 +25,9 @@ import com.cool.store.vo.PartnerUserInfoVO;
import com.cool.store.vo.notice.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.BeanUtils;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Async;
@@ -73,6 +70,8 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
RedisUtilPool redisUtilPool;
@Resource
TaskExecutor noticeThreadPool;
@Resource
MessageIssueService messageIssueService;
@@ -178,6 +177,7 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
List<StoreAreaDTO> storeAreaDTOS = getStoreRange(request.getStoreInfoList());
List<String> storeIds = storeAreaDTOS.stream().map(StoreAreaDTO::getStoreId).collect(Collectors.toList());
Map<String, List<String>> authUser = getAuthUser(request.getUserInfoList(), storeIds);
List<StoreMessageDO> realtimeMessageList = new ArrayList<>();
list.stream().forEach(x -> {
List<StoreMessageDO> result = new ArrayList<>();
storeAreaDTOS.forEach(y->{
@@ -199,6 +199,9 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
result.add(storeMessageDO);
});
storeMessageDAO.batchInsert(result);
if (MatterTypeEnum.REALTIME.getCode().equals(request.getMatterType())) {
realtimeMessageList.addAll(result);
}
});
//存在第一个成功 第二个失败 会有问题 todo
@@ -207,6 +210,8 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
JSONObject.toJSONString(request.getStoreInfoList()),
JSONObject.toJSONString(request.getUserInfoList()),
userId);
// 即时消息下发
messageIssueService.issueMessage(realtimeMessageList);
} catch (Exception e) {
log.info("发布流程异常,已取消发布");
} finally {
@@ -357,6 +362,7 @@ public class MessageTemplateServiceImpl implements MessageTemplateService {
batchPublishRequest.setIds(Arrays.asList(messageTemplateDO.getId()));
batchPublishRequest.setStoreInfoList(JSONObject.parseArray(storeInfo, CommonDTO.class));
batchPublishRequest.setUserInfoList(JSONObject.parseArray(userInfo, CommonDTO.class));
batchPublishRequest.setMatterType(matterConfig.getMatterType());
try {
batchPublishMessageTemplate(batchPublishRequest,"");
} catch (ServiceException e) {

View File

@@ -160,6 +160,9 @@ public class SyncMainSysServerImpl implements SyncMainSysServer {
PointDetailInfoDO pointDetail = pointDetailDAO.getPointDetailInfoByPointId(shopInfo.getPointId());
if (info != null){
storeMasterDTO.setArea(info.getProvince()+info.getCity()+info.getDistrict());
storeMasterDTO.setProvince(info.getProvince());
storeMasterDTO.setCity(info.getCity());
storeMasterDTO.setDistrict(info.getDistrict());
storeMasterDTO.setTown(info.getTownship());
storeMasterDTO.setStoreAddress(info.getAddress());
storeMasterDTO.setLocationAddress(info.getAddress());

View File

@@ -0,0 +1,106 @@
package com.cool.store.service.login;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.constants.CommonConstants;
import com.cool.store.constants.RedisConstant;
import com.cool.store.context.CurrentUserHolder;
import com.cool.store.context.LoginUserInfo;
import com.cool.store.dao.EnterpriseUserDAO;
import com.cool.store.dto.login.UserLoginDTO;
import com.cool.store.dto.login.UserRefreshLoginDTO;
import com.cool.store.entity.EnterpriseUserDO;
import com.cool.store.entity.login.UserLoginDO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.EnterpriseService;
import com.cool.store.userholder.CurrentUser;
import com.cool.store.userholder.RefreshUser;
import com.cool.store.utils.RedisUtilPool;
import com.cool.store.utils.poi.constant.Constants;
import com.cool.store.vo.login.UserBaseInfoVO;
import com.cool.store.vo.login.UserLoginVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.MessageFormat;
import java.time.LocalDate;
/**
* <p>
* 登录基础服务类
* </p>
*
* @author wangff
* @since 2025/9/3
*/
@Slf4j
@Service
public abstract class LoginBaseService implements LoginStrategy {
@Resource
private RedisUtilPool redisUtilPool;
@Resource
private EnterpriseUserDAO enterpriseUserDAO;
@Resource
private EnterpriseService enterpriseService;
/**
* 策略登录实现方法
*/
public abstract ResponseResult userLogin(UserLoginDTO param, UserLoginDO userLoginDO);
@Override
public ResponseResult login(UserLoginDTO param) {
log.info("login:{}", JSONObject.toJSONString(param));
String errorPasswordCountKey = MessageFormat.format(RedisConstant.ERROR_PASSWORD_COUNT_KEY, LocalDate.now(), param.getMobile());
String errorCount = redisUtilPool.getString(errorPasswordCountKey);
//判断密码错误次数
if (StringUtils.isNotBlank(errorCount)) {
if (Integer.parseInt(errorCount) >= CommonConstants.MAX_ERROR_PASSWORD_COUNT) {
return ResponseResult.fail(ErrorCodeEnum.PASSWORD_ERROR_MAX_COUNT, errorCount);
}
}
EnterpriseUserDO enterpriseUserDO = enterpriseUserDAO.selectByMobile(param.getMobile());
UserLoginDO userLoginDO = enterpriseUserDAO.getUserLoginByUnionid(enterpriseUserDO.getUnionid());
return userLogin(param, userLoginDO);
}
@Override
public ResponseResult refreshLogin(UserRefreshLoginDTO param) {
String refreshTokenKey = RedisConstant.REFRESH_TOKEN_PREFIX + param.getRefreshToken();
String refreshUserStr = redisUtilPool.getString(refreshTokenKey);
if (StringUtils.isBlank(refreshUserStr)) {
return ResponseResult.fail(ErrorCodeEnum.REFRESH_TOKEN_INVALID);
}
RefreshUser refreshUser = JSONObject.parseObject(refreshUserStr, RefreshUser.class);
if (StringUtils.isBlank(refreshUser.getMobile())) {
return ResponseResult.fail(ErrorCodeEnum.REFRESH_TOKEN_INVALID);
}
UserLoginDO userLoginDO = new UserLoginDO(refreshUser.getUserId(), refreshUser.getMobile(), null);
return ResponseResult.success(getUserLoginInfo(userLoginDO));
}
@Override
public ResponseResult logout() {
LoginUserInfo currentUser = CurrentUserHolder.getUser();
String accessToken = currentUser.getAccessToken();
String key = RedisConstant.ACCESS_TOKEN_PREFIX + accessToken;
redisUtilPool.delKey(key);
return ResponseResult.success();
}
/**
* 获取登录accessToken
*
* @param userLoginDO 用户登录信息
* @return accessToken
*/
public UserLoginVO getUserLoginInfo(UserLoginDO userLoginDO) {
CurrentUser currentUser = enterpriseService.getLoginInfo(userLoginDO.getMobile());
UserBaseInfoVO userBAseInfoVO = BeanUtil.toBean(currentUser, UserBaseInfoVO.class);
RefreshUser refreshUser = enterpriseService.getRefreshUser(userLoginDO.getUserId(), userLoginDO.getMobile());
return new UserLoginVO(currentUser.getAccessToken(), refreshUser.getRefreshToken(), CommonConstants.ACTION_TOKEN_EXPIRE, userBAseInfoVO);
}
}

View File

@@ -0,0 +1,30 @@
package com.cool.store.service.login;
import com.cool.store.dto.login.UserLoginDTO;
import com.cool.store.dto.login.UserRefreshLoginDTO;
import com.cool.store.response.ResponseResult;
/**
* <p>
* 登录策略
* </p>
*
* @author wangff
* @since 2025/9/3
*/
public interface LoginStrategy {
/**
* 登录基础方法
*/
ResponseResult login(UserLoginDTO param);
/**
* refreshToken登录
*/
ResponseResult refreshLogin(UserRefreshLoginDTO param);
/**
* 登出
*/
ResponseResult logout();
}

View File

@@ -0,0 +1,55 @@
package com.cool.store.service.login.impl;
import com.aliyun.core.utils.StringUtils;
import com.cool.store.constants.CommonConstants;
import com.cool.store.constants.RedisConstant;
import com.cool.store.dto.login.UserLoginDTO;
import com.cool.store.entity.login.UserLoginDO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.login.LoginBaseService;
import com.cool.store.utils.Md5Utils;
import com.cool.store.utils.RedisUtilPool;
import com.cool.store.utils.poi.constant.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.time.LocalDate;
/**
* <p>
* 密码登录服务实现类
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Service
@Slf4j
@RequiredArgsConstructor
public class PasswordLoginServiceImpl extends LoginBaseService {
private final RedisUtilPool redisUtilPool;
@Override
public ResponseResult userLogin(UserLoginDTO param, UserLoginDO userLoginDO) {
if (StringUtils.isBlank(param.getPassword())) {
return ResponseResult.fail(ErrorCodeEnum.PASSWORD_MISSING);
}
if (StringUtils.isBlank(userLoginDO.getPassword())) {
return ResponseResult.fail(ErrorCodeEnum.IMPROVE_USER_INFO);
}
String password = Md5Utils.md5(param.getPassword() + CommonConstants.USER_AUTH_KEY);
if (!password.equals(userLoginDO.getPassword())) {
String errorPasswordCountKey = MessageFormat.format(RedisConstant.ERROR_PASSWORD_COUNT_KEY, LocalDate.now(), param.getMobile());
Long errorNum = redisUtilPool.incrby(errorPasswordCountKey, 1);
redisUtilPool.expire(errorPasswordCountKey, 24 * 60 * 60);
if(errorNum == 1){
return ResponseResult.fail(ErrorCodeEnum.PASSWORD_ERROR);
}
return ResponseResult.fail(ErrorCodeEnum.PASSWORD_ERROR_MULTI, errorNum.toString());
}
return ResponseResult.success(getUserLoginInfo(userLoginDO));
}
}

View File

@@ -220,6 +220,4 @@ public class Constants
public static final String WANG_LEI_JOB_NUMBER = "19060164";
}

View File

@@ -35,6 +35,65 @@
</dependency>
</dependencies>
<profiles>
<profile>
<id>dev</id>
<properties>
<profileActive>dev</profileActive>
</properties>
</profile>
<profile>
<id>dev2</id>
<properties>
<profileActive>dev2</profileActive>
</properties>
</profile>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profileActive>local</profileActive>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<profileActive>test</profileActive>
</properties>
</profile>
<profile>
<id>test3</id>
<properties>
<profileActive>test3</profileActive>
</properties>
</profile>
<profile>
<id>ab</id>
<properties>
<profileActive>ab</profileActive>
</properties>
</profile>
<profile>
<id>online</id>
<properties>
<profileActive>online</profileActive>
</properties>
</profile>
<profile>
<id>hd</id>
<properties>
<profileActive>hd</profileActive>
</properties>
</profile>
<profile>
<id>zcb</id>
<properties>
<profileActive>zcb</profileActive>
</properties>
</profile>
</profiles>
<build>
<plugins>

View File

@@ -40,4 +40,16 @@ public class PartnerWebApplication {
return defaultDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties("platform.datasource")
public DataSourceProperties platformDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public DataSource platformDataSource() {
return platformDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}

View File

@@ -0,0 +1,37 @@
package com.cool.store.aspect;
import com.cool.store.annotation.PlatformDB;
import com.cool.store.datasource.DataSourceContextHolder;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* <p>
* 数据源切换 切面
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(platformDB)")
public void before(PlatformDB platformDB) {
DataSourceContextHolder.setDataSourceType("platform");
}
@After("@annotation(platformDB)")
public void after(PlatformDB platformDB) {
DataSourceContextHolder.clearDataSourceType();
}
@AfterThrowing("@annotation(platformDB)")
public void afterThrowing(PlatformDB platformDB) {
DataSourceContextHolder.clearDataSourceType();
}
}

View File

@@ -60,7 +60,8 @@ public class SignValidateFilter implements Filter {
"/zxjp/**/api/audit/result",
"/zxjp/**/api/license",
"/zxjp/mini/line/getRegionPayPic",
"/zxjp/mini/miniProgram/getUserInfoByToken"
"/zxjp/mini/miniProgram/getUserInfoByToken",
"/zxjp/ws/**"
);

View File

@@ -52,7 +52,10 @@ public class TokenValidateFilter implements Filter {
"/zxjp/pc/sysRole/**",
"/zxjp/**/api/audit/result",
"/zxjp/pc/video/**",
"/zxjp/**/api/license"
"/zxjp/**/api/license",
"/zxjp/pc/v3/login/accountLogin",
"/zxjp/pc/v3/login/refreshLogin",
"/zxjp/ws/**"
);

View File

@@ -0,0 +1,72 @@
package com.cool.store.config.websocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* <p>
* WebSocket配置类
* </p>
*
* @author wangff
* @since 2025/9/2
*/
@Configuration
@EnableWebSocketMessageBroker
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/zxzs")
.setAllowedOrigins("*")
.withSockJS(); // 启用SockJS支持
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 配置消息代理
registry.enableSimpleBroker("/topic", "/queue") // 启用简单代理,用于广播和点对点消息
.setHeartbeatValue(new long[]{0, 10000})
.setTaskScheduler(webSocketTaskScheduler());
registry.setApplicationDestinationPrefixes("/app"); // 设置应用程序端点前缀
// registry.setUserDestinationPrefix("/user");
}
@Bean
public TaskScheduler webSocketTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(1);
taskScheduler.setThreadNamePrefix("websocket-heartbeat-");
taskScheduler.initialize();
return taskScheduler;
}
// @Override
// public void configureClientInboundChannel(ChannelRegistration registration) {
// registration.interceptors(new ChannelInterceptor() {
// @Override
// public Message<?> preSend(Message<?> message, MessageChannel channel) {
// StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
//
// if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// // 从STOMP头部获取login字段作为用户标识
// String login = accessor.getLogin();
// log.info("用户login:{}", login);
// if (login != null && !login.isEmpty()) {
// // 设置用户身份
// accessor.setUser(() -> login);
// }
// }
// return message;
// }
// });
// }
}

View File

@@ -0,0 +1,47 @@
package com.cool.store.controller.webb;
import com.cool.store.dto.login.UserLoginDTO;
import com.cool.store.dto.login.UserRefreshLoginDTO;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.login.LoginBaseService;
import com.cool.store.service.login.LoginStrategy;
import com.cool.store.utils.SpringContextUtil;
import com.cool.store.vo.login.UserLoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 登录 前端控制器
* </p>
*
* @author wangff
* @since 2025/9/4
*/
@Api(tags = "登录")
@RestController
@RequestMapping("/pc/v3/login")
@RequiredArgsConstructor
public class LoginController {
private final LoginBaseService loginBaseService;
@ApiOperation("账号密码登录")
@PostMapping("/accountLogin")
public ResponseResult<UserLoginVO> accountLogin(@RequestBody UserLoginDTO param) {
return SpringContextUtil.getBean(param.getLoginType().getClazzName(), LoginStrategy.class).login(param);
}
@ApiOperation("refresh登录")
@PostMapping("/refreshLogin")
public ResponseResult<UserLoginVO> refreshLogin(@RequestBody UserRefreshLoginDTO param) {
return loginBaseService.refreshLogin(param);
}
@ApiOperation("登出")
@PostMapping("/logout")
public ResponseResult logout() {
return loginBaseService.logout();
}
}

View File

@@ -0,0 +1,34 @@
package com.cool.store.controller.webb;
import com.cool.store.common.PageBasicInfo;
import com.cool.store.context.CurrentUserHolder;
import com.cool.store.response.MiniShopsResponse;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.StoreService;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* PC门店 前端控制器
* </p>
*
* @author wangff
* @since 2025/9/9
*/
@Api(tags = "PC门店")
@RestController
@RequestMapping("/pc/stores")
@RequiredArgsConstructor
public class PCStoreController {
private final StoreService storeService;
@ApiOperation("当前用户门店列表")
@PostMapping("/userStoreList")
public ResponseResult<PageInfo<MiniShopsResponse>> getCurrentUserStoreList(@RequestBody PageBasicInfo request) {
return ResponseResult.success(storeService.getStoreListByMobile(CurrentUserHolder.getUser().getMobile(), request.getPageNum(), request.getPageSize(), null, null));
}
}

View File

@@ -8,7 +8,6 @@ import com.cool.store.dto.ShopAccount.ShopAccountDTO;
import com.cool.store.request.GetPasswordDTO;
import com.cool.store.response.ResponseResult;
import com.cool.store.service.*;
import com.cool.store.vo.PartnerUserInfoVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
@@ -95,7 +94,7 @@ public class MiniShopAccountController {
@ApiOperation("获取标品登录token")
@GetMapping("/getAccessToken")
public ResponseResult<String> getAccessToken() {
return ResponseResult.success(enterpriseService.getAccessToken(PartnerUserHolder.getUser().getMobile()));
return ResponseResult.success(enterpriseService.getLoginInfo(PartnerUserHolder.getUser().getMobile()).getAccessToken());
}
}

View File

@@ -4,6 +4,10 @@ default.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3
default.datasource.username=coolstore
default.datasource.password=CSCErYcXniNYm7bT
platform.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_config?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&autoReconnect=true
platform.datasource.username=coolstore
platform.datasource.password=CSCErYcXniNYm7bT
#redis
redis.host.uri=http://userInfo:Cx111111@tstore-coolcollege.redis.rds.aliyuncs.com:6379/0

View File

@@ -1,9 +1,13 @@
#mysql config
default.datasource.url=jdbc:mysql://store10-coolcollege.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_10027?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
default.datasource.url=jdbc:mysql://zx-coolstore.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_10027?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
default.datasource.username=coolstore
default.datasource.password=CSCErYcXniNYm7bT
platform.datasource.url=jdbc:mysql://zx-coolstore.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_config?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
platform.datasource.username=coolstore
platform.datasource.password=CSCErYcXniNYm7bT
#redis
redis.host.uri=http://userInfo:Cx111111@store-coolcollege.redis.rds.aliyuncs.com:6379/0

View File

@@ -4,6 +4,10 @@ default.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3
default.datasource.username=coolstore
default.datasource.password=CSCErYcXniNYm7bT
platform.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_config?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&autoReconnect=true
platform.datasource.username=coolstore
platform.datasource.password=CSCErYcXniNYm7bT
#redis
redis.host.uri=http://userInfo:Cx111111@tstore-coolcollege-open.redis.rds.aliyuncs.com:6379/0
@@ -133,3 +137,8 @@ special.user.id=wpayJeDAAAhGIFgUJpJN-zg39JuNbYhg_woayJeDAAA0TC8mkCJeXouw94hYA-D3
ask.bot.url=https://test.auth.wx.askbot.cn
hqt.token.url=https://tc.cloud.hecom.cn
hqt.token.username=18161486722
hqt.token.grant_type=client_credentials
hqt.token.client.id=WrPffdGpcWkcPsbN
hqt.token.client.secret=rYe9Cwug5LwQNIBJAiW0a7weF9CAhYCD

View File

@@ -3,6 +3,10 @@ default.datasource.url=jdbc:mysql://zx-coolstore.mysql.rds.aliyuncs.com:3306/coo
default.datasource.username=coolstore
default.datasource.password=CSCErYcXniNYm7bT
platform.datasource.url=jdbc:mysql://zx-coolstore.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_config?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
platform.datasource.username=coolstore
platform.datasource.password=CSCErYcXniNYm7bT
#redis
redis.host.uri=http://userInfo:Cx111111@store-coolcollege.redis.rds.aliyuncs.com:6379/0

View File

@@ -4,6 +4,10 @@ default.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3
default.datasource.username=coolstore
default.datasource.password=CSCErYcXniNYm7bT
platform.datasource.url=jdbc:mysql://dingpushcoolcollege.mysql.rds.aliyuncs.com:3306/coolcollege_intelligent_config?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&autoReconnect=true
platform.datasource.username=coolstore
platform.datasource.password=CSCErYcXniNYm7bT
#redis
redis.host.uri=http://userInfo:Cx111111@tstore-coolcollege-open.redis.rds.aliyuncs.com:6379/0