ソースを参照

Merge branch 'dev_20260630' into dev_20260630_cxf

chenxiaofei 1 週間 前
コミット
ade081db76
30 ファイル変更1611 行追加4 行削除
  1. 3 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/ProxyContractDetailResp.java
  2. 4 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/ProxyContractListResp.java
  3. 12 1
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/impl/KwcContractProxyServiceImpl.java
  4. 223 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsComplaintController.java
  5. 44 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/app/ComplaintsController.java
  6. 13 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintAttachmentMapper.java
  7. 10 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintHandleLogMapper.java
  8. 13 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintMapper.java
  9. 158 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaint.java
  10. 96 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintAttachment.java
  11. 56 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintHandleLog.java
  12. 34 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/pojo/ChannelEnum.java
  13. 24 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppListReqVo.java
  14. 53 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppSubmitReqVo.java
  15. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintManagePageReqVo.java
  16. 26 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintProcessReqVo.java
  17. 35 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppDetailResVo.java
  18. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppItemResVo.java
  19. 34 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAttachmentResVo.java
  20. 46 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintDetailResVo.java
  21. 42 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintHandleLogResVo.java
  22. 48 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintManageItemResVo.java
  23. 23 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintTypeOptionResVo.java
  24. 429 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsComplaintService.java
  25. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintAttachmentService.java
  26. 11 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintHandleLogService.java
  27. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintService.java
  28. 3 3
      sckw-modules/sckw-system/src/main/resources/bootstrap.yml
  29. 25 0
      sckw-modules/sckw-system/src/main/resources/mapper/TComplaintAttachmentMapper.xml
  30. 30 0
      sckw-modules/sckw-system/src/main/resources/mapper/TComplaintMapper.xml

+ 3 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/ProxyContractDetailResp.java

