Merge remote-tracking branch 'origin/master' into dev-chenjiajian

This commit is contained in:
嘉多宝宝 2026-01-21 16:07:10 +08:00
commit cace0eb2f5
38 changed files with 832 additions and 92 deletions

View File

@ -7,7 +7,9 @@ pipeline {
parameters { parameters {
string( string(
name: 'DEPLOY_SERVERS' name: 'DEPLOY_SERVERS',
defaultValue: '192.168.0.212',
description: '部署的目标服务器多个目标服务器以英文逗号分隔192.168.0.212,192.168.0.47,192.168.0.79'
) )
choice( choice(
name: 'spring.profiles.active', name: 'spring.profiles.active',

View File

@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.SERVER_NAME, contextId = "wechatMiniProgramApi", path = "/seer/mp") @FeignClient(name = ApiConstants.SERVER_NAME, contextId = "wechatMiniProgramApi", path = "/seer/mp")
public interface WechatMiniProgramApi { public interface MpMiniProgramApi {
@GetMapping("/wechat/sns/jscode2session") @GetMapping("/wechat/sns/jscode2session")
WxMiniProgramSessionDTO code2Session(@RequestParam("code") String code); WxMiniProgramSessionDTO code2Session(@RequestParam("code") String code);

View File

@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.SERVER_NAME, contextId = "wechatOfficialAccountApi", path = "/seer/mp") @FeignClient(name = ApiConstants.SERVER_NAME, contextId = "wechatOfficialAccountApi", path = "/seer/mp")
public interface WechatOfficialAccountApi { public interface MpOfficialAccountApi {
/** /**
* 获取用户用户信息和授权 Token * 获取用户用户信息和授权 Token

View File

@ -0,0 +1,77 @@
package com.seer.teach.mp.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.seer.teach.common.entity.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 公众号菜单表
* </p>
*
* @author Lingma
* @since 2026-01-21
*/
@Getter
@Setter
@TableName("mp_account_menu")
@Schema(name = "MpAccountMenuEntity", description = "公众号菜单表")
public class MpAccountMenuEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
@Schema(description = "公众号账号ID")
@TableField("account_id")
private Integer accountId;
@Schema(description = "菜单名称")
@TableField("name")
private String name;
@Schema(description = "菜单类型")
@TableField("type")
private String type;
@Schema(description = "菜单KEY值")
@TableField("menu_key")
private String menuKey;
@Schema(description = "菜单URL")
@TableField("url")
private String url;
@Schema(description = "媒体文件ID")
@TableField("media_id")
private String mediaId;
@Schema(description = "小程序APPID")
@TableField("appid")
private String appid;
@Schema(description = "小程序页面路径")
@TableField("pagepath")
private String pagepath;
@Schema(description = "文章ID")
@TableField("article_id")
private String articleId;
@Schema(description = "父菜单ID0表示一级菜单")
@TableField("parent_id")
private Integer parentId;
@Schema(description = "菜单JSON配置")
@TableField("menu_config")
private String menuConfig;
@Schema(description = "排序")
@TableField("sort")
private Integer sort;
@Schema(description = "状态0-禁用1-启用")
@TableField("status")
private Integer status;
}

View File

@ -0,0 +1,39 @@
package com.seer.teach.mp.enums;
import lombok.Getter;
/**
* <p>
* 活动时间状态枚举
* </p>
*
* @author Lingma
* @since 2026-01-21
*/
@Getter
public enum ActivityTimeStatusEnum {
NOT_STARTED(-1, "未开始"),
IN_PROGRESS(1, "进行中"),
ENDED(2, "已结束");
private final Integer code;
private final String description;
ActivityTimeStatusEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*/
public static ActivityTimeStatusEnum fromCode(Integer code) {
for (ActivityTimeStatusEnum status : ActivityTimeStatusEnum.values()) {
if (status.getCode().equals(code)) {
return status;
}
}
return null;
}
}

View File

@ -0,0 +1,16 @@
package com.seer.teach.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.seer.teach.mp.entity.MpAccountMenuEntity;
/**
* <p>
* 公众号菜单表 Mapper 接口
* </p>
*
* @author Lingma
* @since 2026-01-21
*/
public interface MpAccountMenuMapper extends BaseMapper<MpAccountMenuEntity> {
}

View File

@ -0,0 +1,59 @@
package com.seer.teach.mp.admin.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.seer.teach.common.ResultBean;
import com.seer.teach.mp.admin.controller.req.MpMenuSaveReq;
import com.seer.teach.mp.admin.controller.resp.AdminMpMenuResp;
import com.seer.teach.mp.admin.service.AdminMpAccountMenuService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* <p>
* 公众号菜单表 前端控制器
* </p>
*
* @author Wang
* @since 2025-08-02 15:45:13
*/
@Tag(name = "管理端 - 公众号菜单")
@RequiredArgsConstructor
@RestController
@RequestMapping("/mp/account/menu")
public class AdminMpAccountMenuController {
private final AdminMpAccountMenuService adminMpAccountMenuService;
@PostMapping("/create")
@Operation(summary = "创建公众号菜单")
@SaCheckPermission("admin:mp:account:menu:create")
public ResultBean<Boolean> createAccountMenu(@Valid @RequestBody MpMenuSaveReq createReqVO) {
return ResultBean.success(adminMpAccountMenuService.createAccountMenu(createReqVO));
}
@DeleteMapping("/delete")
@Operation(summary = "删除公众号菜单")
@SaCheckPermission("admin:mp:account:menu:delete")
public ResultBean<Boolean> deleteMenu(@RequestParam("accountId") Integer accountId) {
return ResultBean.success(adminMpAccountMenuService.deleteMenuByAppId(accountId));
}
@GetMapping("/list")
@Operation(summary = "获取公众号菜单列表")
@SaCheckPermission("admin:mp:account:menu:list")
public ResultBean<List<AdminMpMenuResp>> getMenuList(@RequestParam("accountId") Integer accountId) {
return ResultBean.success(adminMpAccountMenuService.getMenuList(accountId));
}
}

View File

@ -11,6 +11,6 @@ public class MpActivityQueryReq extends PageRequest {
@Schema(description = "活动名称") @Schema(description = "活动名称")
private String activityName; private String activityName;
@Schema(description = "活动状态:0-禁用1-启用") @Schema(description = "活动状态:-1-未开始1-进行中2-已结束")
private Integer status; private Integer status;
} }

View File

@ -0,0 +1,65 @@
package com.seer.teach.mp.admin.controller.req;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "公众号菜单保存请求")
@Data
public class MpMenuSaveReq {
@Schema(description = "公众号ID", required = true)
@NotNull(message = "公众号ID不能为空")
private Integer accountId;
@Schema(description = "菜单配置JSON字符串")
@Valid
private List<Button> button;
@Schema(description = "菜单按钮结构")
@Data
public static class Button {
@Schema(description = "菜单的响应动作类型", example = "click")
@Size(max = 32, message = "菜单类型长度不能超过32个字符")
private String type;
@Schema(description = "菜单标题", required = true, example = "今日歌曲")
@NotBlank(message = "菜单标题不能为空")
@Size(max = 60, message = "菜单标题长度不能超过60个字符")
private String name;
@Schema(description = "菜单KEY值", example = "V1001_TODAY_MUSIC")
@Size(max = 128, message = "菜单KEY值长度不能超过128个字符")
private String key;
@Schema(description = "网页链接", example = "http://www.soso.com/")
@Size(max = 1024, message = "网页链接长度不能超过1024个字符")
private String url;
@Schema(description = "媒体文件ID", example = "MEDIA_ID1")
@Size(max = 128, message = "媒体文件ID长度不能超过128个字符")
private String mediaId;
@Schema(description = "小程序的appid", example = "wx286b93c14bbf93aa")
@Size(max = 32, message = "小程序appid长度不能超过32个字符")
private String appid;
@Schema(description = "小程序的页面路径", example = "pages/lunar/index")
@Size(max = 128, message = "小程序页面路径长度不能超过128个字符")
private String pagepath;
@Schema(description = "发布后获得的合法 article_id", example = "ARTICLE_ID1")
@Size(max = 128, message = "文章ID长度不能超过128个字符")
private String articleId;
@Schema(description = "二级菜单结构体数组")
@Valid
private List<Button> subButton;
}
}

View File

@ -0,0 +1,63 @@
package com.seer.teach.mp.admin.controller.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "公众号菜单响应")
@Data
public class AdminMpMenuResp {
@Schema(description = "菜单ID")
private Integer id;
@Schema(description = "公众号账号ID")
private Integer accountId;
@Schema(description = "菜单名称")
private String name;
@Schema(description = "菜单类型")
private String type;
@Schema(description = "菜单KEY值")
private String menuKey;
@Schema(description = "菜单URL")
private String url;
@Schema(description = "媒体文件ID")
private String mediaId;
@Schema(description = "小程序APPID")
private String appid;
@Schema(description = "小程序页面路径")
private String pagepath;
@Schema(description = "文章ID")
private String articleId;
@Schema(description = "父菜单ID")
private Integer parentId;
@Schema(description = "菜单JSON配置")
private String menuConfig;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "状态0-禁用1-启用")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "子菜单列表")
private List<AdminMpMenuResp> subMenus;
}

View File

@ -0,0 +1,264 @@
package com.seer.teach.mp.admin.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.seer.teach.mp.admin.controller.req.MpMenuSaveReq;
import com.seer.teach.mp.admin.controller.resp.AdminMpMenuResp;
import com.seer.teach.mp.entity.MpAccountEntity;
import com.seer.teach.mp.entity.MpAccountMenuEntity;
import com.seer.teach.mp.factory.MpServiceFactory;
import com.seer.teach.mp.service.IMpAccountService;
import com.seer.teach.mp.service.IMpAccountMenuService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@RequiredArgsConstructor
@Slf4j
@Validated
@Service
public class AdminMpAccountMenuService {
private final IMpAccountService mpAccountService;
private final IMpAccountMenuService mpMenuService;
@Autowired
@Lazy
private MpServiceFactory mpServiceFactory;
/**
* 创建公众号菜单
*
* @param createReqVO 创建信息
* @return 是否创建成功
*/
public boolean createAccountMenu(@Valid MpMenuSaveReq createReqVO) {
// 验证公众号是否存在
MpAccountEntity account = mpAccountService.getById(createReqVO.getAccountId());
if (account == null) {
throw new RuntimeException("公众号不存在");
}
try {
// 获取微信公众号服务
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(account.getAppId());
// 构建微信菜单对象
WxMenu wxMpMenu = buildWxMpMenu(createReqVO);
// 调用微信API创建菜单
wxMpService.getMenuService().menuCreate(wxMpMenu);
// 保存菜单配置到数据库
saveMenuToDatabase(createReqVO, account);
log.info("[createAccount][创建公众号菜单成功公众号ID为:{}]", account.getId());
return true;
} catch (WxErrorException e) {
log.error("[createAccount][创建公众号菜单失败公众号ID为:{}],错误信息:{}",
createReqVO.getAccountId(), e.getError().getErrorMsg());
throw new RuntimeException("创建公众号菜单失败:" + e.getError().getErrorMsg());
}
}
/**
* 构建微信菜单对象
*/
private WxMenu buildWxMpMenu(MpMenuSaveReq createReqVO) {
WxMenu wxMpMenu = new WxMenu();
List<WxMenuButton> buttons = new ArrayList<>();
for (MpMenuSaveReq.Button button : createReqVO.getButton()) {
buttons.add(convertToWxMpButton(button));
}
wxMpMenu.setButtons(buttons);
return wxMpMenu;
}
/**
* 将请求按钮转换为微信菜单按钮对象
*/
private WxMenuButton convertToWxMpButton(MpMenuSaveReq.Button button) {
WxMenuButton wxButton = new WxMenuButton();
wxButton.setType(button.getType());
wxButton.setName(button.getName());
// 根据按钮类型设置相应属性
if ("click".equalsIgnoreCase(button.getType())) {
wxButton.setKey(button.getKey());
} else if ("view".equalsIgnoreCase(button.getType())) {
wxButton.setUrl(button.getUrl());
} else if ("miniprogram".equalsIgnoreCase(button.getType())) {
wxButton.setAppId(button.getAppid());
wxButton.setPagePath(button.getPagepath());
wxButton.setUrl(button.getUrl()); // 备用URL
} else if ("media_id".equalsIgnoreCase(button.getType()) ||
"view_limited".equalsIgnoreCase(button.getType())) {
wxButton.setMediaId(button.getMediaId());
} else if ("article_id".equalsIgnoreCase(button.getType()) ||
"article_view_limited".equalsIgnoreCase(button.getType())) {
wxButton.setArticleId(button.getArticleId());
} else {
// 其他类型如扫码拍照等设置key值
if (button.getKey() != null) {
wxButton.setKey(button.getKey());
}
}
if (button.getSubButton() != null && !button.getSubButton().isEmpty()) {
List<WxMenuButton> subButtons = new ArrayList<>();
for (MpMenuSaveReq.Button subButton : button.getSubButton()) {
subButtons.add(convertToWxMpButton(subButton));
}
wxButton.setSubButtons(subButtons);
}
return wxButton;
}
/**
* 保存菜单配置到数据库
*/
private void saveMenuToDatabase(MpMenuSaveReq createReqVO, MpAccountEntity account) {
// 保存新的菜单配置
for (int i = 0; i < createReqVO.getButton().size(); i++) {
MpMenuSaveReq.Button button = createReqVO.getButton().get(i);
saveButtonToDatabase(button, account, 0, i);
}
}
private void saveButtonToDatabase(MpMenuSaveReq.Button button, MpAccountEntity account, Integer parentId, Integer sort) {
MpAccountMenuEntity menuEntity = new MpAccountMenuEntity();
menuEntity.setAccountId(account.getId());
menuEntity.setAppid(account.getAppId());
menuEntity.setName(button.getName());
menuEntity.setType(button.getType());
menuEntity.setMenuKey(button.getKey());
menuEntity.setUrl(button.getUrl());
menuEntity.setMediaId(button.getMediaId());
menuEntity.setAppid(button.getAppid());
menuEntity.setPagepath(button.getPagepath());
menuEntity.setArticleId(button.getArticleId());
menuEntity.setParentId(parentId);
menuEntity.setSort(sort);
menuEntity.setStatus(1);
mpMenuService.save(menuEntity);
// 如果有子菜单递归保存
if (button.getSubButton() != null && !button.getSubButton().isEmpty()) {
Integer currentMenuId = menuEntity.getId();
for (int i = 0; i < button.getSubButton().size(); i++) {
com.seer.teach.mp.admin.controller.req.MpMenuSaveReq.Button subButton = button.getSubButton().get(i);
saveButtonToDatabase(subButton, account, currentMenuId, i);
}
}
}
/**
* 获取公众号菜单列表
*
* @param accountId 公众号ID
* @return 菜单列表
*/
public List<AdminMpMenuResp> getMenuList(Integer accountId) {
// 查询一级菜单
List<MpAccountMenuEntity> menus = mpMenuService.list(
com.baomidou.mybatisplus.core.toolkit.Wrappers.<MpAccountMenuEntity>lambdaQuery()
.eq(MpAccountMenuEntity::getAccountId, accountId)
.eq(MpAccountMenuEntity::getParentId, 0)
.orderByAsc(MpAccountMenuEntity::getSort)
);
List<AdminMpMenuResp> result = new ArrayList<>();
for (MpAccountMenuEntity menu : menus) {
AdminMpMenuResp resp = convertToMenuResp(menu);
// 查询子菜单
List<MpAccountMenuEntity> subMenus = mpMenuService.list(
com.baomidou.mybatisplus.core.toolkit.Wrappers.<MpAccountMenuEntity>lambdaQuery()
.eq(MpAccountMenuEntity::getAccountId, accountId)
.eq(MpAccountMenuEntity::getParentId, menu.getId())
.orderByAsc(MpAccountMenuEntity::getSort)
);
if (!subMenus.isEmpty()) {
resp.setSubMenus(subMenus.stream()
.map(this::convertToMenuResp)
.toList());
}
result.add(resp);
}
return result;
}
/**
* 删除公众号菜单
*
* @param accountId 菜单ID
* @return 是否删除成功
*/
public boolean deleteMenuByAppId(Integer accountId) {
MpAccountEntity account = mpAccountService.getById(accountId);
if(Objects.isNull(account)){
log.error("[deleteMenu][删除公众号菜单失败公众号ID为:{}]", accountId);
return false;
}
String appId = account.getAppId();
try {
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
wxMpService.getMenuService().menuDelete();
deleteMenuRecursive(appId);
return true;
} catch (Exception e) {
log.error("[deleteMenu][删除公众号菜单失败菜单AppID为:{}]", appId, e);
return false;
}
}
/**
* 递归删除菜单
*/
private void deleteMenuRecursive(String appId) {
mpMenuService.removeById(new LambdaQueryWrapper<MpAccountMenuEntity>().eq(MpAccountMenuEntity::getAppid, appId));
}
/**
* 转换菜单实体为响应对象
*/
private AdminMpMenuResp convertToMenuResp(MpAccountMenuEntity menu) {
AdminMpMenuResp resp = new AdminMpMenuResp();
resp.setId(menu.getId());
resp.setAccountId(menu.getAccountId());
resp.setName(menu.getName());
resp.setType(menu.getType());
resp.setMenuKey(menu.getMenuKey());
resp.setUrl(menu.getUrl());
resp.setMediaId(menu.getMediaId());
resp.setAppid(menu.getAppid());
resp.setPagepath(menu.getPagepath());
resp.setArticleId(menu.getArticleId());
resp.setParentId(menu.getParentId());
resp.setMenuConfig(menu.getMenuConfig());
resp.setSort(menu.getSort());
resp.setStatus(menu.getStatus());
resp.setCreateTime(menu.getCreateTime());
resp.setUpdateTime(menu.getUpdateTime());
return resp;
}
}

View File

@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.seer.teach.common.PageListBean; import com.seer.teach.common.PageListBean;
import com.seer.teach.common.utils.PageConverterUtils; import com.seer.teach.common.utils.PageConverterUtils;
import com.seer.teach.mp.api.WechatOfficialAccountApi; import com.seer.teach.mp.api.MpOfficialAccountApi;
import com.seer.teach.mp.admin.controller.req.MpAccountPageReq; import com.seer.teach.mp.admin.controller.req.MpAccountPageReq;
import com.seer.teach.mp.admin.controller.req.MpAccountReq; import com.seer.teach.mp.admin.controller.req.MpAccountReq;
import com.seer.teach.mp.admin.controller.resp.AdminMpAccountResp; import com.seer.teach.mp.admin.controller.resp.AdminMpAccountResp;
@ -36,7 +36,7 @@ public class AdminMpAccountService {
private final IMpAccountService mpAccountService; private final IMpAccountService mpAccountService;
private final WechatOfficialAccountApi wechatOfficialAccountApi; private final MpOfficialAccountApi mpOfficialAccountApi;
@Resource @Resource
@Lazy @Lazy
@ -44,7 +44,7 @@ public class AdminMpAccountService {
public void loadCache() { public void loadCache() {
mpServiceFactory.init(); mpServiceFactory.init();
wechatOfficialAccountApi.initMpAccountCache(); mpOfficialAccountApi.initMpAccountCache();
} }
/** /**

View File

@ -7,7 +7,9 @@ pipeline {
parameters { parameters {
string( string(
name: 'DEPLOY_SERVERS' name: 'DEPLOY_SERVERS',
defaultValue: '192.168.0.212',
description: '部署的目标服务器多个目标服务器以英文逗号分隔192.168.0.212,192.168.0.47,192.168.0.79'
) )
choice( choice(
name: 'spring.profiles.active', name: 'spring.profiles.active',

View File

@ -0,0 +1,27 @@
-- 创建公众号菜单表
DROP TABLE IF EXISTS `mp_account_menu`;
CREATE TABLE `mp_account_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`account_id` bigint NOT NULL COMMENT '公众号账号ID',
`name` varchar(100) NOT NULL COMMENT '菜单名称',
`type` varchar(32) DEFAULT NULL COMMENT '菜单类型click,view,scancode_push,scancode_waitmsg,pic_sysphoto,pic_photo_or_album,pic_weixin,location_select,media_id,article_id,article_view_limited,miniprogram等',
`menu_key` varchar(128) DEFAULT NULL COMMENT '菜单KEY值',
`url` varchar(1024) DEFAULT NULL COMMENT '网页链接',
`media_id` varchar(128) DEFAULT NULL COMMENT '媒体文件ID',
`appid` varchar(32) DEFAULT NULL COMMENT '小程序的appid',
`pagepath` varchar(128) DEFAULT NULL COMMENT '小程序的页面路径',
`article_id` varchar(128) DEFAULT NULL COMMENT '文章ID',
`parent_id` int NOT NULL DEFAULT '0' COMMENT '父菜单ID0表示一级菜单',
`menu_config` text COMMENT '菜单JSON配置',
`sort` int DEFAULT '0' COMMENT '排序',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态0-禁用1-启用',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`create_by` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '创建人',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`update_by` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NULL DEFAULT NULL COMMENT '修改人',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` varchar(20) DEFAULT 'Default' COMMENT '租户id',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_account_id` (`account_id`) USING BTREE,
KEY `idx_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公众号菜单表';

View File

@ -8,7 +8,7 @@ import com.seer.teach.common.annotation.EncryptionAnnotation;
import com.seer.teach.common.annotation.LogPrint; import com.seer.teach.common.annotation.LogPrint;
import com.seer.teach.mp.app.controller.req.AppMpAgentActivityQrCodeQueryReq; import com.seer.teach.mp.app.controller.req.AppMpAgentActivityQrCodeQueryReq;
import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp; import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp;
import com.seer.teach.mp.app.service.IAppAgentActivityParticipantService; import com.seer.teach.mp.app.service.IAppAgentActivityRelationService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -38,7 +38,7 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class AppAgentActivityRelationController { public class AppAgentActivityRelationController {
private final IAppAgentActivityParticipantService agentActivityParticipantService; private final IAppAgentActivityRelationService agentActivityParticipantService;
@Operation(summary = "获取代理商参加的活动列表") @Operation(summary = "获取代理商参加的活动列表")
@GetMapping() @GetMapping()

View File

@ -11,6 +11,6 @@ public class AppActivityQueryReq extends PageRequest {
@Schema(description = "活动名称") @Schema(description = "活动名称")
private String activityName; private String activityName;
@Schema(description = "活动状态:0-禁用1-启用") @Schema(description = "活动状态:-1-未开始1-进行中2-已结束")
private Integer status; private Integer status;
} }

View File

@ -3,6 +3,8 @@ package com.seer.teach.mp.app.controller.resp;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime;
@Schema(name = "AgentActivityParticipant", description = "代理商活动参与记录") @Schema(name = "AgentActivityParticipant", description = "代理商活动参与记录")
@Data @Data
public class AgentActivityParticipantResp { public class AgentActivityParticipantResp {
@ -15,7 +17,19 @@ public class AgentActivityParticipantResp {
@Schema(description = "代理商ID") @Schema(description = "代理商ID")
private Integer agentId; private Integer agentId;
@Schema(description = "活动名称")
private String activityName; private String activityName;
@Schema(description = "活动描述")
private String description;
@Schema(description = "活动开始时间")
private LocalDateTime startTime;
@Schema(description = "活动结束时间")
private LocalDateTime endTime;
@Schema(description = "活动状态:-1-未开始1-进行中2-已结束")
private Integer status;
} }

View File

@ -23,7 +23,7 @@ public class AppActivityResp {
@Schema(description = "活动结束时间") @Schema(description = "活动结束时间")
private LocalDateTime endTime; private LocalDateTime endTime;
@Schema(description = "活动状态:0-禁用1-启用") @Schema(description = "活动状态:-1-未开始1-进行中2-已结束")
private Integer status; private Integer status;
@Schema(description = "创建时间") @Schema(description = "创建时间")

View File

@ -64,7 +64,7 @@ public class AppMpNotificationService {
if (outMessage == null) { if (outMessage == null) {
return ""; return "";
} }
log.info("[handleMessage][appId({}) 回复微信服务端的消息,内容:{}]", appId, outMessage);
// 第三步返回消息 // 第三步返回消息
if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式 if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式
return outMessage.toXml(); return outMessage.toXml();

View File

@ -13,7 +13,7 @@ import java.util.List;
* @author Lingma * @author Lingma
* @since 2025-12-30 * @since 2025-12-30
*/ */
public interface IAppAgentActivityParticipantService { public interface IAppAgentActivityRelationService {
/** /**
* 根据活动ID和代理商ID获取参与记录 * 根据活动ID和代理商ID获取参与记录

View File

@ -11,7 +11,7 @@ import com.seer.teach.mp.app.controller.req.MpGenerateQrCodeReq;
import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp; import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp;
import com.seer.teach.mp.app.controller.resp.MpQrCodeResp; import com.seer.teach.mp.app.controller.resp.MpQrCodeResp;
import com.seer.teach.mp.app.service.AppOfficialQrCodeService; import com.seer.teach.mp.app.service.AppOfficialQrCodeService;
import com.seer.teach.mp.app.service.IAppAgentActivityParticipantService; import com.seer.teach.mp.app.service.IAppAgentActivityRelationService;
import com.seer.teach.mp.app.service.IAppAgentService; import com.seer.teach.mp.app.service.IAppAgentService;
import com.seer.teach.mp.entity.MpActivityEntity; import com.seer.teach.mp.entity.MpActivityEntity;
import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity; import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity;
@ -25,6 +25,7 @@ import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -42,9 +43,9 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivityParticipantService { public class AppAgentActivityRelationServiceImpl implements IAppAgentActivityRelationService {
private final IMpAgentActivityRelationService agentActivityParticipantService; private final IMpAgentActivityRelationService agentActivityRelationService;
private final IAppAgentService appAgentService; private final IAppAgentService appAgentService;
@ -63,14 +64,14 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity
if(!userAgentIds.contains(agentId) ){ if(!userAgentIds.contains(agentId) ){
throw new CommonException(ResultCodeEnum.RELATION_NOT_FOUND); throw new CommonException(ResultCodeEnum.RELATION_NOT_FOUND);
} }
var participants = agentActivityParticipantService.getListByAgentId(agentId); var participants = agentActivityRelationService.getListByAgentId(agentId);
if (CollectionUtil.isEmpty(participants)) { if (CollectionUtil.isEmpty(participants)) {
return List.of(); return List.of();
} }
Set<Integer> activityIds = participants.stream().map(MpAgentActivityParticipantEntity::getActivityId).collect(Collectors.toSet()); Set<Integer> activityIds = participants.stream().map(MpAgentActivityParticipantEntity::getActivityId).collect(Collectors.toSet());
List<MpActivityEntity> parentInfos = mpActivityService.listByIds(activityIds); List<MpActivityEntity> parentInfos = mpActivityService.getEffectiveActivityListByIds(activityIds);
var activityInfoMap = parentInfos.stream().collect(Collectors.toMap(MpActivityEntity::getId, activity -> activity)); var activityInfoMap = parentInfos.stream().collect(Collectors.toMap(MpActivityEntity::getId, activity -> activity));
@ -89,10 +90,13 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity
return ""; return "";
} }
if(activity.getStatus() != 1){ // 检查活动是否在有效时间内且处于启用状态
LocalDateTime now = LocalDateTime.now();
boolean isWithinTimeRange = !now.isBefore(activity.getStartTime()) && !now.isAfter(activity.getEndTime());
if(activity.getStatus() != 1 || !isWithinTimeRange){
return ""; return "";
} }
MpAgentActivityParticipantEntity relation = agentActivityParticipantService.getParticipantsByActivityAndAgent(activityId, agentId); MpAgentActivityParticipantEntity relation = agentActivityRelationService.getParticipantsByActivityAndAgent(activityId, agentId);
AssertUtils.notNull(relation, ResultCodeEnum.INVALID_ACTIVITY); AssertUtils.notNull(relation, ResultCodeEnum.INVALID_ACTIVITY);
if(StringUtils.isNotBlank(relation.getQrCodeUrl())){ if(StringUtils.isNotBlank(relation.getQrCodeUrl())){
return relation.getQrCodeUrl(); return relation.getQrCodeUrl();
@ -113,7 +117,7 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity
relation.setQrCodeUrl(mpQrCodeResp.getQrCodeUrl()); relation.setQrCodeUrl(mpQrCodeResp.getQrCodeUrl());
} }
} }
agentActivityParticipantService.updateById(relation); agentActivityRelationService.updateById(relation);
return mpQrCodeResp.getQrCodeUrl(); return mpQrCodeResp.getQrCodeUrl();
} }
@ -126,6 +130,19 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity
MpActivityEntity activity = activityInfoMap.get(entity.getActivityId()); MpActivityEntity activity = activityInfoMap.get(entity.getActivityId());
if(Objects.nonNull(activity)){ if(Objects.nonNull(activity)){
resp.setActivityName(activity.getActivityName()); resp.setActivityName(activity.getActivityName());
resp.setDescription(activity.getDescription());
resp.setStartTime(activity.getStartTime());
resp.setEndTime(activity.getEndTime());
// 根据当前时间判断活动状态
LocalDateTime now = LocalDateTime.now();
if (now.isBefore(activity.getStartTime())) {
resp.setStatus(1); // 未开始
} else if (now.isAfter(activity.getEndTime())) {
resp.setStatus(3); // 已结束
} else {
resp.setStatus(1); // 进行中
}
} }
return resp; return resp;
} }

View File

@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@RestController @RestController
public class AppMiniProgramApiImpl implements WechatMiniProgramApi{ public class AppMiniProgramApiImpl implements MpMiniProgramApi {
private final IWechatMiniProgramService wechatMiniProgramService; private final IWechatMiniProgramService wechatMiniProgramService;

View File

@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j @Slf4j
@RestController @RestController
public class AppOfficialAccountApiImpl implements WechatOfficialAccountApi{ public class AppOfficialAccountApiImpl implements MpOfficialAccountApi {
@Autowired @Autowired
private MpServiceFactory mpServiceFactory; private MpServiceFactory mpServiceFactory;

View File

@ -132,7 +132,7 @@ public class MpServiceFactory {
private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) { private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) {
WxMpMessageRouter router = new WxMpMessageRouter(mpService); WxMpMessageRouter router = new WxMpMessageRouter(mpService);
// 记录所有事件的日志 // 记录所有事件的日志
router.rule().async(true).handler(messageReceiveHandler).next(); router.rule().async(false).handler(messageReceiveHandler).next();
// 关注事件 // 关注事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT) router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)

View File

@ -0,0 +1,64 @@
package com.seer.teach.mp.handler;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpUtil;
import com.seer.teach.mp.entity.MpActivityEntity;
import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity;
import com.seer.teach.mp.service.IMpActivityService;
import com.seer.teach.mp.service.IMpAgentActivityRelationService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
@Slf4j
public abstract class AbstractHandler {
@Autowired
private IMpAgentActivityRelationService mpAgentActivityRelationService;
@Autowired
private IMpActivityService mpActivityService;
protected boolean isAgentActivityScanEvent(WxMpXmlMessage wxMessage) {
String eventKey = wxMessage.getEventKey();
log.info("[handle][用户({})] 获取用户二维码信息:[{}]", wxMessage.getFromUser(), eventKey);
if (StringUtils.hasText(eventKey) && eventKey.startsWith("agentId") && eventKey.contains("activityId")) {
return true;
}
return false;
}
protected WxMpXmlOutMessage buildAgentActivityOutMessage(WxMpXmlMessage wxMessage) {
String eventKey = wxMessage.getEventKey();
Map<String, String> paramMap = HttpUtil.decodeParamMap(eventKey, StandardCharsets.UTF_8);
Integer agentId = MapUtil.getInt(paramMap, "agentId", 0);
Integer activityId = MapUtil.getInt(paramMap, "activityId", 0);
if (agentId > 0 && activityId > 0) {
MpAgentActivityParticipantEntity participantsByActivityAndAgent = mpAgentActivityRelationService.getParticipantsByActivityAndAgent(activityId, agentId);
MpActivityEntity activity = mpActivityService.getById(activityId);
if (Objects.nonNull(participantsByActivityAndAgent)) {
WxMpXmlOutNewsMessage.Item item = new WxMpXmlOutNewsMessage.Item();
item.setTitle(activity.getActivityName());
item.setDescription(activity.getDescription());
item.setPicUrl(participantsByActivityAndAgent.getQrCodeUrl());
item.setUrl("https://mp.seerteach.net/login?" + eventKey);
WxMpXmlOutNewsMessage build = WxMpXmlOutMessage.NEWS()
.addArticle(item)
.toUser(wxMessage.getFromUser())
.fromUser(wxMessage.getToUser())
.build();
log.info("[handle][扫码处理,内容:{}]", build);
return build;
}
}
return null;
}
}

View File

@ -14,6 +14,9 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map; import java.util.Map;
@ -37,11 +40,15 @@ public class MessageReceiveHandler implements WxMpMessageHandler {
wxMessageEntity.setAppId(wxMpService.getWxMpConfigStorage().getAppId()); wxMessageEntity.setAppId(wxMpService.getWxMpConfigStorage().getAppId());
log.info("[handle][保存消息,内容:{}]", wxMessageEntity); log.info("[handle][保存消息,内容:{}]", wxMessageEntity);
mpMessageService.save(wxMessageEntity); mpMessageService.save(wxMessageEntity);
return WxMpXmlOutMessage.TEXT() WxMpXmlOutMessage textMessage = WxMpXmlOutMessage.TEXT()
.toUser(wxMessage.getFromUser()) .toUser(wxMessage.getFromUser())
.fromUser(wxMessage.getToUser()) .fromUser(wxMessage.getToUser())
.content("欢迎使用SeerTeach AI") .content("欢迎使用SeerTeach AI")
.build(); .build();
textMessage.setCreateTime(LocalDateTime.now().atZone(ZoneId.of("Asia/Shanghai"))
.toInstant()
.toEpochMilli());
return textMessage;
} }
} }

View File

@ -16,7 +16,6 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
@ -27,7 +26,7 @@ import java.util.Objects;
*/ */
@Component @Component
@Slf4j @Slf4j
public class ScanHandler implements WxMpMessageHandler { public class ScanHandler extends AbstractHandler implements WxMpMessageHandler {
@Autowired @Autowired
private IMpAgentActivityRelationService mpAgentActivityRelationService; private IMpAgentActivityRelationService mpAgentActivityRelationService;
@ -40,28 +39,8 @@ public class ScanHandler implements WxMpMessageHandler {
log.info("[handle][扫码处理,内容:{}]", wxMessage); log.info("[handle][扫码处理,内容:{}]", wxMessage);
String eventKey = wxMessage.getEventKey(); String eventKey = wxMessage.getEventKey();
log.info("[handle][用户({})] 获取用户二维码信息:[{}]", wxMessage.getFromUser(), eventKey); log.info("[handle][用户({})] 获取用户二维码信息:[{}]", wxMessage.getFromUser(), eventKey);
if(StringUtils.hasText(eventKey) && eventKey.startsWith("agentId") && eventKey.contains("activityId")){ if(isAgentActivityScanEvent(wxMessage)){
Map<String, String> paramMap = HttpUtil.decodeParamMap(eventKey, StandardCharsets.UTF_8); return buildAgentActivityOutMessage(wxMessage);
Integer agentId = MapUtil.getInt(paramMap,"agentId",0);
Integer activityId = MapUtil.getInt(paramMap,"activityId",0);
if(agentId > 0 && activityId > 0){
MpAgentActivityParticipantEntity participantsByActivityAndAgent = mpAgentActivityRelationService.getParticipantsByActivityAndAgent(activityId, agentId);
MpActivityEntity activity = mpActivityService.getById(activityId);
if(Objects.nonNull(participantsByActivityAndAgent)){
WxMpXmlOutNewsMessage.Item item = new WxMpXmlOutNewsMessage.Item();
item.setTitle(activity.getActivityName());
item.setDescription(activity.getDescription());
item.setPicUrl(participantsByActivityAndAgent.getQrCodeUrl());
item.setUrl("https://mp.seerteach.net/login?" + eventKey);
WxMpXmlOutNewsMessage build = WxMpXmlOutMessage.NEWS()
.addArticle(item)
.toUser(wxMessage.getFromUser())
.fromUser(wxMessage.getToUser())
.build();
log.info("[handle][扫码处理,内容:{}]", build);
return build;
}
}
} }
return null; return null;
} }

View File

@ -1,14 +1,11 @@
package com.seer.teach.mp.handler; package com.seer.teach.mp.handler;
import com.seer.teach.common.constants.CommonConstant;
import com.seer.teach.common.enums.UserRelationEnum;
import com.seer.teach.mp.convert.WxMpUserConvert; import com.seer.teach.mp.convert.WxMpUserConvert;
import com.seer.teach.mp.entity.MpUserSubscribeEntity; import com.seer.teach.mp.entity.MpUserSubscribeEntity;
import com.seer.teach.mp.service.IMpUserSubscribeService; import com.seer.teach.mp.service.IMpUserSubscribeService;
import com.seer.teach.user.api.UserInfoServiceApi; import com.seer.teach.user.api.UserInfoServiceApi;
import com.seer.teach.user.api.UserRelationServiceApi; import com.seer.teach.user.api.UserRelationServiceApi;
import com.seer.teach.user.api.dto.UserInfoDTO; import com.seer.teach.user.api.dto.UserInfoDTO;
import com.seer.teach.user.api.dto.UserRelationDTO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.session.WxSessionManager;
@ -19,9 +16,9 @@ import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map; import java.util.Map;
import java.util.Objects;
/** /**
@ -31,7 +28,7 @@ import java.util.Map;
*/ */
@Component @Component
@Slf4j @Slf4j
public class SubscribeHandler implements WxMpMessageHandler { public class SubscribeHandler extends AbstractHandler implements WxMpMessageHandler {
@Autowired @Autowired
private UserInfoServiceApi userInfoServiceApi; private UserInfoServiceApi userInfoServiceApi;
@ -54,23 +51,17 @@ public class SubscribeHandler implements WxMpMessageHandler {
log.info("[handle][用户({})] 转换用户信息成功!", wxMessage.getFromUser()); log.info("[handle][用户({})] 转换用户信息成功!", wxMessage.getFromUser());
Integer userId = userInfoServiceApi.save(userInfoDTO); Integer userId = userInfoServiceApi.save(userInfoDTO);
log.info("[handle][用户({})] 保存用户信息成功!", wxMessage.getFromUser()); log.info("[handle][用户({})] 保存用户信息成功!", wxMessage.getFromUser());
String eventKey = wxMessage.getEventKey();
if(StringUtils.hasText(eventKey) && eventKey.startsWith(CommonConstant.WX_MP_QR_SCENE_PREFIX)){
log.info("[handle][用户({})] 获取用户二维码信息:[{}]", wxMessage.getFromUser(), eventKey);
String userIdStr = eventKey.substring(CommonConstant.WX_MP_QR_SCENE_PREFIX.length());
UserRelationDTO userRelationDTO = new UserRelationDTO();
userRelationDTO.setUserId(userId);
userRelationDTO.setRelationId(Integer.parseInt(userIdStr));
userRelationDTO.setType(UserRelationEnum.PARENT_CHILD.getDescription());
boolean relationResult = userRelationServiceApi.saveUserRelation(userRelationDTO);
log.info("[handle][用户({})] 保存用户关系结果:{}", wxMessage.getFromUser(),relationResult);
}
String appId = wxMpService.getWxMpConfigStorage().getAppId(); String appId = wxMpService.getWxMpConfigStorage().getAppId();
MpUserSubscribeEntity mpUserSubscribeEntity = WxMpUserConvert.INSTANCE.convertOne(wxMpUser, userId); MpUserSubscribeEntity mpUserSubscribeEntity = WxMpUserConvert.INSTANCE.convertOne(wxMpUser, userId);
mpUserSubscribeEntity.setAppId(appId); mpUserSubscribeEntity.setAppId(appId);
boolean saved = mpUserSubscribeService.save(mpUserSubscribeEntity); boolean saved = mpUserSubscribeService.save(mpUserSubscribeEntity);
log.info("[handle][用户({})] 保存用户关注信息结果:{}", wxMessage.getFromUser(),saved); log.info("[handle][用户({})] 保存用户关注信息结果:{}", wxMessage.getFromUser(),saved);
if(isAgentActivityScanEvent(wxMessage)){
WxMpXmlOutMessage message = buildAgentActivityOutMessage(wxMessage);
if(Objects.nonNull(message)){
return message;
}
}
} catch (WxErrorException e) { } catch (WxErrorException e) {
log.error("[handle][用户({})] 获取用户信息失败!", wxMessage.getFromUser(), e); log.error("[handle][用户({})] 获取用户信息失败!", wxMessage.getFromUser(), e);

View File

@ -0,0 +1,16 @@
package com.seer.teach.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.seer.teach.mp.entity.MpAccountMenuEntity;
/**
* <p>
* 公众号菜单表 服务类
* </p>
*
* @author Lingma
* @since 2026-01-21
*/
public interface IMpAccountMenuService extends IService<MpAccountMenuEntity> {
}

View File

@ -1,6 +1,5 @@
package com.seer.teach.mp.service; package com.seer.teach.mp.service;
import cn.hutool.db.PageResult;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.seer.teach.mp.entity.MpAccountEntity; import com.seer.teach.mp.entity.MpAccountEntity;
@ -14,5 +13,11 @@ import com.seer.teach.mp.entity.MpAccountEntity;
*/ */
public interface IMpAccountService extends IService<MpAccountEntity> { public interface IMpAccountService extends IService<MpAccountEntity> {
/**
* 根据AppId查询公众号账号
*
* @param appId AppId
* @return 公众号账号
*/
MpAccountEntity getOneByAppId(String appId); MpAccountEntity getOneByAppId(String appId);
} }

View File

@ -3,6 +3,7 @@ package com.seer.teach.mp.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.seer.teach.mp.entity.MpActivityEntity; import com.seer.teach.mp.entity.MpActivityEntity;
import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
@ -33,8 +34,10 @@ public interface IMpActivityService extends IService<MpActivityEntity> {
boolean deleteActivity(Integer id); boolean deleteActivity(Integer id);
/** /**
* 获取代理商活动名称列表 * 根据ID查询代理商活动
* @return 代理商活动名称列表 *
* @param ids 活动ID
* @return 活动实体
*/ */
List<String> getActivityName(); List<MpActivityEntity> getEffectiveActivityListByIds(Collection<Integer> ids);
} }

View File

@ -0,0 +1,24 @@
package com.seer.teach.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.seer.teach.mp.entity.MpAccountMenuEntity;
import com.seer.teach.mp.mapper.MpAccountMenuMapper;
import com.seer.teach.mp.service.IMpAccountMenuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* <p>
* 公众号菜单表 服务实现类
* </p>
*
* @author Lingma
* @since 2026-01-21
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class MpAccountMenuServiceImpl extends ServiceImpl<MpAccountMenuMapper, MpAccountMenuEntity> implements IMpAccountMenuService {
}

View File

@ -1,10 +1,13 @@
package com.seer.teach.mp.service.impl; package com.seer.teach.mp.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.seer.teach.common.constants.CommonConstant;
import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.common.utils.CommonUtils;
import com.seer.teach.mp.entity.MpActivityEntity; import com.seer.teach.mp.entity.MpActivityEntity;
import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity; import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity;
import com.seer.teach.mp.mapper.MpAgentActivityMapper; import com.seer.teach.mp.mapper.MpAgentActivityMapper;
@ -16,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List; import java.util.List;
/** /**
@ -106,14 +110,16 @@ public class MpActivityServiceImpl extends ServiceImpl<MpAgentActivityMapper, Mp
} }
@Override @Override
public List<String> getActivityName() { public List<MpActivityEntity> getEffectiveActivityListByIds(Collection<Integer> ids) {
return this.list().stream() if(CollectionUtil.isEmpty(ids)){
.map(MpActivityEntity::getActivityName).toList(); return List.of();
} }
return super.list(new LambdaQueryWrapper<MpActivityEntity>().in(MpActivityEntity::getId, ids).in(MpActivityEntity::getStatus, CommonConstant.ENABLE));
}
@Override @Override
public boolean saveOrUpdate(MpActivityEntity entity) { public boolean saveOrUpdate(MpActivityEntity entity) {
boolean result = super.saveOrUpdate(entity); return super.saveOrUpdate(entity);
return result;
} }
} }

View File

@ -8,7 +8,7 @@ import com.seer.teach.common.enums.RoleEnum;
import com.seer.teach.common.enums.UserRelationEnum; import com.seer.teach.common.enums.UserRelationEnum;
import com.seer.teach.common.exception.CommonException; import com.seer.teach.common.exception.CommonException;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.mp.api.WechatMiniProgramApi; import com.seer.teach.mp.api.MpMiniProgramApi;
import com.seer.teach.mp.api.resp.WxMiniProgramSessionDTO; import com.seer.teach.mp.api.resp.WxMiniProgramSessionDTO;
import com.seer.teach.user.app.auth.LoginType; import com.seer.teach.user.app.auth.LoginType;
import com.seer.teach.user.app.auth.request.LoginParam; import com.seer.teach.user.app.auth.request.LoginParam;
@ -45,7 +45,7 @@ import java.util.Objects;
public class MiniProgramLoginStrategy extends AbstractLoginStrategy implements LoginStrategy { public class MiniProgramLoginStrategy extends AbstractLoginStrategy implements LoginStrategy {
private final WechatMiniProgramApi wechatMiniProgramApi; private final MpMiniProgramApi mpMiniProgramApi;
private final IUserService userService; private final IUserService userService;
@ -70,7 +70,7 @@ public class MiniProgramLoginStrategy extends AbstractLoginStrategy implements L
@Override @Override
public LoginUser authenticate(LoginParam request) { public LoginUser authenticate(LoginParam request) {
WxMiniProgramSessionDTO wxMiniProgramSessionDTO = wechatMiniProgramApi.code2Session(request.getJsCode()); WxMiniProgramSessionDTO wxMiniProgramSessionDTO = mpMiniProgramApi.code2Session(request.getJsCode());
AssertUtils.isTrue(wxMiniProgramSessionDTO.isSuccess(), ResultCodeEnum.WX_OAUTH_USED_ERROR); AssertUtils.isTrue(wxMiniProgramSessionDTO.isSuccess(), ResultCodeEnum.WX_OAUTH_USED_ERROR);
UserAuthEntity userAuth = userAuthService.getOneByOpenIdAndUnionId(wxMiniProgramSessionDTO.getOpenid(), wxMiniProgramSessionDTO.getUnionid()); UserAuthEntity userAuth = userAuthService.getOneByOpenIdAndUnionId(wxMiniProgramSessionDTO.getOpenid(), wxMiniProgramSessionDTO.getUnionid());
boolean isExistsChildren = false; boolean isExistsChildren = false;

View File

@ -2,7 +2,7 @@ package com.seer.teach.user.app.auth.service.strategy;
import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.mp.api.WechatOfficialAccountApi; import com.seer.teach.mp.api.MpOfficialAccountApi;
import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO; import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO;
import com.seer.teach.user.app.auth.LoginType; import com.seer.teach.user.app.auth.LoginType;
import com.seer.teach.user.app.auth.request.LoginParam; import com.seer.teach.user.app.auth.request.LoginParam;
@ -26,7 +26,7 @@ import java.util.Objects;
public class OfficialAccountLoginStrategy extends AbstractLoginStrategy implements LoginStrategy { public class OfficialAccountLoginStrategy extends AbstractLoginStrategy implements LoginStrategy {
@Autowired @Autowired
private WechatOfficialAccountApi wechatOfficialAccountApi; private MpOfficialAccountApi mpOfficialAccountApi;
@Autowired @Autowired
private IUserService userService; private IUserService userService;
@ -42,7 +42,7 @@ public class OfficialAccountLoginStrategy extends AbstractLoginStrategy implemen
@Override @Override
public LoginUser authenticate(LoginParam request) { public LoginUser authenticate(LoginParam request) {
log.info("微信公众号登录:{}",request); log.info("微信公众号登录:{}",request);
WxOAuth2AccessTokenDTO wxOAuth2AccessToken = wechatOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(), request.getJsCode()); WxOAuth2AccessTokenDTO wxOAuth2AccessToken = mpOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(), request.getJsCode());
log.debug("获取用户信息:{}",wxOAuth2AccessToken); log.debug("获取用户信息:{}",wxOAuth2AccessToken);
AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR); AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR);
String userOpenId = wxOAuth2AccessToken.getOpenId(); String userOpenId = wxOAuth2AccessToken.getOpenId();

View File

@ -5,7 +5,7 @@ import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.exception.CommonException; import com.seer.teach.common.exception.CommonException;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.common.utils.CommonUtils; import com.seer.teach.common.utils.CommonUtils;
import com.seer.teach.mp.api.WechatOfficialAccountApi; import com.seer.teach.mp.api.MpOfficialAccountApi;
import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO; import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO;
import com.seer.teach.user.app.auth.LoginType; import com.seer.teach.user.app.auth.LoginType;
import com.seer.teach.user.app.auth.request.LoginParam; import com.seer.teach.user.app.auth.request.LoginParam;
@ -33,7 +33,7 @@ public class OfficialOauthWithAccountLoginStrategy extends AbstractLoginStrategy
private final IUserService userService; private final IUserService userService;
@Autowired @Autowired
private WechatOfficialAccountApi wechatOfficialAccountApi; private MpOfficialAccountApi mpOfficialAccountApi;
@Override @Override
public LoginType getType() { public LoginType getType() {
@ -54,7 +54,7 @@ public class OfficialOauthWithAccountLoginStrategy extends AbstractLoginStrategy
AssertUtils.notNull(accountUser, ResultCodeEnum.USERNAME_OR_PASSWORD_IS_ERROR); AssertUtils.notNull(accountUser, ResultCodeEnum.USERNAME_OR_PASSWORD_IS_ERROR);
String password = CommonUtils.encryptPassword(request.getPassword()); String password = CommonUtils.encryptPassword(request.getPassword());
if (password.equals(accountUser.getPassword())) { if (password.equals(accountUser.getPassword())) {
WxOAuth2AccessTokenDTO wxOAuth2AccessToken = wechatOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(), request.getJsCode()); WxOAuth2AccessTokenDTO wxOAuth2AccessToken = mpOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(), request.getJsCode());
log.debug("获取用户信息:{}",wxOAuth2AccessToken); log.debug("获取用户信息:{}",wxOAuth2AccessToken);
AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR); AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR);
String userOpenId = wxOAuth2AccessToken.getOpenId(); String userOpenId = wxOAuth2AccessToken.getOpenId();

View File

@ -5,7 +5,7 @@ import com.seer.teach.common.enums.RoleEnum;
import com.seer.teach.common.exception.CommonException; import com.seer.teach.common.exception.CommonException;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.common.utils.CommonUtils; import com.seer.teach.common.utils.CommonUtils;
import com.seer.teach.mp.api.WechatOfficialAccountApi; import com.seer.teach.mp.api.MpOfficialAccountApi;
import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO; import com.seer.teach.mp.api.resp.WxOAuth2AccessTokenDTO;
import com.seer.teach.user.app.auth.LoginType; import com.seer.teach.user.app.auth.LoginType;
import com.seer.teach.user.app.auth.request.LoginParam; import com.seer.teach.user.app.auth.request.LoginParam;
@ -38,7 +38,7 @@ public class WarehouseAccountLoginStrategy extends AbstractLoginStrategy impleme
private final IUserService userService; private final IUserService userService;
private final WechatOfficialAccountApi wechatOfficialAccountApi; private final MpOfficialAccountApi mpOfficialAccountApi;
private final IUserAuthService userAuthService; private final IUserAuthService userAuthService;
@ -68,7 +68,7 @@ public class WarehouseAccountLoginStrategy extends AbstractLoginStrategy impleme
.findAny() .findAny()
.orElseThrow(() -> new CommonException(ResultCodeEnum.USER_NOT_HAVE_WAREHOUSE_ROLE)); .orElseThrow(() -> new CommonException(ResultCodeEnum.USER_NOT_HAVE_WAREHOUSE_ROLE));
WxOAuth2AccessTokenDTO wxOAuth2AccessToken = wechatOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(),request.getJsCode()); WxOAuth2AccessTokenDTO wxOAuth2AccessToken = mpOfficialAccountApi.getUserWxOAuth2AccessToken(request.getAppId(),request.getJsCode());
AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR); AssertUtils.notNull(wxOAuth2AccessToken, ResultCodeEnum.WX_OAUTH_USED_ERROR);
UserAuthEntity userAuthEntity = userAuthService.getOneByOpenId(wxOAuth2AccessToken.getOpenId()); UserAuthEntity userAuthEntity = userAuthService.getOneByOpenId(wxOAuth2AccessToken.getOpenId());
if (Objects.isNull(userAuthEntity)) { if (Objects.isNull(userAuthEntity)) {

View File

@ -6,7 +6,7 @@ import com.seer.teach.common.exception.CommonException;
import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.common.utils.CommonUtils; import com.seer.teach.common.utils.CommonUtils;
import com.seer.teach.iot.api.UserDeviceServiceApi; import com.seer.teach.iot.api.UserDeviceServiceApi;
import com.seer.teach.mp.api.WechatMiniProgramApi; import com.seer.teach.mp.api.MpMiniProgramApi;
import com.seer.teach.mp.api.resp.WxMiniProgramSessionDTO; import com.seer.teach.mp.api.resp.WxMiniProgramSessionDTO;
import com.seer.teach.user.app.auth.LoginType; import com.seer.teach.user.app.auth.LoginType;
import com.seer.teach.user.app.auth.request.LoginParam; import com.seer.teach.user.app.auth.request.LoginParam;
@ -35,7 +35,7 @@ public class WechatChildrenAccountLoginStrategy extends AbstractLoginStrategy im
private final UserDeviceServiceApi userDeviceServiceApi; private final UserDeviceServiceApi userDeviceServiceApi;
private final WechatMiniProgramApi wechatMiniProgramApi; private final MpMiniProgramApi mpMiniProgramApi;
private final IUserRelationService userRelationService; private final IUserRelationService userRelationService;
@ -54,7 +54,7 @@ public class WechatChildrenAccountLoginStrategy extends AbstractLoginStrategy im
AssertUtils.notNull(accountUser, ResultCodeEnum.USERNAME_OR_PASSWORD_IS_ERROR); AssertUtils.notNull(accountUser, ResultCodeEnum.USERNAME_OR_PASSWORD_IS_ERROR);
String password = CommonUtils.encryptPassword(request.getPassword()); String password = CommonUtils.encryptPassword(request.getPassword());
if (password.equals(accountUser.getPassword())) { if (password.equals(accountUser.getPassword())) {
WxMiniProgramSessionDTO miniProgramSessionDTO = wechatMiniProgramApi.code2Session(request.getJsCode()); WxMiniProgramSessionDTO miniProgramSessionDTO = mpMiniProgramApi.code2Session(request.getJsCode());
AssertUtils.notNull(miniProgramSessionDTO, ResultCodeEnum.WX_SESSION_CODE_USED_ERROR); AssertUtils.notNull(miniProgramSessionDTO, ResultCodeEnum.WX_SESSION_CODE_USED_ERROR);
// 使用开关控制是否检查设备绑定 // 使用开关控制是否检查设备绑定
if (deviceCheckEnabled) { if (deviceCheckEnabled) {