diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
deleted file mode 100644
index 22e3b6c..0000000
--- a/.idea/data_source_mapping.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java
index 600db09..4d24acc 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/annotation/RequireGaoRoleInterceptor.java
@@ -8,6 +8,7 @@ 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;
@@ -32,6 +33,9 @@ public class RequireGaoRoleInterceptor implements HandlerInterceptor {
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;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java
index 3f25cb5..845e349 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoPermissionCode.java
@@ -11,6 +11,16 @@ import lombok.Getter;
/// @since 2026-07-26
public enum GaoPermissionCode implements BuiltinPermissionCode {
+ STORE_CREATE("STORE:CREATE", "门店创建", false, true),
+
+ STORE_READ("STORE:READ", "门店读取", true, true),
+
+ STORE_EXPORT("STORE:EXPORT", "门店导出", false, true),
+
+ STORE_UPDATE("STORE:UPDATE", "门店修改", true, true),
+
+ STORE_DELETE("STORE:DELETE", "门店删除", false, true),
+
CUSTOMER_CREATE("CUSTOMER:CREATE", "客户创建"),
CUSTOMER_READ("CUSTOMER:READ", "客户读取"),
@@ -58,14 +68,23 @@ public enum GaoPermissionCode implements BuiltinPermissionCode {
@Getter
final boolean grantToStoreManager;
+ /// `true` 为初始化时授权给全局管理员角色
+ @Getter
+ final boolean grantToGlobalManager;
+
GaoPermissionCode(String value, String defaultName) {
- this(value, defaultName, false);
+ this(value, defaultName, false, false);
}
GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager) {
+ this(value, defaultName, grantToStoreManager, false);
+ }
+
+ GaoPermissionCode(String value, String defaultName, boolean grantToStoreManager, boolean grantToGlobalManager) {
this.value = value;
this.defaultName = defaultName;
this.grantToStoreManager = grantToStoreManager;
+ this.grantToGlobalManager = grantToGlobalManager;
}
@Override
diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java
index b76855e..04e0b9b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoRoleCode.java
@@ -12,12 +12,16 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum GaoRoleCode implements BuiltinRoleCode {
- /// 店长
- STORE_MANAGER("店长"),
+ /// 全局管理员
+ GLOBAL_MANAGER("全局管理员", null),
- EMPLOYEE("员工");
+ /// 店长
+ STORE_MANAGER("店长", GLOBAL_MANAGER.name()),
+
+ EMPLOYEE("员工", STORE_MANAGER.name());
final String defaultName;
+ final String parentCode;
@Override
public ModuleCode getModuleCode() {
@@ -36,6 +40,6 @@ public enum GaoRoleCode implements BuiltinRoleCode {
@Override
public String getParentCode() {
- return null;
+ return parentCode;
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java
index 260ea13..d237a78 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerController.java
@@ -15,6 +15,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.TimiJava;
+import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.Network;
import com.imyeyu.spring.TimiSpring;
import com.imyeyu.spring.annotation.AOPLog;
@@ -111,13 +112,25 @@ public class GaoCustomerController {
service.delete(id);
}
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
+ @PostMapping("/find/id")
+ @JsonView(ResponseView.Public.class)
+ public GaoCustomer findById(@RequestParam String id) {
+ GaoCustomer customer = service.get(id);
+ TimiException.required(customer, "not found customer");
+ customer.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.GAO_CUSTOMER, customer.getId()));
+ return customer;
+ }
+
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/find/code")
@JsonView(ResponseView.Public.class)
- public GaoCustomer findByCode(@RequestParam String code) {
- GaoCustomer customer = service.getByCode(code);
+ public GaoCustomer findByCode(@RequestParam String storeId, @RequestParam String code) {
+ GaoCustomer customer = service.getByCode(storeId, code);
if (customer == null) {
return null;
}
@@ -130,8 +143,8 @@ public class GaoCustomerController {
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/find/name")
@JsonView(ResponseView.Public.class)
- public GaoCustomer findByName(@RequestParam String name) {
- GaoCustomer customer = service.getByName(name);
+ public GaoCustomer findByName(@RequestParam String storeId, @RequestParam String name) {
+ GaoCustomer customer = service.getByName(storeId, name);
if (customer == null) {
return null;
}
@@ -197,7 +210,7 @@ public class GaoCustomerController {
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/state/gender")
- public List genderStat() {
- return service.stateGender();
+ public List genderStat(@RequestParam String storeId) {
+ return service.stateGender(storeId);
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
index 6103e94..08a19d2 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoEventController.java
@@ -49,8 +49,8 @@ public class GaoEventController {
@AOPLog
@RequestRateLimit
@PostMapping("/list/valid")
- public List listValid() {
- return service.listValid();
+ public List listValid(@RequestParam String storeId) {
+ return service.listValid(storeId);
}
@AOPLog
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java
new file mode 100644
index 0000000..68dad8a
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreBusinessDayController.java
@@ -0,0 +1,82 @@
+package com.imyeyu.api.modules.gao.controller;
+
+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.GaoRoleCode;
+import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
+import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
+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 lombok.RequiredArgsConstructor;
+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-06
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/store/business/day")
+@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
+public class GaoStoreBusinessDayController {
+
+ private final GaoStoreBusinessDayService service;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody Page page) {
+ return service.page(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_READ)
+ @PostMapping("/calendar")
+ public List calendar(@RequestParam String storeId, @RequestParam Integer beginDateValue, @RequestParam Integer endDateValue) {
+ return service.listByDateRange(storeId, beginDateValue, endDateValue);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_READ)
+ @PostMapping("/detail")
+ public GaoStoreBusinessDay detail(@RequestParam String id) {
+ return service.get(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_CREATE)
+ @PostMapping("/create")
+ public String create(@RequestBody GaoStoreBusinessDay businessDay) {
+ service.create(businessDay);
+ return businessDay.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
+ @PostMapping("/update")
+ public void update(@RequestBody GaoStoreBusinessDay businessDay) {
+ service.update(businessDay);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_DELETE)
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id) {
+ service.delete(id);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java
new file mode 100644
index 0000000..86f6229
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreController.java
@@ -0,0 +1,103 @@
+package com.imyeyu.api.modules.gao.controller;
+
+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.GaoRoleCode;
+import com.imyeyu.api.modules.gao.entity.GaoStore;
+import com.imyeyu.api.modules.gao.service.GaoStoreService;
+import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.api.bean.RoleMatchMode;
+import com.imyeyu.network.Network;
+import com.imyeyu.spring.TimiSpring;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
+import com.imyeyu.utils.Time;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+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.io.IOException;
+
+/// GAO 门店接口
+///
+/// @author Codex
+/// @since 2026-08-06
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/gao/store")
+@RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
+public class GaoStoreController {
+
+ private final GaoStoreService service;
+ private final UserLoginService userLoginService;
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody Page page) {
+ return service.page(page);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_READ)
+ @PostMapping("/detail")
+ public GaoStore detail(@RequestParam String id) {
+ return service.get(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_CREATE)
+ @PostMapping("/create")
+ public String create(@RequestBody GaoStore store) {
+ service.create(store);
+ return store.getId();
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_UPDATE)
+ @PostMapping("/update")
+ public void update(@RequestBody GaoStore store) {
+ service.update(store);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STORE_DELETE)
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id) {
+ service.delete(id);
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @IgnoreGlobalReturn
+ @RequireGaoPermission(GaoPermissionCode.STORE_EXPORT)
+ @PostMapping("/export")
+ public void export(@RequestBody Page page) throws IOException {
+ HttpServletResponse resp = TimiSpring.getResponse();
+ String contentDisposition = Network.getFileDownloadHeader("门店列表-%s.xlsx".formatted(Time.serialize.format(Time.now())));
+ resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ resp.setHeader("Content-Disposition", contentDisposition);
+ resp.getOutputStream().write(service.export(page));
+ }
+
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoRole(value = {GaoRoleCode.GLOBAL_MANAGER, GaoRoleCode.STORE_MANAGER, GaoRoleCode.EMPLOYEE}, mode = RoleMatchMode.ANY)
+ @PostMapping("/detail/user")
+ public GaoStore getByLoginUser() {
+ return service.getByUserId(userLoginService.getRequireLoginUserId());
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
index cf26252..dbc0e8b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
@@ -2,14 +2,23 @@ package com.imyeyu.api.modules.gao.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.imyeyu.api.bean.ModuleCode;
+import com.imyeyu.api.modules.common.entity.Attachment;
+import com.imyeyu.api.modules.common.service.AttachmentService;
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.GaoRoleCode;
import com.imyeyu.api.modules.gao.entity.GaoUser;
+import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.gao.service.GaoUserService;
import com.imyeyu.api.modules.user.entity.Role;
+import com.imyeyu.api.modules.user.entity.User;
+import com.imyeyu.api.modules.user.service.RoleChecker;
import com.imyeyu.api.modules.user.service.RoleService;
+import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.api.modules.user.service.UserRoleService;
+import com.imyeyu.api.modules.user.service.UserService;
+import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page;
@@ -23,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
+import java.util.Map;
/// GAO 用户接口
///
@@ -34,8 +44,14 @@ import java.util.List;
@RequireGaoRole(GaoRoleCode.STORE_MANAGER)
public class GaoUserController {
- private final GaoUserService service;
+ private final UserService userService;
private final RoleService roleService;
+ private final RoleChecker roleChecker;
+ private final GaoUserService service;
+ private final UserRoleService userRoleService;
+ private final GaoStoreService gaoStoreService;
+ private final UserLoginService userLoginService;
+ private final AttachmentService attachmentService;
@AOPLog
@RequestRateLimit
@@ -43,7 +59,22 @@ public class GaoUserController {
@PostMapping("/list")
@JsonView(ResponseView.Public.class)
public PageResult list(@RequestBody Page page) {
- return service.pageWithUser(page);
+ if (!roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) {
+ // 非全局管理只能看所在店用户
+ page.setEqualsExample(TimiJava.defaultIfNull(page.getEqualsExample(), new GaoUser()));
+ page.getEqualsExample().setStoreId(gaoStoreService.getByUserId(userLoginService.getRequireLoginUserId()).getId());
+ }
+ PageResult result = service.page(page);
+ {
+ List userIdList = result.getList().stream().map(GaoUser::getUserId).toList();
+ Map userMap = userService.mapByIdList(userIdList);
+ Map> roleMap = userRoleService.mapRoleListByUserIdList(userIdList);
+ for (GaoUser gaoUser : result.getList()) {
+ gaoUser.setUser(userMap.get(gaoUser.getUserId()));
+ gaoUser.getUser().setRoleEntityList(roleMap.get(gaoUser.getUserId()));
+ }
+ }
+ return result;
}
@AOPLog
@@ -52,16 +83,14 @@ public class GaoUserController {
@PostMapping("/detail")
@JsonView(ResponseView.Public.class)
public GaoUser detail(@RequestParam String id) {
- return service.get(id);
- }
-
- @AOPLog
- @RequestRateLimit
- @RequireGaoPermission(GaoPermissionCode.USER_READ)
- @PostMapping("/detail/user")
- @JsonView(ResponseView.Public.class)
- public GaoUser detailByUserId(@RequestParam String userId) {
- return service.getByUserId(userId);
+ GaoUser result = service.get(id);
+ {
+ User user = userService.get(result.getUserId());
+ user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId()));
+ user.setRoleEntityList(userRoleService.listRoleByUserId(user.getId()));
+ result.setUser(user);
+ }
+ return result;
}
@AOPLog
@@ -84,6 +113,17 @@ public class GaoUserController {
return service.get(gaoUser.getId());
}
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoRole(GaoRoleCode.GLOBAL_MANAGER)
+ @RequireGaoPermission(GaoPermissionCode.USER_UPDATE)
+ @PostMapping("/transfer/store")
+ @JsonView(ResponseView.Public.class)
+ public GaoUser transferStore(@RequestBody GaoUser gaoUser) {
+ service.transferStore(gaoUser);
+ return service.get(gaoUser.getId());
+ }
+
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.USER_DELETE)
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
index af788d8..36573a3 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
@@ -48,6 +48,9 @@ public class GaoCustomer extends UUIDEntity {
/// 客户编码
private String code;
+ /// 门店 ID
+ private String storeId;
+
/// 姓名
private String name;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
index 1142178..0a354b1 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEvent.java
@@ -73,6 +73,9 @@ public class GaoEvent extends UUIDEntity {
/// 登记事件名称
private String name;
+ /// 门店 ID
+ private String storeId;
+
/// 说明
private String description;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java
index 4dacd05..9a2a53b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoEventRecord.java
@@ -31,6 +31,9 @@ public class GaoEventRecord extends UUIDEntity {
/// 客户 ID
private String customerId;
+ /// 门店 ID
+ private String storeId;
+
/// 登记登记事件 ID
private String eventId;
@@ -43,6 +46,12 @@ public class GaoEventRecord extends UUIDEntity {
/// 登记时间
private Long registeredAt;
+ /// 登记日期值,格式为 yyyyMMdd
+ private Integer registeredDateValue;
+
+ /// 营业日递增序号
+ private Long businessDayNo;
+
@Transient
private GaoCustomer customer;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java
new file mode 100644
index 0000000..f9042b0
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStore.java
@@ -0,0 +1,57 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+///
+/// GAO 门店
+///
+/// @author Codex
+/// @since 2026-08-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoStore extends UUIDEntity {
+
+ ///
+ /// 门店状态
+ ///
+ /// @author Codex
+ /// @since 2026-08-06
+ public enum Status {
+
+ /// 可用
+ ACTIVE,
+
+ /// 停用
+ INACTIVE,
+
+ /// 已删除
+ DELETED
+ }
+
+ /// 门店编码
+ private String code;
+
+ /// 门店名称
+ private String name;
+
+ /// 状态
+ private Status status;
+
+ /// 电话
+ private String telephone;
+
+ /// 地址
+ private String address;
+
+ /// 备注
+ private String remark;
+
+ /// 排序值
+ private Integer sort;
+
+ public boolean isActivated() {
+ return status == Status.ACTIVE;
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java
new file mode 100644
index 0000000..6192ef0
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoStoreBusinessDay.java
@@ -0,0 +1,44 @@
+package com.imyeyu.api.modules.gao.entity;
+
+import com.imyeyu.spring.entity.UUIDEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+///
+/// GAO 门店营业日
+///
+/// @author Codex
+/// @since 2026-08-06
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GaoStoreBusinessDay extends UUIDEntity {
+
+ ///
+ /// 营业状态
+ ///
+ /// @author Codex
+ /// @since 2026-08-06
+ public enum OpenStatus {
+
+ /// 营业
+ OPEN,
+
+ /// 休息
+ CLOSED
+ }
+
+ /// 门店 ID
+ private String storeId;
+
+ /// 日期值,格式为 yyyyMMdd
+ private Integer dateValue;
+
+ /// 营业状态
+ private OpenStatus openStatus;
+
+ /// 营业日递增序号,仅营业日有值
+ private Long businessDayNo;
+
+ /// 原因
+ private String reason;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java
index fd012ce..133ebdd 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoUser.java
@@ -1,14 +1,11 @@
package com.imyeyu.api.modules.gao.entity;
import com.imyeyu.api.modules.user.entity.User;
-import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
-import java.util.List;
-
///
/// GAO 用户
///
@@ -21,6 +18,9 @@ public class GaoUser extends UUIDEntity {
/// 用户 ID
private String userId;
+ /// 门店 ID
+ private String storeId;
+
/// true 为启用
private Boolean enabled;
@@ -29,10 +29,4 @@ public class GaoUser extends UUIDEntity {
@Transient
private User user;
-
- @Transient
- private List roleList;
-
- @Transient
- private List roleIdList;
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java
index 1ee6b93..cde3fac 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.entity.GaoCustomer;
import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
@@ -22,16 +23,16 @@ public interface GaoCustomerMapper extends BaseMapper {
/// @param beginCreatedAt 开始创建时间
/// @param endCreatedAt 结束创建时间
/// @return 每日新增客户数
- List stateDailyNewCustomer(Long beginCreatedAt, Long endCreatedAt);
+ List stateDailyNewCustomer(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt, @Param("endCreatedAt") Long endCreatedAt);
/// 统计开始时间前客户总数
///
/// @param beginCreatedAt 开始创建时间
/// @return 客户总数
- Long countCreatedBefore(Long beginCreatedAt);
+ Long countCreatedBefore(@Param("storeId") String storeId, @Param("beginCreatedAt") Long beginCreatedAt);
/// 统计客户性别分布
///
/// @return 性别统计列表
- List stateGender();
+ List stateGender(@Param("storeId") String storeId);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java
index 56040d3..259b345 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventMapper.java
@@ -2,6 +2,7 @@ package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.entity.GaoEvent;
import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
@@ -15,5 +16,5 @@ public interface GaoEventMapper extends BaseMapper {
List selectByIdList(Collection idList);
- List selectValidList(Long now);
+ List selectValidList(@Param("storeId") String storeId, @Param("now") Long now);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
index 460900a..f5ac28f 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
@@ -5,6 +5,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage;
import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -14,7 +15,21 @@ import java.util.List;
/// @since 2026-07-27 14:11
public interface GaoEventRecordMapper extends BaseMapper {
- Long count(String customerId, String eventId, Long beginAt, Long endAt);
+ Long count(String storeId, String customerId, String eventId, Long beginAt, Long endAt);
+
+ /// 按门店和登记日期统计登记记录数
+ ///
+ /// @param storeId 门店 ID
+ /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd
+ /// @return 登记记录数
+ Long countByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue);
+
+ /// 同步门店指定日期的登记记录营业日序号快照
+ ///
+ /// @param storeId 门店 ID
+ /// @param registeredDateValue 登记日期值,格式为 yyyyMMdd
+ /// @param businessDayNo 营业日序号
+ void updateBusinessDayNoByStoreIdAndRegisteredDateValue(@Param("storeId") String storeId, @Param("registeredDateValue") Integer registeredDateValue, @Param("businessDayNo") Long businessDayNo);
long countByRange(GaoEventRecordPage page);
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java
new file mode 100644
index 0000000..0ea3a57
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreBusinessDayMapper.java
@@ -0,0 +1,49 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+///
+/// GAO 门店营业日 Mapper
+///
+/// @author Codex
+/// @since 2026-08-06
+public interface GaoStoreBusinessDayMapper extends BaseMapper {
+
+ /// 按门店和日期查询营业日
+ ///
+ /// @param storeId 门店 ID
+ /// @param dateValue 日期值,格式为 yyyyMMdd
+ /// @return 营业日
+ @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` = #{dateValue} AND `deleted_at` IS NULL LIMIT 1")
+ GaoStoreBusinessDay selectByStoreIdAndDateValue(@Param("storeId") String storeId, @Param("dateValue") Integer dateValue);
+
+ /// 查询指定日期前最后一个营业日序号
+ ///
+ /// @param storeId 门店 ID
+ /// @param dateValue 日期值,格式为 yyyyMMdd
+ /// @return 营业日序号
+ @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);
+
+ /// 查询指定日期及之后的营业日
+ ///
+ /// @param storeId 门店 ID
+ /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
+ /// @return 营业日列表
+ @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 selectOpenListFromDate(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue);
+
+ /// 查询日期范围内的营业日
+ ///
+ /// @param storeId 门店 ID
+ /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
+ /// @param endDateValue 结束日期值,格式为 yyyyMMdd
+ /// @return 营业日列表
+ @Select("SELECT * FROM `gao_store_business_day` WHERE `store_id` = #{storeId} AND `date_value` >= #{beginDateValue} AND `date_value` <= #{endDateValue} AND `deleted_at` IS NULL ORDER BY `date_value` ASC")
+ List selectByStoreIdAndDateRange(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue, @Param("endDateValue") Integer endDateValue);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java
new file mode 100644
index 0000000..36eaf89
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoStoreMapper.java
@@ -0,0 +1,19 @@
+package com.imyeyu.api.modules.gao.mapper;
+
+import com.imyeyu.api.modules.gao.entity.GaoStore;
+import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+///
+/// GAO 门店 Mapper
+///
+/// @author Codex
+/// @since 2026-08-06
+public interface GaoStoreMapper extends BaseMapper {
+
+ /// 查询用户所属的可用门店
+ ///
+ /// @param userId 用户 ID
+ /// @return 门店
+ GaoStore selectByUserId(@Param("userId") String userId);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java
index 88d550c..157595a 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoUserMapper.java
@@ -14,4 +14,7 @@ public interface GaoUserMapper extends BaseMapper {
@Select("SELECT * FROM `gao_user` WHERE `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1")
GaoUser selectByUserId(@Param("userId") String userId);
+
+ @Select("SELECT * FROM `gao_user` WHERE `store_id` = #{storeId} AND `user_id` = #{userId} AND `deleted_at` IS NULL LIMIT 1")
+ GaoUser selectByStoreIdAndUserId(@Param("storeId") String storeId, @Param("userId") String userId);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java
index b9b0ca0..2c15d7a 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java
@@ -23,11 +23,17 @@ public interface GaoCustomerService extends BaseService {
/// 按编码查询客户
///
+ /// @param storeId 门店 ID
/// @param code 客户编码
/// @return 客户
- GaoCustomer getByCode(String code);
+ GaoCustomer getByCode(String storeId, String code);
- GaoCustomer getByName(String name);
+ /// 按姓名查询客户
+ ///
+ /// @param storeId 门店 ID
+ /// @param name 姓名
+ /// @return 客户
+ GaoCustomer getByName(String storeId, String name);
List listByIdList(Collection idList);
@@ -65,6 +71,7 @@ public interface GaoCustomerService extends BaseService {
/// 统计客户性别分布
///
+ /// @param storeId 门店 ID
/// @return 性别统计列表
- List stateGender();
+ List stateGender(String storeId);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java
index f37f2db..6331a21 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoEventService.java
@@ -20,8 +20,9 @@ public interface GaoEventService extends BaseService {
/// 查询启用的登记事件
///
+ /// @param storeId 门店 ID
/// @return 登记事件列表
- List listValid();
+ List listValid(String storeId);
/// 查询登记事件要求的角色
///
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java
new file mode 100644
index 0000000..fb6eb78
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreBusinessDayService.java
@@ -0,0 +1,33 @@
+package com.imyeyu.api.modules.gao.service;
+
+import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
+import com.imyeyu.spring.service.BaseService;
+
+///
+/// GAO 门店营业日服务
+///
+/// @author Codex
+/// @since 2026-08-06
+public interface GaoStoreBusinessDayService extends BaseService {
+
+ /// 解析营业日,不存在时按默认营业日创建
+ ///
+ /// @param storeId 门店 ID
+ /// @param dateValue 日期值,格式为 yyyyMMdd
+ /// @return 营业日
+ GaoStoreBusinessDay resolveOpenBusinessDay(String storeId, Integer dateValue);
+
+ /// 重算门店营业日序号并同步登记记录快照
+ ///
+ /// @param storeId 门店 ID
+ /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
+ void rebuildBusinessDayNo(String storeId, Integer beginDateValue);
+
+ /// 查询日期范围内的营业日
+ ///
+ /// @param storeId 门店 ID
+ /// @param beginDateValue 开始日期值,格式为 yyyyMMdd
+ /// @param endDateValue 结束日期值,格式为 yyyyMMdd
+ /// @return 营业日列表
+ java.util.List listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue);
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java
new file mode 100644
index 0000000..5036e71
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreService.java
@@ -0,0 +1,27 @@
+package com.imyeyu.api.modules.gao.service;
+
+import com.imyeyu.api.modules.gao.entity.GaoStore;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.service.BaseService;
+
+import java.io.IOException;
+
+///
+/// GAO 门店服务
+///
+/// @author Codex
+/// @since 2026-08-06
+public interface GaoStoreService extends BaseService {
+
+ /// 查询用户所属门店
+ ///
+ /// @param userId 用户 ID
+ /// @return 门店
+ GaoStore getByUserId(String userId);
+
+ /// 导出门店列表
+ ///
+ /// @param page 分页查询条件
+ /// @return xlsx 文件字节
+ byte[] export(Page page) throws IOException;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java
index a7d3efd..b3de23b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoUserService.java
@@ -1,8 +1,6 @@
package com.imyeyu.api.modules.gao.service;
import com.imyeyu.api.modules.gao.entity.GaoUser;
-import com.imyeyu.spring.bean.Page;
-import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService;
///
@@ -12,15 +10,8 @@ import com.imyeyu.spring.service.BaseService;
/// @since 2026-07-30
public interface GaoUserService extends BaseService {
- /// 按用户 ID 查询 GAO 用户
+ /// 调整 GAO 用户所属门店
///
- /// @param userId 用户 ID
- /// @return GAO 用户
- GaoUser getByUserId(String userId);
-
- /// 分页查询并填充用户详情
- ///
- /// @param page 分页请求
- /// @return GAO 用户分页
- PageResult pageWithUser(Page page);
+ /// @param gaoUser GAO 用户,必须包含 ID 和目标门店 ID
+ void transferStore(GaoUser gaoUser);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java
index 9053b0a..66cf3e5 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java
@@ -34,8 +34,10 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/// 客户服务实现
///
@@ -66,21 +68,24 @@ public class GaoCustomerServiceImplement extends AbstractEntityService listByIdList(Collection idList) {
- return mapper.selectByIdList(idList);
+ return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
@@ -252,6 +263,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService stateDailyTrend(GaoCustomerTrendStateReq req) {
TimiException.required(req, "not found req");
+ TimiException.required(req.getStoreId(), "not found req.storeId");
TimiException.required(req.getBeginCreatedAt(), "not found req.beginCreatedAt");
TimiException.required(req.getEndCreatedAt(), "not found req.endCreatedAt");
TimiException.requiredTrue(req.getEndCreatedAt() >= req.getBeginCreatedAt(), "req.endCreatedAt lt req.beginCreatedAt");
@@ -325,10 +338,10 @@ public class GaoCustomerServiceImplement extends AbstractEntityService newCustomerCountMap = new HashMap<>();
- for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(beginCreatedAt, endCreatedAt)) {
+ for (GaoCustomerDailyStateView view : mapper.stateDailyNewCustomer(req.getStoreId(), beginCreatedAt, endCreatedAt)) {
newCustomerCountMap.put(view.getDateValue(), TimiJava.defaultIfNull(view.getNewCustomerCount(), 0L));
}
- long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(beginCreatedAt), 0L);
+ long totalCustomerCount = TimiJava.defaultIfNull(mapper.countCreatedBefore(req.getStoreId(), beginCreatedAt), 0L);
List list = new ArrayList<>();
for (LocalDate date = beginDate; !date.isAfter(endDate); date = date.plusDays(1)) {
int dateValue = Integer.parseInt(DAY_FORMATTER.format(date));
@@ -344,8 +357,9 @@ public class GaoCustomerServiceImplement extends AbstractEntityService stateGender() {
- return mapper.stateGender();
+ public List stateGender(String storeId) {
+ TimiException.required(storeId, "not found storeId");
+ return mapper.stateGender(storeId);
}
private void changeInvitedCount(String customerId, long delta) {
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
index 7f1c93a..e3af9a0 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
@@ -12,6 +12,7 @@ import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.api.modules.gao.service.GaoEventService;
+import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
import com.imyeyu.api.modules.gao.util.EventRecordXSSFBuilder;
import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage;
@@ -34,8 +35,10 @@ import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.time.Instant;
+import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
@@ -50,6 +53,8 @@ import java.util.List;
@RequiredArgsConstructor
public class GaoEventRecordServiceImplement extends AbstractEntityService implements GaoEventRecordService {
+ private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
+
private final ObjectMapper jackson;
private final RoleChecker roleChecker;
private final LoggerService loggerService;
@@ -57,6 +62,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService customerIdList = TimiJava.defaultIfEmpty(record.getCustomerIdList(), List.of(record.getCustomerId()));
TimiException.required(customerIdList, "not found customerId or empty customerIdList");
@@ -91,13 +100,19 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService pageByRange(GaoEventRecordPage page) {
+ TimiException.required(page.getStoreId(), "not found page.storeId");
PageResult result = new PageResult<>();
result.setTotal(mapper.countByRange(page));
result.setList(mapper.selectByRange(page));
@@ -205,12 +229,14 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService list = mapper.selectByRange(page);
return xssfBuilder.build(list);
}
@Override
public PageResult pageRankByRange(GaoEventRecordRankPage page) {
+ TimiException.required(page.getStoreId(), "not found page.storeId");
PageResult result = new PageResult<>();
result.setTotal(mapper.countRankByRange(page));
result.setList(mapper.selectRankByRange(page));
@@ -241,18 +267,18 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService {
long beginAt = Time.today(registeredAt);
long endAt = beginAt + Time.D;
- Long count = mapper.count(customerId, event.getId(), beginAt, endAt);
+ Long count = mapper.count(event.getStoreId(), customerId, event.getId(), beginAt, endAt);
TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户当前半日已登记该事件");
}
case INTERVAL_HOUR -> {
long beginAt = registeredAt - event.getLimitValue() * Time.H;
- Long count = mapper.count(customerId, event.getId(), beginAt, registeredAt);
+ Long count = mapper.count(event.getStoreId(), customerId, event.getId(), beginAt, registeredAt);
TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该客户登记该事件间隔不足");
}
case NATURAL_MONTH -> {
@@ -260,17 +286,22 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService {
- Long count = mapper.count(null, event.getId(), null, null);
+ Long count = mapper.count(event.getStoreId(), null, event.getId(), null, null);
TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "登记事件总次数已达上限");
}
case CUSTOMER_TOTAL_LIMIT -> {
- Long count = mapper.count(customerId, event.getId(), null, null);
+ Long count = mapper.count(event.getStoreId(), customerId, event.getId(), null, null);
TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < event.getLimitValue(), "该客户登记该事件次数已达上限");
}
}
}
+
+ private int dateValue(long time) {
+ LocalDate date = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).toLocalDate();
+ return Integer.parseInt(DAY_FORMATTER.format(date));
+ }
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java
index 6f42c65..cac18ba 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventServiceImplement.java
@@ -56,6 +56,7 @@ public class GaoEventServiceImplement extends AbstractEntityService listValid() {
- return mapper.selectValidList(Time.now());
+ public List listValid(String storeId) {
+ TimiException.required(storeId, "not found storeId");
+ return mapper.selectValidList(storeId, Time.now());
}
@Override
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java
new file mode 100644
index 0000000..a359323
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreBusinessDayServiceImplement.java
@@ -0,0 +1,135 @@
+package com.imyeyu.api.modules.gao.service.implement;
+
+import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
+import com.imyeyu.api.modules.gao.entity.GaoStoreBusinessDay;
+import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper;
+import com.imyeyu.api.modules.gao.mapper.GaoStoreBusinessDayMapper;
+import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.mapper.BaseMapper;
+import com.imyeyu.spring.service.AbstractEntityService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+///
+/// GAO 门店营业日服务实现
+///
+/// @author Codex
+/// @since 2026-08-06
+@Service
+@RequiredArgsConstructor
+public class GaoStoreBusinessDayServiceImplement extends AbstractEntityService implements GaoStoreBusinessDayService {
+
+ private final GaoStoreBusinessDayMapper mapper;
+ private final GaoEventRecordMapper eventRecordMapper;
+
+ @Override
+ protected BaseMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ public void create(GaoStoreBusinessDay businessDay) {
+ prepareBusinessDay(businessDay);
+ super.create(businessDay);
+ if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) {
+ rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue());
+ }
+ }
+
+ @Override
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ public void update(GaoStoreBusinessDay businessDay) {
+ TimiException.required(businessDay, "not found businessDay");
+ TimiException.required(businessDay.getId(), "not found businessDay.id");
+ GaoStoreBusinessDay dbBusinessDay = get(businessDay.getId());
+ TimiException.required(dbBusinessDay, "not found dbBusinessDay");
+ TimiException.requiredTrue(dbBusinessDay.getStoreId().equals(businessDay.getStoreId()), "businessDay.storeId invalid");
+ TimiException.requiredTrue(dbBusinessDay.getDateValue().equals(businessDay.getDateValue()), "businessDay.dateValue invalid");
+ if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) {
+ checkNoRecord(dbBusinessDay);
+ }
+ prepareBusinessDay(businessDay);
+ super.update(businessDay);
+ rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue());
+ }
+
+ @Override
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ public void delete(String id) {
+ GaoStoreBusinessDay businessDay = get(id);
+ TimiException.required(businessDay, "not found businessDay");
+ if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN) {
+ checkNoRecord(businessDay);
+ }
+ super.delete(id);
+ rebuildBusinessDayNo(businessDay.getStoreId(), businessDay.getDateValue());
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public GaoStoreBusinessDay resolveOpenBusinessDay(String storeId, Integer dateValue) {
+ TimiException.required(storeId, "not found storeId");
+ TimiException.required(dateValue, "not found dateValue");
+ GaoStoreBusinessDay businessDay = mapper.selectByStoreIdAndDateValue(storeId, dateValue);
+ if (businessDay != null) {
+ TimiException.requiredTrue(businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.OPEN, "门店当日休息,不能登记");
+ TimiException.required(businessDay.getBusinessDayNo(), "not found businessDay.businessDayNo");
+ return businessDay;
+ }
+ businessDay = new GaoStoreBusinessDay();
+ businessDay.setStoreId(storeId);
+ businessDay.setDateValue(dateValue);
+ businessDay.setOpenStatus(GaoStoreBusinessDay.OpenStatus.OPEN);
+ super.create(businessDay);
+ rebuildBusinessDayNo(storeId, dateValue);
+ return mapper.selectByStoreIdAndDateValue(storeId, dateValue);
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void rebuildBusinessDayNo(String storeId, Integer beginDateValue) {
+ TimiException.required(storeId, "not found storeId");
+ TimiException.required(beginDateValue, "not found beginDateValue");
+ long businessDayNo = TimiJava.defaultIfNull(mapper.selectLastBusinessDayNoBefore(storeId, beginDateValue), 0L);
+ for (GaoStoreBusinessDay businessDay : mapper.selectOpenListFromDate(storeId, beginDateValue)) {
+ businessDayNo++;
+ if (!Long.valueOf(businessDayNo).equals(businessDay.getBusinessDayNo())) {
+ businessDay.setBusinessDayNo(businessDayNo);
+ mapper.update(businessDay);
+ }
+ eventRecordMapper.updateBusinessDayNoByStoreIdAndRegisteredDateValue(storeId, businessDay.getDateValue(), businessDayNo);
+ }
+ }
+
+ @Override
+ public List listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue) {
+ TimiException.required(storeId, "not found storeId");
+ TimiException.required(beginDateValue, "not found beginDateValue");
+ TimiException.required(endDateValue, "not found endDateValue");
+ TimiException.requiredTrue(beginDateValue <= endDateValue, "endDateValue lt beginDateValue");
+ return mapper.selectByStoreIdAndDateRange(storeId, beginDateValue, endDateValue);
+ }
+
+ private void prepareBusinessDay(GaoStoreBusinessDay businessDay) {
+ TimiException.required(businessDay, "not found businessDay");
+ TimiException.required(businessDay.getStoreId(), "not found businessDay.storeId");
+ TimiException.required(businessDay.getDateValue(), "not found businessDay.dateValue");
+ businessDay.setOpenStatus(TimiJava.defaultIfNull(businessDay.getOpenStatus(), GaoStoreBusinessDay.OpenStatus.OPEN));
+ if (businessDay.getOpenStatus() == GaoStoreBusinessDay.OpenStatus.CLOSED) {
+ businessDay.setBusinessDayNo(null);
+ return;
+ }
+ businessDay.setBusinessDayNo(null);
+ }
+
+ private void checkNoRecord(GaoStoreBusinessDay businessDay) {
+ Long count = eventRecordMapper.countByStoreIdAndRegisteredDateValue(businessDay.getStoreId(), businessDay.getDateValue());
+ TimiException.requiredTrue(TimiJava.defaultIfNull(count, 0L) < 1, "该营业日已有登记记录,不能改为休息或删除");
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java
new file mode 100644
index 0000000..9d66219
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreServiceImplement.java
@@ -0,0 +1,160 @@
+package com.imyeyu.api.modules.gao.service.implement;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.imyeyu.api.modules.gao.entity.GaoStore;
+import com.imyeyu.api.modules.gao.mapper.GaoStoreMapper;
+import com.imyeyu.api.modules.gao.service.GaoStoreService;
+import com.imyeyu.api.modules.gao.util.StoreXSSFBuilder;
+import com.imyeyu.api.modules.system.entity.Logger;
+import com.imyeyu.api.modules.system.service.LoggerService;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.mapper.BaseMapper;
+import com.imyeyu.spring.service.AbstractEntityService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.util.List;
+
+///
+/// GAO 门店服务实现
+///
+/// @author Codex
+/// @since 2026-08-06
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class GaoStoreServiceImplement extends AbstractEntityService implements GaoStoreService {
+
+ private final ObjectMapper jackson;
+
+ private final LoggerService loggerService;
+
+ private final GaoStoreMapper mapper;
+ private final StoreXSSFBuilder storeXSSFBuilder;
+
+ @Override
+ protected BaseMapper mapper() {
+ return mapper;
+ }
+
+ @Override
+ public void create(GaoStore store) {
+ TimiException.required(store, "not found store");
+ TimiException.required(store.getCode(), "not found store.code");
+ TimiException.required(store.getName(), "not found store.name");
+
+ Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_CREATE");
+ try {
+ logger.setContent(jackson.writeValueAsString(store));
+ checkUnique(store);
+ store.setStatus(TimiJava.defaultIfNull(store.getStatus(), GaoStore.Status.ACTIVE));
+ store.setSort(TimiJava.defaultIfNull(store.getSort(), 0));
+ super.create(store);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(store.getId());
+ } 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);
+ }
+ }
+
+ @Override
+ public void update(GaoStore store) {
+ TimiException.required(store, "not found store");
+ TimiException.required(store.getId(), "not found store.id");
+ TimiException.required(store.getCode(), "not found store.code");
+ TimiException.required(store.getName(), "not found store.name");
+
+ Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_UPDATE");
+ try {
+ logger.setContent(jackson.writeValueAsString(store));
+ checkUnique(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());
+ dbStore.setSort(store.getSort());
+ super.update(dbStore);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(store.getId());
+ } 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);
+ }
+ }
+
+ @Override
+ public void delete(String id) {
+ Logger logger = new Logger(Logger.Module.GAO, "GAO_STORE_DELETE");
+ try {
+ logger.setContent(id);
+ GaoStore store = get(id);
+ store.setStatus(GaoStore.Status.DELETED);
+ super.update(store);
+ super.delete(id);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(id);
+ } 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);
+ }
+ }
+
+ @Override
+ public GaoStore getByUserId(String userId) {
+ TimiException.required(userId, "not found userId");
+ return mapper.selectByUserId(userId);
+ }
+
+ @Override
+ public byte[] export(Page page) throws IOException {
+ page.setIndex(0);
+ page.setSize(Long.MAX_VALUE);
+ List list = mapper.selectByPage(page);
+ return storeXSSFBuilder.build(list);
+ }
+
+ private void checkUnique(GaoStore store) {
+ GaoStore codeExample = new GaoStore();
+ codeExample.setCode(store.getCode());
+ GaoStore byCode = mapper.selectByExample(codeExample);
+ TimiException.requiredTrue(byCode == null || byCode.getId().equals(store.getId()), "该门店编码已存在");
+
+ GaoStore nameExample = new GaoStore();
+ nameExample.setName(store.getName());
+ GaoStore byName = mapper.selectByExample(nameExample);
+ TimiException.requiredTrue(byName == null || byName.getId().equals(store.getId()), "该门店名称已存在");
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java
index 1ec583b..a77d6e2 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserServiceImplement.java
@@ -1,39 +1,26 @@
package com.imyeyu.api.modules.gao.service.implement;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
+import com.imyeyu.api.modules.gao.entity.GaoStore;
import com.imyeyu.api.modules.gao.entity.GaoUser;
import com.imyeyu.api.modules.gao.mapper.GaoUserMapper;
+import com.imyeyu.api.modules.gao.service.GaoStoreService;
import com.imyeyu.api.modules.gao.service.GaoUserService;
import com.imyeyu.api.modules.system.entity.Logger;
import com.imyeyu.api.modules.system.service.LoggerService;
-import com.imyeyu.api.modules.user.entity.Role;
-import com.imyeyu.api.modules.user.entity.User;
-import com.imyeyu.api.modules.user.mapper.UserMapper;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserService;
-import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
-import com.imyeyu.spring.TimiSpring;
-import com.imyeyu.spring.bean.Page;
-import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
-import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
///
/// GAO 用户服务实现
///
@@ -44,12 +31,13 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class GaoUserServiceImplement extends AbstractEntityService implements GaoUserService {
- private final GaoUserMapper mapper;
private final ObjectMapper jackson;
- private final LoggerService loggerService;
- private final UserMapper userMapper;
private final UserService userService;
private final RoleService roleService;
+ private final LoggerService loggerService;
+ private final GaoStoreService gaoStoreService;
+
+ private final GaoUserMapper mapper;
@Override
protected BaseMapper mapper() {
@@ -58,17 +46,28 @@ public class GaoUserServiceImplement extends AbstractEntityService pageWithUser(Page page) {
- PageResult result = page(page);
- loadUser(result.getList());
- return result;
- }
+ Logger logger = new Logger(Logger.Module.GAO, "GAO_USER_TRANSFER_STORE");
+ try {
+ logger.setContent(jackson.writeValueAsString(gaoUser));
- private void checkEntity(GaoUser entity, boolean requireId) {
- TimiException.required(entity, "not found gao user");
- if (requireId) {
- TimiException.required(entity.getId(), "not found gao user.id");
- }
- TimiException.required(entity.getUserId(), "not found gao user.userId");
- TimiException.required(userMapper.select(entity.getUserId()), "not found user");
- GaoUser dbGaoUser = mapper.selectByUserId(entity.getUserId());
- if (dbGaoUser != null && !dbGaoUser.getId().equals(entity.getId())) {
- throw new TimiException(TimiCode.ARG_BAD, "GAO 用户已存在");
- }
- if (entity.getEnabled() == null) {
- entity.setEnabled(true);
- }
- }
+ GaoStore store = gaoStoreService.get(gaoUser.getStoreId());
+ TimiException.required(store, "not found store");
+ TimiException.requiredTrue(store.isActivated(), "门店不可用");
- private void prepareUser(GaoUser entity) {
- TimiException.required(entity, "not found gao user");
- if (TimiJava.isNotEmpty(entity.getUserId())) {
- return;
- }
- User user = entity.getUser();
- TimiException.required(user, "not found gao user.user");
- user.setPhoneNoVerifyAt(TimiJava.defaultIfNull(user.getPhoneNoVerifyAt(), Time.now()));
- userService.create(user);
- entity.setUserId(user.getId());
- }
+ GaoUser dbGaoUser = super.get(gaoUser.getId());
+ TimiException.required(dbGaoUser, "not found gao user");
- private void updateUser(GaoUser entity) {
- User reqUser = entity.getUser();
- if (reqUser == null) {
- return;
- }
- User user = userService.get(entity.getUserId());
- if (reqUser.getName() != null) {
- user.setName(reqUser.getName());
- }
- if (reqUser.getNick() != null) {
- user.setNick(reqUser.getNick());
- }
- if (reqUser.getPhoneNo() != null) {
- user.setPhoneNo(reqUser.getPhoneNo());
- }
- if (reqUser.getEmail() != null) {
- user.setEmail(reqUser.getEmail());
- }
- userService.update(user);
- }
+ dbGaoUser.setStoreId(gaoUser.getStoreId());
+ super.update(dbGaoUser);
- private void saveUserRole(GaoUser entity) {
- if (entity.getRoleIdList() == null) {
- return;
- }
- UserRoleAuthorizeReq req = new UserRoleAuthorizeReq();
- req.setUserId(entity.getUserId());
- req.setModuleCode(ModuleCode.GAO);
- req.setRoleIdList(entity.getRoleIdList());
- roleService.authorizeUserRole(req);
- }
-
- private void loadUser(GaoUser gaoUser) {
- if (gaoUser == null) {
- return;
- }
- gaoUser.setUser(userMapper.select(gaoUser.getUserId()));
- gaoUser.setRoleList(roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO));
- }
-
- private void loadUser(List gaoUserList) {
- if (gaoUserList == null || gaoUserList.isEmpty()) {
- return;
- }
- List userIdList = gaoUserList.stream().map(GaoUser::getUserId).distinct().toList();
- Map userMap = userMapper.listByIdList(new ArrayList<>(userIdList)).stream()
- .collect(Collectors.toMap(User::getId, Function.identity(), (left, right) -> left));
- for (GaoUser gaoUser : gaoUserList) {
- gaoUser.setUser(userMap.get(gaoUser.getUserId()));
- List roleList = roleService.listByUserId(gaoUser.getUserId(), ModuleCode.GAO);
- gaoUser.setRoleList(roleList);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(gaoUser.getId());
+ } 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("transfer user store error", e);
+ throw new TimiException(TimiCode.ERROR, "transfer user store error", e);
+ } finally {
+ loggerService.create(logger);
}
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java b/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java
new file mode 100644
index 0000000..000f7eb
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/util/StoreXSSFBuilder.java
@@ -0,0 +1,61 @@
+package com.imyeyu.api.modules.gao.util;
+
+import com.imyeyu.api.modules.gao.entity.GaoStore;
+import com.imyeyu.api.util.BaseXSSFBuilder;
+import com.imyeyu.utils.Time;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Component;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+///
+/// 门店 Excel 构建器
+///
+/// @author Codex
+/// @since 2026-08-06
+@Component
+public class StoreXSSFBuilder extends BaseXSSFBuilder {
+
+ /// 构建门店列表 Excel
+ ///
+ /// @param list 门店列表
+ /// @return xlsx 文件字节
+ /// @throws IOException 写入异常
+ public byte[] build(List list) throws IOException {
+ try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
+ Sheet sheet = workbook.createSheet("门店列表");
+ CellStyle titleStyle = createTitleStyle(workbook);
+ CellStyle headerStyle = createHeaderStyle(workbook);
+
+ appendRow(sheet, 0, titleStyle, "门店列表导出");
+ appendRow(sheet, 1, null, "生成时间", Time.nowString());
+ appendRow(sheet, 2, null, "记录总数", String.valueOf(list.size()));
+ appendRow(sheet, 3, headerStyle, "门店编码", "门店名称", "状态", "电话", "地址", "备注", "排序", "创建时间");
+ int rowIndex = 4;
+ for (GaoStore store : list) {
+ appendRow(sheet, rowIndex, null,
+ text(store.getCode()),
+ text(store.getName()),
+ store.getStatus() == null ? "" : store.getStatus().name(),
+ text(store.getTelephone()),
+ text(store.getAddress()),
+ text(store.getRemark()),
+ store.getSort() == null ? "" : String.valueOf(store.getSort()),
+ Time.toDateTime(store.getCreatedAt())
+ );
+ rowIndex++;
+ }
+ for (int index = 0; index < 8; index++) {
+ sheet.autoSizeColumn(index);
+ sheet.setColumnWidth(index, Math.clamp(sheet.getColumnWidth(index), 10 * 256, 36 * 256));
+ }
+ workbook.write(output);
+ return output.toByteArray();
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java
index 2763f5e..881d521 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerTrendStateReq.java
@@ -10,6 +10,9 @@ import lombok.Data;
@Data
public class GaoCustomerTrendStateReq {
+ /// 门店 ID
+ private String storeId;
+
/// 开始创建时间
private Long beginCreatedAt;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java
index d64917d..870c859 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordPage.java
@@ -15,6 +15,9 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoEventRecordPage extends BasePage {
+ /// 门店 ID
+ private String storeId;
+
/// 客户 ID,为空查询全部客户
private String customerId;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java
index 7073947..537b853 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordRankPage.java
@@ -14,6 +14,9 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoEventRecordRankPage extends BasePage {
+ /// 门店 ID
+ private String storeId;
+
/// 客户 ID,为空查询全部客户
private String customerId;
diff --git a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java
index fc882d9..cfe1425 100644
--- a/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java
+++ b/src/main/java/com/imyeyu/api/modules/user/controller/RoleController.java
@@ -10,7 +10,8 @@ import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.service.RoleService;
-import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
+import com.imyeyu.api.modules.user.service.UserRoleService;
+import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
@@ -37,6 +38,7 @@ import java.util.List;
public class RoleController {
private final RoleService service;
+ private final UserRoleService userRoleService;
private final PermissionService permissionService;
@RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY)
@@ -86,26 +88,26 @@ public class RoleController {
@RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY)
@RequireCorePermission(CorePermissionCode.USER_ROLE_READ)
@PostMapping("/authorized/list")
- public List listUserRole(@RequestParam String userId, @RequestParam(required = false) ModuleCode moduleCode) {
+ public List listUserRole(@RequestParam(required = false) ModuleCode moduleCode, @RequestParam String userId) {
if (moduleCode != null) {
- return service.listByUserId(userId, moduleCode);
+ return userRoleService.listRoleByUserId(moduleCode, userId);
}
- return service.listByUserId(userId);
+ return userRoleService.listRoleByUserId(userId);
}
@AOPLog
@RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY)
@RequireCorePermission(value = {CorePermissionCode.USER_ROLE_CREATE, CorePermissionCode.USER_ROLE_DELETE})
@PostMapping("/authorized/create")
- public void authorizeUserRole(@RequestBody UserRoleAuthorizeReq req) {
- service.authorizeUserRole(req);
+ public void authorizeUserRole(@RequestBody UserRoleAuthorizeRequest req) {
+ userRoleService.authorizeUserRole(req);
}
@RequireCoreRole(value = {CoreRoleCode.ADMIN, CoreRoleCode.USER_ROLE_ADMIN}, mode = RoleMatchMode.ANY)
@RequireCorePermission(CorePermissionCode.USER_ROLE_READ)
@PostMapping("/authorized/permission")
public List listAuthorizedPermission(@RequestParam String userId) {
- return service.listAllPermissionByUserId(userId);
+ return userRoleService.listAllPermissionByUserId(userId);
}
private void fillRoleDetail(Role role) {
diff --git a/src/main/java/com/imyeyu/api/modules/user/entity/User.java b/src/main/java/com/imyeyu/api/modules/user/entity/User.java
index 2b7fe33..cd58258 100644
--- a/src/main/java/com/imyeyu/api/modules/user/entity/User.java
+++ b/src/main/java/com/imyeyu/api/modules/user/entity/User.java
@@ -8,6 +8,7 @@ import com.imyeyu.api.modules.user.bean.Gender;
import com.imyeyu.spring.annotation.table.Transient;
import com.imyeyu.spring.entity.UUIDEntity;
import com.imyeyu.spring.util.ResponseView;
+import com.imyeyu.utils.Regex;
import com.imyeyu.utils.Time;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -123,6 +124,10 @@ public class User extends UUIDEntity {
@JsonView(ResponseView.Public.class)
protected List permissionList;
+ @Transient
+ @JsonView(ResponseView.Public.class)
+ protected List roleEntityList;
+
@Transient
@JsonView(ResponseView.Public.class)
protected List attachmentList;
@@ -135,6 +140,10 @@ public class User extends UUIDEntity {
@JsonView(ResponseView.Public.class)
protected List tempFileIdList;
+ public boolean isValidPhone() {
+ return Regex.isMatch(Regex.MOBILE_PHONE, phoneNo);
+ }
+
/** @return true 为禁言中 */
public boolean isMuting() {
return unmuteAt != null && Time.now() < unmuteAt;
diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java
index 459e5f4..931b5ab 100644
--- a/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/user/mapper/RoleMapper.java
@@ -3,8 +3,8 @@ package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.spring.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Param;
+import java.util.Collection;
import java.util.List;
/**
@@ -15,9 +15,7 @@ import java.util.List;
*/
public interface RoleMapper extends BaseMapper {
- List selectByIdList(List idList);
+ List selectByIdList(Collection idList);
- List selectByModuleCodeAndCodeList(@Param("moduleCode") ModuleCode moduleCode, @Param("codeList") List codeList);
-
- List selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode);
+ List selectByModuleCode(ModuleCode moduleCode);
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java
index feefe9f..eb62db4 100644
--- a/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserMapper.java
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;
+import java.util.Collection;
import java.util.List;
/**
@@ -32,5 +33,5 @@ public interface UserMapper extends BaseMapper {
* @param idList 用户 ID 列表
* @return 用户列表
*/
- List listByIdList(List idList);
+ List listByIdList(Collection idList);
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java
index 729e0fc..9314e55 100644
--- a/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/user/mapper/UserRoleMapper.java
@@ -2,7 +2,9 @@ package com.imyeyu.api.modules.user.mapper;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.spring.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.Collection;
import java.util.List;
/**
@@ -15,5 +17,7 @@ public interface UserRoleMapper extends BaseMapper {
void createBatch(List userRoleList);
+ List selectByUserIdList(@Param("userIdList") Collection userIdList);
+
void deleteByRoleIdList(String userId, List roleIdList);
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java
index eda63dd..06bf481 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/RoleService.java
@@ -3,9 +3,9 @@ package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
-import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.spring.service.BaseService;
+import java.util.Collection;
import java.util.List;
/// 角色服务
@@ -14,60 +14,13 @@ import java.util.List;
/// @since 2026-05-13 14:46
public interface RoleService extends BaseService {
- /// 给用户授权角色
- ///
- /// @param req 用户角色授权请求
- void authorizeUserRole(UserRoleAuthorizeReq req);
-
- /// 查询用户直属角色
- ///
- /// @param userId 用户 ID
- /// @return 角色列表
- List listByUserId(String userId);
-
- /// 查询用户指定模块直属角色
- ///
- /// @param userId 用户 ID
- /// @param moduleCode 模块代码
- /// @return 角色列表
- List listByUserId(String userId, ModuleCode moduleCode);
-
/// 查询模块角色
///
/// @param moduleCode 模块代码
/// @return 角色列表
List listByModuleCode(ModuleCode moduleCode);
- /// 查询用户直属角色编码
- ///
- /// @param userId 用户 ID
- /// @return 角色编码列表,格式为 MODULE:ROLE
- default List listCodeByUserId(String userId) {
- return listByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList();
- }
-
- /// 查询用户全部角色编码
- ///
- /// @param userId 用户 ID
- /// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE
- List listAllCodeByUserId(String userId);
-
- /// 查询用户全部权限
- ///
- /// @param userId 用户 ID
- /// @return 权限列表
- List listAllPermissionByUserId(String userId);
-
- /// 查询用户全部权限编码
- ///
- /// @param userId 用户 ID
- /// @return 权限编码列表,格式为 MODULE:PERMISSION
- default List listAllPermissionCodeByUserId(String userId) {
- return listAllPermissionByUserId(userId).stream()
- .map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode()))
- .distinct()
- .toList();
- }
+ List listByIdList(Collection idList);
/// 查询直属子角色
///
@@ -80,4 +33,10 @@ public interface RoleService extends BaseService {
/// @param roleId 角色 ID
/// @return 权限列表
List listPermissionByRoleId(String roleId);
+
+ /// 递归获取角色及其所有子角色的 ID 列表
+ ///
+ /// @param idList 角色 ID 列表
+ /// @return 包含所有子角色的 ID 列表
+ List listAllRoleIdIncludingChildren(Collection idList);
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java
index e2e3ee3..11423f7 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/UserRoleService.java
@@ -1,9 +1,15 @@
package com.imyeyu.api.modules.user.service;
+import com.imyeyu.api.bean.ModuleCode;
+import com.imyeyu.api.modules.user.entity.Permission;
+import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.UserRole;
+import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest;
import com.imyeyu.spring.service.BaseService;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
/// 用户角色绑定服务
///
@@ -16,11 +22,62 @@ public interface UserRoleService extends BaseService {
/// @param list 用户角色绑定列表
void createBatch(List list);
- /// 查询用户角色绑定
+ /// 给用户授权角色
+ ///
+ /// @param req 用户角色授权请求
+ void authorizeUserRole(UserRoleAuthorizeRequest req);
+
+ List listByUserId(String userId);
+
+ /// 查询多个用户角色绑定
+ ///
+ /// @param userIdList 用户 ID 列表
+ /// @return 用户角色绑定列表
+ List listByUserIdList(Collection userIdList);
+
+ /// 查询用户直属角色
///
/// @param userId 用户 ID
- /// @return 用户角色绑定列表
- List listByUserId(String userId);
+ /// @return 角色列表
+ List listRoleByUserId(String userId);
+
+ /// 查询用户指定模块直属角色
+ ///
+ /// @param moduleCode 模块代码
+ /// @param userId 用户 ID
+ /// @return 角色列表
+ List listRoleByUserId(ModuleCode moduleCode, String userId);
+
+ /// 查询用户直属角色编码
+ ///
+ /// @param userId 用户 ID
+ /// @return 角色编码列表,格式为 MODULE:ROLE
+ default List listRoleCodeByUserId(String userId) {
+ return listRoleByUserId(userId).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).toList();
+ }
+
+ /// 查询用户全部角色编码
+ ///
+ /// @param userId 用户 ID
+ /// @return 包含子角色的角色编码列表,格式为 MODULE:ROLE
+ List listAllRoleCodeByUserId(String userId);
+
+ /// 查询用户全部权限
+ ///
+ /// @param userId 用户 ID
+ /// @return 权限列表
+ List listAllPermissionByUserId(String userId);
+
+ /// 查询用户全部权限编码
+ ///
+ /// @param userId 用户 ID
+ /// @return 权限编码列表,格式为 MODULE:PERMISSION
+ default List listAllPermissionCodeByUserId(String userId) {
+ return listAllPermissionByUserId(userId).stream()
+ .map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode()))
+ .distinct()
+ .toList();
+ }
/// 查询用户直属角色 ID
///
@@ -31,6 +88,12 @@ public interface UserRoleService extends BaseService {
return bindingList.stream().map(UserRole::getRoleId).toList();
}
+ /// 按用户 ID 分组查询直属角色
+ ///
+ /// @param idList 用户 ID 列表
+ /// @return 用户角色映射
+ Map> mapRoleListByUserIdList(Collection idList);
+
/// 删除用户指定角色绑定
///
/// @param userId 用户 ID
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java
index 465ab0e..c0b9fe2 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/UserService.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/UserService.java
@@ -3,6 +3,9 @@ package com.imyeyu.api.modules.user.service;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.spring.service.BaseService;
+import java.util.Collection;
+import java.util.Map;
+
/**
* 用户管理服务
*
@@ -20,4 +23,10 @@ public interface UserService extends BaseService {
User getByPhoneNo(String phoneNo);
User getByVerifiedPhoneNo(String phoneNo);
+
+ void verifyEmail(String id);
+
+ void verifyPhoneNo(String id);
+
+ Map mapByIdList(Collection idList);
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java
index fd6427c..32e0f39 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/PermissionCheckerImplement.java
@@ -4,6 +4,7 @@ import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.service.PermissionChecker;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.api.modules.user.service.UserRoleService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
@@ -25,7 +26,7 @@ import java.util.Set;
@RequiredArgsConstructor
public class PermissionCheckerImplement implements PermissionChecker {
- private final RoleService roleService;
+ private final UserRoleService userRoleService;
private final UserLoginService userLoginService;
public Set codeSet() {
@@ -34,7 +35,7 @@ public class PermissionCheckerImplement implements PermissionChecker {
@Override
public Set codeSet(String userId) {
- return new HashSet<>(roleService.listAllPermissionCodeByUserId(userId));
+ return new HashSet<>(userRoleService.listAllPermissionCodeByUserId(userId));
}
@Override
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java
index 57a81f5..9fad2a1 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleCheckerImplement.java
@@ -2,8 +2,8 @@ package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.modules.user.service.RoleChecker;
-import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.api.modules.user.service.UserRoleService;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
@@ -24,12 +24,12 @@ import java.util.Set;
@RequiredArgsConstructor
public class RoleCheckerImplement implements RoleChecker {
- private final RoleService roleService;
+ private final UserRoleService userRoleService;
private final UserLoginService userLoginService;
@Override
public Set codeSet() {
- return new HashSet<>(roleService.listAllCodeByUserId(userLoginService.getRequireLoginUserId()));
+ return new HashSet<>(userRoleService.listAllRoleCodeByUserId(userLoginService.getRequireLoginUserId()));
}
@Override
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java
index eb9aa83..6569e88 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/RoleServiceImplement.java
@@ -2,23 +2,17 @@ package com.imyeyu.api.modules.user.service.implement;
import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
-import com.imyeyu.api.modules.common.entity.Multilingual;
-import com.imyeyu.api.modules.common.mapper.MultilingualMapper;
import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.user.entity.Permission;
import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.RolePermission;
import com.imyeyu.api.modules.user.entity.RoleRelation;
-import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.RoleMapper;
import com.imyeyu.api.modules.user.mapper.RolePermissionMapper;
import com.imyeyu.api.modules.user.mapper.RoleRelationMapper;
import com.imyeyu.api.modules.user.service.PermissionService;
import com.imyeyu.api.modules.user.service.RoleService;
-import com.imyeyu.api.modules.user.service.UserRoleService;
-import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeReq;
import com.imyeyu.java.TimiJava;
-import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
@@ -28,6 +22,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -42,9 +37,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class RoleServiceImplement extends AbstractEntityService implements RoleService {
- private final UserRoleService userRoleService;
private final PermissionService permissionService;
- private final MultilingualMapper multilingualMapper;
private final MultilingualService multilingualService;
private final RoleMapper mapper;
@@ -153,67 +146,6 @@ public class RoleServiceImplement extends AbstractEntityService im
}
}
- @Transactional(TimiServerDBConfig.ROLLBACKER)
- @Override
- public void authorizeUserRole(UserRoleAuthorizeReq req) {
- TimiException.required(req, "not found req");
- TimiException.required(req.getUserId(), "not found req.userId");
- if (req.getModuleCode() == null) {
- throw new TimiException(TimiCode.ARG_MISS, "moduleCode");
- }
- List bindingList = userRoleService.listByUserId(req.getUserId());
- List bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList();
- Set dbIdSet = mapper.selectByIdList(bindingRoleIdList).stream()
- .filter(role -> req.getModuleCode() == role.getModuleCode())
- .map(Role::getId)
- .collect(Collectors.toSet());
- Set newIdSet = new HashSet<>();
- if (TimiJava.isNotEmpty(req.getRoleIdList())) {
- newIdSet.addAll(mapper.selectByIdList(req.getRoleIdList()).stream()
- .filter(role -> req.getModuleCode() == role.getModuleCode())
- .map(Role::getId)
- .toList());
- }
- if (TimiJava.isNotEmpty(req.getRoleCodeList())) {
- newIdSet.addAll(mapper.selectByModuleCodeAndCodeList(req.getModuleCode(), req.getRoleCodeList()).stream().map(Role::getId).toList());
- }
- Set deleteIdList = new HashSet<>(dbIdSet);
- deleteIdList.removeAll(newIdSet);
-
- Set addIdList = new HashSet<>(newIdSet);
- addIdList.removeAll(dbIdSet);
-
- if (TimiJava.isNotEmpty(deleteIdList)) {
- userRoleService.deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList));
- }
- if (TimiJava.isNotEmpty(addIdList)) {
- List roleBindingList = new ArrayList<>();
- long now = Time.now();
- for (String roleId : addIdList) {
- UserRole roleBinding = new UserRole();
- roleBinding.setId(UUID.randomUUID().toString());
- roleBinding.setUserId(req.getUserId());
- roleBinding.setRoleId(roleId);
- roleBinding.setCreatedAt(now);
- roleBindingList.add(roleBinding);
- }
- userRoleService.createBatch(roleBindingList);
- }
- }
-
- @Override
- public List listByUserId(String userId) {
- return mapper.selectByIdList(userRoleService.listRoleIdByUserId(userId));
- }
-
- @Override
- public List listByUserId(String userId, ModuleCode moduleCode) {
- TimiException.required(moduleCode, "not found moduleCode");
- return listByUserId(userId).stream()
- .filter(role -> moduleCode == role.getModuleCode())
- .toList();
- }
-
@Override
public List listByModuleCode(ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
@@ -221,20 +153,8 @@ public class RoleServiceImplement extends AbstractEntityService im
}
@Override
- public List listAllCodeByUserId(String userId) {
- List roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
- List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList);
- if (TimiJava.isEmpty(allRoleIdList)) {
- return List.of();
- }
- return mapper.selectByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList();
- }
-
- @Override
- public List listAllPermissionByUserId(String userId) {
- List roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
- List allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList);
- return permissionService.listByRoleId(allRoleIdList);
+ public List listByIdList(Collection idList) {
+ return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
@@ -248,20 +168,13 @@ public class RoleServiceImplement extends AbstractEntityService im
@Override
public List listPermissionByRoleId(String roleId) {
- List allRoleIdList = getAllRoleIdsIncludingChildren(List.of(roleId));
+ List allRoleIdList = listAllRoleIdIncludingChildren(List.of(roleId));
return permissionService.listByRoleId(allRoleIdList);
}
- /// 递归获取角色及其所有子角色的 ID 列表
- ///
- /// @param roleIdList 角色 ID 列表
- /// @return 包含所有子角色的 ID 列表
- private List getAllRoleIdsIncludingChildren(List roleIdList) {
- if (TimiJava.isEmpty(roleIdList)) {
- return new ArrayList<>();
- }
- Set allRoleIdSet = new HashSet<>(roleIdList);
- List currentLevelRoleIdList = new ArrayList<>(roleIdList);
+ public List listAllRoleIdIncludingChildren(Collection idList) {
+ Set allRoleIdSet = new HashSet<>(idList);
+ List currentLevelRoleIdList = new ArrayList<>(idList);
while (TimiJava.isNotEmpty(currentLevelRoleIdList)) {
List childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList);
@@ -277,18 +190,4 @@ public class RoleServiceImplement extends AbstractEntityService im
}
return new ArrayList<>(allRoleIdSet);
}
-
- private String saveNameMultilingual(String langId, String name) {
- if (TimiJava.isEmpty(name)) {
- return langId;
- }
- if (TimiJava.isEmpty(langId)) {
- return multilingualService.create(name);
- }
- Multilingual multilingual = new Multilingual();
- multilingual.setId(langId);
- multilingual.setZhCN(name);
- multilingualMapper.updateSelective(multilingual);
- return langId;
- }
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java
index 8977985..c9d9455 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java
@@ -9,6 +9,7 @@ import com.imyeyu.api.modules.user.bean.BootstrapData;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.api.modules.user.service.UserRoleService;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.api.modules.user.vo.LoginRequest;
import com.imyeyu.api.modules.user.vo.LoginResponse;
@@ -45,6 +46,7 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava {
private final UserService userService;
private final RoleService roleService;
private final SettingService settingService;
+ private final UserRoleService userRoleService;
private final AttachmentService attachmentService;
private final Redis redisUserToken;
@@ -86,8 +88,8 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava {
result.setUser(userService.get(userId));
User user = result.getUser();
- user.setPermissionList(roleService.listAllPermissionCodeByUserId(user.getId()));
- user.setRoleList(roleService.listAllCodeByUserId(user.getId()));
+ user.setPermissionList(userRoleService.listAllPermissionCodeByUserId(user.getId()));
+ user.setRoleList(userRoleService.listAllRoleCodeByUserId(user.getId()));
user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId()));
return result;
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java
index 1ed6dc2..769a1ea 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserRoleServiceImplement.java
@@ -1,14 +1,34 @@
package com.imyeyu.api.modules.user.service.implement;
+import com.imyeyu.api.bean.ModuleCode;
+import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
+import com.imyeyu.api.modules.user.entity.Permission;
+import com.imyeyu.api.modules.user.entity.Role;
import com.imyeyu.api.modules.user.entity.UserRole;
import com.imyeyu.api.modules.user.mapper.UserRoleMapper;
+import com.imyeyu.api.modules.user.service.PermissionService;
+import com.imyeyu.api.modules.user.service.RoleService;
import com.imyeyu.api.modules.user.service.UserRoleService;
+import com.imyeyu.api.modules.user.vo.role.UserRoleAuthorizeRequest;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.java.bean.timi.TimiCode;
+import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
+import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
/// 用户角色服务实现
///
@@ -18,6 +38,9 @@ import java.util.List;
@RequiredArgsConstructor
public class UserRoleServiceImplement extends AbstractEntityService implements UserRoleService {
+ private final RoleService roleService;
+ private final PermissionService permissionService;
+
private final UserRoleMapper mapper;
@Override
@@ -30,6 +53,49 @@ public class UserRoleServiceImplement extends AbstractEntityService bindingList = listByUserId(req.getUserId());
+ List bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList();
+ Set dbIdSet = roleService.listByIdList(bindingRoleIdList)
+ .stream()
+ .filter(role -> req.getModuleCode() == role.getModuleCode())
+ .map(Role::getId)
+ .collect(Collectors.toSet());
+ Set newIdSet = new HashSet<>();
+ if (TimiJava.isNotEmpty(req.getRoleIdList())) {
+ newIdSet.addAll(roleService.listByIdList(req.getRoleIdList()).stream().filter(role -> req.getModuleCode() == role.getModuleCode()).map(Role::getId).toList());
+ }
+ Set deleteIdList = new HashSet<>(dbIdSet);
+ deleteIdList.removeAll(newIdSet);
+
+ Set addIdList = new HashSet<>(newIdSet);
+ addIdList.removeAll(dbIdSet);
+
+ if (TimiJava.isNotEmpty(deleteIdList)) {
+ deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList));
+ }
+ if (TimiJava.isNotEmpty(addIdList)) {
+ List roleBindingList = new ArrayList<>();
+ long now = Time.now();
+ for (String roleId : addIdList) {
+ UserRole roleBinding = new UserRole();
+ roleBinding.setId(UUID.randomUUID().toString());
+ roleBinding.setUserId(req.getUserId());
+ roleBinding.setRoleId(roleId);
+ roleBinding.setCreatedAt(now);
+ roleBindingList.add(roleBinding);
+ }
+ createBatch(roleBindingList);
+ }
+ }
+
@Override
public List listByUserId(String userId) {
UserRole example = new UserRole();
@@ -37,6 +103,57 @@ public class UserRoleServiceImplement extends AbstractEntityService listByUserIdList(Collection userIdList) {
+ return mapper.selectByUserIdList(userIdList);
+ }
+
+ @Override
+ public List listRoleByUserId(String userId) {
+ List userRoleList = listByUserId(userId);
+ return roleService.listByIdList(userRoleList.stream().map(UserRole::getRoleId).toList());
+ }
+
+ @Override
+ public List listRoleByUserId(ModuleCode moduleCode, String userId) {
+ TimiException.required(moduleCode, "not found moduleCode");
+ return listRoleByUserId(userId).stream().filter(role -> moduleCode == role.getModuleCode()).toList();
+ }
+
+ @Override
+ public List listAllRoleCodeByUserId(String userId) {
+ List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
+ List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList);
+ if (TimiJava.isEmpty(allRoleIdList)) {
+ return List.of();
+ }
+ return roleService.listByIdList(allRoleIdList).stream().map(role -> "%s:%s".formatted(role.getModuleCode().name(), role.getCode())).distinct().toList();
+ }
+
+ @Override
+ public List listAllPermissionByUserId(String userId) {
+ List roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
+ List allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList);
+ return permissionService.listByRoleId(allRoleIdList);
+ }
+
+ @Override
+ public Map> mapRoleListByUserIdList(Collection idList) {
+ if (TimiJava.isEmpty(idList)) {
+ return Map.of();
+ }
+ List userRoleList = listByUserIdList(idList);
+ if (TimiJava.isEmpty(userRoleList)) {
+ return Map.of();
+ }
+ Map roleMap = roleService.listByIdList(userRoleList.stream().map(UserRole::getRoleId).distinct().toList())
+ .stream()
+ .collect(Collectors.toMap(Role::getId, Function.identity()));
+ return userRoleList.stream()
+ .filter(userRole -> roleMap.containsKey(userRole.getRoleId()))
+ .collect(Collectors.groupingBy(UserRole::getUserId, Collectors.mapping(userRole -> roleMap.get(userRole.getRoleId()), Collectors.toList())));
+ }
+
@Override
public void deleteByRoleIdList(String userId, List roleIdList) {
mapper.deleteByRoleIdList(userId, roleIdList);
diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java
index 33a79c3..0f0ba8f 100644
--- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserServiceImplement.java
@@ -11,11 +11,18 @@ import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Regex;
+import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
/**
* 用户管理服务实现
*
@@ -27,8 +34,6 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class UserServiceImplement extends AbstractEntityService implements UserService, TimiJava {
- private static final String REGEX_PHONE_NO = "^1\\d{10}$";
-
private final UserMapper mapper;
@Override
@@ -51,7 +56,7 @@ public class UserServiceImplement extends AbstractEntityService im
}
{
if (TimiJava.isNotEmpty(user.getPhoneNo())) {
- validatePhoneNo(user.getPhoneNo());
+ TimiException.requiredTrue(user.isValidPhone(), "invalid phone");
User byPhoneNo = getByVerifiedPhoneNo(user.getPhoneNo());
TimiException.requiredNull(byPhoneNo, "existed phone number");
}
@@ -65,7 +70,6 @@ public class UserServiceImplement extends AbstractEntityService im
dbUser.setPassword(Digest.argon2id(user.getPassword()));
dbUser.setEmail(user.getEmail());
dbUser.setPhoneNo(user.getPhoneNo());
- dbUser.setPhoneNoVerifyAt(user.getPhoneNoVerifyAt());
dbUser.setExp(TimiJava.defaultIfNull(user.getExp(), 0));
super.create(dbUser);
user.setId(dbUser.getId());
@@ -88,7 +92,7 @@ public class UserServiceImplement extends AbstractEntityService im
TimiException.requiredTrue(byEmail == null || byEmail.getId().equals(dbUser.getId()), "existed email");
}
if (TimiJava.isNotEmpty(phoneNo)) {
- validatePhoneNo(phoneNo);
+ TimiException.requiredTrue(user.isValidPhone(), "invalid phone");
if (!phoneNo.equals(dbUser.getPhoneNo())) {
User byPhoneNo = getByVerifiedPhoneNo(phoneNo);
TimiException.requiredTrue(byPhoneNo == null || byPhoneNo.getId().equals(dbUser.getId()), "existed phone number");
@@ -138,7 +142,22 @@ public class UserServiceImplement extends AbstractEntityService im
return mapper.selectByVerifiedPhoneNo(phoneNo);
}
- private void validatePhoneNo(String phoneNo) {
- TimiException.requiredTrue(Regex.isMatch(REGEX_PHONE_NO, phoneNo), "invalid phone number");
+ @Override
+ public void verifyEmail(String id) {
+ User user = get(id);
+ user.setEmailVerifyAt(Time.now());
+ update(user);
+ }
+
+ @Override
+ public void verifyPhoneNo(String id) {
+ User user = get(id);
+ user.setPhoneNoVerifyAt(Time.now());
+ update(user);
+ }
+
+ @Override
+ public Map mapByIdList(Collection idList) {
+ return mapper.listByIdList(new HashSet<>(idList)).stream().collect(Collectors.toMap(User::getId, Function.identity()));
}
}
diff --git a/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java b/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java
similarity index 79%
rename from src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java
rename to src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java
index 1187ddf..3c70a32 100644
--- a/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeReq.java
+++ b/src/main/java/com/imyeyu/api/modules/user/vo/role/UserRoleAuthorizeRequest.java
@@ -10,13 +10,11 @@ import java.util.List;
/// @author 夜雨
/// @since 2026-07-17 14:16
@Data
-public class UserRoleAuthorizeReq {
+public class UserRoleAuthorizeRequest {
private String userId;
private ModuleCode moduleCode;
private List roleIdList;
-
- private List roleCodeList;
}
diff --git a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java
index 5850f68..6dfad63 100644
--- a/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java
+++ b/src/main/java/com/imyeyu/api/util/InitBuiltinUser.java
@@ -192,6 +192,7 @@ public class InitBuiltinUser implements ApplicationRunner {
private void bindBuiltinPermission(Map roleMap, Map permissionMap) {
Role systemRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.SYS_ROLE_MODULE_CODE, BootstrapData.SYS_ROLE_CODE));
Role adminRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(BootstrapData.ADMIN_ROLE_MODULE_CODE, BootstrapData.ADMIN_ROLE_CODE));
+ Role gaoGlobalManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()));
Role gaoStoreManagerRole = roleMap.get(BuiltinAuthRegistry.toRoleKey(ModuleCode.GAO, GaoRoleCode.STORE_MANAGER.name()));
for (BuiltinPermissionCode item : BuiltinAuthRegistry.PERMISSION_CODE_LIST) {
Permission permission = permissionMap.get(BuiltinAuthRegistry.toPermissionKey(item.getModuleCode(), item.getValue()));
@@ -204,6 +205,9 @@ public class InitBuiltinUser implements ApplicationRunner {
if (item.isGrantToAdmin() && adminRole != null) {
createRolePermissionIfAbsent(adminRole.getId(), permission.getId());
}
+ if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToGlobalManager() && gaoGlobalManagerRole != null) {
+ createRolePermissionIfAbsent(gaoGlobalManagerRole.getId(), permission.getId());
+ }
if (item instanceof GaoPermissionCode gaoPermission && gaoPermission.isGrantToStoreManager() && gaoStoreManagerRole != null) {
createRolePermissionIfAbsent(gaoStoreManagerRole.getId(), permission.getId());
}
diff --git a/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql b/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql
new file mode 100644
index 0000000..3474b47
--- /dev/null
+++ b/src/main/resources/db/migration/timiserver/V24__add_gao_store_scope.sql
@@ -0,0 +1,192 @@
+-- 新增门店主表。active_code / active_name 沿用项目现有软删除唯一索引方案:
+-- 未删除数据参与唯一约束,已删除数据生成 NULL,不阻塞后续重建同编码或同名门店。
+CREATE TABLE IF NOT EXISTS `gao_store` (
+ `id` VARCHAR(36) NOT NULL,
+ `code` VARCHAR(64) NOT NULL COMMENT '门店编码',
+ `active_code` VARCHAR(64) GENERATED ALWAYS AS (
+ CASE WHEN `deleted_at` IS NULL THEN NULLIF(TRIM(`code`), '') ELSE NULL END
+ ) STORED COMMENT '未删除门店编码' INVISIBLE,
+ `name` VARCHAR(64) NOT NULL COMMENT '门店名称',
+ `active_name` VARCHAR(64) GENERATED ALWAYS AS (
+ CASE WHEN `deleted_at` IS NULL THEN NULLIF(TRIM(`name`), '') ELSE NULL END
+ ) STORED COMMENT '未删除门店名称' INVISIBLE,
+ `status` VARCHAR(32) NOT NULL COMMENT '门店状态',
+ `telephone` VARCHAR(32) NULL COMMENT '电话',
+ `address` VARCHAR(255) NULL COMMENT '地址',
+ `remark` TEXT NULL COMMENT '备注',
+ `sort` INT NOT NULL DEFAULT 0 COMMENT '排序值',
+ `created_at` BIGINT NOT NULL,
+ `updated_at` BIGINT NULL,
+ `deleted_at` BIGINT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_gao_store_active_code` (`active_code`),
+ UNIQUE KEY `uk_gao_store_active_name` (`active_name`),
+ KEY `idx_gao_store_status_sort` (`status`, `deleted_at`, `sort`, `created_at`)
+) COMMENT='GAO 门店';
+
+-- 旧数据来自单门店模型,迁移时全部归入固定默认门店。
+-- 使用固定 ID 而不是 UUID(),避免迁移中断后排查或手工重跑时产生多个默认门店。
+SET @gao_default_store_id = '00000000-0000-0000-0000-000000000001';
+SET @gao_now = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000);
+
+-- Flyway 正常不会重复执行版本脚本;这里保留 ON DUPLICATE KEY,方便本地或预发排查时手工重放片段。
+INSERT INTO `gao_store` (
+ `id`,
+ `code`,
+ `name`,
+ `status`,
+ `telephone`,
+ `address`,
+ `remark`,
+ `sort`,
+ `created_at`
+) VALUES (
+ @gao_default_store_id,
+ 'DEFAULT',
+ '默认门店',
+ 'ACTIVE',
+ NULL,
+ NULL,
+ '系统迁移自动创建',
+ 0,
+ @gao_now
+) ON DUPLICATE KEY UPDATE
+ `name` = VALUES(`name`),
+ `status` = VALUES(`status`),
+ `updated_at` = @gao_now;
+
+-- 门店营业日历是连续登记统计的事实维度。
+-- business_day_no 只给营业日使用,休息日 business_day_no 为 NULL。
+-- 后续统计连续登记时按 business_day_no 连续,不按自然日连续。
+CREATE TABLE IF NOT EXISTS `gao_store_business_day` (
+ `id` VARCHAR(36) NOT NULL,
+ `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID',
+ `date_value` INT NOT NULL COMMENT '日期值,格式为 yyyyMMdd',
+ `active_date_value` INT GENERATED ALWAYS AS (
+ CASE WHEN `deleted_at` IS NULL THEN `date_value` ELSE NULL END
+ ) STORED COMMENT '未删除日期值' INVISIBLE,
+ `open_status` VARCHAR(32) NOT NULL COMMENT '营业状态',
+ `business_day_no` BIGINT NULL COMMENT '营业日递增序号,仅营业日有值',
+ `active_business_day_no` BIGINT GENERATED ALWAYS AS (
+ CASE WHEN `deleted_at` IS NULL AND `open_status` = 'OPEN' THEN `business_day_no` ELSE NULL END
+ ) STORED COMMENT '未删除营业日序号' INVISIBLE,
+ `reason` VARCHAR(255) NULL COMMENT '原因',
+ `created_at` BIGINT NOT NULL,
+ `updated_at` BIGINT NULL,
+ `deleted_at` BIGINT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_gao_store_business_day_active_date` (`store_id`, `active_date_value`),
+ UNIQUE KEY `uk_gao_store_business_day_active_no` (`store_id`, `active_business_day_no`),
+ KEY `idx_gao_store_business_day_open_no` (`store_id`, `open_status`, `business_day_no`)
+) COMMENT='GAO 门店营业日';
+
+-- 客户归属门店。先允许 NULL,加字段后回填,再改 NOT NULL,避免已有数据直接违反约束。
+ALTER TABLE `gao_customer`
+ ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`;
+
+UPDATE `gao_customer`
+SET `store_id` = @gao_default_store_id
+WHERE `store_id` IS NULL;
+
+ALTER TABLE `gao_customer`
+ MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID',
+ -- 客户编码、姓名从全局唯一改为门店内唯一。
+ DROP KEY `uk_gao_customer_active_code`,
+ DROP KEY `uk_gao_customer_active_name`,
+ ADD UNIQUE KEY `uk_gao_customer_store_active_code` (`store_id`, `active_code`),
+ ADD UNIQUE KEY `uk_gao_customer_store_active_name` (`store_id`, `active_name`),
+ ADD KEY `idx_gao_customer_store_created_at` (`store_id`, `deleted_at`, `created_at`);
+
+-- 登记事件归属门店。历史事件统一归入默认门店。
+ALTER TABLE `gao_event`
+ ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`;
+
+UPDATE `gao_event`
+SET `store_id` = @gao_default_store_id
+WHERE `store_id` IS NULL;
+
+ALTER TABLE `gao_event`
+ MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID',
+ ADD KEY `idx_gao_event_store_status` (`store_id`, `status`, `deleted_at`, `sort`, `created_at`);
+
+-- 登记记录是事实表,需要保存门店和营业日快照。
+-- store_id:优先跟随历史登记事件所属门店,找不到事件时兜底默认门店。
+-- registered_date_value:由 registered_at 计算 yyyyMMdd,避免查询时反复做时间函数。
+-- business_day_no:按门店营业日历回填,用于连续登记统计。
+ALTER TABLE `gao_event_record`
+ ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `customer_id`,
+ ADD COLUMN `registered_date_value` INT NULL COMMENT '登记日期值,格式为 yyyyMMdd' AFTER `registered_at`,
+ ADD COLUMN `business_day_no` BIGINT NULL COMMENT '营业日递增序号' AFTER `registered_date_value`;
+
+UPDATE `gao_event_record` r
+LEFT JOIN `gao_event` e ON r.`event_id` = e.`id`
+SET
+ r.`store_id` = COALESCE(e.`store_id`, @gao_default_store_id),
+ r.`registered_date_value` = CAST(DATE_FORMAT(FROM_UNIXTIME(r.`registered_at` / 1000), '%Y%m%d') AS UNSIGNED)
+WHERE
+ r.`store_id` IS NULL
+ OR r.`registered_date_value` IS NULL;
+
+-- 以历史登记记录中实际发生过登记的日期生成营业日。
+-- 这是最平滑的迁移口径:有登记的日期一定视为营业日;没有登记的历史日期不主动生成休息日或营业日。
+-- 如果门店历史休息日需要展示,可在迁移后通过营业日接口补录 CLOSED 数据,不影响已有登记记录。
+INSERT INTO `gao_store_business_day` (
+ `id`,
+ `store_id`,
+ `date_value`,
+ `open_status`,
+ `business_day_no`,
+ `reason`,
+ `created_at`
+)
+SELECT
+ UUID(),
+ d.`store_id`,
+ d.`registered_date_value`,
+ 'OPEN',
+ ROW_NUMBER() OVER (PARTITION BY d.`store_id` ORDER BY d.`registered_date_value`),
+ '系统迁移按历史登记日期生成',
+ @gao_now
+FROM (
+ SELECT DISTINCT
+ `store_id`,
+ `registered_date_value`
+ FROM `gao_event_record`
+ WHERE
+ `store_id` IS NOT NULL
+ AND `registered_date_value` IS NOT NULL
+) d;
+
+-- 把营业日序号快照回填到历史登记记录。
+-- 后续即使营业日历被调整,历史记录仍保留迁移时的事实快照;如需重算,应做显式重算脚本。
+UPDATE `gao_event_record` r
+LEFT JOIN `gao_store_business_day` d ON r.`store_id` = d.`store_id` AND r.`registered_date_value` = d.`date_value` AND d.`deleted_at` IS NULL
+SET r.`business_day_no` = d.`business_day_no`
+WHERE r.`business_day_no` IS NULL;
+
+ALTER TABLE `gao_event_record`
+ MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID',
+ MODIFY COLUMN `registered_date_value` INT NOT NULL COMMENT '登记日期值,格式为 yyyyMMdd',
+ MODIFY COLUMN `business_day_no` BIGINT NOT NULL COMMENT '营业日递增序号',
+ ADD KEY `idx_gao_event_record_store_registered_at` (`store_id`, `deleted_at`, `registered_at`),
+ ADD KEY `idx_gao_event_record_store_date` (`store_id`, `registered_date_value`, `deleted_at`),
+ ADD KEY `idx_gao_event_record_store_customer_time` (`store_id`, `customer_id`, `deleted_at`, `registered_at`),
+ ADD KEY `idx_gao_event_record_store_event_time` (`store_id`, `event_id`, `deleted_at`, `registered_at`),
+ ADD KEY `idx_gao_event_record_store_customer_day` (`store_id`, `customer_id`, `business_day_no`);
+
+-- GAO 用户归属门店。用户可在不同门店分别存在一条 GAO 用户记录。
+-- 注意:当前用户角色关系仍是用户维度,不是门店维度;不同门店不同角色需要后续扩展 user_role 作用域。
+ALTER TABLE `gao_user`
+ ADD COLUMN `store_id` VARCHAR(36) NULL COMMENT '门店 ID' AFTER `id`,
+ ADD COLUMN `active_user_id` VARCHAR(36) GENERATED ALWAYS AS (
+ CASE WHEN `deleted_at` IS NULL THEN `user_id` ELSE NULL END
+ ) STORED COMMENT '未删除用户 ID' INVISIBLE;
+
+UPDATE `gao_user`
+SET `store_id` = @gao_default_store_id
+WHERE `store_id` IS NULL;
+
+ALTER TABLE `gao_user`
+ MODIFY COLUMN `store_id` VARCHAR(36) NOT NULL COMMENT '门店 ID',
+ ADD UNIQUE KEY `uk_gao_user_store_active_user` (`store_id`, `active_user_id`),
+ ADD KEY `idx_gao_user_store_enabled` (`store_id`, `enabled`, `deleted_at`, `created_at`);
diff --git a/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql b/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql
new file mode 100644
index 0000000..df810d9
--- /dev/null
+++ b/src/main/resources/db/migration/timiserver/V25__restrict_gao_user_to_single_store.sql
@@ -0,0 +1,8 @@
+-- V24 已经上线为“用户可在多个门店分别存在 GAO 用户记录”。
+-- 现在业务改为一个用户只能归属一个门店,所以不能再改 V24,必须通过 V25 切换唯一约束。
+
+-- 如果线上已经存在同一 user_id 绑定多个未删除门店的数据,下面新增唯一索引会失败。
+-- 这是正确行为:这种数据无法自动判断保留哪个门店,必须人工先清理重复 GAO 用户记录。
+ALTER TABLE `gao_user`
+ DROP KEY `uk_gao_user_store_active_user`,
+ ADD UNIQUE KEY `uk_gao_user_active_user` (`active_user_id`);
diff --git a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml
index 6cbed41..ae69942 100644
--- a/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml
+++ b/src/main/resources/mapper/timi-server/GaoCustomerMapper.xml
@@ -6,10 +6,16 @@
*
FROM `gao_customer`
WHERE
- `id` IN
-
- #{id}
-
+ TRUE
+
+ AND `id` IN
+
+ #{id}
+
+
+
+ AND FALSE
+
AND `deleted_at` IS NULL
@@ -19,7 +25,8 @@
COUNT(1) AS `newCustomerCount`
FROM `gao_customer`
WHERE
- `created_at` >= #{beginCreatedAt}
+ `store_id` = #{storeId}
+ AND `created_at` >= #{beginCreatedAt}
AND `created_at` <= #{endCreatedAt}
AND `deleted_at` IS NULL
GROUP BY DATE_FORMAT(FROM_UNIXTIME(`created_at` / 1000), '%Y%m%d')
@@ -31,7 +38,8 @@
COUNT(1)
FROM `gao_customer`
WHERE
- `created_at` < #{beginCreatedAt}
+ `store_id` = #{storeId}
+ AND `created_at` < #{beginCreatedAt}
AND `deleted_at` IS NULL
@@ -41,7 +49,8 @@
COUNT(1) AS `count`
FROM `gao_customer`
WHERE
- `deleted_at` IS NULL
+ `store_id` = #{storeId}
+ AND `deleted_at` IS NULL
GROUP BY IFNULL(CAST(`gender` AS CHAR), 'UNKNOWN')
diff --git a/src/main/resources/mapper/timi-server/GaoEventMapper.xml b/src/main/resources/mapper/timi-server/GaoEventMapper.xml
index 8750dcd..9b08997 100644
--- a/src/main/resources/mapper/timi-server/GaoEventMapper.xml
+++ b/src/main/resources/mapper/timi-server/GaoEventMapper.xml
@@ -24,7 +24,8 @@
*
FROM `gao_event`
WHERE
- `status` = '${@com.imyeyu.api.modules.gao.entity.GaoEvent$Status@ACTIVE.name()}'
+ `store_id` = #{storeId}
+ AND `status` = '${@com.imyeyu.api.modules.gao.entity.GaoEvent$Status@ACTIVE.name()}'
AND (`begin_at` IS NULL OR `begin_at` <= #{now})
AND (`end_at` IS NULL OR #{now} <= `end_at`)
AND `deleted_at` IS NULL
diff --git a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml
index aaea72d..2a05053 100644
--- a/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml
+++ b/src/main/resources/mapper/timi-server/GaoEventRecordMapper.xml
@@ -8,6 +8,9 @@
`gao_event_record`
WHERE
TRUE
+
+ AND `store_id` = #{storeId}
+
AND `customer_id` = #{customerId}
@@ -18,19 +21,48 @@
AND `registered_at` >= #{beginAt}
- AND `registered_at` >= #{endAt}
+ AND `registered_at` <= #{endAt}
AND `deleted_at` IS NULL
+
+
+
+ UPDATE `gao_event_record`
+ SET
+ `business_day_no` = #{businessDayNo},
+ `updated_at` = ROUND(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000)
+ WHERE
+ `store_id` = #{storeId}
+ AND `registered_date_value` = #{registeredDateValue}
+ AND `deleted_at` IS NULL
+
+
FROM `gao_event_record` r
LEFT JOIN `gao_customer` c ON r.`customer_id` = c.`id`
- r.`registered_at` >= #{beginAt}
- AND r.`registered_at` <= #{endAt}
+ TRUE
+
+ AND r.`registered_at` >= #{beginAt}
+
+
+ AND r.`registered_at` <= #{endAt}
+
AND r.`deleted_at` IS NULL
+
+ AND r.`store_id` = #{storeId}
+
AND r.`customer_id` = #{customerId}
@@ -69,6 +101,9 @@
r.`deleted_at` IS NULL
AND c.`deleted_at` IS NULL
+
+ AND r.`store_id` = #{storeId}
+
AND r.`customer_id` = #{customerId}
@@ -97,7 +132,7 @@
WITH `daily_stat` AS (
SELECT
r.`customer_id` AS `customerId`,
- DATE(FROM_UNIXTIME(r.`registered_at` / 1000)) AS `eventDate`,
+ r.`business_day_no` AS `businessDayNo`,
COUNT(1) AS `dailyCount`,
MAX(r.`registered_at`) AS `latestRegisteredAt`
FROM `gao_event_record` r
@@ -106,7 +141,7 @@
GROUP BY
r.`customer_id`,
- DATE(FROM_UNIXTIME(r.`registered_at` / 1000))
+ r.`business_day_no`
),
`rank_stat` AS (
SELECT
@@ -120,14 +155,14 @@
`continuous_ordered` AS (
SELECT
`customerId`,
- `eventDate`,
- ROW_NUMBER() OVER (PARTITION BY `customerId` ORDER BY `eventDate` DESC) AS `dayIndex`
+ `businessDayNo`,
+ ROW_NUMBER() OVER (PARTITION BY `customerId` ORDER BY `businessDayNo` DESC) AS `dayIndex`
FROM `daily_stat`
),
`latest_date` AS (
SELECT
`customerId`,
- MAX(`eventDate`) AS `latestEventDate`
+ MAX(`businessDayNo`) AS `latestBusinessDayNo`
FROM `daily_stat`
GROUP BY `customerId`
),
@@ -137,7 +172,7 @@
COUNT(1) AS `recentContinuousEventDays`
FROM `continuous_ordered` o
LEFT JOIN `latest_date` l ON o.`customerId` = l.`customerId`
- WHERE DATE_ADD(o.`eventDate`, INTERVAL o.`dayIndex` DAY) = DATE_ADD(l.`latestEventDate`, INTERVAL 1 DAY)
+ WHERE o.`businessDayNo` + o.`dayIndex` = l.`latestBusinessDayNo` + 1
GROUP BY o.`customerId`
),
`ranked` AS (
diff --git a/src/main/resources/mapper/timi-server/GaoStoreMapper.xml b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml
new file mode 100644
index 0000000..b30c92e
--- /dev/null
+++ b/src/main/resources/mapper/timi-server/GaoStoreMapper.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/src/main/resources/mapper/timi-server/RoleMapper.xml b/src/main/resources/mapper/timi-server/RoleMapper.xml
index ddda0ca..736551c 100644
--- a/src/main/resources/mapper/timi-server/RoleMapper.xml
+++ b/src/main/resources/mapper/timi-server/RoleMapper.xml
@@ -6,7 +6,7 @@
*
FROM `role`
WHERE
- 1 = 1
+ TRUE
AND `id` IN
@@ -14,29 +14,10 @@
- AND 1 = 0
+ AND FALSE
AND `deleted_at` IS NULL
-
-
diff --git a/src/main/resources/mapper/timi-server/UserRoleMapper.xml b/src/main/resources/mapper/timi-server/UserRoleMapper.xml
index 55ce81d..e3e8a16 100644
--- a/src/main/resources/mapper/timi-server/UserRoleMapper.xml
+++ b/src/main/resources/mapper/timi-server/UserRoleMapper.xml
@@ -12,6 +12,23 @@
(#{item.id}, #{item.userId}, #{item.roleId}, #{item.createdAt})
+
UPDATE `user_role`
SET `deleted_at` = UNIX_TIMESTAMP()