@@ -60,6 +60,9 @@ public class ProxyContractDetailResp implements Serializable {
     @Schema(description = "创建人")
     private Long createBy;
 
+    @Schema(description = "创建人名称")
+    private String createByName;
+
     @Schema(description = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;

+ 4 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/ProxyContractListResp.java

@@ -22,6 +22,7 @@ public class ProxyContractListResp implements Serializable {
 
     @Schema(description = "合同名称")
     private String name;
+    private String entName;
 
     @Schema(description = "供应单位ID")
     private Long supplyId;
@@ -62,6 +63,9 @@ public class ProxyContractListResp implements Serializable {
     @Schema(description = "创建人")
     private Long createBy;
 
+    @Schema(description = "创建人名称")
+    private String createByName;
+
     @Schema(description = "创建时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime createTime;

+ 12 - 1
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/impl/KwcContractProxyServiceImpl.java

@@ -21,6 +21,7 @@ import com.sckw.core.web.response.result.PageDataResult;
 import com.sckw.product.api.dubbo.GoodsInfoService;
 import com.sckw.system.api.RemoteSystemService;
 import com.sckw.system.api.model.dto.res.EntCacheResDto;
+import com.sckw.system.api.model.dto.res.UserCacheResDto;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.dubbo.config.annotation.DubboReference;
@@ -65,10 +66,16 @@ public class KwcContractProxyServiceImpl implements KwcContractProxyService {
         Map<Long, KwcContractProxyUnit> supplyUnitMap = allUnits.stream().filter(u -> Objects.equals(u.getUnitType(), 1)).collect(Collectors.toMap(KwcContractProxyUnit::getContractId, Function.identity(), (a, b) -> a));
 
         Map<Long, KwcContractProxyUnit> proxyUnitMap = allUnits.stream().filter(u -> Objects.equals(u.getUnitType(), 2)).collect(Collectors.toMap(KwcContractProxyUnit::getContractId, Function.identity(), (a, b) -> a));
+        List<Long> ents = records.stream().map(KwcContractProxy::getEntId).distinct().toList();
+        Map<Long, EntCacheResDto> longEntCacheResDtoMap = remoteSystemService.queryEntCacheMapByIds(ents);
+        List<Long> createByIds = records.stream().map(KwcContractProxy::getCreateBy).filter(Objects::nonNull).distinct().toList();
+        Map<Long, UserCacheResDto> userMap = remoteSystemService.queryUserCacheMapByIds(createByIds);
 
         List<ProxyContractListResp> list = records.stream().map(r -> {
             ProxyContractListResp resp = new ProxyContractListResp();
+            EntCacheResDto entCacheResDto = longEntCacheResDtoMap.get(r.getEntId());
             resp.setId(r.getId());
+            resp.setEntName(Objects.nonNull(entCacheResDto) ? entCacheResDto.getFirmName() : "");
             resp.setContractNo(r.getContractNo());
             resp.setName(r.getName());
             resp.setSupplyId(r.getSupplyId());
@@ -81,6 +88,8 @@ public class KwcContractProxyServiceImpl implements KwcContractProxyService {
             resp.setStatus(r.getStatus());
             resp.setStatusDesc(ProxyStatusEnum.getLabel(r.getStatus()));
             resp.setCreateBy(r.getCreateBy());
+            UserCacheResDto userCacheResDto = userMap.get(r.getCreateBy());
+            resp.setCreateByName(Objects.nonNull(userCacheResDto) ? userCacheResDto.getName() : "");
             resp.setCreateTime(r.getCreateTime());
 
             KwcContractProxyUnit supplyUnit = supplyUnitMap.get(r.getId());
@@ -119,6 +128,8 @@ public class KwcContractProxyServiceImpl implements KwcContractProxyService {
         resp.setRemark(proxy.getRemark());
         resp.setStatus(proxy.getStatus());
         resp.setCreateBy(proxy.getCreateBy());
+        UserCacheResDto userCacheResDto = remoteSystemService.queryUserCacheById(proxy.getCreateBy());
+        resp.setCreateByName(Objects.nonNull(userCacheResDto) ? userCacheResDto.getName() : "");
         resp.setCreateTime(proxy.getCreateTime());
         resp.setUpdateBy(proxy.getUpdateBy());
         resp.setUpdateTime(proxy.getUpdateTime());
@@ -221,7 +232,7 @@ public class KwcContractProxyServiceImpl implements KwcContractProxyService {
         if (Objects.equals(auditStatus, 1)) {
             List<KwcContractProxyGoods> kwcContractProxyGoods = proxyGoodsRepository.queryByContractId(proxy.getId());
             List<Long> list = kwcContractProxyGoods.stream().map(KwcContractProxyGoods::getGoodsId).toList();
-            goodsInfoService.insertProxyGoods(list,proxy.getProxyId());
+            goodsInfoService.insertProxyGoods(list, proxy.getProxyId());
         }
         return Boolean.TRUE;
     }

+ 223 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsComplaintController.java

@@ -0,0 +1,223 @@
+package com.sckw.system.controller;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.vo.req.ComplaintManagePageReqVo;
+import com.sckw.system.model.vo.req.ComplaintProcessReqVo;
+import com.sckw.system.model.vo.req.ComplaintAppSubmitReqVo;
+import com.sckw.system.model.vo.res.ComplaintDetailResVo;
+import com.sckw.system.model.vo.res.ComplaintManageItemResVo;
+import com.sckw.system.service.KwsComplaintService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/kwsComplaint")
+@Tag(name = "投诉管理(PC)")
+@RequiredArgsConstructor
+public class KwsComplaintController {
+
+    private final KwsComplaintService kwsComplaintService;
+
+    @PostMapping("/page")
+    @Operation(summary = "投诉分页查询", description = "PC端投诉管理列表分页查询")
+    public BaseResult<PageDataResult<ComplaintManageItemResVo>> page(@RequestBody ComplaintManagePageReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.manageComplaintPage(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "投诉详情", description = "PC端投诉详情(包含附件、处理记录)")
+    public BaseResult<ComplaintDetailResVo> detail(@RequestParam("id") Long id) {
+        return BaseResult.success(kwsComplaintService.manageComplaintDetail(id));
+    }
+
+    @PostMapping("/process")
+    @Operation(summary = "处理投诉", description = "PC端处理投诉,填写处理回复并更新状态")
+    public BaseResult<Boolean> process(@RequestBody @Valid ComplaintProcessReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.processComplaint(reqVo));
+    }
+
+    @GetMapping(value = "/scan", produces = "text/html;charset=UTF-8")
+    @Operation(summary = "h5投诉页面", description = "扫码打开h5页面")
+    public String scanPage() {
+        return """
+                <!doctype html>
+                <html lang="zh-CN">
+                <head>
+                  <meta charset="utf-8" />
+                  <meta name="viewport" content="width=device-width, initial-scale=1" />
+                  <title>提交投诉</title>
+                  <style>
+                    :root { --primary:#1677ff; --bg:#f5f7fb; --card:#fff; --text:#1f2329; --muted:#646a73; --border:#e5e6eb; }
+                    *{ box-sizing:border-box; }
+                    body{ margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue","PingFang SC","Microsoft YaHei",Arial; background:var(--bg); color:var(--text); }
+                    .wrap{ max-width:560px; margin:0 auto; padding:18px 14px 28px; }
+                    .card{ background:var(--card); border:1px solid var(--border); border-radius:12px; padding:16px; }
+                    h1{ font-size:18px; margin:0 0 14px; }
+                    .field{ margin:12px 0; }
+                    .label{ font-size:13px; color:var(--muted); margin:0 0 6px; }
+                    .req{ color:#f53f3f; margin-left:4px; }
+                    select,input,textarea{ width:100%; border:1px solid var(--border); border-radius:10px; padding:12px; font-size:14px; outline:none; background:#fff; }
+                    textarea{ min-height:110px; resize:vertical; }
+                    .upload{ border:1px dashed var(--border); border-radius:10px; padding:14px; text-align:center; color:var(--muted); }
+                    .upload input{ padding:10px; }
+                    .row{ display:flex; gap:12px; }
+                    .row .btn{ flex:1; }
+                    .btn{ border-radius:10px; padding:12px; font-size:14px; border:1px solid var(--border); background:#fff; cursor:pointer; }
+                    .btn.primary{ background:var(--primary); color:#fff; border-color:var(--primary); }
+                    .hint{ font-size:12px; color:var(--muted); margin-top:8px; }
+                    .status{ font-size:12px; color:var(--muted); margin-top:8px; min-height:16px; }
+                    .preview{ margin-top:10px; display:none; }
+                    .preview img{ width:100%; border-radius:10px; border:1px solid var(--border); }
+                  </style>
+                </head>
+                <body>
+                <div class="wrap">
+                  <div class="card">
+                    <h1>提交投诉</h1>
+                    <div class="field">
+                      <div class="label">投诉类型<span class="req">*</span></div>
+                      <select id="typeCode">
+                        <option value="1">违规</option>
+                        <option value="2" selected>服务态度</option>
+                        <option value="3">其他</option>
+                      </select>
+                    </div>
+                    <div class="field">
+                      <div class="label">投诉对象<span class="req">*</span></div>
+                      <input id="targetName" placeholder="请输入投诉对象姓名" />
+                    </div>
+                    <div class="field">
+                      <div class="label">投诉内容<span class="req">*</span></div>
+                      <textarea id="content" placeholder="请详细描述投诉内容"></textarea>
+                    </div>
+                    <div class="field">
+                      <div class="label">发生时间<span class="req">*</span></div>
+                      <input id="occurTime" type="datetime-local" />
+                    </div>
+                    <div class="field">
+                      <div class="label">上传图片</div>
+                      <div class="upload">
+                        <input id="file" type="file" accept="image/*" />
+                        <div class="hint">支持上传1张图片</div>
+                        <div class="status" id="uploadStatus"></div>
+                        <div class="preview" id="preview"><img id="previewImg" alt="预览" /></div>
+                      </div>
+                    </div>
+                    <div class="row">
+                      <button class="btn" id="resetBtn" type="button">取消</button>
+                      <button class="btn primary" id="submitBtn" type="button">提交投诉</button>
+                    </div>
+                  </div>
+                </div>
+                <script>
+                  const state = { fileUrl: "", fileName: "", fileType: "", fileSize: 0 };
+                  function pad(n){ return n<10 ? "0"+n : ""+n; }
+                  function initDate(){
+                    const d = new Date();
+                    const v = d.getFullYear()+"-"+pad(d.getMonth()+1)+"-"+pad(d.getDate())+" "+pad(d.getHours())+":"+pad(d.getMinutes());
+                    document.getElementById("occurTime").value = v;
+                  }
+                  initDate();
+                
+                  async function uploadFile(file){
+                    const statusEl = document.getElementById("uploadStatus");
+                    statusEl.textContent = "上传中...";
+                    const fd = new FormData();
+                    fd.append("file", file);
+                    const resp = await fetch("/sckw-ng-file/file/appUploadFileInfo", { method:"POST", body: fd });
+                    const json = await resp.json();
+                    if (!(json && (json.code === 200 || json.code === 60200))) {
+                      throw new Error((json && json.msg) ? json.msg : "上传失败");
+                    }
+                    const data = json.data || {};
+                    const filePath = data.fileAbsolutePath || "";
+                    if (!filePath) {
+                      throw new Error("上传成功但未返回文件路径");
+                    }
+                    state.fileUrl = filePath;
+                    state.fileName = file.name || (data.fileOriginalName || "");
+                    state.fileType = file.type || "";
+                    state.fileSize = file.size || 0;
+                    statusEl.textContent = "上传成功";
+                    const preview = document.getElementById("preview");
+                    const img = document.getElementById("previewImg");
+                    img.src = filePath.startsWith("http") ? filePath : (location.origin + "/" + filePath.replace(/^\\/+/, ""));
+                    preview.style.display = "block";
+                  }
+                
+                  document.getElementById("file").addEventListener("change", async (e) => {
+                    const file = e.target.files && e.target.files[0];
+                    if (!file) return;
+                    try {
+                      await uploadFile(file);
+                    } catch (err) {
+                      state.fileUrl = "";
+                      document.getElementById("uploadStatus").textContent = err && err.message ? err.message : "上传失败";
+                      document.getElementById("preview").style.display = "none";
+                    }
+                  });
+                
+                  document.getElementById("resetBtn").addEventListener("click", () => {
+                    document.getElementById("typeCode").value = "1";
+                    document.getElementById("targetName").value = "";
+                    document.getElementById("content").value = "";
+                    initDate();
+                    document.getElementById("file").value = "";
+                    document.getElementById("uploadStatus").textContent = "";
+                    document.getElementById("preview").style.display = "none";
+                    state.fileUrl = ""; state.fileName=""; state.fileType=""; state.fileSize=0;
+                  });
+                
+                  document.getElementById("submitBtn").addEventListener("click", async () => {
+                    const typeCode = parseInt(document.getElementById("typeCode").value, 10);
+                    const targetName = (document.getElementById("targetName").value || "").trim();
+                    const content = (document.getElementById("content").value || "").trim();
+                    const occurTime = document.getElementById("occurTime").value;
+                    if (!typeCode) { alert("请选择投诉类型"); return; }
+                    if (!targetName) { alert("请输入投诉对象"); return; }
+                    if (!content) { alert("请输入投诉内容"); return; }
+                    if (!occurTime) { alert("请选择发生时间"); return; }
+                
+                    const body = {
+                      typeCode,
+                      targetName,
+                      content,
+                      occurTime: occurTime.length === 16 ? (occurTime.replace('T', ' ') + ":00") : occurTime.replace('T', ' '),
+                      isAnonymous: true,
+                      fileUrl: state.fileUrl || "",
+                      fileName: state.fileName || "",
+                      fileType: state.fileType || "",
+                      fileSize: state.fileSize || 0
+                    };
+                
+                    const resp = await fetch("/sckw-ng-system/kwsComplaint/scan/submit", {
+                      method:"POST",
+                      headers: { "Content-Type":"application/json" },
+                      body: JSON.stringify(body)
+                    });
+                    const json = await resp.json();
+                    if (json && json.code === 60200) {
+                      alert("提交成功");
+                      document.getElementById("resetBtn").click();
+                      return;
+                    }
+                    alert((json && (json.message || json.msg)) ? (json.message || json.msg) : "提交失败");
+                  });
+                </script>
+                </body>
+                </html>
+                """;
+    }
+
+    @PostMapping("/scan/submit")
+    @Operation(summary = "h5投诉接口", description = "h5投诉接口")
+    public BaseResult<ComplaintDetailResVo> scanSubmit(@RequestBody @Valid ComplaintAppSubmitReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.submitScanComplaint(reqVo));
+    }
+
+
+}

+ 44 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/app/ComplaintsController.java

@@ -0,0 +1,44 @@
+package com.sckw.system.controller.app;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.vo.req.ComplaintAppListReqVo;
+import com.sckw.system.model.vo.req.ComplaintAppSubmitReqVo;
+import com.sckw.system.model.vo.res.ComplaintAppDetailResVo;
+import com.sckw.system.model.vo.res.ComplaintAppItemResVo;
+import com.sckw.system.model.vo.res.ComplaintDetailResVo;
+import com.sckw.system.service.KwsComplaintService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/complaint")
+@Tag(name = "投诉相关接口(App)")
+@RequiredArgsConstructor
+public class ComplaintsController {
+
+    private final KwsComplaintService kwsComplaintService;
+
+    @PostMapping("/submit")
+    @Operation(summary = "提交投诉", description = "司机提交投诉,可上传单张图片作为证据")
+    public BaseResult<ComplaintDetailResVo> submit(@RequestBody @Valid ComplaintAppSubmitReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.submitAppComplaint(reqVo));
+    }
+
+    @PostMapping("/list")
+    @Operation(summary = "投诉列表", description = "分页查询当前用户投诉列表,支持按状态筛选")
+    public BaseResult<PageDataResult<ComplaintAppItemResVo>> list(@RequestBody @Valid ComplaintAppListReqVo reqVo) {
+        return BaseResult.success(kwsComplaintService.appComplaintPage(reqVo));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "投诉详情", description = "根据投诉ID查询详情")
+    public BaseResult<ComplaintAppDetailResVo> detail(@RequestParam("id") Long id) {
+        return BaseResult.success(kwsComplaintService.appComplaintDetail(id));
+    }
+
+}
+

+ 13 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintAttachmentMapper.java

@@ -0,0 +1,13 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaintAttachment;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Mapper
+public interface TComplaintAttachmentMapper extends BaseMapper<TComplaintAttachment> {
+}

+ 10 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintHandleLogMapper.java

@@ -0,0 +1,10 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaintHandleLog;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface TComplaintHandleLogMapper extends BaseMapper<TComplaintHandleLog> {
+}
+

+ 13 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/TComplaintMapper.java

@@ -0,0 +1,13 @@
+package com.sckw.system.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sckw.system.model.TComplaint;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Mapper
+public interface TComplaintMapper extends BaseMapper<TComplaint> {
+}

+ 158 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaint.java

@@ -0,0 +1,158 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 投诉主表
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Schema(description="投诉主表")
+@Data
+@TableName(value = "t_complaint")
+public class TComplaint {
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description="")
+    private Long id;
+
+    /**
+     * 投诉编号,如CT20260603001
+     */
+    @TableField(value = "complaint_no")
+    @Schema(description="投诉编号,如CT20260603001")
+    private String complaintNo;
+
+    /**
+     * 来源渠道:1司机App 2扫码
+     */
+    @TableField(value = "channel")
+    @Schema(description="来源渠道:1司机App 2扫码")
+    private Integer channel;
+
+    /**
+     * 投诉类型编码
+     */
+    @TableField(value = "type_code")
+    @Schema(description="投诉类型编码")
+    private Integer typeCode;
+
+    /**
+     * 对象名称快照
+     */
+    @TableField(value = "target_name")
+    @Schema(description="对象名称快照")
+    private String targetName;
+
+
+    /**
+     * 投诉人ID(若有)
+     */
+    @TableField(value = "submitter_id")
+    @Schema(description="投诉人ID(若有)")
+    private Long submitterId;
+
+    /**
+     * 投诉人名称快照
+     */
+    @TableField(value = "submitter_name")
+    @Schema(description="投诉人名称快照")
+    private String submitterName;
+
+    /**
+     * 是否匿名:1是0否
+     */
+    @TableField(value = "is_anonymous")
+    @Schema(description="是否匿名:1是0否")
+    private Boolean isAnonymous;
+
+    /**
+     * 投诉内容
+     */
+    @TableField(value = "content")
+    @Schema(description="投诉内容")
+    private String content;
+
+    /**
+     * 处理状态:0待处理 1已处理
+     */
+    @TableField(value = "`status`")
+    @Schema(description="处理状态:0待处理 1已处理")
+    private Integer status;
+
+    /**
+     * 发生时间
+     */
+    @TableField(value = "occur_time")
+    @Schema(description="发生时间")
+    private LocalDateTime occurTime;
+
+    /**
+     * 处理完成时间
+     */
+    @TableField(value = "processed_at")
+    @Schema(description="处理完成时间")
+    private LocalDateTime processedAt;
+
+    /**
+     * 处理人ID(后台用户)
+     */
+    @TableField(value = "processor_id")
+    @Schema(description="处理人ID(后台用户)")
+    private Long processorId;
+
+    /**
+     * 处理人名称快照
+     */
+    @TableField(value = "processor_name")
+    @Schema(description="处理人名称快照")
+    private String processorName;
+
+    /**
+     * 处理回复(PC端“处理”填写)
+     */
+    @TableField(value = "process_reply")
+    @Schema(description="处理回复(PC端“处理”填写)")
+    private String processReply;
+
+    /**
+     * 创建人
+     */
+    @TableField(value = "create_by")
+    @Schema(description="创建人")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time")
+    @Schema(description="创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    @TableField(value = "update_by")
+    @Schema(description="更新人")
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time")
+    @Schema(description="更新时间")
+    private LocalDateTime updateTime;
+
+    /**
+     * 删除标识(0正常/1删除)
+     */
+    @TableField(value = "del_flag")
+    @Schema(description="删除标识(0正常/1删除)")
+    private Integer delFlag;
+}

+ 96 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintAttachment.java

@@ -0,0 +1,96 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.time.LocalDateTime;
+import lombok.Data;
+
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+/**
+ * 投诉附件表
+ */
+@Schema(description="投诉附件表")
+@Data
+@TableName(value = "t_complaint_attachment")
+public class TComplaintAttachment {
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description="")
+    private Long id;
+
+    @TableField(value = "complaint_id")
+    @Schema(description="")
+    private Long complaintId;
+
+    /**
+     * 附件URL/对象存储地址
+     */
+    @TableField(value = "file_url")
+    @Schema(description="附件URL/对象存储地址")
+    private String fileUrl;
+
+    @TableField(value = "file_name")
+    @Schema(description="")
+    private String fileName;
+
+    /**
+     * image/jpeg 等
+     */
+    @TableField(value = "file_type")
+    @Schema(description="image/jpeg 等")
+    private String fileType;
+
+    /**
+     * 字节
+     */
+    @TableField(value = "file_size")
+    @Schema(description="字节")
+    private Long fileSize;
+
+    /**
+     * 排序,单张就固定1
+     */
+    @TableField(value = "sort_no")
+    @Schema(description="排序,单张就固定1")
+    private Integer sortNo;
+
+    /**
+     * 创建人
+     */
+    @TableField(value = "create_by")
+    @Schema(description="创建人")
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(value = "create_time")
+    @Schema(description="创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新人
+     */
+    @TableField(value = "update_by")
+    @Schema(description="更新人")
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField(value = "update_time")
+    @Schema(description="更新时间")
+    private LocalDateTime updateTime;
+
+    /**
+     * 删除标识(0正常/1删除)
+     */
+    @TableField(value = "del_flag")
+    @Schema(description="删除标识(0正常/1删除)")
+    private Integer delFlag;
+}

+ 56 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/TComplaintHandleLog.java

@@ -0,0 +1,56 @@
+package com.sckw.system.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "投诉处理记录")
+@Data
+@TableName(value = "t_complaint_handle_log")
+public class TComplaintHandleLog {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField(value = "complaint_id")
+    private Long complaintId;
+
+    @TableField(value = "action")
+    private String action;
+
+    @TableField(value = "from_status")
+    private Integer fromStatus;
+
+    @TableField(value = "to_status")
+    private Integer toStatus;
+
+    @TableField(value = "operator_id")
+    private Long operatorId;
+
+    @TableField(value = "operator_name")
+    private String operatorName;
+
+    @TableField(value = "remark")
+    private String remark;
+
+    @TableField(value = "create_by")
+    private Long createBy;
+
+    @TableField(value = "create_time")
+    private LocalDateTime createTime;
+
+    @TableField(value = "update_by")
+    private Long updateBy;
+
+    @TableField(value = "update_time")
+    private LocalDateTime updateTime;
+
+    @TableField(value = "del_flag")
+    private Integer delFlag;
+}
+

+ 34 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/pojo/ChannelEnum.java

@@ -0,0 +1,34 @@
+package com.sckw.system.model.pojo;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+
+@AllArgsConstructor
+@Getter
+public enum ChannelEnum {
+
+
+    APP(1, "APP"),
+    SCAN(2, "扫码");
+
+    private final Integer code;
+    private final String msg;
+
+
+    public static String getNameByCode(Integer code) {
+        ChannelEnum[] enums = ChannelEnum.values();
+        for (ChannelEnum instance : enums) {
+            if (Objects.equals(instance.getCode(), code)) {
+                return instance.getMsg();
+            }
+        }
+        return null;
+    }
+}

+ 24 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppListReqVo.java

@@ -0,0 +1,24 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "App投诉列表查询请求")
+public class ComplaintAppListReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "处理状态:0待处理 1已处理;不传/传-1表示全部", example = "0")
+    private Integer status = -1;
+
+    @Schema(description = "当前页码", example = "1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页数量", example = "10")
+    private Integer pageSize = 10;
+}

+ 53 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintAppSubmitReqVo.java

@@ -0,0 +1,53 @@
+package com.sckw.system.model.vo.req;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "App提交投诉请求")
+public class ComplaintAppSubmitReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "投诉类型不能为空")
+    @Schema(description = "投诉类型编码(字典值)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "发生时间", example = "10001")
+    @NotNull(message = "发生时间不能为空")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @NotBlank(message = "投诉对象不能为空")
+    @Schema(description = "投诉对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    private String targetName;
+
+    @Schema(description = "是否匿名:true/false", example = "true")
+    private Boolean isAnonymous = false;
+
+    @NotBlank(message = "投诉内容不能为空")
+    @Schema(description = "投诉内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String content;
+
+    @Schema(description = "附件URL(单张图片)", example = "https://xxx/1.jpg")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "1.jpg")
+    private String fileName;
+
+    @Schema(description = "附件类型", example = "image/jpeg")
+    private String fileType;
+
+    @Schema(description = "附件大小(字节)", example = "12345")
+    private Long fileSize;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintManagePageReqVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "PC投诉管理分页查询请求")
+public class ComplaintManagePageReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉编号(支持模糊)", example = "CT20260603")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "投诉人", example = "张三")
+    private String submitterKeyword;
+
+    @Schema(description = "开始日期(yyyy-MM-dd)", example = "2026-06-01")
+    private String startDate;
+
+    @Schema(description = "结束日期(yyyy-MM-dd)", example = "2026-06-06")
+    private String endDate;
+
+    @Schema(description = "处理状态:0待处理 1已处理;不传/传-1表示全部", example = "0")
+    private Integer status = -1;
+
+    @Schema(description = "当前页码", example = "1")
+    private Integer pageNum = 1;
+
+    @Schema(description = "每页数量", example = "10")
+    private Integer pageSize = 10;
+}

+ 26 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/ComplaintProcessReqVo.java

@@ -0,0 +1,26 @@
+package com.sckw.system.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "PC处理投诉请求")
+public class ComplaintProcessReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @NotNull(message = "投诉ID不能为空")
+    @Schema(description = "投诉ID", required = true, example = "1")
+    private Long id;
+
+    @NotBlank(message = "处理回复不能为空")
+    @Schema(description = "处理回复")
+    private String processReply;
+}
+

