diff --git a/pom.xml b/pom.xml
index 7293937..84f973d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.imyeyu.timiserverapi
TimiServerAPI
- 1.0.16
+ 1.0.17
jar
TimiServerAPI
imyeyu.com API
diff --git a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java
index 652cdb4..2668094 100644
--- a/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java
+++ b/src/main/java/com/imyeyu/api/bean/CorePermissionCode.java
@@ -50,6 +50,11 @@ public enum CorePermissionCode implements BuiltinPermissionCode {
USER_UPDATE("USER:UPDATE", "用户修改", false, true),
USER_DELETE("USER:DELETE", "用户删除", false, true),
+ FEEDBACK_CREATE("FEEDBACK:CREATE", "反馈创建", false, true),
+ FEEDBACK_READ("FEEDBACK:READ", "反馈读取", false, true),
+ FEEDBACK_UPDATE("FEEDBACK:UPDATE", "反馈修改", false, true),
+ FEEDBACK_DELETE("FEEDBACK:DELETE", "反馈删除", false, true),
+
USER_ROLE_CREATE("USER_ROLE:CREATE", "用户角色创建", true, true),
USER_ROLE_READ("USER_ROLE:READ", "用户角色读取", true, true),
USER_ROLE_DELETE("USER_ROLE:DELETE", "用户角色删除", true, true);
diff --git a/src/main/java/com/imyeyu/api/modules/common/controller/FeedbackController.java b/src/main/java/com/imyeyu/api/modules/common/controller/FeedbackController.java
new file mode 100644
index 0000000..9ae8e40
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/controller/FeedbackController.java
@@ -0,0 +1,116 @@
+package com.imyeyu.api.modules.common.controller;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.imyeyu.api.annotation.RequireCorePermission;
+import com.imyeyu.api.bean.CorePermissionCode;
+import com.imyeyu.api.modules.common.entity.Feedback;
+import com.imyeyu.api.modules.common.service.FeedbackService;
+import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.spring.TimiSpring;
+import com.imyeyu.spring.annotation.AOPLog;
+import com.imyeyu.spring.annotation.CaptchaValid;
+import com.imyeyu.spring.annotation.RequestRateLimit;
+import com.imyeyu.spring.bean.CaptchaData;
+import com.imyeyu.spring.bean.Page;
+import com.imyeyu.spring.bean.PageResult;
+import com.imyeyu.spring.util.ResponseView;
+import jakarta.validation.Valid;
+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;
+
+/// 通用反馈接口
+///
+/// @author Codex
+/// @since 2026-08-19
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/feedback")
+public class FeedbackController {
+
+ private final FeedbackService service;
+ private final UserLoginService userLoginService;
+
+ /// 提交反馈。登录用户和匿名用户均需提交图形验证码,匿名用户按 IP 限流
+ ///
+ /// @param req 带图形验证码的反馈请求
+ /// @return 已创建反馈
+ @AOPLog
+ @JsonView(ResponseView.Public.class)
+ @CaptchaValid
+ @RequestRateLimit(value = 3, inSeconds = 60)
+ @PostMapping("/submit")
+ public Feedback submit(@RequestBody @Valid CaptchaData req) {
+ Feedback feedback = req.getData();
+ feedback.setIp(TimiSpring.getRequestIP());
+ if (userLoginService.getLoginUser() != null) {
+ feedback.setUserId(userLoginService.getLoginUser().getId());
+ }
+ service.create(feedback);
+ return feedback;
+ }
+
+ /// 查询反馈列表
+ ///
+ /// @param page 分页参数
+ /// @return 反馈分页
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.FEEDBACK_READ)
+ @PostMapping("/list")
+ public PageResult list(@RequestBody Page page) {
+ return service.page(page);
+ }
+
+ /// 查询反馈详情
+ ///
+ /// @param id 反馈 ID
+ /// @return 反馈详情
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.FEEDBACK_READ)
+ @PostMapping("/detail")
+ public Feedback detail(@RequestParam String id) {
+ return service.get(id);
+ }
+
+ /// 后台创建反馈
+ ///
+ /// @param feedback 反馈
+ /// @return 反馈
+ @AOPLog
+ @JsonView(ResponseView.Admin.class)
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.FEEDBACK_CREATE)
+ @PostMapping("/create")
+ public Feedback create(@RequestBody Feedback feedback) {
+ service.create(feedback);
+ return feedback;
+ }
+
+ /// 更新反馈
+ ///
+ /// @param feedback 反馈
+ @AOPLog
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.FEEDBACK_UPDATE)
+ @PostMapping("/update")
+ public void update(@RequestBody Feedback feedback) {
+ feedback.setRespondedBy(userLoginService.getRequireLoginUserId());
+ service.update(feedback);
+ }
+
+ /// 删除反馈
+ ///
+ /// @param id 反馈 ID
+ @AOPLog
+ @RequestRateLimit
+ @RequireCorePermission(CorePermissionCode.FEEDBACK_DELETE)
+ @PostMapping("/delete")
+ public void delete(@RequestParam String id) {
+ service.delete(id);
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Article.java b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java
index ce3353a..52b9973 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Article.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Article.java
@@ -28,7 +28,10 @@ public class Article extends UUIDEntity implements SettingSupport {
public enum BizType {
/** 博客 */
- BLOG
+ BLOG,
+
+ /// 反馈
+ FEEDBACK
}
/**
@@ -79,4 +82,7 @@ public class Article extends UUIDEntity implements SettingSupport {
@Transient
protected List commentList;
+
+ @Transient
+ protected List attachmentList;
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
index 3ef00fe..8b829bb 100644
--- a/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Attachment.java
@@ -30,6 +30,8 @@ public class Attachment extends UUIDEntity {
/// @since 2026-07-17 20:52
public enum BizType {
+ ARTICLE,
+
USER,
GAO_CUSTOMER,
diff --git a/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java b/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java
new file mode 100644
index 0000000..98bc433
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/entity/Feedback.java
@@ -0,0 +1,79 @@
+package com.imyeyu.api.modules.common.entity;
+
+import com.fasterxml.jackson.annotation.JsonView;
+import com.imyeyu.api.bean.ModuleCode;
+import com.imyeyu.spring.annotation.table.Transient;
+import com.imyeyu.spring.entity.UUIDEntity;
+import com.imyeyu.spring.util.ResponseView;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+/// 通用反馈
+///
+/// @author Codex
+/// @since 2026-08-19
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper = true)
+public class Feedback extends UUIDEntity {
+
+ /// 反馈处理状态
+ public enum Status {
+
+ /// 待处理
+ PENDING,
+
+ /// 已回复
+ REPLIED,
+
+ /// 已关闭
+ CLOSED
+ }
+
+ /// 所属模块
+ @JsonView(ResponseView.Public.class)
+ private ModuleCode module;
+
+ /// 提交用户 ID,匿名反馈为 null
+ @JsonView(ResponseView.Admin.class)
+ private String userId;
+
+ /// 反馈内容文章 ID
+ @JsonView(ResponseView.Public.class)
+ private String contentId;
+
+ /// 联系方式
+ @JsonView(ResponseView.Public.class)
+ private String contact;
+
+ /// 提交 IP
+ @JsonView(ResponseView.Admin.class)
+ private String ip;
+
+ /// 处理状态
+ @JsonView(ResponseView.Public.class)
+ private Status status;
+
+ /// 管理员回复文章 ID
+ @JsonView(ResponseView.Public.class)
+ private String responseId;
+
+ /// 回复用户 ID
+ @JsonView(ResponseView.Admin.class)
+ private String respondedBy;
+
+ /// 回复时间
+ @JsonView(ResponseView.Public.class)
+ private Long respondedAt;
+
+ /// 管理员回复文章
+ @Transient
+ @JsonView(ResponseView.Public.class)
+ private Article content;
+
+ /// 管理员回复文章
+ @Transient
+ @JsonView(ResponseView.Public.class)
+ private Article response;
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
index 6c5d57f..bed703e 100644
--- a/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/AttachmentMapper.java
@@ -32,7 +32,7 @@ public interface AttachmentMapper extends BaseMapper, RawMap
List selectByBizId(Attachment.BizType bizType, String bizId, List attachTypes, Page page);
- List selectByBizIdList(Attachment.BizType bizType, Collection bizIdList, List attachTypes);
+ List selectAllByBizIdList(Attachment.BizType bizType, Collection bizIdList, List attachTypes);
long countByBizId(Attachment.BizType bizType, String bizId, List attachTypes);
diff --git a/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java b/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java
new file mode 100644
index 0000000..d96bf9c
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/mapper/FeedbackMapper.java
@@ -0,0 +1,11 @@
+package com.imyeyu.api.modules.common.mapper;
+
+import com.imyeyu.api.modules.common.entity.Feedback;
+import com.imyeyu.spring.mapper.BaseMapper;
+
+/// 通用反馈 Mapper
+///
+/// @author Codex
+/// @since 2026-08-19
+public interface FeedbackMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java b/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java
new file mode 100644
index 0000000..0bc4524
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/FeedbackService.java
@@ -0,0 +1,11 @@
+package com.imyeyu.api.modules.common.service;
+
+import com.imyeyu.api.modules.common.entity.Feedback;
+import com.imyeyu.spring.service.BaseService;
+
+/// 通用反馈服务
+///
+/// @author Codex
+/// @since 2026-08-19
+public interface FeedbackService extends BaseService {
+}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
index 616b3b4..9f8b58a 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/ArticleServiceImplement.java
@@ -1,8 +1,15 @@
package com.imyeyu.api.modules.common.service.implement;
import com.imyeyu.api.modules.common.entity.Article;
+import com.imyeyu.api.modules.common.entity.Attachment;
import com.imyeyu.api.modules.common.mapper.ArticleMapper;
import com.imyeyu.api.modules.common.service.ArticleService;
+import com.imyeyu.api.modules.common.service.AttachmentService;
+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.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor;
@@ -20,10 +27,57 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class ArticleServiceImplement extends AbstractEntityService implements ArticleService {
+ private final LoggerService loggerService;
+ private final AttachmentService attachmentService;
+
private final ArticleMapper mapper;
@Override
protected BaseMapper mapper() {
return mapper;
}
+
+ @Override
+ public void create(Article article) {
+ Logger logger = new Logger(Logger.Module.COMMON, "COMMON_ARTICLE_CREATE");
+ try {
+ super.create(article);
+ attachmentService.updateByBizId(Attachment.BizType.ARTICLE, article.getId(), article.getAttachmentList());
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(article.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("create article error", e);
+ throw new TimiException(TimiCode.ERROR, "create article error", e);
+ } finally {
+ loggerService.create(logger);
+ }
+ }
+
+ @Override
+ public void update(Article article) {
+ Logger logger = new Logger(Logger.Module.COMMON, "COMMON_ARTICLE_UPDATE");
+ try {
+ super.update(article);
+ attachmentService.updateByBizId(Attachment.BizType.ARTICLE, article.getId(), article.getAttachmentList());
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(article.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("update article error", e);
+ throw new TimiException(TimiCode.ERROR, "update article error", e);
+ } finally {
+ loggerService.create(logger);
+ }
+ }
}
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java
index 701d4db..9bc93c2 100644
--- a/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/AttachmentServiceImplement.java
@@ -319,12 +319,12 @@ public class AttachmentServiceImplement extends AbstractEntityService listByBizId(Attachment.BizType bizType, String bizId, String... attachTypes) {
- return mapper.selectByBizId(bizType, bizId, List.of(attachTypes), null);
+ return mapper.selectAllByBizIdList(bizType, List.of(bizId), List.of(attachTypes));
}
@Override
public Map> mapByBizIdList(Attachment.BizType bizType, Collection bizIdList, String... attachTypes) {
- return mapper.selectByBizIdList(bizType, bizIdList, List.of(attachTypes)).stream().collect(Collectors.groupingBy(Attachment::getBizId));
+ return mapper.selectAllByBizIdList(bizType, bizIdList, List.of(attachTypes)).stream().collect(Collectors.groupingBy(Attachment::getBizId));
}
@Override
diff --git a/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java b/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java
new file mode 100644
index 0000000..5383458
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/common/service/implement/FeedbackServiceImplement.java
@@ -0,0 +1,141 @@
+package com.imyeyu.api.modules.common.service.implement;
+
+import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
+import com.imyeyu.api.modules.common.entity.Feedback;
+import com.imyeyu.api.modules.common.mapper.FeedbackMapper;
+import com.imyeyu.api.modules.common.service.ArticleService;
+import com.imyeyu.api.modules.common.service.AttachmentService;
+import com.imyeyu.api.modules.common.service.FeedbackService;
+import com.imyeyu.api.modules.system.entity.Logger;
+import com.imyeyu.api.modules.system.service.LoggerService;
+import com.imyeyu.api.modules.user.service.UserLoginService;
+import com.imyeyu.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 lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/// 通用反馈服务实现
+///
+/// @author Codex
+/// @since 2026-08-19
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class FeedbackServiceImplement extends AbstractEntityService implements FeedbackService {
+
+ private static final long ANONYMOUS_ATTACHMENT_MAX_SIZE = 500L * 1024;
+
+ private final LoggerService loggerService;
+ private final ArticleService articleService;
+ private final UserLoginService userLoginService;
+ private final AttachmentService attachmentService;
+
+ private final FeedbackMapper mapper;
+
+ @Override
+ protected BaseMapper mapper() {
+ return mapper;
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void create(Feedback feedback) {
+ Logger logger = new Logger(Logger.Module.COMMON, "COMMON_FEEDBACK_CREATE");
+ try {
+ TimiException.required(feedback, "未找到反馈");
+ articleService.create(feedback.getContent());
+ feedback.setContentId(feedback.getContent().getId());
+ feedback.setStatus(TimiJava.defaultIfNull(feedback.getStatus(), Feedback.Status.PENDING));
+ super.create(feedback);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(feedback.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("create feedback error", e);
+ throw new TimiException(TimiCode.ERROR, "create feedback error", e);
+ } finally {
+ loggerService.create(logger);
+ }
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void update(Feedback feedback) {
+ Logger logger = new Logger(Logger.Module.COMMON, "COMMON_FEEDBACK_UPDATE");
+ try {
+ TimiException.required(feedback, "未找到反馈");
+ TimiException.required(feedback.getId(), "未找到反馈 ID");
+ Feedback dbFeedback = get(feedback.getId());
+ TimiException.required(dbFeedback, "未找到反馈");
+
+ feedback.getContent().setId(dbFeedback.getContentId());
+ articleService.update(feedback.getContent());
+
+ dbFeedback.setModule(TimiJava.defaultIfNull(feedback.getModule(), dbFeedback.getModule()));
+ if (TimiJava.isNotEmpty(feedback.getResponse())) {
+ if (TimiJava.isEmpty(feedback.getResponseId())) {
+ articleService.create(feedback.getResponse());
+ dbFeedback.setResponseId(feedback.getResponse().getId());
+ dbFeedback.setRespondedAt(Time.now());
+ } else {
+ feedback.getResponse().setId(dbFeedback.getResponseId());
+ articleService.update(feedback.getResponse());
+ }
+ dbFeedback.setStatus(Feedback.Status.REPLIED);
+ dbFeedback.setRespondedBy(userLoginService.getRequireLoginUserId());
+ }
+ super.update(dbFeedback);
+ logger.setLevel(Logger.Level.INFO);
+ logger.setResult(feedback.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("修改反馈失败", e);
+ throw new TimiException(TimiCode.ERROR, "修改反馈失败", e);
+ } finally {
+ loggerService.create(logger);
+ }
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void delete(String id) {
+ Logger logger = new Logger(Logger.Module.COMMON, "COMMON_FEEDBACK_DELETE");
+ try {
+ Feedback feedback = get(id);
+ articleService.delete(feedback.getContentId());
+ if (TimiJava.isNotEmpty(feedback.getResponseId())) {
+ articleService.delete(feedback.getResponseId());
+ }
+ 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("delete feedback error", e);
+ throw new TimiException(TimiCode.ERROR, "delete feedback error", e);
+ } finally {
+ loggerService.create(logger);
+ }
+ }
+}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java
index 02c6e9d..e786ca1 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoPointRuleController.java
@@ -21,6 +21,8 @@ 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
@@ -78,6 +80,19 @@ public class GaoPointRuleController {
ruleService.update(rule);
}
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.POINT_RULE_UPDATE)
+ @PostMapping("/rule/sort")
+ public void sort(@RequestBody List idList) {
+ if (idList != null) {
+ for (GaoPointRule rule : ruleService.listByIdList(idList)) {
+ checkStore(rule.getStoreId());
+ }
+ }
+ ruleService.sort(idList);
+ }
+
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.POINT_RULE_DELETE)
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java
index 88a004e..b68c4b0 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java
@@ -7,6 +7,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoEventRecordNumberValueDailyStatView;
import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq;
import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.RequestRateLimit;
@@ -57,6 +58,15 @@ public class GaoStoreChartController {
return service.eventRecordDailyStat(req);
}
+ /// 查询登记事件每日数值合计
+ @AOPLog
+ @RequestRateLimit
+ @RequireGaoPermission(GaoPermissionCode.STAT_READ)
+ @PostMapping("/event/record/number-value/daily")
+ public List eventRecordNumberValueDaily(@RequestBody GaoStoreChartReq req) {
+ return service.eventRecordNumberValueDailyStat(req);
+ }
+
/// 查询登记事件日历记录
@AOPLog
@RequestRateLimit
diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
index 65570ba..ced8d8b 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoUserController.java
@@ -123,7 +123,9 @@ public class GaoUserController {
{
User user = userService.get(result.getUserId());
user.setAttachmentList(attachmentService.listByBizId(Attachment.BizType.USER, user.getId()));
+ user.setRoleList(userRoleService.listAllRoleCodeByUserId(user.getId()));
user.setRoleEntityList(userRoleService.listRoleByUserId(user.getId()));
+ user.setPermissionList(userRoleService.listAllPermissionCodeByUserId(user.getId()));
result.setUser(user);
}
return result;
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
index 36573a3..d4613fc 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoCustomer.java
@@ -30,7 +30,11 @@ public class GaoCustomer extends UUIDEntity {
/// @since 2026-07-27
public enum AttachType {
- PHOTO
+ PHOTO,
+
+ POINT_CARD,
+
+ OTHER
}
///
diff --git a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
index 1e63444..00ab9ab 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/entity/GaoPointLedger.java
@@ -17,7 +17,7 @@ public class GaoPointLedger extends UUIDEntity {
/// 流水变化类型
public enum ChangeType {
- /// 登记事件获得积分
+ /// 登记事件积分变化
EARN,
/// 删除登记记录撤销积分
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
index 07d79d4..5e7a644 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoEventRecordMapper.java
@@ -5,6 +5,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq;
import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoEventRecordNumberValueDailyStatView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordPage;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordRankPage;
import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq;
@@ -88,6 +89,9 @@ public interface GaoEventRecordMapper extends BaseMapper
/// @return 每日登记数
List stateDailyByRange(GaoStoreChartReq req);
+ /// 按时间范围和事件统计每日登记数值合计
+ List stateNumberValueDailyByRange(GaoStoreChartReq req);
+
/// 按时间范围查询登记日历记录
///
/// @param req 门店报表查询请求
diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
index 15a5807..97a1fe1 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoPointRuleMapper.java
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.entity.GaoPointRule;
import com.imyeyu.spring.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
+import java.util.Collection;
import java.util.List;
/// GAO 客户积分规则 Mapper
@@ -12,6 +13,8 @@ import java.util.List;
/// @since 2026-08-11
public interface GaoPointRuleMapper extends BaseMapper {
+ List selectByIdList(Collection idList);
+
/// 查询当前时间可匹配的启用规则
///
/// @param storeId 门店 ID
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java
index 5dbc7ff..7101126 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoPointRuleService.java
@@ -19,6 +19,17 @@ public interface GaoPointRuleService extends BaseService {
/// @return 规则列表
List listActive(String storeId, String eventId, long now);
+ /// 查询规则 ID 列表
+ ///
+ /// @param idList 规则 ID 列表
+ /// @return 规则列表
+ List listByIdList(List idList);
+
+ /// 按客户端提交的顺序更新启用规则优先级
+ ///
+ /// @param idList 规则 ID 列表
+ void sort(List idList);
+
/// 创建积分规则
///
/// @param rule 积分规则
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java
index da556d5..cc2e6e1 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java
@@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoEventRecordNumberValueDailyStatView;
import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq;
import java.util.List;
@@ -23,6 +24,9 @@ public interface GaoStoreChartService {
/// 查询登记事件每日统计
List eventRecordDailyStat(GaoStoreChartReq req);
+ /// 查询登记事件每日数值合计
+ List eventRecordNumberValueDailyStat(GaoStoreChartReq req);
+
/// 查询登记事件日历记录
List eventRecordCalendar(GaoStoreChartReq req);
}
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
index 14afa78..3c68083 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoEventRecordServiceImplement.java
@@ -10,9 +10,9 @@ import com.imyeyu.api.modules.gao.entity.GaoEvent;
import com.imyeyu.api.modules.gao.entity.GaoEventRecord;
import com.imyeyu.api.modules.gao.mapper.GaoEventRecordMapper;
import com.imyeyu.api.modules.gao.service.GaoCustomerService;
-import com.imyeyu.api.modules.gao.service.GaoPointTriggerService;
import com.imyeyu.api.modules.gao.service.GaoEventRecordService;
import com.imyeyu.api.modules.gao.service.GaoEventService;
+import com.imyeyu.api.modules.gao.service.GaoPointTriggerService;
import com.imyeyu.api.modules.gao.service.GaoStoreBusinessDayService;
import com.imyeyu.api.modules.gao.util.EventRecordXSSFBuilder;
import com.imyeyu.api.modules.gao.vo.GaoCustomerEventRankView;
@@ -321,7 +321,7 @@ public class GaoEventRecordServiceImplement extends AbstractEntityService listByIdList(List idList) {
+ if (TimiJava.isEmpty(idList)) {
+ return List.of();
+ }
+ return mapper.selectByIdList(new LinkedHashSet<>(idList));
+ }
+
+ @Transactional(TimiServerDBConfig.ROLLBACKER)
+ @Override
+ public void sort(List idList) {
+ if (TimiJava.isEmpty(idList)) {
+ return;
+ }
+ List distinctIdList = new ArrayList<>(new LinkedHashSet<>(idList));
+ Map ruleMap = mapper.selectByIdList(new LinkedHashSet<>(distinctIdList)).stream()
+ .collect(Collectors.toMap(GaoPointRule::getId, Function.identity()));
+ TimiException.requiredTrue(ruleMap.size() == distinctIdList.size(), "积分规则列表包含不存在的规则");
+ for (int i = 0; i < distinctIdList.size(); i++) {
+ GaoPointRule rule = ruleMap.get(distinctIdList.get(i));
+ TimiException.requiredTrue(rule.getStatus() == GaoPointRule.Status.ACTIVE, "只能调整启用规则排序");
+ rule.setPriority(i);
+ mapper.update(rule);
+ }
+ }
+
@Transactional(TimiServerDBConfig.ROLLBACKER)
@Override
public void create(GaoPointRule rule) {
@@ -127,7 +158,7 @@ public class GaoPointRuleServiceImplement extends AbstractEntityService ruleList = ruleService.listActive(record.getStoreId(), record.getEventId(), Time.now());
Long count = null;
- long earnedPointValue = 0L;
+ long pointDeltaTotal = 0L;
for (GaoPointRule rule : ruleList) {
RuleResolution resolution = resolveRule(record, rule, count);
if (resolution == null) {
@@ -72,16 +72,16 @@ public class GaoPointTriggerServiceImplement implements GaoPointTriggerService {
count = count == null ? count(record) : count;
}
long pointDelta = resolution.pointDelta();
- if (0 < pointDelta) {
+ if (pointDelta != 0) {
ledgerService.earn(record, rule, pointDelta, resolution.sourcePayload());
- earnedPointValue = Math.addExact(earnedPointValue, pointDelta);
+ pointDeltaTotal = Math.addExact(pointDeltaTotal, pointDelta);
}
if (Boolean.TRUE.equals(rule.getExclusive())) {
break;
}
}
- record.setEarnedPointValue(earnedPointValue);
- eventRecordMapper.updateEarnedPointValue(record.getId(), earnedPointValue);
+ record.setEarnedPointValue(pointDeltaTotal);
+ eventRecordMapper.updateEarnedPointValue(record.getId(), pointDeltaTotal);
}
@Transactional(TimiServerDBConfig.ROLLBACKER)
@@ -140,7 +140,7 @@ public class GaoPointTriggerServiceImplement implements GaoPointTriggerService {
}
permissionChecker.checkAny(ModuleCode.GAO, GaoPermissionCode.POINT_SOURCE_VALUE.getValue());
long pointValue = record.getPointValue();
- TimiException.requiredTrue(0 < pointValue, "pointValue invalid");
+ TimiException.requiredTrue(pointValue != 0, "pointValue invalid");
JsonNode condition = rule.getConditionJson();
if (condition != null) {
JsonNode min = condition.get("min");
diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java
index 00f02ff..f25e073 100644
--- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java
+++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java
@@ -8,6 +8,7 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView;
import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView;
+import com.imyeyu.api.modules.gao.vo.GaoEventRecordNumberValueDailyStatView;
import com.imyeyu.api.modules.gao.vo.GaoStoreChartReq;
import com.imyeyu.java.bean.timi.TimiException;
import lombok.RequiredArgsConstructor;
@@ -48,6 +49,12 @@ public class GaoStoreChartServiceImplement implements GaoStoreChartService {
return eventRecordMapper.stateDailyByRange(req);
}
+ @Override
+ public List eventRecordNumberValueDailyStat(GaoStoreChartReq req) {
+ checkRangeReq(req);
+ return eventRecordMapper.stateNumberValueDailyByRange(req);
+ }
+
@Override
public List eventRecordCalendar(GaoStoreChartReq req) {
checkRangeReq(req);
diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordNumberValueDailyStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordNumberValueDailyStatView.java
new file mode 100644
index 0000000..6912627
--- /dev/null
+++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoEventRecordNumberValueDailyStatView.java
@@ -0,0 +1,17 @@
+package com.imyeyu.api.modules.gao.vo;
+
+import lombok.Data;
+
+/// 登记事件数值每日统计视图
+@Data
+public class GaoEventRecordNumberValueDailyStatView {
+
+ /// 登记事件 ID
+ private String eventId;
+
+ /// 日期值,格式为 yyyyMMdd
+ private Integer dateValue;
+
+ /// 当日登记数值合计
+ private Long dailyNumberValue;
+}
diff --git a/src/main/resources/db/migration/timiserver/V33__create_feedback_table.sql b/src/main/resources/db/migration/timiserver/V33__create_feedback_table.sql
new file mode 100644
index 0000000..041c696
--- /dev/null
+++ b/src/main/resources/db/migration/timiserver/V33__create_feedback_table.sql
@@ -0,0 +1,21 @@
+CREATE TABLE `feedback` (
+ `id` CHAR(36) NOT NULL COMMENT '主键',
+ `module` VARCHAR(32) NOT NULL COMMENT '所属模块',
+ `user_id` CHAR(36) NULL COMMENT '提交用户 ID,匿名反馈为空',
+ `content_id` VARCHAR(36) NOT NULL COMMENT '反馈内容文章 ID',
+ `contact` VARCHAR(255) NULL COMMENT '联系方式',
+ `ip` VARCHAR(64) NULL COMMENT '提交 IP',
+ `status` VARCHAR(16) NOT NULL DEFAULT 'PENDING' COMMENT '处理状态',
+ `response_id` VARCHAR(36) NULL COMMENT '管理员回复文章 ID',
+ `responded_by` CHAR(36) NULL COMMENT '回复用户 ID',
+ `responded_at` BIGINT NULL COMMENT '回复时间',
+ `created_at` BIGINT NOT NULL COMMENT '创建时间',
+ `updated_at` BIGINT NOT NULL COMMENT '更新时间',
+ `deleted_at` BIGINT NULL COMMENT '删除时间',
+ PRIMARY KEY (`id`),
+ KEY `idx_feedback_module_status_created` (`module`, `status`, `created_at`),
+ KEY `idx_feedback_user_created` (`user_id`, `created_at`),
+ KEY `idx_feedback_content_id` (`content_id`),
+ KEY `idx_feedback_response_id` (`response_id`),
+ KEY `idx_feedback_deleted_at` (`deleted_at`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '通用反馈';
diff --git a/src/main/resources/mapper/timi-server/common/AttachmentMapper.xml b/src/main/resources/mapper/timi-server/common/AttachmentMapper.xml
index 65bbae8..2910e83 100644
--- a/src/main/resources/mapper/timi-server/common/AttachmentMapper.xml
+++ b/src/main/resources/mapper/timi-server/common/AttachmentMapper.xml
@@ -1,7 +1,7 @@
-