Compare commits

..
18 Commits
Author SHA1 Message Date
timi 4f1408b9d0 Merge pull request 'v1.0.11' (#18) from dev into master
Reviewed-on: #18
2026-08-11 16:49:08 +00:00
timi 029eea70cc Merge pull request 'v1.0.10' (#17) from dev into master
Reviewed-on: #17
2026-08-11 16:45:36 +00:00
timi 22b1e65385 Merge pull request 'v1.0.9' (#16) from dev into master
Reviewed-on: #16
2026-08-11 16:01:10 +00:00
timi 2170c48763 Merge pull request 'v1.0.8' (#15) from dev into master
Reviewed-on: #15
2026-08-03 16:36:19 +00:00
timi c2d1c48aae Merge pull request 'v0.0.18' (#14) from dev into master
Reviewed-on: #14
2026-08-03 11:59:20 +00:00
timi e14958c643 Merge pull request 'v0.0.18' (#13) from dev into master
Reviewed-on: #13
2026-08-03 11:58:16 +00:00
timi da288338cd Merge pull request 'v1.0.7' (#12) from dev into master
Reviewed-on: #12
2026-08-01 09:34:18 +00:00
timi ea430f2468 Merge pull request 'v1.0.6' (#11) from dev into master
Reviewed-on: #11
2026-08-01 09:03:48 +00:00
timi f76f79744a Merge pull request 'v1.0.5' (#10) from dev into master
Reviewed-on: #10
2026-08-01 08:52:52 +00:00
timi 19daaff907 Merge pull request 'v1.0.4' (#9) from dev into master
Reviewed-on: #9
2026-08-01 03:13:00 +00:00
timi db94d8f932 Merge pull request 'v1.0.3' (#8) from dev into master
Reviewed-on: #8
2026-07-31 17:19:48 +00:00
timi 53618fe362 Merge pull request 'v1.0.3' (#7) from dev into master
Reviewed-on: #7
2026-07-31 17:13:37 +00:00
timi 523edffc4e Merge pull request 'v1.0.2' (#6) from dev into master
Reviewed-on: #6
2026-04-13 10:28:11 +00:00
timi c7ddb1a8b0 Merge pull request 'v1.0.1' (#5) from dev into master
Reviewed-on: #5
2026-04-12 16:17:02 +00:00
timi 45c9fc814a Merge pull request 'v1.0.0' (#4) from dev into master
Reviewed-on: #4
2026-04-09 05:16:04 +00:00
timi 407dc13ac4 Merge pull request 'v1.0.0' (#3) from dev into master
Reviewed-on: #3
2026-04-09 04:09:16 +00:00
timi 0c06bf16c2 Merge pull request 'v1.0.0' (#2) from dev into master
Reviewed-on: #2
2026-04-09 04:03:43 +00:00
timi 1db39e77d3 Merge pull request 'v1.0.0' (#1) from dev into master
Reviewed-on: #1
2026-04-08 08:48:12 +00:00
59 changed files with 225 additions and 951 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<component name="SqlDialectMappings"> <component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V1__init_schema.sql" dialect="MariaDB" /> <file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V1__init_schema.sql" dialect="MariaDB" />
<file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V2__init_system_data.sql" dialect="GenericSQL" /> <file url="file://$PROJECT_DIR$/src/main/resources/db/migration/timiserver/V2__init_system_data.sql" dialect="GenericSQL" />
<file url="file://$PROJECT_DIR$/src/main/resources/mapper/timi-server/gao/GaoEventRecordMapper.xml" dialect="MariaDB" /> <file url="file://$PROJECT_DIR$/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml" dialect="MariaDB" />
<file url="PROJECT" dialect="MariaDB" /> <file url="PROJECT" dialect="MariaDB" />
</component> </component>
</project> </project>
+1 -1
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.13</version> <version>1.0.11</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -39,12 +39,6 @@ public enum CorePermissionCode implements BuiltinPermissionCode {
MULTILINGUAL_UPDATE("MULTILINGUAL:UPDATE", "多语言修改", false, true), MULTILINGUAL_UPDATE("MULTILINGUAL:UPDATE", "多语言修改", false, true),
MULTILINGUAL_DELETE("MULTILINGUAL:DELETE", "多语言删除", false, true), MULTILINGUAL_DELETE("MULTILINGUAL:DELETE", "多语言删除", false, true),
TAG_CREATE("TAG:CREATE", "标签创建", false, true),
TAG_READ("TAG:READ", "标签读取", false, true),
TAG_APPLY("TAG:APPLY", "标签应用", false, true),
TAG_UPDATE("TAG:UPDATE", "标签修改", false, true),
TAG_DELETE("TAG:DELETE", "标签删除", false, true),
USER_CREATE("USER:CREATE", "用户创建", false, true), USER_CREATE("USER:CREATE", "用户创建", false, true),
USER_READ("USER:READ", "用户读取", false, true), USER_READ("USER:READ", "用户读取", false, true),
USER_UPDATE("USER:UPDATE", "用户修改", false, true), USER_UPDATE("USER:UPDATE", "用户修改", false, true),
@@ -10,6 +10,7 @@ import com.imyeyu.api.annotation.EnableSettingInterceptor;
import com.imyeyu.api.annotation.RequestRateLimitInterceptor; import com.imyeyu.api.annotation.RequestRateLimitInterceptor;
import com.imyeyu.api.annotation.RequireCorePermissionInterceptor; import com.imyeyu.api.annotation.RequireCorePermissionInterceptor;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermissionInterceptor;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRoleInterceptor;
import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor; import com.imyeyu.api.modules.journal.util.JournalAPIInterceptor;
import com.imyeyu.api.modules.system.util.SystemAPIInterceptor; import com.imyeyu.api.modules.system.util.SystemAPIInterceptor;
import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver; import com.imyeyu.spring.annotation.RequestBodyValueArgumentResolver;
@@ -41,6 +42,7 @@ public class WebConfig implements WebMvcConfigurer {
private final SystemAPIInterceptor systemAPIInterceptor; private final SystemAPIInterceptor systemAPIInterceptor;
private final JournalAPIInterceptor journalAPIInterceptor; private final JournalAPIInterceptor journalAPIInterceptor;
private final EnableSettingInterceptor enableSettingInterceptor; private final EnableSettingInterceptor enableSettingInterceptor;
private final RequireGaoRoleInterceptor requireGaoRoleInterceptor;
private final RequestRateLimitInterceptor requestRateLimitInterceptor; private final RequestRateLimitInterceptor requestRateLimitInterceptor;
private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor; private final RequireGaoPermissionInterceptor requireGaoPermissionInterceptor;
private final RequireCorePermissionInterceptor requireCorePermissionInterceptor; private final RequireCorePermissionInterceptor requireCorePermissionInterceptor;
@@ -56,6 +58,7 @@ public class WebConfig implements WebMvcConfigurer {
registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH); registry.addInterceptor(journalAPIInterceptor).addPathPatterns(JournalAPIInterceptor.PATH);
registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**"); registry.addInterceptor(enableSettingInterceptor).addPathPatterns("/**");
registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireCorePermissionInterceptor).addPathPatterns("/**");
registry.addInterceptor(requireGaoRoleInterceptor).addPathPatterns("/**");
registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**"); registry.addInterceptor(requireGaoPermissionInterceptor).addPathPatterns("/**");
registry.addInterceptor(requestRateLimitInterceptor).addPathPatterns("/**"); registry.addInterceptor(requestRateLimitInterceptor).addPathPatterns("/**");
} }
@@ -1,111 +0,0 @@
package com.imyeyu.api.modules.common.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.entity.TagApply;
import com.imyeyu.api.modules.common.service.TagService;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.ResponseView;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
/// 标签接口
///
/// @author 夜雨
/// @since 2026-08-12 15:09
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/tag")
public class TagController {
private final TagService service;
/// 查询标签列表
///
/// @param page 分页参数
/// @return 标签分页
@RequestRateLimit
@JsonView(ResponseView.Public.class)
@PostMapping("/list")
public PageResult<Tag> list(@RequestBody Page<Tag> page) {
return service.page(page);
}
/// 查询标签详情
///
/// @param id 标签 ID
/// @return 标签详情
@RequestRateLimit
@JsonView(ResponseView.Public.class)
@PostMapping("/detail")
public Tag detail(@RequestParam String id) {
return service.get(id);
}
/// 创建标签
///
/// @param tag 标签
/// @return 标签
@AOPLog
@RequestRateLimit
@JsonView(ResponseView.Public.class)
@PostMapping("/create")
public Tag create(@RequestBody Tag tag) {
service.create(tag);
return tag;
}
/// 更新标签
///
/// @param tag 标签
@AOPLog
@RequestRateLimit
@PostMapping("/update")
public void update(@RequestBody Tag tag) {
service.update(tag);
}
/// 删除标签
///
/// @param id 标签 ID
@AOPLog
@RequestRateLimit
@PostMapping("/delete")
public void delete(@RequestParam String id) {
service.delete(id);
}
/// 按业务查询标签
///
/// @param bizType 业务类型
/// @param bizId 业务 ID
/// @return 标签列表
@RequestRateLimit
@JsonView(ResponseView.Public.class)
@GetMapping("/list/biz/id")
public List<Tag> listByBizType(@RequestParam TagApply.BizType bizType, @RequestParam String bizId) {
return service.listByBizType(bizType, bizId);
}
/// 差分保存业务标签
///
/// @param apply 标签请求
@AOPLog
@RequestRateLimit
@PostMapping("/apply")
public void apply(@RequestBody TagApply apply) {
service.apply(apply.getBizType(), apply.getBizId(), apply.getTagIdList());
}
}
@@ -1,43 +1,23 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.imyeyu.api.annotation.MultilingualField;
import com.imyeyu.spring.annotation.table.Transient; import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/// 标签 /**
/// * @author 夜雨
/// @author 夜雨 * @since 2024-08-28 14:26
/// @since 2024-08-28 14:26 */
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Tag extends UUIDEntity { public class Tag extends UUIDEntity {
/// protected String langKey;
///
/// @author 夜雨
/// @since 2026-08-12 16:49
public enum OwnerType {
GAO_STORE
}
/// 归属类型
protected OwnerType ownerType;
/// 归属 ID
protected String ownerId;
/// 名称多语言 ID
protected String nameLangId;
/// 当前语言名称
@Transient @Transient
@MultilingualField("nameLangId") protected String value;
protected String name;
/// 中文名称
@Transient @Transient
protected String zhCN; protected String zhCN;
} }
@@ -1,12 +1,9 @@
package com.imyeyu.api.modules.common.entity; package com.imyeyu.api.modules.common.entity;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.List;
/** /**
* 标签应用 * 标签应用
* *
@@ -17,25 +14,12 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class TagApply extends UUIDEntity { public class TagApply extends UUIDEntity {
///
///
/// @author 夜雨
/// @since 2026-08-12 14:27
public enum BizType {
GAO_CUSTOMER
}
/** 业务类型 */ /** 业务类型 */
protected BizType bizType; protected String bizType;
/** 业务 ID */ /** 业务 ID */
protected String bizId; protected String bizId;
/** 标签 ID */ /** 标签 ID */
protected String tagId; protected String tagId;
/// 标签 ID 列表
@Transient
private List<String> tagIdList;
} }
@@ -1,11 +0,0 @@
package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.TagApply;
import com.imyeyu.spring.mapper.BaseMapper;
/// 标签应用 Mapper
///
/// @author 夜雨
/// @since 2026-08-12 15:05
public interface TagApplyMapper extends BaseMapper<TagApply, String> {
}
@@ -2,18 +2,10 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Tag; import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/** /**
* @author 夜雨 * @author 夜雨
* @since 2025-05-30 22:48 * @since 2025-05-30 22:48
*/ */
public interface TagMapper extends BaseMapper<Tag, String> { public interface TagMapper extends BaseMapper<Tag, String> {
List<Tag> selectByIdList(Collection<String> idList);
Tag selectByOwnerAndZhCN(@Param("ownerType") Tag.OwnerType ownerType, @Param("ownerId") String ownerId, @Param("zhCN") String zhCN);
} }
@@ -1,31 +1,11 @@
package com.imyeyu.api.modules.common.service; package com.imyeyu.api.modules.common.service;
import com.imyeyu.api.modules.common.entity.Tag; import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.entity.TagApply;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.util.Collection; /**
import java.util.List; * @author 夜雨
* @since 2025-05-30 22:46
/// 标签服务 */
///
/// @author 夜雨
/// @since 2025-05-30 22:46
public interface TagService extends BaseService<Tag, String> { public interface TagService extends BaseService<Tag, String> {
List<Tag> listByIdList(Collection<String> idList);
/// 按业务查询标签列表
///
/// @param bizType 业务类型
/// @param bizId 业务 ID
/// @return 标签列表
List<Tag> listByBizType(TagApply.BizType bizType, String bizId);
/// 差分保存业务标签
///
/// @param bizType 业务类型
/// @param bizId 业务 ID
/// @param tagIdList 标签 ID 列表
void apply(TagApply.BizType bizType, String bizId, List<String> tagIdList);
} }
@@ -1,111 +1,27 @@
package com.imyeyu.api.modules.common.service.implement; package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.api.modules.common.entity.Tag; import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.entity.TagApply;
import com.imyeyu.api.modules.common.mapper.TagApplyMapper;
import com.imyeyu.api.modules.common.mapper.TagMapper; import com.imyeyu.api.modules.common.mapper.TagMapper;
import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.common.service.TagService; import com.imyeyu.api.modules.common.service.TagService;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collection; /**
import java.util.HashSet; * @author 夜雨
import java.util.LinkedHashSet; * @since 2025-05-30 22:47
import java.util.List; */
/// 标签服务实现
///
/// @author 夜雨
/// @since 2025-05-30 22:47
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class TagServiceImplement extends AbstractEntityService<Tag, String> implements TagService { public class TagServiceImplement extends AbstractEntityService<Tag, String> implements TagService {
private final TagMapper mapper; private final TagMapper mapper;
private final TagApplyMapper applyMapper;
private final MultilingualService multilingualService;
@Override @Override
protected BaseMapper<Tag, String> mapper() { protected BaseMapper<Tag, String> mapper() {
return mapper; return mapper;
} }
@Override
public void create(Tag tag) {
TimiException.required(tag, "not found tag");
TimiException.required(tag.getZhCN(), "not found tag.zhCN");
TimiException.required(tag.getOwnerType(), "not found ownerType");
TimiException.required(tag.getOwnerId(), "not found ownerId");
checkZhCNUniqueInOwner(tag, null);
tag.setNameLangId(multilingualService.create(tag.getZhCN()));
super.create(tag);
}
@Override
public void update(Tag tag) {
TimiException.required(tag, "not found tag");
TimiException.required(tag.getId(), "not found tag.id");
Tag dbTag = get(tag.getId());
TimiException.required(dbTag, "not found tag");
tag.setOwnerType(dbTag.getOwnerType());
tag.setOwnerId(dbTag.getOwnerId());
if (tag.getZhCN() != null) {
if (tag.getNameLangId() == null) {
tag.setNameLangId(dbTag.getNameLangId());
}
checkZhCNUniqueInOwner(tag, tag.getId());
tag.setNameLangId(multilingualService.createIfDifferent(tag.getNameLangId(), tag.getZhCN()));
}
super.update(tag);
}
@Override
public List<Tag> listByIdList(Collection<String> idList) {
return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
public List<Tag> listByBizType(TagApply.BizType bizType, String bizId) {
return listByIdList(listApply(bizType, bizId).stream().map(TagApply::getTagId).toList());
}
@Override
public void apply(TagApply.BizType bizType, String bizId, List<String> tagIdList) {
LinkedHashSet<String> tagIdSet = new LinkedHashSet<>(tagIdList == null ? List.of() : tagIdList);
for (TagApply apply : listApply(bizType, bizId)) {
if (tagIdSet.remove(apply.getTagId())) {
continue;
}
applyMapper.delete(apply.getId());
}
List<Tag> tagList = listByIdList(tagIdSet);
for (Tag tag : tagList) {
TagApply apply = new TagApply();
apply.setBizType(bizType);
apply.setBizId(bizId);
apply.setTagId(tag.getId());
applyMapper.insert(apply);
}
}
private List<TagApply> listApply(TagApply.BizType bizType, String bizId) {
TimiException.required(bizType, "not found bizType");
TimiException.required(bizId, "not found bizId");
TagApply example = new TagApply();
example.setBizType(bizType);
example.setBizId(bizId);
return applyMapper.selectAllByExample(example);
}
private void checkZhCNUniqueInOwner(Tag tag, String allowedId) {
Tag existed = mapper.selectByOwnerAndZhCN(tag.getOwnerType(), tag.getOwnerId(), tag.getZhCN());
TimiException.requiredTrue(existed == null || existed.getId().equals(allowedId), "该标签名称已存在");
}
} }
@@ -0,0 +1,15 @@
package com.imyeyu.api.modules.common.vo.tag;
import lombok.Data;
/**
* @author 夜雨
* @since 2025-05-30 22:58
*/
@Data
public class TagRequest {
private String bizId;
private String zhCN;
}
@@ -0,0 +1,29 @@
package com.imyeyu.api.modules.gao.annotation;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
///
/// 要求 GAO 模块角色
///
/// @author Codex
/// @since 2026-07-26
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireGaoRole {
/// 所需角色
///
/// @return 角色列表
GaoRoleCode[] value();
/// 匹配模式
///
/// @return 匹配模式
RoleMatchMode mode() default RoleMatchMode.ALL;
}
@@ -0,0 +1,50 @@
package com.imyeyu.api.modules.gao.annotation;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.bean.RoleMatchMode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.java.TimiJava;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
///
/// GAO 角色注解处理器
///
/// @author Codex
/// @since 2026-07-27
@Component
public class RequireGaoRoleInterceptor implements HandlerInterceptor {
private final RoleChecker roleChecker;
public RequireGaoRoleInterceptor(RoleChecker roleChecker) {
this.roleChecker = roleChecker;
}
@Override
public boolean preHandle(@NonNull HttpServletRequest req, @NonNull HttpServletResponse resp, @NonNull Object handler) {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
RequireGaoRole annotation = handlerMethod.getMethodAnnotation(RequireGaoRole.class);
if (annotation == null) {
annotation = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(), RequireGaoRole.class);
}
if (annotation == null || TimiJava.isEmpty(annotation.value())) {
return true;
}
String[] roleCodeList = java.util.Arrays.stream(annotation.value()).map(GaoRoleCode::name).toArray(String[]::new);
if (annotation.mode() == RoleMatchMode.ANY) {
roleChecker.checkAny(ModuleCode.GAO, roleCodeList);
} else {
roleChecker.checkAll(ModuleCode.GAO, roleCodeList);
}
return true;
}
}
@@ -12,7 +12,6 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
import com.imyeyu.api.modules.user.service.RoleChecker; import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
@@ -61,8 +60,8 @@ public class GaoCustomerController {
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/list") @PostMapping("/list")
@JsonView(ResponseView.Public.class) @JsonView(ResponseView.Public.class)
public PageResult<GaoCustomer> list(@RequestBody GaoCustomerPage page) { public PageResult<GaoCustomer> list(@RequestBody Page<GaoCustomer> page) {
PageResult<GaoCustomer> result = service.pageByQuery(page); PageResult<GaoCustomer> result = service.page(page);
List<String> idList = result.getList().stream().map(GaoCustomer::getId).distinct().toList(); List<String> idList = result.getList().stream().map(GaoCustomer::getId).distinct().toList();
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList); Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, idList);
@@ -1,8 +1,8 @@
package com.imyeyu.api.modules.gao.controller; package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.user.entity.Role; import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.service.AuthorizationScopeService; import com.imyeyu.api.modules.user.service.AuthorizationScopeService;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
@@ -20,6 +20,7 @@ import java.util.List;
/// @author 夜雨 /// @author 夜雨
/// @since 2026-07-28 14:15 /// @since 2026-07-28 14:15
@RestController @RestController
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/gao/role") @RequestMapping("/gao/role")
public class GaoRoleController { public class GaoRoleController {
@@ -29,7 +30,6 @@ public class GaoRoleController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireCorePermission(CorePermissionCode.ROLE_READ)
@PostMapping("/available/list") @PostMapping("/available/list")
public List<Role> availableList() { public List<Role> availableList() {
return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO); return authorizationScopeService.listGrantableRole(userLoginService.getRequireLoginUserId(), ModuleCode.GAO);
@@ -1,7 +1,9 @@
package com.imyeyu.api.modules.gao.controller; package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay; import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService; import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
@@ -1,13 +1,12 @@
package com.imyeyu.api.modules.gao.controller; package com.imyeyu.api.modules.gao.controller;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoStore; import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.service.GaoStoreService; import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava;
import com.imyeyu.network.Network; import com.imyeyu.network.Network;
import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
@@ -38,9 +37,9 @@ public class GaoStoreController {
private final GaoStoreService service; private final GaoStoreService service;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final PermissionChecker permissionChecker;
@AOPLog @AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_READ) @RequireGaoPermission(GaoPermissionCode.STORE_READ)
@PostMapping("/list") @PostMapping("/list")
@@ -49,6 +48,7 @@ public class GaoStoreController {
} }
@AOPLog @AOPLog
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_READ) @RequireGaoPermission(GaoPermissionCode.STORE_READ)
@PostMapping("/detail") @PostMapping("/detail")
@@ -66,26 +66,16 @@ public class GaoStoreController {
} }
@AOPLog @AOPLog
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
@PostMapping("/update") @PostMapping("/update")
public void update(@RequestBody GaoStore store) { public void update(@RequestBody GaoStore store) {
if (permissionChecker.hasAny(ModuleCode.GAO, GaoPermissionCode.STORE_CREATE.getValue())) { service.update(store);
// 门店管理
service.update(store);
} else {
// 店长
GaoStore dbStore = service.get(store.getId());
dbStore.setName(store.getName());
dbStore.setTelephone(store.getTelephone());
dbStore.setAddress(store.getAddress());
dbStore.setRemark(store.getRemark());
dbStore.setAutoClosedDay(TimiJava.defaultIfNull(store.getAutoClosedDay(), false));
service.update(dbStore);
}
} }
@AOPLog @AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_DELETE) @RequireGaoPermission(GaoPermissionCode.STORE_DELETE)
@PostMapping("/delete") @PostMapping("/delete")
@@ -94,6 +84,7 @@ public class GaoStoreController {
} }
@AOPLog @AOPLog
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_UPDATE) @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
@PostMapping("/sort") @PostMapping("/sort")
@@ -1,139 +0,0 @@
package com.imyeyu.api.modules.gao.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.annotation.RequireCorePermission;
import com.imyeyu.api.bean.CorePermissionCode;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.common.entity.Tag;
import com.imyeyu.api.modules.common.entity.TagApply;
import com.imyeyu.api.modules.common.service.TagService;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.util.ResponseView;
import lombok.RequiredArgsConstructor;
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;
/// GAO 标签接口
///
/// @author Codex
/// @since 2026-08-13
@RestController
@RequiredArgsConstructor
@RequestMapping("/gao/tag")
public class GaoTagController {
private final TagService service;
private final RoleChecker roleChecker;
private final GaoStoreService storeService;
/// 查询标签列表
///
/// @param page 分页参数
/// @return 标签分页
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_READ)
@PostMapping("/list")
public PageResult<Tag> list(@RequestBody Page<Tag> page) {
TimiException.required(page, "未找到分页参数");
page.setEqualsExample(TimiJava.defaultIfNull(page.getEqualsExample(), new Tag()));
page.getEqualsExample().setOwnerType(Tag.OwnerType.GAO_STORE);
if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) {
page.getEqualsExample().setOwnerId(storeService.getBelongIdByRequiredLoginUserId());
}
return service.page(page);
}
/// 查询标签详情
///
/// @param id 标签 ID
/// @return 标签详情
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_READ)
@PostMapping("/detail")
public Tag detail(@RequestParam String id) {
return service.get(id);
}
/// 创建标签
///
/// @param tag 标签
/// @return 标签
@AOPLog
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_CREATE)
@PostMapping("/create")
public Tag create(@RequestBody Tag tag) {
tag.setOwnerType(Tag.OwnerType.GAO_STORE);
if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) {
tag.setOwnerId(storeService.getBelongIdByRequiredLoginUserId());
}
service.create(tag);
return tag;
}
/// 更新标签
///
/// @param tag 标签
@AOPLog
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_UPDATE)
@PostMapping("/update")
public void update(@RequestBody Tag tag) {
Tag dbTag = service.get(tag.getId());
tag.setOwnerType(dbTag.getOwnerType());
tag.setOwnerId(dbTag.getOwnerId());
service.update(tag);
}
/// 删除标签
///
/// @param id 标签 ID
@AOPLog
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_DELETE)
@PostMapping("/delete")
public void delete(@RequestParam String id) {
service.delete(id);
}
/// 按业务查询标签
///
/// @param bizType 业务类型
/// @param bizId 业务 ID
/// @return 标签列表
@JsonView(ResponseView.Public.class)
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_READ)
@GetMapping("/list/biz/id")
public List<Tag> listByBizType(@RequestParam TagApply.BizType bizType, @RequestParam String bizId) {
return service.listByBizType(bizType, bizId);
}
/// 差分保存业务标签
///
/// @param apply 标签请求
@AOPLog
@RequestRateLimit
@RequireCorePermission(CorePermissionCode.TAG_APPLY)
@PostMapping("/apply")
public void apply(@RequestBody TagApply apply) {
service.apply(apply.getBizType(), apply.getBizId(), apply.getTagIdList());
}
}
@@ -5,6 +5,7 @@ import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission;
import com.imyeyu.api.modules.gao.annotation.RequireGaoRole;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoUser; import com.imyeyu.api.modules.gao.entity.GaoUser;
@@ -116,6 +117,7 @@ public class GaoUserController {
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
@RequireGaoPermission(GaoPermissionCode.USER_UPDATE) @RequireGaoPermission(GaoPermissionCode.USER_UPDATE)
@PostMapping("/transfer/store") @PostMapping("/transfer/store")
@JsonView(ResponseView.Public.class) @JsonView(ResponseView.Public.class)
@@ -1,7 +1,6 @@
package com.imyeyu.api.modules.gao.entity; package com.imyeyu.api.modules.gao.entity;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity; import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -84,8 +83,4 @@ public class GaoPointLedger extends UUIDEntity {
/// 发生时间 /// 发生时间
private Long occurredAt; private Long occurredAt;
/// 客户信息
@Transient
private GaoCustomer customer;
} }
@@ -48,9 +48,6 @@ public class GaoStore extends UUIDEntity {
/// 备注 /// 备注
private String remark; private String remark;
/// true 为启用自动计算营业日休息日
private Boolean autoClosedDay;
/// 排序值 /// 排序值
private Integer sort; private Integer sort;
@@ -18,7 +18,7 @@ public class GaoStoreBusinessDay extends UUIDEntity {
/// ///
/// @author Codex /// @author Codex
/// @since 2026-08-06 /// @since 2026-08-06
public enum Status { public enum OpenStatus {
/// 营业 /// 营业
OPEN, OPEN,
@@ -34,7 +34,7 @@ public class GaoStoreBusinessDay extends UUIDEntity {
private Integer dateValue; private Integer dateValue;
/// 营业状态 /// 营业状态
private Status status; private OpenStatus openStatus;
/// 营业日递增序号,仅营业日有值 /// 营业日递增序号,仅营业日有值
private Long businessDayNo; private Long businessDayNo;
@@ -3,7 +3,6 @@ package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -36,16 +35,4 @@ public interface GaoCustomerMapper extends BaseMapper<GaoCustomer, String> {
/// ///
/// @return 性别统计列表 /// @return 性别统计列表
List<GaoCustomerGenderStatView> stateGender(@Param("storeId") String storeId); List<GaoCustomerGenderStatView> stateGender(@Param("storeId") String storeId);
/// 查询客户总数
///
/// @param page 查询条件
/// @return 客户总数
long countByQuery(GaoCustomerPage page);
/// 分页查询客户
///
/// @param page 查询条件
/// @return 客户列表
List<GaoCustomer> selectByQuery(GaoCustomerPage page);
} }
@@ -4,7 +4,6 @@ import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List; import java.util.List;
@@ -28,7 +27,7 @@ public interface GaoStoreBusinessDayMapper extends BaseMapper<GaoStoreBusinessDa
/// @param storeId 门店 ID /// @param storeId 门店 ID
/// @param dateValue 日期值,格式为 yyyyMMdd /// @param dateValue 日期值,格式为 yyyyMMdd
/// @return 营业日序号 /// @return 营业日序号
@Select("SELECT MAX(`business_day_no`) FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` < #{dateValue} AND `status` = 'OPEN' AND `deleted_at` IS NULL") @Select("SELECT MAX(`business_day_no`) FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` < #{dateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL")
Long selectLastBusinessDayNoBefore(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue); Long selectLastBusinessDayNoBefore(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue);
/// 查询指定日期及之后的营业日 /// 查询指定日期及之后的营业日
@@ -36,16 +35,9 @@ public interface GaoStoreBusinessDayMapper extends BaseMapper<GaoStoreBusinessDa
/// @param storeId 门店 ID /// @param storeId 门店 ID
/// @param beginDateValue 开始日期值,格式为 yyyyMMdd /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
/// @return 营业日列表 /// @return 营业日列表
@Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `status` = 'OPEN' AND `deleted_at` IS NULL ORDER BY `date_value` ASC") @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `open_status` = 'OPEN' AND `deleted_at` IS NULL ORDER BY `date_value` ASC")
List<GaoStoreBusinessDay> selectOpenListFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue); List<GaoStoreBusinessDay> selectOpenListFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue);
/// 清空指定日期及之后的营业日序号
///
/// @param storeId 门店 ID
/// @param beginDateValue 开始日期值,格式为 yyyyMMdd
@Update("UPDATE `gao_store_business_day` SET `business_day_no` = NULL, `updated_at` = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000) WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `status` = 'OPEN' AND `deleted_at` IS NULL")
void clearBusinessDayNoFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue);
/// 查询日期范围内的营业日 /// 查询日期范围内的营业日
/// ///
/// @param storeId 门店 ID /// @param storeId 门店 ID
@@ -3,7 +3,6 @@ package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoStore; import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -17,12 +16,6 @@ public interface GaoStoreMapper extends BaseMapper<GaoStore, String> {
List<GaoStore> selectByIdList(@Param("idList") Collection<String> idList); List<GaoStore> selectByIdList(@Param("idList") Collection<String> idList);
/// 查询启用自动计算营业日休息日的可用门店
///
/// @return 门店列表
@Select("SELECT * FROM `gao_store` WHERE `auto_closed_day` = TRUE AND `status` = 'ACTIVE' " + NOT_DELETE)
List<GaoStore> selectAllByAutoClosedDay();
/// 查询用户所属的可用门店 /// 查询用户所属的可用门店
/// ///
/// @param userId 用户 ID /// @param userId 用户 ID
@@ -3,10 +3,8 @@ package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoCustomer; import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.io.IOException; import java.io.IOException;
@@ -21,12 +19,6 @@ import java.util.stream.Collectors;
/// @since 2026-07-27 14:12 /// @since 2026-07-27 14:12
public interface GaoCustomerService extends BaseService<GaoCustomer, String> { public interface GaoCustomerService extends BaseService<GaoCustomer, String> {
/// 按查询条件分页查询客户
///
/// @param page 查询条件
/// @return 客户分页
PageResult<GaoCustomer> pageByQuery(GaoCustomerPage page);
void updateWithAttachment(GaoCustomer customer); void updateWithAttachment(GaoCustomer customer);
/// 按编码查询客户 /// 按编码查询客户
@@ -23,12 +23,6 @@ public interface GaoStoreBusinessDayService extends BaseService<GaoStoreBusiness
/// @param beginDateValue 开始日期值,格式为 yyyyMMdd /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
void rebuildBusinessDayNo(String storeId, Integer beginDateValue); void rebuildBusinessDayNo(String storeId, Integer beginDateValue);
/// 自动补记门店休息日
///
/// @param storeId 门店 ID
/// @param dateValue 日期值,格式为 yyyyMMdd
void autoCloseBusinessDay(String storeId, Integer dateValue);
/// 查询日期范围内的营业日 /// 查询日期范围内的营业日
/// ///
/// @param storeId 门店 ID /// @param storeId 门店 ID
@@ -22,8 +22,6 @@ public interface GaoStoreService extends BaseService<GaoStore, String> {
String getBelongIdByRequiredLoginUserId(); String getBelongIdByRequiredLoginUserId();
List<GaoStore> listByAutoClosedDay();
/// 导出门店列表 /// 导出门店列表
/// ///
/// @param page 分页查询条件 /// @param page 分页查询条件
@@ -10,7 +10,6 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.util.CustomerXSSFBuilder; import com.imyeyu.api.modules.gao.util.CustomerXSSFBuilder;
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerPage;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
import com.imyeyu.api.modules.system.entity.Logger; import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService; import com.imyeyu.api.modules.system.service.LoggerService;
@@ -19,7 +18,6 @@ import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -67,15 +65,6 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
return mapper; return mapper;
} }
@Override
public PageResult<GaoCustomer> pageByQuery(GaoCustomerPage page) {
TimiException.required(page, "not found page");
PageResult<GaoCustomer> result = new PageResult<>();
result.setTotal(mapper.countByQuery(page));
result.setList(mapper.selectByQuery(page));
return result;
}
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public void create(GaoCustomer customer) { public void create(GaoCustomer customer) {
@@ -279,7 +279,6 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
@Override @Override
public PageResult<GaoCustomerEventRankView> pageRankByRange(GaoEventRecordRankPage page) { public PageResult<GaoCustomerEventRankView> pageRankByRange(GaoEventRecordRankPage page) {
TimiException.required(page.getStoreId(), "not found page.storeId"); TimiException.required(page.getStoreId(), "not found page.storeId");
page.setTodayDateValue(dateValue(Time.now()));
PageResult<GaoCustomerEventRankView> result = new PageResult<>(); PageResult<GaoCustomerEventRankView> result = new PageResult<>();
result.setTotal(mapper.countRankByRange(page)); result.setTotal(mapper.countRankByRange(page));
result.setList(mapper.selectRankByRange(page)); result.setList(mapper.selectRankByRange(page));
@@ -2,21 +2,15 @@ package com.imyeyu.api.modules.gao.service.implement;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.service.AttachmentService;
import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.entity.GaoPointAccount; import com.imyeyu.api.modules.gao.entity.GaoPointAccount;
import com.imyeyu.api.modules.gao.entity.GaoPointLedger; import com.imyeyu.api.modules.gao.entity.GaoPointLedger;
import com.imyeyu.api.modules.gao.entity.GaoPointRule; import com.imyeyu.api.modules.gao.entity.GaoPointRule;
import com.imyeyu.api.modules.gao.entity.GaoEventRecord; import com.imyeyu.api.modules.gao.entity.GaoEventRecord;
import com.imyeyu.api.modules.gao.mapper.GaoPointLedgerMapper; import com.imyeyu.api.modules.gao.mapper.GaoPointLedgerMapper;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoPointAccountService; import com.imyeyu.api.modules.gao.service.GaoPointAccountService;
import com.imyeyu.api.modules.gao.service.GaoPointLedgerService; import com.imyeyu.api.modules.gao.service.GaoPointLedgerService;
import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest; import com.imyeyu.api.modules.gao.vo.GaoPointAdjustRequest;
import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage; import com.imyeyu.api.modules.gao.vo.GaoPointLedgerPage;
import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService;
import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
@@ -26,27 +20,19 @@ import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/// GAO 客户积分流水服务实现 /// GAO 客户积分流水服务实现
/// ///
/// @author Codex /// @author Codex
/// @since 2026-08-11 /// @since 2026-08-11
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class GaoPointLedgerServiceImplement extends AbstractEntityService<GaoPointLedger, String> implements GaoPointLedgerService { public class GaoPointLedgerServiceImplement extends AbstractEntityService<GaoPointLedger, String> implements GaoPointLedgerService {
private final GaoPointLedgerMapper mapper; private final GaoPointLedgerMapper mapper;
private final GaoPointAccountService accountService; private final GaoPointAccountService accountService;
private final GaoCustomerService customerService;
private final AttachmentService attachmentService;
private final LoggerService loggerService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
@Override @Override
@@ -110,87 +96,47 @@ public class GaoPointLedgerServiceImplement extends AbstractEntityService<GaoPoi
PageResult<GaoPointLedger> result = new PageResult<>(); PageResult<GaoPointLedger> result = new PageResult<>();
result.setTotal(mapper.countByQuery(page)); result.setTotal(mapper.countByQuery(page));
result.setList(mapper.selectByQuery(page)); result.setList(mapper.selectByQuery(page));
fillCustomer(result.getList());
return result; return result;
} }
private void fillCustomer(List<GaoPointLedger> ledgerList) {
List<String> customerIdList = ledgerList.stream().map(GaoPointLedger::getCustomerId).filter(TimiJava::isNotEmpty).distinct().toList();
if (customerIdList.isEmpty()) {
return;
}
Map<String, GaoCustomer> customerMap = customerService.mapByIdList(customerIdList);
Map<String, List<Attachment>> attachmentMap = attachmentService.mapByBizIdList(Attachment.BizType.GAO_CUSTOMER, customerIdList);
for (GaoPointLedger ledger : ledgerList) {
GaoCustomer customer = customerMap.get(ledger.getCustomerId());
if (customer == null) {
continue;
}
customer.setAttachmentList(attachmentMap.get(customer.getId()));
ledger.setCustomer(customer);
}
}
private GaoPointLedger apply(String storeId, String customerId, GaoPointLedger.ChangeType changeType, long pointDelta, String ruleId, String eventId, String eventRecordId, JsonNode sourcePayload, String idempotencyKey, String remark, String operatorUserId, Long occurredAt) { private GaoPointLedger apply(String storeId, String customerId, GaoPointLedger.ChangeType changeType, long pointDelta, String ruleId, String eventId, String eventRecordId, JsonNode sourcePayload, String idempotencyKey, String remark, String operatorUserId, Long occurredAt) {
Logger logger = new Logger(Logger.Module.GAO, "GAO_POINT_LEDGER_APPLY"); GaoPointLedger existing = mapper.selectByIdempotencyKey(idempotencyKey);
logger.setContent("storeId=%s, customerId=%s, changeType=%s, pointDelta=%d, idempotencyKey=%s, remark=%s".formatted(storeId, customerId, changeType, pointDelta, idempotencyKey, remark)); if (existing != null) {
try { return existing;
GaoPointLedger existing = mapper.selectByIdempotencyKey(idempotencyKey);
if (existing != null) {
logger.setLevel(Logger.Level.INFO);
logger.setResult(existing.getId());
return existing;
}
GaoPointAccount account = accountService.getOrCreateForUpdate(storeId, customerId);
existing = mapper.selectByIdempotencyKey(idempotencyKey);
if (existing != null) {
logger.setLevel(Logger.Level.INFO);
logger.setResult(existing.getId());
return existing;
}
long balanceBefore = account.getBalance();
long balanceAfter = Math.addExact(balanceBefore, pointDelta);
long totalEarnedDelta = changeType == GaoPointLedger.ChangeType.EARN ? pointDelta : 0L;
long totalRevokedDelta = changeType == GaoPointLedger.ChangeType.REVOKE ? -pointDelta : 0L;
long totalAdjustedDelta = changeType == GaoPointLedger.ChangeType.ADJUST ? pointDelta : 0L;
GaoPointLedger ledger = new GaoPointLedger();
ledger.setAccountId(account.getId());
ledger.setStoreId(storeId);
ledger.setCustomerId(customerId);
ledger.setChangeType(changeType);
ledger.setDirection(0 < pointDelta ? GaoPointLedger.Direction.INCREASE : GaoPointLedger.Direction.DECREASE);
ledger.setPointDelta(pointDelta);
ledger.setBalanceBefore(balanceBefore);
ledger.setBalanceAfter(balanceAfter);
ledger.setRuleId(ruleId);
ledger.setEventId(eventId);
ledger.setEventRecordId(eventRecordId);
ledger.setSourcePayload(sourcePayload);
ledger.setIdempotencyKey(idempotencyKey);
ledger.setRemark(remark);
ledger.setOperatorUserId(operatorUserId);
ledger.setOccurredAt(occurredAt);
create(ledger);
int updated = accountService.updateBalance(account.getId(), pointDelta, totalEarnedDelta, totalRevokedDelta, totalAdjustedDelta);
TimiException.requiredTrue(0 < updated, "积分余额不足");
logger.setLevel(Logger.Level.INFO);
logger.setResult(ledger.getId());
return ledger;
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
throw e;
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("应用 GAO 积分流水失败", e);
throw new TimiException(TimiCode.ERROR, "应用 GAO 积分流水失败", e);
} finally {
loggerService.create(logger);
} }
GaoPointAccount account = accountService.getOrCreateForUpdate(storeId, customerId);
existing = mapper.selectByIdempotencyKey(idempotencyKey);
if (existing != null) {
return existing;
}
long balanceBefore = account.getBalance();
long balanceAfter = Math.addExact(balanceBefore, pointDelta);
long totalEarnedDelta = changeType == GaoPointLedger.ChangeType.EARN ? pointDelta : 0L;
long totalRevokedDelta = changeType == GaoPointLedger.ChangeType.REVOKE ? -pointDelta : 0L;
long totalAdjustedDelta = changeType == GaoPointLedger.ChangeType.ADJUST ? pointDelta : 0L;
GaoPointLedger ledger = new GaoPointLedger();
ledger.setAccountId(account.getId());
ledger.setStoreId(storeId);
ledger.setCustomerId(customerId);
ledger.setChangeType(changeType);
ledger.setDirection(0 < pointDelta ? GaoPointLedger.Direction.INCREASE : GaoPointLedger.Direction.DECREASE);
ledger.setPointDelta(pointDelta);
ledger.setBalanceBefore(balanceBefore);
ledger.setBalanceAfter(balanceAfter);
ledger.setRuleId(ruleId);
ledger.setEventId(eventId);
ledger.setEventRecordId(eventRecordId);
ledger.setSourcePayload(sourcePayload);
ledger.setIdempotencyKey(idempotencyKey);
ledger.setRemark(remark);
ledger.setOperatorUserId(operatorUserId);
ledger.setOccurredAt(occurredAt);
create(ledger);
int updated = accountService.updateBalance(account.getId(), pointDelta, totalEarnedDelta, totalRevokedDelta, totalAdjustedDelta);
TimiException.requiredTrue(0 < updated, "积分余额不足");
return ledger;
} }
} }
@@ -37,7 +37,7 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
public void create(GaoStoreBusinessDay businessDay) { public void create(GaoStoreBusinessDay businessDay) {
prepareBusinessDay(businessDay); prepareBusinessDay(businessDay);
super.create(businessDay); super.create(businessDay);
if (businessDay.getStatus() == GaoStoreBusinessDay.Status.OPEN) { if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) {
rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue()); rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue());
} }
} }
@@ -51,7 +51,7 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
TimiException.required(dbBusinessDay, "not found dbBusinessDay"); TimiException.required(dbBusinessDay, "not found dbBusinessDay");
TimiException.requiredTrue(dbBusinessDay.getStoreId().equals(businessDay.getStoreId()), "businessDay.storeId invalid"); TimiException.requiredTrue(dbBusinessDay.getStoreId().equals(businessDay.getStoreId()), "businessDay.storeId invalid");
TimiException.requiredTrue(dbBusinessDay.getDateValue().equals(businessDay.getDateValue()), "businessDay.dateValue invalid"); TimiException.requiredTrue(dbBusinessDay.getDateValue().equals(businessDay.getDateValue()), "businessDay.dateValue invalid");
if (businessDay.getStatus() == GaoStoreBusinessDay.Status.CLOSED) { if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) {
checkNoRecord(dbBusinessDay); checkNoRecord(dbBusinessDay);
} }
prepareBusinessDay(businessDay); prepareBusinessDay(businessDay);
@@ -64,7 +64,7 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
public void delete(String id) { public void delete(String id) {
GaoStoreBusinessDay businessDay = get(id); GaoStoreBusinessDay businessDay = get(id);
TimiException.required(businessDay, "not found businessDay"); TimiException.required(businessDay, "not found businessDay");
if (businessDay.getStatus() == GaoStoreBusinessDay.Status.OPEN) { if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) {
checkNoRecord(businessDay); checkNoRecord(businessDay);
} }
super.delete(id); super.delete(id);
@@ -78,14 +78,14 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
TimiException.required(dateValue, "not found dateValue"); TimiException.required(dateValue, "not found dateValue");
GaoStoreBusinessDay businessDay = mapper.selectByStoreIdAndDateValue(storeId, dateValue); GaoStoreBusinessDay businessDay = mapper.selectByStoreIdAndDateValue(storeId, dateValue);
if (businessDay != null) { if (businessDay != null) {
TimiException.requiredTrue(businessDay.getStatus() == GaoStoreBusinessDay.Status.OPEN, "门店当日休息,不能登记"); TimiException.requiredTrue(businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN, "门店当日休息,不能登记");
TimiException.required(businessDay.getBusinessDayNo(), "not found businessDay.businessDayNo"); TimiException.required(businessDay.getBusinessDayNo(), "not found businessDay.businessDayNo");
return businessDay; return businessDay;
} }
businessDay = new GaoStoreBusinessDay(); businessDay = new GaoStoreBusinessDay();
businessDay.setStoreId(storeId); businessDay.setStoreId(storeId);
businessDay.setDateValue(dateValue); businessDay.setDateValue(dateValue);
businessDay.setStatus(GaoStoreBusinessDay.Status.OPEN); businessDay.setOpenStatus(GaoStoreBusinessDay.OpenStatus.OPEN);
super.create(businessDay); super.create(businessDay);
rebuildBusinessDayNo(storeId, dateValue); rebuildBusinessDayNo(storeId, dateValue);
return mapper.selectByStoreIdAndDateValue(storeId, dateValue); return mapper.selectByStoreIdAndDateValue(storeId, dateValue);
@@ -97,35 +97,16 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
TimiException.required(storeId, "not found storeId"); TimiException.required(storeId, "not found storeId");
TimiException.required(beginDateValue, "not found beginDateValue"); TimiException.required(beginDateValue, "not found beginDateValue");
long businessDayNo = TimiJava.defaultIfNull(mapper.selectLastBusinessDayNoBefore(storeId, beginDateValue), 0L); long businessDayNo = TimiJava.defaultIfNull(mapper.selectLastBusinessDayNoBefore(storeId, beginDateValue), 0L);
mapper.clearBusinessDayNoFromDate(storeId, beginDateValue);
for (GaoStoreBusinessDay businessDay : mapper.selectOpenListFromDate(storeId, beginDateValue)) { for (GaoStoreBusinessDay businessDay : mapper.selectOpenListFromDate(storeId, beginDateValue)) {
businessDayNo++; businessDayNo++;
businessDay.setBusinessDayNo(businessDayNo); if (!Long.valueOf(businessDayNo).equals(businessDay.getBusinessDayNo())) {
mapper.update(businessDay); businessDay.setBusinessDayNo(businessDayNo);
mapper.update(businessDay);
}
eventRecordMapper.updateBusinessDayNoByStoreIdAndRegisteredDateValue(storeId, businessDay.getDateValue(), businessDayNo); eventRecordMapper.updateBusinessDayNoByStoreIdAndRegisteredDateValue(storeId, businessDay.getDateValue(), businessDayNo);
} }
} }
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void autoCloseBusinessDay(String storeId, Integer dateValue) {
TimiException.required(storeId, "not found storeId");
TimiException.required(dateValue, "not found dateValue");
if (mapper.selectByStoreIdAndDateValue(storeId, dateValue) != null) {
return;
}
Long count = eventRecordMapper.countByStoreIdAndRegisteredDateValue(storeId, dateValue);
if (0 < TimiJava.defaultIfNull(count, 0L)) {
return;
}
GaoStoreBusinessDay businessDay = new GaoStoreBusinessDay();
businessDay.setStoreId(storeId);
businessDay.setDateValue(dateValue);
businessDay.setStatus(GaoStoreBusinessDay.Status.CLOSED);
businessDay.setReason("系统自动判定休息");
super.create(businessDay);
}
@Override @Override
public List<GaoStoreBusinessDay> listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue) { public List<GaoStoreBusinessDay> listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue) {
TimiException.required(storeId, "not found storeId"); TimiException.required(storeId, "not found storeId");
@@ -139,8 +120,8 @@ public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService<G
TimiException.required(businessDay, "not found businessDay"); TimiException.required(businessDay, "not found businessDay");
TimiException.required(businessDay.getStoreId(), "not found businessDay.storeId"); TimiException.required(businessDay.getStoreId(), "not found businessDay.storeId");
TimiException.required(businessDay.getDateValue(), "not found businessDay.dateValue"); TimiException.required(businessDay.getDateValue(), "not found businessDay.dateValue");
businessDay.setStatus(TimiJava.defaultIfNull(businessDay.getStatus(), GaoStoreBusinessDay.Status.OPEN)); businessDay.setOpenStatus(TimiJava.defaultIfNull(businessDay.getOpenStatus(), GaoStoreBusinessDay.OpenStatus.OPEN));
if (businessDay.getStatus() == GaoStoreBusinessDay.Status.CLOSED) { if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) {
businessDay.setBusinessDayNo(null); businessDay.setBusinessDayNo(null);
return; return;
} }
@@ -62,7 +62,6 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
logger.setContent(jackson.writeValueAsString(store)); logger.setContent(jackson.writeValueAsString(store));
checkUnique(store); checkUnique(store);
store.setStatus(TimiJava.defaultIfNull(store.getStatus(), GaoStore.Status.ACTIVE)); store.setStatus(TimiJava.defaultIfNull(store.getStatus(), GaoStore.Status.ACTIVE));
store.setAutoClosedDay(TimiJava.defaultIfNull(store.getAutoClosedDay(), false));
store.setSort(TimiJava.defaultIfNull(store.getSort(), 0)); store.setSort(TimiJava.defaultIfNull(store.getSort(), 0));
super.create(store); super.create(store);
logger.setLevel(Logger.Level.INFO); logger.setLevel(Logger.Level.INFO);
@@ -74,8 +73,8 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
} catch (Exception e) { } catch (Exception e) {
logger.setLevel(Logger.Level.ERROR); logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e)); logger.setException(TimiJava.serializeThrowable(e));
log.error("create store error", e); log.error("新增 GAO 门店失败", e);
throw new TimiException(TimiCode.ERROR, "新增门店失败", e); throw new TimiException(TimiCode.ERROR, "新增 GAO 门店失败", e);
} finally { } finally {
loggerService.create(logger); loggerService.create(logger);
} }
@@ -93,7 +92,15 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
try { try {
logger.setContent(jackson.writeValueAsString(store)); logger.setContent(jackson.writeValueAsString(store));
checkUnique(store); checkUnique(store);
super.update(store); GaoStore dbStore = get(store.getId());
dbStore.setCode(store.getCode());
dbStore.setName(store.getName());
dbStore.setStatus(store.getStatus());
dbStore.setTelephone(store.getTelephone());
dbStore.setAddress(store.getAddress());
dbStore.setRemark(store.getRemark());
super.update(dbStore);
logger.setLevel(Logger.Level.INFO);
logger.setResult(store.getId()); logger.setResult(store.getId());
} catch (TimiException e) { } catch (TimiException e) {
logger.setLevel(Logger.Level.WARN); logger.setLevel(Logger.Level.WARN);
@@ -102,8 +109,8 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
} catch (Exception e) { } catch (Exception e) {
logger.setLevel(Logger.Level.ERROR); logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e)); logger.setException(TimiJava.serializeThrowable(e));
log.error("update store error", e); log.error("修改 GAO 门店失败", e);
throw new TimiException(TimiCode.ERROR, "修改门店失败", e); throw new TimiException(TimiCode.ERROR, "修改 GAO 门店失败", e);
} finally { } finally {
loggerService.create(logger); loggerService.create(logger);
} }
@@ -128,8 +135,8 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
} catch (Exception e) { } catch (Exception e) {
logger.setLevel(Logger.Level.ERROR); logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e)); logger.setException(TimiJava.serializeThrowable(e));
log.error("delete store error", e); log.error("删除 GAO 门店失败", e);
throw new TimiException(TimiCode.ERROR, "删除门店失败", e); throw new TimiException(TimiCode.ERROR, "删除 GAO 门店失败", e);
} finally { } finally {
loggerService.create(logger); loggerService.create(logger);
} }
@@ -146,11 +153,6 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
return getByUserId(userLoginService.getRequireLoginUserId()).getId(); return getByUserId(userLoginService.getRequireLoginUserId()).getId();
} }
@Override
public List<GaoStore> listByAutoClosedDay() {
return mapper.selectAllByAutoClosedDay();
}
@Override @Override
public byte[] export(Page<GaoStore> page) throws IOException { public byte[] export(Page<GaoStore> page) throws IOException {
page.setIndex(0); page.setIndex(0);
@@ -165,31 +167,16 @@ public class GaoStoreServiceImplement extends AbstractEntityService<GaoStore, St
if (TimiJava.isEmpty(idList)) { if (TimiJava.isEmpty(idList)) {
return; return;
} }
Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_SORT");
try { List<GaoStore> storeList = mapper.selectByIdList(new LinkedHashSet<>(idList));
logger.setContent(jackson.writeValueAsString(idList)); Map<String, GaoStore> storeMap = storeList.stream().collect(Collectors.toMap(GaoStore::getId, Function.identity()));
List<GaoStore> storeList = mapper.selectByIdList(new LinkedHashSet<>(idList)); for (int i = 0; i < idList.size(); i++) {
Map<String, GaoStore> storeMap = storeList.stream().collect(Collectors.toMap(GaoStore::getId, Function.identity())); GaoStore store = storeMap.get(idList.get(i));
for (int i = 0; i < idList.size(); i++) { if (store == null) {
GaoStore store = storeMap.get(idList.get(i)); continue;
if (store == null) {
continue;
}
store.setSort(i);
mapper.update(store);
} }
logger.setLevel(Logger.Level.INFO); store.setSort(i);
} catch (TimiException e) { mapper.update(store);
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
throw e;
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("删除 GAO 门店失败", e);
throw new TimiException(TimiCode.ERROR, "删除 GAO 门店失败", e);
} finally {
loggerService.create(logger);
} }
} }
@@ -1,43 +0,0 @@
package com.imyeyu.api.modules.gao.task;
import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
import com.imyeyu.api.modules.gao.service.GaoStoreService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
///
/// GAO 门店营业日自动休息任务
///
/// @author Codex
/// @since 2026-08-12
@Slf4j
@Service
@RequiredArgsConstructor
public class GaoStoreAutoCloseDayTask {
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private final GaoStoreService storeService;
private final GaoStoreBusinessDayService businessDayService;
/// 每天零点后自动补记前一天无登记门店的休息日
@Scheduled(cron = "0 1 0 * * ?")
public void run() {
int dateValue = Integer.parseInt(DAY_FORMATTER.format(LocalDate.now().minusDays(1)));
List<GaoStore> storeList = storeService.listByAutoClosedDay();
for (GaoStore store : storeList) {
try {
businessDayService.autoCloseBusinessDay(store.getId(), dateValue);
} catch (Exception e) {
log.error("自动补记 GAO 门店休息日失败: storeId[{}], dateValue[{}]", store.getId(), dateValue, e);
}
}
}
}
@@ -35,7 +35,7 @@ public class StoreXSSFBuilder extends BaseXSSFBuilder<GaoStore> {
appendRow(sheet, 0, titleStyle, "门店列表导出"); appendRow(sheet, 0, titleStyle, "门店列表导出");
appendRow(sheet, 1, null, "生成时间", Time.nowString()); appendRow(sheet, 1, null, "生成时间", Time.nowString());
appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size())); appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size()));
appendRow(sheet, 3, headerStyle, "门店编码", "门店名称", "状态", "电话", "地址", "备注", "自动计算休息日", "排序", "创建时间"); appendRow(sheet, 3, headerStyle, "门店编码", "门店名称", "状态", "电话", "地址", "备注", "排序", "创建时间");
int rowIndex = 4; int rowIndex = 4;
for (GaoStore store : list) { for (GaoStore store : list) {
appendRow(sheet, rowIndex, null, appendRow(sheet, rowIndex, null,
@@ -45,13 +45,12 @@ public class StoreXSSFBuilder extends BaseXSSFBuilder<GaoStore> {
text(store.getTelephone()), text(store.getTelephone()),
text(store.getAddress()), text(store.getAddress()),
text(store.getRemark()), text(store.getRemark()),
Boolean.TRUE.equals(store.getAutoClosedDay()) ? "" : "",
store.getSort() == null ? "" : String.valueOf(store.getSort()), store.getSort() == null ? "" : String.valueOf(store.getSort()),
Time.toDateTime(store.getCreatedAt()) Time.toDateTime(store.getCreatedAt())
); );
rowIndex++; rowIndex++;
} }
for (int index = 0; index < 9; index++) { for (int index = 0; index < 8; index++) {
sheet.autoSizeColumn(index); sheet.autoSizeColumn(index);
sheet.setColumnWidth(index, Math.clamp(sheet.getColumnWidth(index), 10 * 256, 36 * 256)); sheet.setColumnWidth(index, Math.clamp(sheet.getColumnWidth(index), 10 * 256, 36 * 256));
} }
@@ -1,25 +0,0 @@
package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.java.bean.BasePage;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/// GAO 客户分页查询请求
///
/// @author Codex
/// @since 2026-08-13
@Data
@EqualsAndHashCode(callSuper = true)
public class GaoCustomerPage extends BasePage {
/// 门店 ID
private String storeId;
/// 关键字,匹配编码、姓名、电话
private String keyword;
/// 标签 ID 列表,客户必须同时拥有列表内全部标签
private List<String> tagIdList;
}
@@ -23,24 +23,9 @@ public class GaoEventRecordRankPage extends BasePage {
/// 登记事件 ID 列表,为空查询全部登记事件 /// 登记事件 ID 列表,为空查询全部登记事件
private List<String> eventIdList; private List<String> eventIdList;
/// 排行类型
private RankType rankType;
/// 开始登记时间 /// 开始登记时间
private Long beginAt; private Long beginAt;
/// 结束登记时间 /// 结束登记时间
private Long endAt; private Long endAt;
/// 查询当天日期值,服务端内部填充,格式为 yyyyMMdd
private Integer todayDateValue;
public enum RankType {
/// 按累计登记天数
TOTAL_DAYS,
/// 按累计登记次数
TOTAL_COUNT,
/// 按最近连续登记天数
CONTINUOUS_DAYS
}
} }
@@ -143,7 +143,7 @@ public class FileController implements TimiJava, OS.FileSystem {
public void read(HttpServletRequest req, HttpServletResponse resp) { public void read(HttpServletRequest req, HttpServletResponse resp) {
try { try {
String path = req.getServletPath().substring("/system/file/read".length()); String path = req.getServletPath().substring("/system/file/read".length());
path = "/root/bin/data/mnt" + path; path = "E:\\IDEAProject\\TimiServerAPI\\data\\file" + path;
if (TimiJava.isEmpty(path)) { if (TimiJava.isEmpty(path)) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return; return;
@@ -1,14 +0,0 @@
ALTER TABLE `gao_store`
ADD COLUMN `auto_closed_day` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'true 为启用自动计算休息日' AFTER `remark`;
ALTER TABLE `gao_store_business_day`
DROP KEY `uk_gao_store_business_day_active_no`,
DROP KEY `idx_gao_store_business_day_open_no`,
DROP COLUMN `active_business_day_no`;
ALTER TABLE `gao_store_business_day`
CHANGE COLUMN `open_status` `status` VARCHAR(32) NOT NULL COMMENT '营业状态';
ALTER TABLE `gao_store_business_day`
ADD COLUMN `active_business_day_no` BIGINT GENERATED ALWAYS AS (
CASE WHEN `deleted_at` IS NULL AND `status` = 'OPEN' THEN `business_day_no` ELSE NULL END
) STORED COMMENT '未删除营业日序号' INVISIBLE AFTER `business_day_no`,
ADD UNIQUE KEY `uk_gao_store_business_day_active_no` (`store_id`, `active_business_day_no`),
ADD KEY `idx_gao_store_business_day_status_no` (`store_id`, `status`, `business_day_no`);
@@ -1,18 +0,0 @@
-- 历史标签统一归入迁移时创建的默认门店。
SET @gao_default_store_id = '00000000-0000-0000-0000-000000000001';
ALTER TABLE `tag`
ADD COLUMN `owner_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '归属类型' AFTER `id`,
ADD COLUMN `owner_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '归属门店 ID' AFTER `owner_type`;
UPDATE `tag`
SET
`owner_type` = 'GAO_STORE',
`owner_id` = @gao_default_store_id
WHERE `owner_type` IS NULL
OR `owner_id` IS NULL;
ALTER TABLE `tag`
MODIFY COLUMN `owner_type` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '归属类型',
MODIFY COLUMN `owner_id` VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '归属门店 ID',
ADD KEY `idx_tag_owner` (`owner_type`, `owner_id`, `deleted_at`);
@@ -19,52 +19,6 @@
AND `deleted_at` IS NULL AND `deleted_at` IS NULL
</select> </select>
<sql id="queryWhere">
`c`.`deleted_at` IS NULL
<if test="storeId != null and storeId != ''">
AND `c`.`store_id` = #{storeId}
</if>
<if test="keyword != null and keyword != ''">
AND (
`c`.`code` LIKE CONCAT('%', #{keyword}, '%')
OR `c`.`name` LIKE CONCAT('%', #{keyword}, '%')
OR `c`.`telephone` LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="tagIdList != null and !tagIdList.isEmpty()">
<bind name="tagCount" value="tagIdList.size()"/>
AND `c`.`id` IN (
SELECT `ta`.`biz_id`
FROM `tag_apply` `ta`
WHERE
`ta`.`biz_type` = 'GAO_CUSTOMER'
AND `ta`.`tag_id` IN
<foreach collection="tagIdList" item="tagId" separator="," open="(" close=")">
#{tagId}
</foreach>
AND `ta`.`deleted_at` IS NULL
GROUP BY `ta`.`biz_id`
HAVING COUNT(DISTINCT `ta`.`tag_id`) = #{tagCount}
)
</if>
</sql>
<select id="countByQuery" resultType="long">
SELECT COUNT(1)
FROM `gao_customer` `c`
WHERE
<include refid="queryWhere"/>
</select>
<select id="selectByQuery" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer">
SELECT `c`.*
FROM `gao_customer` `c`
WHERE
<include refid="queryWhere"/>
ORDER BY `c`.`created_at` DESC, `c`.`id` DESC
LIMIT #{offset}, #{limit}
</select>
<select id="stateDailyNewCustomer" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView"> <select id="stateDailyNewCustomer" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView">
SELECT SELECT
CAST(DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d') AS UNSIGNED) AS `dateValue`, CAST(DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d') AS UNSIGNED) AS `dateValue`,
@@ -129,10 +129,10 @@
<include refid="rankWhere"/> <include refid="rankWhere"/>
</select> </select>
<select id="selectRankByRange" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView"> <select id="selectRankByRange" resultType="com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView">
WITH RECURSIVE `daily_stat` AS ( WITH `daily_stat` AS (
SELECT SELECT
r.`customer_id` AS `customerId`, r.`customer_id` AS `customerId`,
r.`registered_date_value` AS `registeredDateValue`, r.`business_day_no` AS `businessDayNo`,
COUNT(1) AS `dailyCount`, COUNT(1) AS `dailyCount`,
MAX(r.`registered_at`) AS `latestRegisteredAt` MAX(r.`registered_at`) AS `latestRegisteredAt`
FROM `gao_event_record` r FROM `gao_event_record` r
@@ -141,39 +141,7 @@
<include refid="rankWhere"/> <include refid="rankWhere"/>
GROUP BY GROUP BY
r.`customer_id`, r.`customer_id`,
r.`registered_date_value` r.`business_day_no`
),
`range_bound` AS (
SELECT
LEAST(
STR_TO_DATE(CAST(MIN(`registeredDateValue`) AS CHAR), '%Y%m%d'),
STR_TO_DATE(CAST(#{todayDateValue} AS CHAR), '%Y%m%d')
) AS `beginDate`,
STR_TO_DATE(CAST(#{todayDateValue} AS CHAR), '%Y%m%d') AS `endDate`
FROM `daily_stat`
),
`calendar` AS (
SELECT
CAST(DATE_FORMAT(`beginDate`, '%Y%m%d') AS UNSIGNED) AS `dateValue`,
`beginDate` AS `dateDate`
FROM `range_bound`
WHERE `beginDate` IS NOT NULL
UNION ALL
SELECT
CAST(DATE_FORMAT(DATE_ADD(c.`dateDate`, INTERVAL 1 DAY), '%Y%m%d') AS UNSIGNED) AS `dateValue`,
DATE_ADD(c.`dateDate`, INTERVAL 1 DAY) AS `dateDate`
FROM `calendar` c
JOIN `range_bound` b ON c.`dateDate` &lt; b.`endDate`
),
`open_calendar` AS (
SELECT
c.`dateValue`,
ROW_NUMBER() OVER (ORDER BY c.`dateDate`) AS `openIndex`
FROM `calendar` c
LEFT JOIN `gao_store_business_day` b ON b.`store_id` = #{storeId}
AND b.`date_value` = c.`dateValue`
AND b.`deleted_at` IS NULL
WHERE COALESCE(b.`status`, 'OPEN') != 'CLOSED'
), ),
`rank_stat` AS ( `rank_stat` AS (
SELECT SELECT
@@ -186,58 +154,35 @@
), ),
`continuous_ordered` AS ( `continuous_ordered` AS (
SELECT SELECT
d.`customerId`, `customerId`,
o.`openIndex`, `businessDayNo`,
ROW_NUMBER() OVER (PARTITION BY d.`customerId` ORDER BY o.`openIndex` DESC) AS `dayIndex` ROW_NUMBER() OVER (PARTITION BY `customerId` ORDER BY `businessDayNo` DESC) AS `dayIndex`
FROM `daily_stat` d FROM `daily_stat`
JOIN `open_calendar` o ON d.`registeredDateValue` = o.`dateValue`
), ),
`anchor_date` AS ( `latest_date` AS (
SELECT SELECT
MAX(`openIndex`) AS `anchorOpenIndex` `customerId`,
FROM `open_calendar` MAX(`businessDayNo`) AS `latestBusinessDayNo`
FROM `daily_stat`
GROUP BY `customerId`
), ),
`continuous_stat` AS ( `continuous_stat` AS (
SELECT SELECT
o.`customerId`, o.`customerId`,
CASE COUNT(1) AS `recentContinuousEventDays`
WHEN COUNT(1) &lt; 2 THEN 0
ELSE COUNT(1)
END AS `recentContinuousEventDays`
FROM `continuous_ordered` o FROM `continuous_ordered` o
JOIN `anchor_date` a LEFT JOIN `latest_date` l ON o.`customerId` = l.`customerId`
WHERE WHERE o.`businessDayNo` + o.`dayIndex` = l.`latestBusinessDayNo` + 1
o.`openIndex` + o.`dayIndex` = a.`anchorOpenIndex` + 1
<if test="eventIdList == null or eventIdList.size() != 1">
AND 1 = 0
</if>
GROUP BY o.`customerId` GROUP BY o.`customerId`
), ),
`ranked` AS ( `ranked` AS (
SELECT SELECT
ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
ORDER BY ORDER BY
<choose>
<when test="rankType != null and rankType.name() == 'TOTAL_COUNT'">
r.`totalEventCount` DESC,
r.`totalEventDays` DESC,
r.`latestRegisteredAt` DESC,
r.`customerId`
</when>
<when test="rankType != null and rankType.name() == 'CONTINUOUS_DAYS'">
COALESCE(c.`recentContinuousEventDays`, 0) DESC,
r.`totalEventDays` DESC,
r.`totalEventCount` DESC,
r.`latestRegisteredAt` DESC,
r.`customerId`
</when>
<otherwise>
r.`totalEventDays` DESC, r.`totalEventDays` DESC,
r.`totalEventCount` DESC, r.`totalEventCount` DESC,
r.`latestRegisteredAt` DESC, r.`latestRegisteredAt` DESC,
r.`customerId` r.`customerId`
</otherwise>
</choose>
) AS `rank`, ) AS `rank`,
r.`customerId`, r.`customerId`,
r.`totalEventDays`, r.`totalEventDays`,
@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.common.mapper.TagMapper">
<select id="selectByIdList" resultType="com.imyeyu.api.modules.common.entity.Tag">
SELECT
*
FROM `tag`
WHERE
TRUE
<if test="idList != null and !idList.isEmpty()">
AND `id` IN
<foreach collection="idList" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<if test="idList == null or idList.isEmpty()">
AND FALSE
</if>
AND `deleted_at` IS NULL
</select>
<select id="selectByOwnerAndZhCN" resultType="com.imyeyu.api.modules.common.entity.Tag">
SELECT
`t`.*
FROM `tag` `t`
INNER JOIN `multilingual` `m`
ON `m`.`id` = `t`.`name_lang_id`
AND `m`.`deleted_at` IS NULL
WHERE
`t`.`owner_type` = #{ownerType}
AND `t`.`owner_id` = #{ownerId}
AND `m`.`zh_cn` = #{zhCN}
AND `t`.`deleted_at` IS NULL
LIMIT 1
</select>
</mapper>