+ 35 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppDetailResVo.java

@@ -0,0 +1,35 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Schema(description = "App投诉详情")
+public class ComplaintAppDetailResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String complaintNo;
+    private Integer typeCode;
+    private String typeName;
+    private String targetName;
+    private Boolean isAnonymous;
+    private String submitterName;
+    private String content;
+    private Integer status;
+    private String statusName;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime processedAt;
+    private String processReply;
+    private List<ComplaintAttachmentResVo> attachments;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAppItemResVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "App投诉列表项")
+public class ComplaintAppItemResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉ID", example = "1")
+    private Long id;
+
+    @Schema(description = "投诉编号", example = "CT20260603000001")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉类型名称", example = "服务态度")
+    private String typeName;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @Schema(description = "处理状态:0待处理 1已处理", example = "0")
+    private Integer status;
+
+    @Schema(description = "处理状态名称", example = "待处理")
+    private String statusName;
+}

+ 34 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintAttachmentResVo.java

@@ -0,0 +1,34 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "投诉附件")
+public class ComplaintAttachmentResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "附件ID", example = "1")
+    private Long id;
+
+    @Schema(description = "附件URL", example = "https://xxx/1.jpg")
+    private String fileUrl;
+
+    @Schema(description = "附件名称", example = "1.jpg")
+    private String fileName;
+
+    @Schema(description = "附件类型", example = "image/jpeg")
+    private String fileType;
+
+    @Schema(description = "附件大小(字节)", example = "12345")
+    private Long fileSize;
+
+    @Schema(description = "排序", example = "1")
+    private Integer sortNo;
+}
+

