add store

This commit is contained in:
Timi
2026-08-07 11:37:54 +08:00
parent 7ffdd715ee
commit 7e40e0202e
60 changed files with 1657 additions and 446 deletions
@@ -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;
}
@@ -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
@@ -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;
}
}
@@ -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<GaoCustomerGenderStatView> genderStat() {
return service.stateGender();
public List<GaoCustomerGenderStatView> genderStat(@RequestParam String storeId) {
return service.stateGender(storeId);
}
}
@@ -49,8 +49,8 @@ public class GaoEventController {
@AOPLog
@RequestRateLimit
@PostMapping("/list/valid")
public List<GaoEvent> listValid() {
return service.listValid();
public List<GaoEvent> listValid(@RequestParam String storeId) {
return service.listValid(storeId);
}
@AOPLog
@@ -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<GaoStoreBusinessDay> list(@RequestBody Page<GaoStoreBusinessDay> page) {
return service.page(page);
}
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.STORE_READ)
@PostMapping("/calendar")
public List<GaoStoreBusinessDay> 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);
}
}
@@ -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<GaoStore> list(@RequestBody Page<GaoStore> 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<GaoStore> 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());
}
}
@@ -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<GaoUser> list(@RequestBody Page<GaoUser> 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<GaoUser> result = service.page(page);
{
List<String> userIdList = result.getList().stream().map(GaoUser::getUserId).toList();
Map<String, User> userMap = userService.mapByIdList(userIdList);
Map<String, List<Role>> 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)
@@ -48,6 +48,9 @@ public class GaoCustomer extends UUIDEntity {
/// 客户编码
private String code;
/// 门店 ID
private String storeId;
/// 姓名
private String name;
@@ -73,6 +73,9 @@ public class GaoEvent extends UUIDEntity {
/// 登记事件名称
private String name;
/// 门店 ID
private String storeId;
/// 说明
private String description;
@@ -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;
@@ -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;
}
}
@@ -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;
}
@@ -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<Role> roleList;
@Transient
private List<String> roleIdList;
}
@@ -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<GaoCustomer, String> {
/// @param beginCreatedAt 开始创建时间
/// @param endCreatedAt 结束创建时间
/// @return 每日新增客户数
List<GaoCustomerDailyStateView> stateDailyNewCustomer(Long beginCreatedAt, Long endCreatedAt);
List<GaoCustomerDailyStateView> 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<GaoCustomerGenderStatView> stateGender();
List<GaoCustomerGenderStatView> stateGender(@Param("storeId") String storeId);
}
@@ -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<GaoEvent, String> {
List<GaoEvent> selectByIdList(Collection<String> idList);
List<GaoEvent> selectValidList(Long now);
List<GaoEvent> selectValidList(@Param("storeId") String storeId, @Param("now") Long now);
}
@@ -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<GaoEventRecord, String> {
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);
@@ -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<GaoStoreBusinessDay, String> {
/// 按门店和日期查询营业日
///
/// @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<GaoStoreBusinessDay> 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<GaoStoreBusinessDay> selectByStoreIdAndDateRange(@Param("storeId") String storeId, @Param("beginDateValue") Integer beginDateValue, @Param("endDateValue") Integer endDateValue);
}
@@ -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<GaoStore, String> {
/// 查询用户所属的可用门店
///
/// @param userId 用户 ID
/// @return 门店
GaoStore selectByUserId(@Param("userId") String userId);
}
@@ -14,4 +14,7 @@ public interface GaoUserMapper extends BaseMapper<GaoUser, String> {
@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);
}
@@ -23,11 +23,17 @@ public interface GaoCustomerService extends BaseService<GaoCustomer, String> {
/// 按编码查询客户
///
/// @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<GaoCustomer> listByIdList(Collection<String> idList);
@@ -65,6 +71,7 @@ public interface GaoCustomerService extends BaseService<GaoCustomer, String> {
/// 统计客户性别分布
///
/// @param storeId 门店 ID
/// @return 性别统计列表
List<GaoCustomerGenderStatView> stateGender();
List<GaoCustomerGenderStatView> stateGender(String storeId);
}
@@ -20,8 +20,9 @@ public interface GaoEventService extends BaseService<GaoEvent, String> {
/// 查询启用的登记事件
///
/// @param storeId 门店 ID
/// @return 登记事件列表
List<GaoEvent> listValid();
List<GaoEvent> listValid(String storeId);
/// 查询登记事件要求的角色
///
@@ -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<GaoStoreBusinessDay, String> {
/// 解析营业日,不存在时按默认营业日创建
///
/// @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<GaoStoreBusinessDay> listByDateRange(String storeId, Integer beginDateValue, Integer endDateValue);
}
@@ -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<GaoStore, String> {
/// 查询用户所属门店
///
/// @param userId 用户 ID
/// @return 门店
GaoStore getByUserId(String userId);
/// 导出门店列表
///
/// @param page 分页查询条件
/// @return xlsx 文件字节
byte[] export(Page<GaoStore> page) throws IOException;
}
@@ -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<GaoUser, String> {
/// 按用户 ID 查询 GAO 用户
/// 调整 GAO 用户所属门店
///
/// @param userId 用户 ID
/// @return GAO 用户
GaoUser getByUserId(String userId);
/// 分页查询并填充用户详情
///
/// @param page 分页请求
/// @return GAO 用户分页
PageResult<GaoUser> pageWithUser(Page<GaoUser> page);
/// @param gaoUser GAO 用户,必须包含 ID 和目标门店 ID
void transferStore(GaoUser gaoUser);
}
@@ -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<GaoCustom
@Override
public void create(GaoCustomer customer) {
TimiException.required(customer.getName(), "not found customer.name");
TimiException.required(customer.getStoreId(), "not found customer.storeId");
Logger logger = new Logger(Logger.Module.GAO, "GAO_CUSTOMER_CREATE");
try {
logger.setContent(jackson.writeValueAsString(customer));
{
GaoCustomer byName = getByName(customer.getName());
GaoCustomer byName = getByName(customer.getStoreId(), customer.getName());
TimiException.requiredNull(byName, "该姓名客户已登记");
}
if (TimiJava.isNotEmpty(customer.getCode())) {
GaoCustomer byCode = getByCode(customer.getCode());
GaoCustomer byCode = getByCode(customer.getStoreId(), customer.getCode());
TimiException.requiredNull(byCode, "该编码客户已登记");
}
if (TimiJava.isNotEmpty(customer.getIntroducerCustomerId())) {
TimiException.requiredTrue(!customer.getIntroducerCustomerId().equals(customer.getId()), "customer.introducerCustomerId invalid");
TimiException.required(super.get(customer.getIntroducerCustomerId()), "not found introducer customer");
GaoCustomer introducerCustomer = super.get(customer.getIntroducerCustomerId());
TimiException.required(introducerCustomer, "not found introducer customer");
TimiException.requiredTrue(customer.getStoreId().equals(introducerCustomer.getStoreId()), "介绍人和客户不属于同一门店");
}
customer.setBirthdateCalendar(TimiJava.defaultIfNull(customer.getBirthdateCalendar(), GaoCustomer.BirthdateCalendar.SOLAR));
@@ -118,19 +123,21 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
TimiException.required(customer, "not found customer");
TimiException.required(customer.getId(), "not found customer.id");
TimiException.required(customer.getName(), "not found customer.name");
TimiException.required(customer.getStoreId(), "not found customer.storeId");
Logger logger = new Logger(Logger.Module.GAO, "GAO_CUSTOMER_UPDATE");
try {
logger.setContent(jackson.writeValueAsString(customer));
GaoCustomer dbCustomer = get(customer.getId());
TimiException.requiredTrue(dbCustomer.getStoreId().equals(customer.getStoreId()), "customer.storeId invalid");
dbCustomer.setCode(TimiJava.defaultIfEmpty(customer.getCode(), null));
{
if (!dbCustomer.getCode().equals(customer.getCode())) {
GaoCustomer byCode = getByCode(customer.getCode());
if (!Objects.equals(dbCustomer.getCode(), customer.getCode()) && TimiJava.isNotEmpty(customer.getCode())) {
GaoCustomer byCode = getByCode(customer.getStoreId(), customer.getCode());
TimiException.requiredTrue(byCode == null || !byCode.getId().equals(dbCustomer.getId()), "该姓名客户已登记");
}
if (!dbCustomer.getName().equals(customer.getName())) {
GaoCustomer byName = getByName(customer.getName());
if (!Objects.equals(dbCustomer.getName(), customer.getName())) {
GaoCustomer byName = getByName(customer.getStoreId(), customer.getName());
TimiException.requiredTrue(byName == null || !byName.getId().equals(dbCustomer.getId()), "该姓名客户已登记");
}
}
@@ -196,24 +203,28 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
}
@Override
public GaoCustomer getByCode(String code) {
public GaoCustomer getByCode(String storeId, String code) {
TimiException.required(storeId, "not found storeId");
TimiException.required(code, "not found code");
GaoCustomer example = new GaoCustomer();
example.setStoreId(storeId);
example.setCode(code);
return mapper.selectByExample(example);
}
@Override
public GaoCustomer getByName(String name) {
public GaoCustomer getByName(String storeId, String name) {
TimiException.required(storeId, "not found storeId");
TimiException.required(name, "not found name");
GaoCustomer example = new GaoCustomer();
example.setStoreId(storeId);
example.setName(name);
return mapper.selectByExample(example);
}
@Override
public List<GaoCustomer> listByIdList(Collection<String> idList) {
return mapper.selectByIdList(idList);
return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
@@ -252,6 +263,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
TimiException.required(customer, "not found customer");
GaoCustomer introducerCustomer = get(introducerCustomerId);
TimiException.required(introducerCustomer, "not found introducerCustomer");
TimiException.requiredTrue(customer.getStoreId().equals(introducerCustomer.getStoreId()), "介绍人和客户不属于同一门店");
if (introducerCustomer.getIntroducerCustomerId().equals(customer.getIntroducerCustomerId())) {
throw new TimiException(TimiCode.ARG_BAD, "customer can not upper be introducer customer");
}
@@ -312,6 +324,7 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
@Override
public List<GaoCustomerDailyStateView> 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<GaoCustom
long beginCreatedAt = beginDate.atStartOfDay(zoneId).toInstant().toEpochMilli();
long endCreatedAt = endDate.plusDays(1).atStartOfDay(zoneId).toInstant().toEpochMilli() - 1;
Map<Integer, Long> 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<GaoCustomerDailyStateView> 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<GaoCustom
}
@Override
public List<GaoCustomerGenderStatView> stateGender() {
return mapper.stateGender();
public List<GaoCustomerGenderStatView> stateGender(String storeId) {
TimiException.required(storeId, "not found storeId");
return mapper.stateGender(storeId);
}
private void changeInvitedCount(String customerId, long delta) {
@@ -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<GaoEventRecord, String> 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<GaoEve
private final UserLoginService userLoginService;
private final AttachmentService attachmentService;
private final GaoCustomerService customerService;
private final GaoStoreBusinessDayService businessDayService;
private final GaoEventRecordMapper mapper;
@@ -79,8 +85,11 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
String loginUserId = userLoginService.getRequireLoginUserId();
Long registeredAt = TimiJava.defaultIfNull(record.getRegisteredAt(), Time.now());
GaoEvent event = eventService.get(record.getEventId());
TimiException.required(event.getStoreId(), "not found event.storeId");
// 检查权限
checkEventPermission(event);
int registeredDateValue = dateValue(registeredAt);
long businessDayNo = businessDayService.resolveOpenBusinessDay(event.getStoreId(), registeredDateValue).getBusinessDayNo();
List<String> 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<GaoEve
String customerId = customerIdList.get(i);
// 检查事件可登记
checkEventCreatable(customerId, event, registeredAt);
GaoCustomer customer = customerService.get(customerId);
TimiException.required(customer, "not found customer");
TimiException.requiredTrue(event.getStoreId().equals(customer.getStoreId()), "客户和登记事件不属于同一门店");
GaoEventRecord dbRecord = new GaoEventRecord();
dbRecord.setCustomerId(customerId);
dbRecord.setStoreId(event.getStoreId());
dbRecord.setEventId(record.getEventId());
dbRecord.setOperatorUserId(loginUserId);
dbRecord.setRemark(record.getRemark());
dbRecord.setRegisteredAt(registeredAt);
dbRecord.setRegisteredDateValue(registeredDateValue);
dbRecord.setBusinessDayNo(businessDayNo);
super.create(dbRecord);
// 附件
if (i == 0) {
@@ -146,8 +161,16 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
record.setRegisteredAt(TimiJava.defaultIfNull(record.getRegisteredAt(), Time.now()));
GaoEvent event = eventService.get(record.getEventId());
TimiException.required(event.getStoreId(), "not found event.storeId");
checkEventPermission(event);
checkEventCreatable(record.getCustomerId(), event, record.getRegisteredAt());
GaoCustomer customer = customerService.get(record.getCustomerId());
TimiException.required(customer, "not found customer");
TimiException.requiredTrue(event.getStoreId().equals(customer.getStoreId()), "客户和登记事件不属于同一门店");
int registeredDateValue = dateValue(record.getRegisteredAt());
record.setStoreId(event.getStoreId());
record.setRegisteredDateValue(registeredDateValue);
record.setBusinessDayNo(businessDayService.resolveOpenBusinessDay(event.getStoreId(), registeredDateValue).getBusinessDayNo());
super.update(record);
attachmentService.updateByBizId(Attachment.BizType.GAO_EVENT_RECORD, record.getId(), record.getAttachmentList());
@@ -176,7 +199,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
GaoCustomer dbCustomer = customerService.get(customer.getId());
TimiException.required(dbCustomer, "not found customer");
if (TimiJava.isNotEmpty(customer.getCode())) {
GaoCustomer byCode = customerService.getByCode(customer.getCode());
GaoCustomer byCode = customerService.getByCode(dbCustomer.getStoreId(), customer.getCode());
TimiException.requiredTrue(byCode == null || dbCustomer.getId().equals(byCode.getId()), "该编码客户已登记");
dbCustomer.setCode(customer.getCode());
customerService.update(dbCustomer);
@@ -197,6 +220,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
@Override
public PageResult<GaoEventRecord> pageByRange(GaoEventRecordPage page) {
TimiException.required(page.getStoreId(), "not found page.storeId");
PageResult<GaoEventRecord> result = new PageResult<>();
result.setTotal(mapper.countByRange(page));
result.setList(mapper.selectByRange(page));
@@ -205,12 +229,14 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
@Override
public byte[] export(GaoEventRecordPage page) throws IOException {
TimiException.required(page.getStoreId(), "not found page.storeId");
List<GaoEventRecord> list = mapper.selectByRange(page);
return xssfBuilder.build(list);
}
@Override
public PageResult<GaoCustomerEventRankView> pageRankByRange(GaoEventRecordRankPage page) {
TimiException.required(page.getStoreId(), "not found page.storeId");
PageResult<GaoCustomerEventRankView> result = new PageResult<>();
result.setTotal(mapper.countRankByRange(page));
result.setList(mapper.selectRankByRange(page));
@@ -241,18 +267,18 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService<GaoEve
beginAt = noon;
endAt = today + 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 NATURAL_DAY -> {
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<GaoEve
ZonedDateTime zdt = Instant.ofEpochMilli(registeredAt).atZone(zone);
long beginAt = zdt.with(TemporalAdjusters.firstDayOfMonth()).truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli();
long endAt = zdt.with(TemporalAdjusters.firstDayOfNextMonth()).truncatedTo(ChronoUnit.DAYS).toInstant().toEpochMilli();
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 TOTAL_LIMIT -> {
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));
}
}
@@ -56,6 +56,7 @@ public class GaoEventServiceImplement extends AbstractEntityService<GaoEvent, St
@Override
public void create(GaoEvent event) {
TimiException.required(event, "not found event");
TimiException.required(event.getStoreId(), "not found event.storeId");
TimiException.required(event.getName(), "not found event.name");
Logger logger = new Logger(Logger.Module.GAO, "GAO_EVENT_CREATE");
@@ -98,6 +99,7 @@ public class GaoEventServiceImplement extends AbstractEntityService<GaoEvent, St
try {
logger.setContent(jackson.writeValueAsString(entity));
GaoEvent dbEvent = get(entity.getId());
TimiException.requiredTrue(dbEvent.getStoreId().equals(entity.getStoreId()), "event.storeId invalid");
dbEvent.setName(entity.getName());
dbEvent.setDescription(entity.getDescription());
dbEvent.setColor(entity.getColor());
@@ -163,8 +165,9 @@ public class GaoEventServiceImplement extends AbstractEntityService<GaoEvent, St
}
@Override
public List<GaoEvent> listValid() {
return mapper.selectValidList(Time.now());
public List<GaoEvent> listValid(String storeId) {
TimiException.required(storeId, "not found storeId");
return mapper.selectValidList(storeId, Time.now());
}
@Override
@@ -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<GaoStoreBusinessDay, String> implements GaoStoreBusinessDayService {
private final GaoStoreBusinessDayMapper mapper;
private final GaoEventRecordMapper eventRecordMapper;
@Override
protected BaseMapper<GaoStoreBusinessDay, String> 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<GaoStoreBusinessDay> 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, "该营业日已有登记记录,不能改为休息或删除");
}
}
@@ -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<GaoStore, String> implements GaoStoreService {
private final ObjectMapper jackson;
private final LoggerService loggerService;
private final GaoStoreMapper mapper;
private final StoreXSSFBuilder storeXSSFBuilder;
@Override
protected BaseMapper<GaoStore, String> 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<GaoStore> page) throws IOException {
page.setIndex(0);
page.setSize(Long.MAX_VALUE);
List<GaoStore> 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()), "该门店名称已存在");
}
}
@@ -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<GaoUser, String> 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<GaoUser, String> mapper() {
@@ -58,17 +46,28 @@ public class GaoUserServiceImplement extends AbstractEntityService<GaoUser, Stri
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoUser entity) {
public void create(GaoUser gaoUser) {
TimiException.required(gaoUser.getStoreId(), "not found storeId");
TimiException.required(gaoUser.getUser(), "not found user");
TimiException.required(gaoUser.getUser().getName(), "not found user.name");
TimiException.required(gaoUser.getUser().getPhoneNo(), "not found user.phoneNo");
Logger logger = new Logger(Logger.Module.GAO, "GAO_USER_CREATE");
try {
logger.setContent(jackson.writeValueAsString(entity));
prepareUser(entity);
checkEntity(entity, false);
super.create(entity);
saveUserRole(entity);
loadUser(entity);
logger.setContent(jackson.writeValueAsString(gaoUser));
GaoStore store = gaoStoreService.get(gaoUser.getStoreId());
TimiException.required(store, "not found store");
TimiException.requiredTrue(store.isActivated(), "门店不可用");
userService.create(gaoUser.getUser());
userService.verifyPhoneNo(gaoUser.getUser().getId());
gaoUser.setUserId(gaoUser.getUser().getId());
gaoUser.setEnabled(TimiJava.defaultIfNull(gaoUser.getEnabled(), true));
super.create(gaoUser);
logger.setLevel(Logger.Level.INFO);
logger.setResult(entity.getId());
logger.setResult(gaoUser.getId());
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
@@ -76,8 +75,8 @@ public class GaoUserServiceImplement extends AbstractEntityService<GaoUser, Stri
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("新增 GAO 用户失败", e);
throw new TimiException(TimiCode.ERROR, "新增 GAO 用户失败", e);
log.error("create user error", e);
throw new TimiException(TimiCode.ERROR, "create user error", e);
} finally {
loggerService.create(logger);
}
@@ -85,16 +84,23 @@ public class GaoUserServiceImplement extends AbstractEntityService<GaoUser, Stri
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void update(GaoUser entity) {
public void update(GaoUser gaoUser) {
TimiException.required(gaoUser, "not found gao user");
TimiException.required(gaoUser.getId(), "not found gao user.id");
Logger logger = new Logger(Logger.Module.GAO, "GAO_USER_UPDATE");
try {
logger.setContent(jackson.writeValueAsString(entity));
checkEntity(entity, true);
updateUser(entity);
super.update(entity);
saveUserRole(entity);
logger.setContent(jackson.writeValueAsString(gaoUser));
GaoUser dbGaoUser = super.get(gaoUser.getId());
TimiException.required(dbGaoUser, "not found gao user");
dbGaoUser.setEnabled(gaoUser.getEnabled());
dbGaoUser.setRemark(gaoUser.getRemark());
super.update(dbGaoUser);
logger.setLevel(Logger.Level.INFO);
logger.setResult(entity.getId());
logger.setResult(gaoUser.getId());
} catch (TimiException e) {
logger.setLevel(Logger.Level.WARN);
logger.setException(e.getMessage());
@@ -102,115 +108,47 @@ public class GaoUserServiceImplement extends AbstractEntityService<GaoUser, Stri
} catch (Exception e) {
logger.setLevel(Logger.Level.ERROR);
logger.setException(TimiJava.serializeThrowable(e));
log.error("修改 GAO 用户失败", e);
throw new TimiException(TimiCode.ERROR, "修改 GAO 用户失败", e);
log.error("update user error", e);
throw new TimiException(TimiCode.ERROR, "update user error", e);
} finally {
loggerService.create(logger);
}
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public GaoUser get(String id) {
GaoUser gaoUser = super.get(id);
loadUser(gaoUser);
return gaoUser;
}
@Override
public GaoUser getByUserId(String userId) {
TimiException.required(userId, "not found userId");
GaoUser gaoUser = mapper.selectByUserId(userId);
public void transferStore(GaoUser gaoUser) {
TimiException.required(gaoUser, "not found gao user");
loadUser(gaoUser);
return gaoUser;
}
TimiException.required(gaoUser.getId(), "not found gao user.id");
TimiException.required(gaoUser.getStoreId(), "not found gao user.storeId");
@Override
public PageResult<GaoUser> pageWithUser(Page<GaoUser> page) {
PageResult<GaoUser> 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<GaoUser> gaoUserList) {
if (gaoUserList == null || gaoUserList.isEmpty()) {
return;
}
List<String> userIdList = gaoUserList.stream().map(GaoUser::getUserId).distinct().toList();
Map<String, User> 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<Role> 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);
}
}
}
@@ -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<GaoStore> {
/// 构建门店列表 Excel
///
/// @param list 门店列表
/// @return xlsx 文件字节
/// @throws IOException 写入异常
public byte[] build(List<GaoStore> 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();
}
}
}
@@ -10,6 +10,9 @@ import lombok.Data;
@Data
public class GaoCustomerTrendStateReq {
/// 门店 ID
private String storeId;
/// 开始创建时间
private Long beginCreatedAt;
@@ -15,6 +15,9 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoEventRecordPage extends BasePage {
/// 门店 ID
private String storeId;
/// 客户 ID,为空查询全部客户
private String customerId;
@@ -14,6 +14,9 @@ import java.util.List;
@EqualsAndHashCode(callSuper = true)
public class GaoEventRecordRankPage extends BasePage {
/// 门店 ID
private String storeId;
/// 客户 ID,为空查询全部客户
private String customerId;
@@ -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<Role> listUserRole(@RequestParam String userId, @RequestParam(required = false) ModuleCode moduleCode) {
public List<Role> 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<Permission> listAuthorizedPermission(@RequestParam String userId) {
return service.listAllPermissionByUserId(userId);
return userRoleService.listAllPermissionByUserId(userId);
}
private void fillRoleDetail(Role role) {
@@ -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<String> permissionList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<Role> roleEntityList;
@Transient
@JsonView(ResponseView.Public.class)
protected List<Attachment> attachmentList;
@@ -135,6 +140,10 @@ public class User extends UUIDEntity {
@JsonView(ResponseView.Public.class)
protected List<String> tempFileIdList;
public boolean isValidPhone() {
return Regex.isMatch(Regex.MOBILE_PHONE, phoneNo);
}
/** @return true 为禁言中 */
public boolean isMuting() {
return unmuteAt != null && Time.now() < unmuteAt;
@@ -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<Role, String> {
List<Role> selectByIdList(List<String> idList);
List<Role> selectByIdList(Collection<String> idList);
List<Role> selectByModuleCodeAndCodeList(@Param("moduleCode") ModuleCode moduleCode, @Param("codeList") List<String> codeList);
List<Role> selectByModuleCode(@Param("moduleCode") ModuleCode moduleCode);
List<Role> selectByModuleCode(ModuleCode moduleCode);
}
@@ -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<User, String> {
* @param idList 用户 ID 列表
* @return 用户列表
*/
List<User> listByIdList(List<String> idList);
List<User> listByIdList(Collection<String> idList);
}
@@ -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<UserRole, String> {
void createBatch(List<UserRole> userRoleList);
List<UserRole> selectByUserIdList(@Param("userIdList") Collection<String> userIdList);
void deleteByRoleIdList(String userId, List<String> roleIdList);
}
@@ -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<Role, String> {
/// 给用户授权角色
///
/// @param req 用户角色授权请求
void authorizeUserRole(UserRoleAuthorizeReq req);
/// 查询用户直属角色
///
/// @param userId 用户 ID
/// @return 角色列表
List<Role> listByUserId(String userId);
/// 查询用户指定模块直属角色
///
/// @param userId 用户 ID
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listByUserId(String userId, ModuleCode moduleCode);
/// 查询模块角色
///
/// @param moduleCode 模块代码
/// @return 角色列表
List<Role> listByModuleCode(ModuleCode moduleCode);
/// 查询用户直属角色编码
///
/// @param userId 用户 ID
/// @return 角色编码列表,格式为 MODULE:ROLE
default List<String> 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<String> listAllCodeByUserId(String userId);
/// 查询用户全部权限
///
/// @param userId 用户 ID
/// @return 权限列表
List<Permission> listAllPermissionByUserId(String userId);
/// 查询用户全部权限编码
///
/// @param userId 用户 ID
/// @return 权限编码列表,格式为 MODULE:PERMISSION
default List<String> listAllPermissionCodeByUserId(String userId) {
return listAllPermissionByUserId(userId).stream()
.map(permission -> "%s:%s".formatted(permission.getModuleCode().name(), permission.getCode()))
.distinct()
.toList();
}
List<Role> listByIdList(Collection<String> idList);
/// 查询直属子角色
///
@@ -80,4 +33,10 @@ public interface RoleService extends BaseService<Role, String> {
/// @param roleId 角色 ID
/// @return 权限列表
List<Permission> listPermissionByRoleId(String roleId);
/// 递归获取角色及其所有子角色的 ID 列表
///
/// @param idList 角色 ID 列表
/// @return 包含所有子角色的 ID 列表
List<String> listAllRoleIdIncludingChildren(Collection<String> idList);
}
@@ -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<UserRole, String> {
/// @param list 用户角色绑定列表
void createBatch(List<UserRole> list);
/// 查询用户角色绑定
/// 给用户授权角色
///
/// @param req 用户角色授权请求
void authorizeUserRole(UserRoleAuthorizeRequest req);
List<UserRole> listByUserId(String userId);
/// 查询多个用户角色绑定
///
/// @param userIdList 用户 ID 列表
/// @return 用户角色绑定列表
List<UserRole> listByUserIdList(Collection<String> userIdList);
/// 查询用户直属角色
///
/// @param userId 用户 ID
/// @return 用户角色绑定列表
List<UserRole> listByUserId(String userId);
/// @return 角色列表
List<Role> listRoleByUserId(String userId);
/// 查询用户指定模块直属角色
///
/// @param moduleCode 模块代码
/// @param userId 用户 ID
/// @return 角色列表
List<Role> listRoleByUserId(ModuleCode moduleCode, String userId);
/// 查询用户直属角色编码
///
/// @param userId 用户 ID
/// @return 角色编码列表,格式为 MODULE:ROLE
default List<String> 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<String> listAllRoleCodeByUserId(String userId);
/// 查询用户全部权限
///
/// @param userId 用户 ID
/// @return 权限列表
List<Permission> listAllPermissionByUserId(String userId);
/// 查询用户全部权限编码
///
/// @param userId 用户 ID
/// @return 权限编码列表,格式为 MODULE:PERMISSION
default List<String> 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<UserRole, String> {
return bindingList.stream().map(UserRole::getRoleId).toList();
}
/// 按用户 ID 分组查询直属角色
///
/// @param idList 用户 ID 列表
/// @return 用户角色映射
Map<String, List<Role>> mapRoleListByUserIdList(Collection<String> idList);
/// 删除用户指定角色绑定
///
/// @param userId 用户 ID
@@ -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, String> {
User getByPhoneNo(String phoneNo);
User getByVerifiedPhoneNo(String phoneNo);
void verifyEmail(String id);
void verifyPhoneNo(String id);
Map<String, User> mapByIdList(Collection<String> idList);
}
@@ -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<String> codeSet() {
@@ -34,7 +35,7 @@ public class PermissionCheckerImplement implements PermissionChecker {
@Override
public Set<String> codeSet(String userId) {
return new HashSet<>(roleService.listAllPermissionCodeByUserId(userId));
return new HashSet<>(userRoleService.listAllPermissionCodeByUserId(userId));
}
@Override
@@ -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<String> codeSet() {
return new HashSet<>(roleService.listAllCodeByUserId(userLoginService.getRequireLoginUserId()));
return new HashSet<>(userRoleService.listAllRoleCodeByUserId(userLoginService.getRequireLoginUserId()));
}
@Override
@@ -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<Role, String> 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<Role, String> 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<UserRole> bindingList = userRoleService.listByUserId(req.getUserId());
List<String> bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList();
Set<String> dbIdSet = mapper.selectByIdList(bindingRoleIdList).stream()
.filter(role -> req.getModuleCode() == role.getModuleCode())
.map(Role::getId)
.collect(Collectors.toSet());
Set<String> 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<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
if (TimiJava.isNotEmpty(deleteIdList)) {
userRoleService.deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList));
}
if (TimiJava.isNotEmpty(addIdList)) {
List<UserRole> 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<Role> listByUserId(String userId) {
return mapper.selectByIdList(userRoleService.listRoleIdByUserId(userId));
}
@Override
public List<Role> listByUserId(String userId, ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
return listByUserId(userId).stream()
.filter(role -> moduleCode == role.getModuleCode())
.toList();
}
@Override
public List<Role> listByModuleCode(ModuleCode moduleCode) {
TimiException.required(moduleCode, "not found moduleCode");
@@ -221,20 +153,8 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
}
@Override
public List<String> listAllCodeByUserId(String userId) {
List<String> roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
List<String> 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<Permission> listAllPermissionByUserId(String userId) {
List<String> roleIdList = listByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = getAllRoleIdsIncludingChildren(roleIdList);
return permissionService.listByRoleId(allRoleIdList);
public List<Role> listByIdList(Collection<String> idList) {
return mapper.selectByIdList(new HashSet<>(idList));
}
@Override
@@ -248,20 +168,13 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> im
@Override
public List<Permission> listPermissionByRoleId(String roleId) {
List<String> allRoleIdList = getAllRoleIdsIncludingChildren(List.of(roleId));
List<String> allRoleIdList = listAllRoleIdIncludingChildren(List.of(roleId));
return permissionService.listByRoleId(allRoleIdList);
}
/// 递归获取角色及其所有子角色的 ID 列表
///
/// @param roleIdList 角色 ID 列表
/// @return 包含所有子角色的 ID 列表
private List<String> getAllRoleIdsIncludingChildren(List<String> roleIdList) {
if (TimiJava.isEmpty(roleIdList)) {
return new ArrayList<>();
}
Set<String> allRoleIdSet = new HashSet<>(roleIdList);
List<String> currentLevelRoleIdList = new ArrayList<>(roleIdList);
public List<String> listAllRoleIdIncludingChildren(Collection<String> idList) {
Set<String> allRoleIdSet = new HashSet<>(idList);
List<String> currentLevelRoleIdList = new ArrayList<>(idList);
while (TimiJava.isNotEmpty(currentLevelRoleIdList)) {
List<String> childRoleIdList = roleRelationMapper.selectChildRoleIdListByParentRoleIdList(currentLevelRoleIdList);
@@ -277,18 +190,4 @@ public class RoleServiceImplement extends AbstractEntityService<Role, String> 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;
}
}
@@ -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<String, String> 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;
}
@@ -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<UserRole, String> implements UserRoleService {
private final RoleService roleService;
private final PermissionService permissionService;
private final UserRoleMapper mapper;
@Override
@@ -30,6 +53,49 @@ public class UserRoleServiceImplement extends AbstractEntityService<UserRole, St
mapper.createBatch(list);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void authorizeUserRole(UserRoleAuthorizeRequest 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<UserRole> bindingList = listByUserId(req.getUserId());
List<String> bindingRoleIdList = bindingList.stream().map(UserRole::getRoleId).toList();
Set<String> dbIdSet = roleService.listByIdList(bindingRoleIdList)
.stream()
.filter(role -> req.getModuleCode() == role.getModuleCode())
.map(Role::getId)
.collect(Collectors.toSet());
Set<String> 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<String> deleteIdList = new HashSet<>(dbIdSet);
deleteIdList.removeAll(newIdSet);
Set<String> addIdList = new HashSet<>(newIdSet);
addIdList.removeAll(dbIdSet);
if (TimiJava.isNotEmpty(deleteIdList)) {
deleteByRoleIdList(req.getUserId(), new ArrayList<>(deleteIdList));
}
if (TimiJava.isNotEmpty(addIdList)) {
List<UserRole> 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<UserRole> listByUserId(String userId) {
UserRole example = new UserRole();
@@ -37,6 +103,57 @@ public class UserRoleServiceImplement extends AbstractEntityService<UserRole, St
return mapper.selectAllByExample(example);
}
@Override
public List<UserRole> listByUserIdList(Collection<String> userIdList) {
return mapper.selectByUserIdList(userIdList);
}
@Override
public List<Role> listRoleByUserId(String userId) {
List<UserRole> userRoleList = listByUserId(userId);
return roleService.listByIdList(userRoleList.stream().map(UserRole::getRoleId).toList());
}
@Override
public List<Role> listRoleByUserId(ModuleCode moduleCode, String userId) {
TimiException.required(moduleCode, "not found moduleCode");
return listRoleByUserId(userId).stream().filter(role -> moduleCode == role.getModuleCode()).toList();
}
@Override
public List<String> listAllRoleCodeByUserId(String userId) {
List<String> roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
List<String> 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<Permission> listAllPermissionByUserId(String userId) {
List<String> roleIdList = listRoleByUserId(userId).stream().map(Role::getId).toList();
List<String> allRoleIdList = roleService.listAllRoleIdIncludingChildren(roleIdList);
return permissionService.listByRoleId(allRoleIdList);
}
@Override
public Map<String, List<Role>> mapRoleListByUserIdList(Collection<String> idList) {
if (TimiJava.isEmpty(idList)) {
return Map.of();
}
List<UserRole> userRoleList = listByUserIdList(idList);
if (TimiJava.isEmpty(userRoleList)) {
return Map.of();
}
Map<String, Role> 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<String> roleIdList) {
mapper.deleteByRoleIdList(userId, roleIdList);
@@ -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<User, String> 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<User, String> 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<User, String> 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<User, String> 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<User, String> 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<String, User> mapByIdList(Collection<String> idList) {
return mapper.listByIdList(new HashSet<>(idList)).stream().collect(Collectors.toMap(User::getId, Function.identity()));
}
}
@@ -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<String> roleIdList;
private List<String> roleCodeList;
}
@@ -192,6 +192,7 @@ public class InitBuiltinUser implements ApplicationRunner {
private void bindBuiltinPermission(Map<String, Role> roleMap, Map<String, Permission> 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());
}