feat:会员管理增加搜索条件
This commit is contained in:
1 parent
9811d336e0
commit
e04e1ca08b
8 files changed
+229
-17
No files matched your search
+7
-1
@@ -35,6 +35,9 @@ public class MemberUserPageReqVO extends PageParam {
|
||||
@Schema(description = "会员标签编号列表", example = "[1, 2]")
|
||||
private List<Long> tagIds;
|
||||
|
||||
@Schema(description = "用户标签类型列表,对应 member_recent_tag 字典,匹配任一所选标签", example = "[\"1\", \"4\"]")
|
||||
private List<String> memberRecentTagList;
|
||||
|
||||
@Schema(description = "会员等级编号", example = "1")
|
||||
private Long levelId;
|
||||
|
||||
@@ -44,9 +47,12 @@ public class MemberUserPageReqVO extends PageParam {
|
||||
@Schema(description = "用户 Code(App 我的页面展示的 ID)", example = "924199")
|
||||
private String userCode;
|
||||
|
||||
@Schema(description = "性别:1 男 2 女", example = "1")
|
||||
@Schema(description = "性别:0 未知,1 男,2 女", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否禁用:0 未禁用,1 已禁用", example = "1")
|
||||
private Integer status;
|
||||
|
||||
// TODO 芋艿:注册用户类型;
|
||||
|
||||
// TODO 芋艿:登录用户类型;
|
||||
|
||||
+16
-3
@@ -8,12 +8,14 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* 会员 User Mapper
|
||||
@@ -82,10 +84,10 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
|
||||
if (CollUtil.isNotEmpty(reqVO.getTagIds())) {
|
||||
tagIdSql = reqVO.getTagIds().stream()
|
||||
.map(tagId -> "FIND_IN_SET(" + tagId + ", tag_ids)")
|
||||
.collect(Collectors.joining(" OR "));
|
||||
.collect(Collectors.joining(" OR ", "(", ")"));
|
||||
}
|
||||
// 分页查询
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MemberUserDO>()
|
||||
LambdaQueryWrapper<MemberUserDO> query = new LambdaQueryWrapperX<MemberUserDO>()
|
||||
.likeIfPresent(MemberUserDO::getMobile, reqVO.getMobile())
|
||||
.betweenIfPresent(MemberUserDO::getLoginDate, reqVO.getLoginDate())
|
||||
.likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())
|
||||
@@ -94,8 +96,19 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
|
||||
.eqIfPresent(MemberUserDO::getGroupId, reqVO.getGroupId())
|
||||
.eqIfPresent(MemberUserDO::getUserCode, reqVO.getUserCode())
|
||||
.eqIfPresent(MemberUserDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(MemberUserDO::getStatus, reqVO.getStatus())
|
||||
.apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql)
|
||||
.orderByDesc(MemberUserDO::getId));
|
||||
.orderByDesc(MemberUserDO::getId);
|
||||
if (CollUtil.isNotEmpty(reqVO.getMemberRecentTagList())) {
|
||||
String placeholders = IntStream.range(0, reqVO.getMemberRecentTagList().size())
|
||||
.mapToObj(index -> "{" + index + "}")
|
||||
.collect(Collectors.joining(", "));
|
||||
// 与列表展示的用户标签使用同一张表;EXISTS 避免多标签或重复标签影响分页总数。
|
||||
query.exists("SELECT 1 FROM member_recent_tag tag WHERE tag.member_id = member_user.id"
|
||||
+ " AND tag.deleted = 0 AND tag.tag_biz_type IN (" + placeholders + ")",
|
||||
reqVO.getMemberRecentTagList().toArray());
|
||||
}
|
||||
return selectPage(reqVO, query);
|
||||
}
|
||||
|
||||
default Long selectCountByGroupId(Long groupId) {
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package cn.iocoder.yudao.module.member.dal.mysql.user;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
|
||||
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@TestPropertySource(properties = {
|
||||
"spring.datasource.url=jdbc:h2:mem:member_user_page_filters;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value;",
|
||||
"spring.sql.init.schema-locations=classpath:/sql/create_tables.sql,classpath:/sql/member_user_page_filter_schema.sql"
|
||||
})
|
||||
class MemberUserMapperTest extends BaseDbUnitTest {
|
||||
|
||||
@Resource
|
||||
private MemberUserMapper memberUserMapper;
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@BeforeEach
|
||||
void setUpUsersAndTags() {
|
||||
jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
int[] sexes = {1, 2, 1, 1, 1, 2, 0, 1};
|
||||
for (int index = 0; index < sexes.length; index++) {
|
||||
long id = index + 1L;
|
||||
jdbcTemplate.update("INSERT INTO member_user"
|
||||
+ " (id, user_code, nickname, sex, status, mobile, register_ip, tag_ids, deleted)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, '127.0.0.1', ?, ?)",
|
||||
id, String.format("%06d", id), "会员" + id, sexes[index],
|
||||
id == 2 || id == 5 ? 1 : 0, "1390000000" + id, id == 4 ? "4" : null, id == 8);
|
||||
}
|
||||
insertTag(1L, "4", false);
|
||||
insertTag(1L, "4", false); // 历史重复标签不能重复计算用户。
|
||||
insertTag(1L, "1", false);
|
||||
insertTag(2L, "4", false);
|
||||
insertTag(3L, "5", false);
|
||||
insertTag(5L, "4", true); // 已删除的标签不参与筛选。
|
||||
insertTag(6L, "5", false);
|
||||
insertTag(8L, "4", false); // 已删除的用户不参与筛选。
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanRecentTags() {
|
||||
jdbcTemplate.update("DELETE FROM member_recent_tag");
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageWithoutFiltersIncludesUntaggedUsers() {
|
||||
assertPage(memberUserMapper.selectPage(new MemberUserPageReqVO()), 7L,
|
||||
7L, 6L, 5L, 4L, 3L, 2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageBySex() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setSex(1);
|
||||
assertPage(memberUserMapper.selectPage(request), 4L, 5L, 4L, 3L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageByUnknownSexDoesNotTreatZeroAsEmpty() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setSex(0);
|
||||
assertPage(memberUserMapper.selectPage(request), 1L, 7L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageByDisabledStatus() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setStatus(1);
|
||||
assertPage(memberUserMapper.selectPage(request), 2L, 5L, 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageByEnabledStatusDoesNotTreatZeroAsEmpty() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setStatus(0);
|
||||
assertPage(memberUserMapper.selectPage(request), 5L, 7L, 6L, 4L, 3L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageCombinesStatusSexAndRecentTags() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setSex(2);
|
||||
request.setStatus(1);
|
||||
request.setMemberRecentTagList(Arrays.asList("4", "5"));
|
||||
assertPage(memberUserMapper.selectPage(request), 1L, 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageByRecentTagIgnoresLegacyAndDeletedTags() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setMemberRecentTagList(Collections.singletonList("4"));
|
||||
assertPage(memberUserMapper.selectPage(request), 2L, 2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageBySexAndAnySelectedTag() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setSex(1);
|
||||
request.setMemberRecentTagList(Arrays.asList("4", "5"));
|
||||
assertPage(memberUserMapper.selectPage(request), 2L, 3L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageCombinesRecentTagsWithExistingFilters() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setSex(1);
|
||||
request.setMobile("13900000001");
|
||||
request.setMemberRecentTagList(Arrays.asList("4", "5"));
|
||||
assertPage(memberUserMapper.selectPage(request), 1L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageDoesNotDuplicateUsersOrInflateTotal() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setMemberRecentTagList(Arrays.asList("1", "4"));
|
||||
request.setPageSize(1);
|
||||
assertPage(memberUserMapper.selectPage(request), 2L, 2L);
|
||||
request.setPageNo(2);
|
||||
assertPage(memberUserMapper.selectPage(request), 2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageWithEmptyTagListDoesNotFilterUsers() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setMemberRecentTagList(Collections.emptyList());
|
||||
assertPage(memberUserMapper.selectPage(request), 7L, 7L, 6L, 5L, 4L, 3L, 2L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectPageTreatsUnknownAndSpecialTagValuesAsParameters() {
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
request.setMemberRecentTagList(Arrays.asList("missing", "4') OR 1=1 --"));
|
||||
assertPage(memberUserMapper.selectPage(request), 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindIndexedTagParametersFromFrontendAndFilterPage() {
|
||||
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
|
||||
servletRequest.addParameter("sex", "2");
|
||||
servletRequest.addParameter("status", "1");
|
||||
servletRequest.addParameter("memberRecentTagList[0]", "4");
|
||||
servletRequest.addParameter("memberRecentTagList[1]", "5");
|
||||
MemberUserPageReqVO request = new MemberUserPageReqVO();
|
||||
ServletRequestDataBinder binder = new ServletRequestDataBinder(request);
|
||||
binder.bind(servletRequest);
|
||||
|
||||
assertThat(binder.getBindingResult().hasErrors()).isFalse();
|
||||
assertThat(request.getMemberRecentTagList()).containsExactly("4", "5");
|
||||
assertPage(memberUserMapper.selectPage(request), 1L, 2L);
|
||||
}
|
||||
|
||||
private void insertTag(Long memberId, String type, boolean deleted) {
|
||||
jdbcTemplate.update("INSERT INTO member_recent_tag (member_id, tag_biz_type, deleted) VALUES (?, ?, ?)",
|
||||
memberId, type, deleted);
|
||||
}
|
||||
|
||||
private void assertPage(PageResult<MemberUserDO> result, long total, Long... ids) {
|
||||
assertThat(result.getTotal()).isEqualTo(total);
|
||||
assertThat(result.getList()).extracting(MemberUserDO::getId).containsExactly(ids);
|
||||
}
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS register_terminal int;
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS app_refresh_date datetime;
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS occ varchar(64);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS height double;
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS weight varchar(32);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS description varchar(255);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS wechat_num varchar(64);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS wechat_qr_code varchar(255);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS lng double;
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS lat double;
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS province varchar(64);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS city varchar(64);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS district varchar(64);
|
||||
ALTER TABLE member_user ADD COLUMN IF NOT EXISTS address varchar(255);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS member_recent_tag (
|
||||
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
member_id bigint NOT NULL,
|
||||
tag_biz_type varchar(32),
|
||||
deleted bit NOT NULL DEFAULT FALSE
|
||||
);
|
||||
@@ -246,8 +246,8 @@ tx-map:
|
||||
app-secret: uYWt4DalxHgFnk6d9ul2KLdPVM1ABD9m
|
||||
|
||||
al-config:
|
||||
access-key-id: LTAI5tBrkf3ynA5CMLb4TYbo
|
||||
access-key-secret: u3Za5XigBfRXo0iJHSbPgOSZTDVN3C
|
||||
access-key-id: LTAI5tAK9M3fnMhmsxVZnuTn
|
||||
access-key-secret: 5J4EubEtDKHxvBytbzkgHTMQmzqpwd
|
||||
face-scene-id: 1000016556
|
||||
face-verify-callback-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
@@ -255,16 +255,9 @@ tx-map:
|
||||
app-key: GGCBZ-SVPWT-IO6XL-LHZ3I-YPMBS-O6B6D
|
||||
app-secret: uYWt4DalxHgFnk6d9ul2KLdPVM1ABD9m
|
||||
|
||||
#al-config:
|
||||
# access-key-id: LTAI5tJ7sSxB96XaY9T3Vq3D
|
||||
# access-key-secret: EVXWFTS2SB6UkjUVFLhTDBfIvIXgI3
|
||||
# face-scene-id: 1000016045
|
||||
# face-verify-callback-url: https://dzc001.fy.takin.cc
|
||||
# face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
al-config:
|
||||
access-key-id: LTAI5tBrkf3ynA5CMLb4TYbo
|
||||
access-key-secret: u3Za5XigBfRXo0iJHSbPgOSZTDVN3C
|
||||
access-key-id: LTAI5tAK9M3fnMhmsxVZnuTn
|
||||
access-key-secret: 5J4EubEtDKHxvBytbzkgHTMQmzqpwd
|
||||
face-scene-id: 1000016556
|
||||
face-verify-callback-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
@@ -248,7 +248,7 @@ tx-map:
|
||||
|
||||
al-config:
|
||||
access-key-id: LTAI5tBrkf3ynA5CMLb4TYbo
|
||||
access-key-secret: u3Za5XigBfRXo0iJHSbPgOSZTDVN3C
|
||||
access-key-secret: 5J4EubEtDKHxvBytbzkgHTMQmzqpwd
|
||||
face-scene-id: 1000016556
|
||||
face-verify-callback-url: https://api.htysapp.com
|
||||
face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
@@ -278,7 +278,7 @@ yudao:
|
||||
front-type: 20 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
|
||||
unit-test-enable: false # 是否生成单元测试
|
||||
tenant: # 多租户相关配置项
|
||||
enable: true
|
||||
enable: false
|
||||
ignore-urls:
|
||||
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
|
||||
- /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
|
||||
|
||||
Reference in new issue
Block a user