+ 46 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintDetailResVo.java

@@ -0,0 +1,46 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@Schema(description = "投诉详情")
+public class ComplaintDetailResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String complaintNo;
+    private Integer channel;
+    private Integer typeCode;
+    private String typeName;
+
+    private String targetName;
+
+    private Long submitterId;
+    private String submitterName;
+    private Boolean isAnonymous;
+
+    private String content;
+
+    private Integer status;
+    private String statusName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime processedAt;
+    private Long processorId;
+    private String processorName;
+    private String processReply;
+
+    private List<ComplaintAttachmentResVo> attachments;
+    private List<ComplaintHandleLogResVo> handleLogs;
+}

+ 42 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintHandleLogResVo.java

@@ -0,0 +1,42 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "投诉处理记录")
+public class ComplaintHandleLogResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "记录ID", example = "1")
+    private Long id;
+
+    @Schema(description = "动作", example = "PROCESS")
+    private String action;
+
+    @Schema(description = "原状态", example = "0")
+    private Integer fromStatus;
+
+    @Schema(description = "新状态", example = "1")
+    private Integer toStatus;
+
+    @Schema(description = "操作人ID", example = "1001")
+    private Long operatorId;
+
+    @Schema(description = "操作人名称", example = "管理员")
+    private String operatorName;
+
+    @Schema(description = "说明/回复")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+}

+ 48 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintManageItemResVo.java

@@ -0,0 +1,48 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "PC投诉管理列表项")
+public class ComplaintManageItemResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "投诉ID", example = "1")
+    private Long id;
+
+    @Schema(description = "来源", example = "app")
+    private String channel;
+
+    @Schema(description = "投诉编号", example = "CT20260603000001")
+    private String complaintNo;
+
+    @Schema(description = "投诉类型编码(字典值)", example = "2")
+    private Integer typeCode;
+
+    @Schema(description = "投诉类型名称", example = "服务态度")
+    private String typeName;
+
+    @Schema(description = "投诉对象", example = "李四")
+    private String targetName;
+
+    @Schema(description = "投诉人显示名(匿名显示为“匿名”)", example = "匿名")
+    private String submitterNameDisplay;
+
+    @Schema(description = "发生时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime occurTime;
+
+    @Schema(description = "处理状态:0待处理 1已处理", example = "0")
+    private Integer status;
+
+    @Schema(description = "处理状态名称", example = "待处理")
+    private String statusName;
+}

+ 23 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/ComplaintTypeOptionResVo.java

@@ -0,0 +1,23 @@
+package com.sckw.system.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@AllArgsConstructor
+@Schema(description = "投诉类型选项")
+public class ComplaintTypeOptionResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "类型编码(字典值)", example = "2")
+    private Integer code;
+
+    @Schema(description = "类型名称", example = "服务态度")
+    private String name;
+}

+ 429 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsComplaintService.java

@@ -0,0 +1,429 @@
+package com.sckw.system.service;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.sckw.core.exception.SystemException;
+import com.sckw.core.model.constant.Global;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.core.web.model.LoginUserInfo;
+import com.sckw.core.web.response.result.PageDataResult;
+import com.sckw.system.model.TComplaint;
+import com.sckw.system.model.TComplaintAttachment;
+import com.sckw.system.model.TComplaintHandleLog;
+import com.sckw.system.model.pojo.ChannelEnum;
+import com.sckw.system.model.vo.req.ComplaintAppListReqVo;
+import com.sckw.system.model.vo.req.ComplaintAppSubmitReqVo;
+import com.sckw.system.model.vo.req.ComplaintManagePageReqVo;
+import com.sckw.system.model.vo.req.ComplaintProcessReqVo;
+import com.sckw.system.model.vo.res.*;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+
+@Service
+@RequiredArgsConstructor
+public class KwsComplaintService {
+
+    private static final DateTimeFormatter DATE_NO_DASH = DateTimeFormatter.ofPattern("yyyyMMdd");
+
+    private final TComplaintService tComplaintService;
+    private final TComplaintAttachmentService tComplaintAttachmentService;
+    private final TComplaintHandleLogService tComplaintHandleLogService;
+
+    @Transactional(rollbackFor = Exception.class)
+    public ComplaintDetailResVo submitAppComplaint(ComplaintAppSubmitReqVo reqVo) {
+        LoginUserInfo loginUserInfo = LoginUserHolder.get();
+
+        LocalDateTime now = LocalDateTime.now();
+
+        TComplaint complaint = new TComplaint();
+        complaint.setComplaintNo(buildComplaintNo(now));
+        complaint.setChannel(1);
+        complaint.setTypeCode(reqVo.getTypeCode());
+        complaint.setTargetName(reqVo.getTargetName());
+        if (Objects.nonNull(loginUserInfo)) {
+            complaint.setSubmitterId(loginUserInfo.getId());
+            complaint.setSubmitterName(loginUserInfo.getUserName());
+        }
+
+        complaint.setIsAnonymous(Boolean.TRUE.equals(reqVo.getIsAnonymous()));
+        complaint.setContent(reqVo.getContent());
+        complaint.setStatus(0);
+        complaint.setOccurTime(reqVo.getOccurTime());
+
+        complaint.setCreateBy(LoginUserHolder.getUserId());
+        complaint.setCreateTime(now);
+        complaint.setDelFlag(Global.UN_DELETED);
+
+        if (!tComplaintService.save(complaint)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "提交投诉失败");
+        }
+
+        if (StrUtil.isNotBlank(reqVo.getFileUrl())) {
+            TComplaintAttachment attachment = new TComplaintAttachment();
+            attachment.setComplaintId(complaint.getId());
+            attachment.setFileUrl(reqVo.getFileUrl());
+            attachment.setFileName(reqVo.getFileName());
+            attachment.setFileType(reqVo.getFileType());
+            attachment.setFileSize(reqVo.getFileSize());
+            attachment.setSortNo(1);
+            attachment.setCreateBy(LoginUserHolder.getUserId());
+            attachment.setCreateTime(now);
+            attachment.setDelFlag(Global.UN_DELETED);
+            if (!tComplaintAttachmentService.save(attachment)) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "保存投诉附件失败");
+            }
+        }
+
+        return buildComplaintDetail(complaint, true);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public ComplaintDetailResVo submitScanComplaint(ComplaintAppSubmitReqVo reqVo) {
+        LocalDateTime now = LocalDateTime.now();
+
+        TComplaint complaint = new TComplaint();
+        complaint.setComplaintNo(buildComplaintNo(now));
+        complaint.setChannel(2);
+        complaint.setTypeCode(reqVo.getTypeCode());
+        complaint.setTargetName(reqVo.getTargetName());
+        complaint.setSubmitterId(null);
+        complaint.setSubmitterName(null);
+        complaint.setIsAnonymous(true);
+        complaint.setContent(reqVo.getContent());
+        complaint.setStatus(0);
+        complaint.setOccurTime(reqVo.getOccurTime());
+
+        complaint.setCreateBy(0L);
+        complaint.setCreateTime(now);
+        complaint.setDelFlag(Global.UN_DELETED);
+
+        if (!tComplaintService.save(complaint)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "提交投诉失败");
+        }
+
+        if (StrUtil.isNotBlank(reqVo.getFileUrl())) {
+            TComplaintAttachment attachment = new TComplaintAttachment();
+            attachment.setComplaintId(complaint.getId());
+            attachment.setFileUrl(reqVo.getFileUrl());
+            attachment.setFileName(reqVo.getFileName());
+            attachment.setFileType(reqVo.getFileType());
+            attachment.setFileSize(reqVo.getFileSize());
+            attachment.setSortNo(1);
+            attachment.setCreateBy(0L);
+            attachment.setCreateTime(now);
+            attachment.setUpdateBy(0L);
+            attachment.setUpdateTime(now);
+            attachment.setDelFlag(Global.UN_DELETED);
+            if (!tComplaintAttachmentService.save(attachment)) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "保存投诉附件失败");
+            }
+        }
+
+        return buildComplaintDetail(complaint, true);
+    }
+
+    public PageDataResult<ComplaintAppItemResVo> appComplaintPage(ComplaintAppListReqVo reqVo) {
+        Page<TComplaint> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
+        LambdaQueryWrapper<TComplaint> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TComplaint::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaint::getCreateBy, LoginUserHolder.getUserId())
+                .orderByDesc(TComplaint::getCreateTime);
+
+        if (reqVo.getStatus() != null && reqVo.getStatus() >= 0) {
+            wrapper.eq(TComplaint::getStatus, reqVo.getStatus());
+        }
+
+        Page<TComplaint> complaintPage = tComplaintService.page(page, wrapper);
+        return PageDataResult.of(page, complaintPage.getRecords().stream().map(this::toAppItem).collect(Collectors.toList()));
+    }
+
+    public ComplaintAppDetailResVo appComplaintDetail(Long complaintId) {
+        TComplaint complaint = tComplaintService.getById(complaintId);
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+        ComplaintDetailResVo detail = buildComplaintDetail(complaint, true);
+        return toAppDetail(detail);
+    }
+
+    public PageDataResult<ComplaintManageItemResVo> manageComplaintPage(ComplaintManagePageReqVo reqVo) {
+        Page<TComplaint> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
+        LambdaQueryWrapper<TComplaint> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TComplaint::getDelFlag, Global.UN_DELETED)
+                .orderByDesc(TComplaint::getOccurTime);
+
+        if (StrUtil.isNotBlank(reqVo.getComplaintNo())) {
+            wrapper.like(TComplaint::getComplaintNo, reqVo.getComplaintNo().trim());
+        }
+        if (reqVo.getTypeCode() != null) {
+            wrapper.eq(TComplaint::getTypeCode, reqVo.getTypeCode());
+        }
+        if (StrUtil.isNotBlank(reqVo.getTargetName())) {
+            wrapper.like(TComplaint::getTargetName, reqVo.getTargetName().trim());
+        }
+        if (StrUtil.isNotBlank(reqVo.getSubmitterKeyword())) {
+            wrapper.eq(TComplaint::getIsAnonymous, 0);
+            wrapper.like(TComplaint::getSubmitterName, reqVo.getSubmitterKeyword().trim());
+        }
+        if (reqVo.getStatus() != null && reqVo.getStatus() >= 0) {
+            wrapper.eq(TComplaint::getStatus, reqVo.getStatus());
+        }
+
+        LocalDateTime startTime = parseStartDate(reqVo.getStartDate());
+        LocalDateTime endTimeExclusive = parseEndDateExclusive(reqVo.getEndDate());
+        if (startTime != null) {
+            wrapper.ge(TComplaint::getOccurTime, startTime);
+        }
+        if (endTimeExclusive != null) {
+            wrapper.lt(TComplaint::getOccurTime, endTimeExclusive);
+        }
+
+        Page<TComplaint> complaintPage = tComplaintService.page(page, wrapper);
+        return PageDataResult.of(page, complaintPage.getRecords().stream().map(this::toManageItem).collect(Collectors.toList()));
+    }
+
+    public ComplaintDetailResVo manageComplaintDetail(Long complaintId) {
+        TComplaint complaint = tComplaintService.getById(complaintId);
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+        return buildComplaintDetail(complaint, true);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean processComplaint(ComplaintProcessReqVo reqVo) {
+        TComplaint complaint = tComplaintService.getById(reqVo.getId());
+        if (Objects.isNull(complaint) || !Objects.equals(complaint.getDelFlag(), Global.UN_DELETED)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "投诉记录不存在");
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        Integer fromStatus = complaint.getStatus();
+
+        Long operatorId = LoginUserHolder.getUserId();
+        String operatorName = LoginUserHolder.getUserName();
+
+        boolean updated = tComplaintService.lambdaUpdate()
+                .eq(TComplaint::getId, reqVo.getId())
+                .set(TComplaint::getStatus, 1)
+                .set(TComplaint::getProcessedAt, now)
+                .set(TComplaint::getProcessorId, operatorId)
+                .set(TComplaint::getProcessorName, operatorName)
+                .set(TComplaint::getProcessReply, reqVo.getProcessReply())
+                .set(TComplaint::getUpdateBy, operatorId)
+                .set(TComplaint::getUpdateTime, now)
+                .update();
+
+        if (!updated) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "处理投诉失败");
+        }
+
+        TComplaintHandleLog log = new TComplaintHandleLog();
+        log.setComplaintId(reqVo.getId());
+        log.setAction("PROCESS");
+        log.setFromStatus(fromStatus == null ? 0 : fromStatus);
+        log.setToStatus(1);
+        log.setOperatorId(operatorId);
+        log.setOperatorName(operatorName);
+        log.setRemark(reqVo.getProcessReply());
+        log.setCreateBy(operatorId);
+        log.setCreateTime(now);
+        log.setUpdateBy(operatorId);
+        log.setUpdateTime(now);
+        log.setDelFlag(Global.UN_DELETED);
+        if (!tComplaintHandleLogService.save(log)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "保存处理记录失败");
+        }
+
+        return true;
+    }
+
+
+    private ComplaintAppItemResVo toAppItem(TComplaint complaint) {
+        ComplaintAppItemResVo vo = new ComplaintAppItemResVo();
+        vo.setId(complaint.getId());
+        vo.setComplaintNo(complaint.getComplaintNo());
+        vo.setTypeCode(complaint.getTypeCode());
+        vo.setTypeName(typeName(complaint.getTypeCode()));
+        vo.setTargetName(complaint.getTargetName());
+        vo.setOccurTime(complaint.getOccurTime());
+        vo.setStatus(complaint.getStatus());
+        vo.setStatusName(statusName(complaint.getStatus()));
+        return vo;
+    }
+
+    private ComplaintManageItemResVo toManageItem(TComplaint complaint) {
+        ComplaintManageItemResVo vo = new ComplaintManageItemResVo();
+        vo.setId(complaint.getId());
+        vo.setComplaintNo(complaint.getComplaintNo());
+        vo.setChannel(ChannelEnum.getNameByCode(complaint.getChannel()));
+        vo.setTypeCode(complaint.getTypeCode());
+        vo.setTypeName(typeName(complaint.getTypeCode()));
+        vo.setTargetName(complaint.getTargetName());
+        vo.setSubmitterNameDisplay(Boolean.TRUE.equals(complaint.getIsAnonymous()) ? "匿名" : complaint.getSubmitterName());
+        vo.setOccurTime(complaint.getOccurTime());
+        vo.setStatus(complaint.getStatus());
+        vo.setStatusName(statusName(complaint.getStatus()));
+        return vo;
+    }
+
+    private ComplaintDetailResVo buildComplaintDetail(TComplaint complaint, boolean maskSubmitter) {
+        ComplaintDetailResVo res = new ComplaintDetailResVo();
+        res.setId(complaint.getId());
+        res.setComplaintNo(complaint.getComplaintNo());
+        res.setChannel(complaint.getChannel());
+        res.setTypeCode(complaint.getTypeCode());
+        res.setTypeName(typeName(complaint.getTypeCode()));
+        res.setTargetName(complaint.getTargetName());
+        res.setSubmitterId(complaint.getSubmitterId());
+        res.setSubmitterName(maskSubmitter && Boolean.TRUE.equals(complaint.getIsAnonymous()) ? "匿名" : complaint.getSubmitterName());
+        res.setIsAnonymous(Boolean.TRUE.equals(complaint.getIsAnonymous()));
+        res.setContent(complaint.getContent());
+        res.setStatus(complaint.getStatus());
+        res.setStatusName(statusName(complaint.getStatus()));
+        res.setOccurTime(complaint.getOccurTime());
+        res.setProcessedAt(complaint.getProcessedAt());
+        res.setProcessorId(complaint.getProcessorId());
+        res.setProcessorName(complaint.getProcessorName());
+        res.setProcessReply(complaint.getProcessReply());
+
+        List<TComplaintAttachment> attachments = tComplaintAttachmentService.lambdaQuery()
+                .eq(TComplaintAttachment::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaintAttachment::getComplaintId, complaint.getId())
+                .orderByAsc(TComplaintAttachment::getSortNo)
+                .list();
+        if (CollUtil.isEmpty(attachments)) {
+            res.setAttachments(new ArrayList<>());
+        } else {
+            res.setAttachments(attachments.stream().map(this::toAttachment).collect(Collectors.toList()));
+        }
+
+        List<TComplaintHandleLog> logs = tComplaintHandleLogService.lambdaQuery()
+                .eq(TComplaintHandleLog::getDelFlag, Global.UN_DELETED)
+                .eq(TComplaintHandleLog::getComplaintId, complaint.getId())
+                .orderByDesc(TComplaintHandleLog::getCreateTime)
+                .list();
+        if (CollUtil.isEmpty(logs)) {
+            res.setHandleLogs(new ArrayList<>());
+        } else {
+            res.setHandleLogs(logs.stream().map(this::toHandleLog).collect(Collectors.toList()));
+        }
+
+        return res;
+    }
+
+    private ComplaintAttachmentResVo toAttachment(TComplaintAttachment attachment) {
+        ComplaintAttachmentResVo vo = new ComplaintAttachmentResVo();
+        vo.setId(attachment.getId());
+        vo.setFileUrl(attachment.getFileUrl());
+        vo.setFileName(attachment.getFileName());
+        vo.setFileType(attachment.getFileType());
+        vo.setFileSize(attachment.getFileSize());
+        vo.setSortNo(attachment.getSortNo());
+        return vo;
+    }
+
+    private ComplaintHandleLogResVo toHandleLog(TComplaintHandleLog log) {
+        ComplaintHandleLogResVo vo = new ComplaintHandleLogResVo();
+        vo.setId(log.getId());
+        vo.setAction(log.getAction());
+        vo.setFromStatus(log.getFromStatus());
+        vo.setToStatus(log.getToStatus());
+        vo.setOperatorId(log.getOperatorId());
+        vo.setOperatorName(log.getOperatorName());
+        vo.setRemark(log.getRemark());
+        vo.setCreateTime(log.getCreateTime());
+        return vo;
+    }
+
+    private ComplaintAppDetailResVo toAppDetail(ComplaintDetailResVo detail) {
+        ComplaintAppDetailResVo vo = new ComplaintAppDetailResVo();
+        vo.setId(detail.getId());
+        vo.setComplaintNo(detail.getComplaintNo());
+        vo.setTypeCode(detail.getTypeCode());
+        vo.setTypeName(detail.getTypeName());
+        vo.setTargetName(detail.getTargetName());
+        vo.setIsAnonymous(detail.getIsAnonymous());
+        vo.setSubmitterName(detail.getSubmitterName());
+        vo.setContent(detail.getContent());
+        vo.setAttachments(detail.getAttachments());
+        vo.setStatus(detail.getStatus());
+        vo.setStatusName(detail.getStatusName());
+        vo.setOccurTime(detail.getOccurTime());
+        vo.setProcessedAt(detail.getProcessedAt());
+        vo.setProcessReply(detail.getProcessReply());
+        return vo;
+    }
+
+    private String statusName(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        return status == 1 ? "已处理" : "待处理";
+    }
+
+    private String typeName(Integer typeCode) {
+        if (typeCode == null) {
+            return "";
+        }
+        return switch (typeCode) {
+            case 1 -> "违规";
+            case 2 -> "服务态度";
+            case 3 -> "其他";
+            default -> String.valueOf(typeCode);
+        };
+    }
+
+    private String buildTempComplaintNo(LocalDateTime now) {
+        String date = now.toLocalDate().format(DATE_NO_DASH);
+        String suffix = String.valueOf(System.nanoTime());
+        if (suffix.length() > 12) {
+            suffix = suffix.substring(suffix.length() - 12);
+        }
+        return "CT" + date + "TMP" + suffix;
+    }
+
+    private String buildComplaintNo(LocalDateTime now) {
+        String date = now.toLocalDate().format(DATE_NO_DASH);
+        for (int i = 0; i < 5; i++) {
+            String suffix = String.format("%06d", ThreadLocalRandom.current().nextInt(0, 1_000_000));
+            String no = "CT" + date + suffix;
+            boolean exists = tComplaintService.lambdaQuery()
+                    .eq(TComplaint::getComplaintNo, no)
+                    .exists();
+            if (!exists) {
+                return no;
+            }
+        }
+        return buildTempComplaintNo(now);
+    }
+
+
+    private LocalDateTime parseStartDate(String startDate) {
+        if (StrUtil.isBlank(startDate)) {
+            return null;
+        }
+        return LocalDate.parse(startDate.trim()).atStartOfDay();
+    }
+
+    private LocalDateTime parseEndDateExclusive(String endDate) {
+        if (StrUtil.isBlank(endDate)) {
+            return null;
+        }
+        return LocalDate.parse(endDate.trim()).plusDays(1).atStartOfDay();
+    }
+}

+ 16 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintAttachmentService.java

@@ -0,0 +1,16 @@
+package com.sckw.system.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Autowired;
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintAttachmentMapper;
+import com.sckw.system.model.TComplaintAttachment;
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Service
+public class TComplaintAttachmentService extends ServiceImpl<TComplaintAttachmentMapper, TComplaintAttachment> {
+
+}

+ 11 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintHandleLogService.java

@@ -0,0 +1,11 @@
+package com.sckw.system.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintHandleLogMapper;
+import com.sckw.system.model.TComplaintHandleLog;
+import org.springframework.stereotype.Service;
+
+@Service
+public class TComplaintHandleLogService extends ServiceImpl<TComplaintHandleLogMapper, TComplaintHandleLog> {
+}
+

+ 16 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/TComplaintService.java

@@ -0,0 +1,16 @@
+package com.sckw.system.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Autowired;
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.sckw.system.dao.TComplaintMapper;
+import com.sckw.system.model.TComplaint;
+/**
+* @date 2026-06-06 15:14:09
+* @author xucaiqin
+*/
+@Service
+public class TComplaintService extends ServiceImpl<TComplaintMapper, TComplaint> {
+
+}

+ 3 - 3
sckw-modules/sckw-system/src/main/resources/bootstrap.yml

@@ -5,8 +5,8 @@ spring:
   application:
     name: sckw-ng-system
   profiles:
-    active: @profiles.active@
-#    active: dev
+#    active: @profiles.active@
+    active: local
 #    active: test
   main:
     allow-bean-definition-overriding: true
@@ -32,4 +32,4 @@ feign:
   httpclient:
     enabled: true
     max-connections: 200
-    max-connections-per-route: 50
+    max-connections-per-route: 50

+ 25 - 0
sckw-modules/sckw-system/src/main/resources/mapper/TComplaintAttachmentMapper.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sckw.system.dao.TComplaintAttachmentMapper">
+  <resultMap id="BaseResultMap" type="com.sckw.system.model.TComplaintAttachment">
+    <!--@mbg.generated-->
+    <!--@Table t_complaint_attachment-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="complaint_id" jdbcType="BIGINT" property="complaintId" />
+    <result column="file_url" jdbcType="VARCHAR" property="fileUrl" />
+    <result column="file_name" jdbcType="VARCHAR" property="fileName" />
+    <result column="file_type" jdbcType="VARCHAR" property="fileType" />
+    <result column="file_size" jdbcType="BIGINT" property="fileSize" />
+    <result column="sort_no" jdbcType="INTEGER" property="sortNo" />
+    <result column="create_by" jdbcType="BIGINT" property="createBy" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_by" jdbcType="BIGINT" property="updateBy" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="del_flag" jdbcType="INTEGER" property="delFlag" />
+  </resultMap>
+  <sql id="Base_Column_List">
+    <!--@mbg.generated-->
+    id, complaint_id, file_url, file_name, file_type, file_size, sort_no, create_by, 
+    create_time, update_by, update_time, del_flag
+  </sql>
+</mapper>

+ 30 - 0
sckw-modules/sckw-system/src/main/resources/mapper/TComplaintMapper.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sckw.system.dao.TComplaintMapper">
+  <resultMap id="BaseResultMap" type="com.sckw.system.model.TComplaint">
+    <!--@mbg.generated-->
+    <!--@Table t_complaint-->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="complaint_no" jdbcType="VARCHAR" property="complaintNo" />
+    <result column="channel" jdbcType="TINYINT" property="channel" />
+    <result column="type_code" jdbcType="VARCHAR" property="typeCode" />
+    <result column="target_name" jdbcType="VARCHAR" property="targetName" />
+    <result column="submitter_type" jdbcType="TINYINT" property="submitterType" />
+    <result column="submitter_id" jdbcType="BIGINT" property="submitterId" />
+    <result column="submitter_name" jdbcType="VARCHAR" property="submitterName" />
+    <result column="is_anonymous" jdbcType="BOOLEAN" property="isAnonymous" />
+    <result column="content" jdbcType="LONGVARCHAR" property="content" />
+    <result column="status" jdbcType="TINYINT" property="status" />
+    <result column="occur_time" jdbcType="TIMESTAMP" property="occurTime" />
+    <result column="processed_at" jdbcType="TIMESTAMP" property="processedAt" />
+    <result column="processor_id" jdbcType="BIGINT" property="processorId" />
+    <result column="processor_name" jdbcType="VARCHAR" property="processorName" />
+    <result column="process_reply" jdbcType="LONGVARCHAR" property="processReply" />
+    <result column="create_by" jdbcType="BIGINT" property="createBy" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="update_by" jdbcType="BIGINT" property="updateBy" />
+    <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
+    <result column="del_flag" jdbcType="INTEGER" property="delFlag" />
+  </resultMap>
+
+</mapper>