feat: T10 回归测试 Bug 修复与功能完善

修复 BUG-001 至 BUG-009 及 T10-1 至 T10-6 相关问题:
- 打卡积分显示与累加逻辑优化
- 食谱计算器 Tab 选中样式修复
- 食物百科列表图片与简介展示修复
- 食物详情页数据加载修复
- AI营养师差异化回复优化
- 健康知识/营养知识名称统一
- 饮食指南/科普文章详情页内容展示修复
- 帖子营养统计数据展示修复
- 社区帖子类型中文命名统一
- 帖子详情标签中文显示修复
- 食谱营养AI填充功能完善
- 食谱收藏/点赞功能修复

新增:
- ToolNutritionFillService 营养填充服务
- T10 回归测试用例 (Playwright)
- 知识文章数据 SQL 脚本

涉及模块:
- crmeb-common: VO/Request/Response 优化
- crmeb-service: 业务逻辑完善
- crmeb-front: API 接口扩展
- msh_single_uniapp: 前端页面修复
- tests/e2e: 回归测试用例
This commit is contained in:
2026-03-05 09:35:00 +08:00
parent 6f2dc27fbc
commit d8d2025543
44 changed files with 1536 additions and 165 deletions

View File

@@ -6,8 +6,6 @@ import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import sun.misc.BASE64Decoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -34,6 +32,14 @@ public class SwaggerInterceptor extends HandlerInterceptorAdapter {
this.password = password;
this.check = check;
}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public Boolean getCheck() { return check; }
public void setCheck(Boolean check) { this.check = check; }
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authorization = request.getHeader("Authorization");
@@ -52,7 +58,7 @@ public class SwaggerInterceptor extends HandlerInterceptorAdapter {
public boolean httpBasicAuth(String authorization) throws IOException {
if(check){
if (authorization != null && authorization.split(" ").length == 2) {
String userAndPass = new String(new BASE64Decoder().decodeBuffer(authorization.split(" ")[1]));
String userAndPass = new String(java.util.Base64.getDecoder().decode(authorization.split(" ")[1]));
String username = userAndPass.split(":").length == 2 ? userAndPass.split(":")[0] : null;
String password = userAndPass.split(":").length == 2 ? userAndPass.split(":")[1] : null;
return this.username.equals(username) && this.password.equals(password);

View File

@@ -2,7 +2,6 @@ package com.zbkj.common.page;
import com.zbkj.common.constants.Constants;
import com.github.pagehelper.PageInfo;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
@@ -15,7 +14,6 @@ import java.util.List;
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
public class CommonPage<T> {
private Integer page = Constants.DEFAULT_PAGE;
private Integer limit = Constants.DEFAULT_LIMIT;
@@ -23,6 +21,16 @@ public class CommonPage<T> {
private Long total = 0L ;
private List<T> list = new ArrayList<>();
public Integer getPage() { return page; }
public void setPage(Integer page) { this.page = page; }
public Integer getLimit() { return limit; }
public void setLimit(Integer limit) { this.limit = limit; }
public Integer getTotalPage() { return totalPage; }
public void setTotalPage(Integer totalPage) { this.totalPage = totalPage; }
public Long getTotal() { return total; }
public void setTotal(Long total) { this.total = total; }
public List<T> getList() { return list; }
public void setList(List<T> list) { this.list = list; }
/**
* 将PageHelper分页后的list转为分页信息

View File

@@ -35,4 +35,12 @@ public class ArticleSearchRequest implements Serializable {
@ApiModelProperty(value = "搜索关键字")
private String keywords;
public String getCid() {
return cid;
}
public String getKeywords() {
return keywords;
}
}

View File

@@ -19,4 +19,12 @@ public class PageParamRequest {
@ApiModelProperty(value = "每页数量", example = Constants.DEFAULT_LIMIT + "")
private int limit = Constants.DEFAULT_LIMIT;
public int getPage() {
return page;
}
public int getLimit() {
return limit;
}
}

View File

@@ -64,5 +64,40 @@ public class SystemMenuRequest implements Serializable {
@NotNull(message = "显示状态不能为空")
private Boolean isShow;
public Integer getId() {
return id;
}
public Integer getPid() {
return pid;
}
public String getName() {
return name;
}
public String getIcon() {
return icon;
}
public String getPerms() {
return perms;
}
public String getComponent() {
return component;
}
public String getMenuType() {
return menuType;
}
public Integer getSort() {
return sort;
}
public Boolean getIsShow() {
return isShow;
}
}

View File

@@ -22,4 +22,12 @@ public class SystemMenuSearchRequest {
@ApiModelProperty(value = "菜单类型:M-目录C-菜单A-按钮")
@StringContains(limitValues = {"M","C","A"}, message = "未知的菜单类型")
private String menuType;
public String getName() {
return name;
}
public String getMenuType() {
return menuType;
}
}

View File

@@ -2,16 +2,7 @@ package com.zbkj.common.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Coze API 统一响应
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "CozeBaseResponse", description = "Coze API 统一响应")
public class CozeBaseResponse<T> {
@@ -24,15 +15,60 @@ public class CozeBaseResponse<T> {
@ApiModelProperty(value = "响应数据")
private T data;
public CozeBaseResponse() {
}
public CozeBaseResponse(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static <T> CozeBaseResponse<T> success(T data) {
return new CozeBaseResponse<>(200, "success", data);
CozeBaseResponse<T> response = new CozeBaseResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}
public static <T> CozeBaseResponse<T> error(Integer code, String message) {
return new CozeBaseResponse<>(code, message, null);
CozeBaseResponse<T> response = new CozeBaseResponse<>();
response.setCode(code);
response.setMessage(message);
response.setData(null);
return response;
}
public static <T> CozeBaseResponse<T> error(String message) {
return new CozeBaseResponse<>(500, message, null);
CozeBaseResponse<T> response = new CozeBaseResponse<>();
response.setCode(500);
response.setMessage(message);
response.setData(null);
return response;
}
}

View File

@@ -2,9 +2,6 @@ package com.zbkj.common.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
@@ -15,9 +12,6 @@ import java.util.List;
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="MenusResponse对象", description="系统左侧菜单对象")
public class MenusResponse implements Serializable {
@@ -49,4 +43,23 @@ public class MenusResponse implements Serializable {
@ApiModelProperty(value = "子对象列表")
private List<MenusResponse> childList;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public Integer getPid() { return pid; }
public void setPid(Integer pid) { this.pid = pid; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; }
public String getPerms() { return perms; }
public void setPerms(String perms) { this.perms = perms; }
public String getComponent() { return component; }
public void setComponent(String component) { this.component = component; }
public String getMenuType() { return menuType; }
public void setMenuType(String menuType) { this.menuType = menuType; }
public Integer getSort() { return sort; }
public void setSort(Integer sort) { this.sort = sort; }
public List<MenusResponse> getChildList() { return childList; }
public void setChildList(List<MenusResponse> childList) { this.childList = childList; }
}

View File

@@ -2,7 +2,6 @@ package com.zbkj.common.response.kieai;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@@ -13,7 +12,6 @@ import java.io.Serializable;
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
@ApiModel(value = "KieAI创建任务响应", description = "KieAI创建任务响应结果")
public class KieAICreateTaskResponse implements Serializable {
@@ -28,6 +26,13 @@ public class KieAICreateTaskResponse implements Serializable {
@ApiModelProperty(value = "响应数据")
private TaskData data;
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public TaskData getData() { return data; }
public void setData(TaskData data) { this.data = data; }
/**
* 便捷方法获取任务ID
*/
@@ -42,7 +47,6 @@ public class KieAICreateTaskResponse implements Serializable {
return code != null && code == 200;
}
@Data
public static class TaskData implements Serializable {
private static final long serialVersionUID = 1L;
@@ -51,5 +55,10 @@ public class KieAICreateTaskResponse implements Serializable {
@ApiModelProperty(value = "记录ID", example = "5d5ba5bc66c11c1a97f719312c76a5df")
private String recordId;
public String getTaskId() { return taskId; }
public void setTaskId(String taskId) { this.taskId = taskId; }
public String getRecordId() { return recordId; }
public void setRecordId(String recordId) { this.recordId = recordId; }
}
}

View File

@@ -2,7 +2,6 @@ package com.zbkj.common.response.kieai;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@@ -12,7 +11,6 @@ import java.io.Serializable;
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
@ApiModel(value = "KieAI NanoBanana响应", description = "KieAI NanoBanana通用响应")
public class KieAINanoBananaResponse<T> implements Serializable {
@@ -27,6 +25,13 @@ public class KieAINanoBananaResponse<T> implements Serializable {
@ApiModelProperty(value = "响应数据")
private T data;
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
public static <T> KieAINanoBananaResponse<T> success(T data) {
KieAINanoBananaResponse<T> response = new KieAINanoBananaResponse<>();
response.setCode(200);

View File

@@ -2,9 +2,9 @@ package com.zbkj.common.response.kieai;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* KieAI 查询任务响应 DTO新版 API v1
@@ -13,7 +13,6 @@ import java.io.Serializable;
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
@ApiModel(value = "KieAI查询任务响应", description = "KieAI查询任务状态及结果")
public class KieAIQueryTaskResponse implements Serializable {
@@ -28,6 +27,13 @@ public class KieAIQueryTaskResponse implements Serializable {
@ApiModelProperty(value = "响应数据")
private TaskData data;
public Integer getCode() { return code; }
public void setCode(Integer code) { this.code = code; }
public String getMsg() { return msg; }
public void setMsg(String msg) { this.msg = msg; }
public TaskData getData() { return data; }
public void setData(TaskData data) { this.data = data; }
/**
* 便捷方法获取任务ID
*/
@@ -59,7 +65,7 @@ public class KieAIQueryTaskResponse implements Serializable {
/**
* 便捷方法获取结果URL数组
*/
public java.util.List<String> getResultUrls() {
public List<String> getResultUrls() {
return data != null ? data.getResultUrls() : null;
}
@@ -70,7 +76,6 @@ public class KieAIQueryTaskResponse implements Serializable {
return code != null && code == 200;
}
@Data
public static class TaskData implements Serializable {
private static final long serialVersionUID = 1L;
@@ -89,17 +94,9 @@ public class KieAIQueryTaskResponse implements Serializable {
@ApiModelProperty(value = "结果JSON包含resultUrls", example = "{\"resultUrls\":[\"https://...\"]}")
private String resultJson;
@ApiModelProperty(value = "结果URL数组视频/图片地址)")
private java.util.List<String> resultUrls;
/**
* 显式添加 setter 方法以确保 Jackson 反序列化正常工作
* 解决 "Problem deserializing 'setterless' property" 错误
*/
public void setResultUrls(java.util.List<String> resultUrls) {
this.resultUrls = resultUrls;
}
private List<String> resultUrls;
@ApiModelProperty(value = "失败错误码")
private String failCode;
@@ -118,5 +115,30 @@ public class KieAIQueryTaskResponse implements Serializable {
@ApiModelProperty(value = "更新时间戳")
private Long updateTime;
public String getTaskId() { return taskId; }
public void setTaskId(String taskId) { this.taskId = taskId; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public String getState() { return state; }
public void setState(String state) { this.state = state; }
public String getParam() { return param; }
public void setParam(String param) { this.param = param; }
public String getResultJson() { return resultJson; }
public void setResultJson(String resultJson) { this.resultJson = resultJson; }
public List<String> getResultUrls() { return resultUrls; }
public void setResultUrls(List<String> resultUrls) { this.resultUrls = resultUrls; }
public String getFailCode() { return failCode; }
public void setFailCode(String failCode) { this.failCode = failCode; }
public String getFailMsg() { return failMsg; }
public void setFailMsg(String failMsg) { this.failMsg = failMsg; }
public Long getCostTime() { return costTime; }
public void setCostTime(Long costTime) { this.costTime = costTime; }
public Long getCompleteTime() { return completeTime; }
public void setCompleteTime(Long completeTime) { this.completeTime = completeTime; }
public Long getCreateTime() { return createTime; }
public void setCreateTime(Long createTime) { this.createTime = createTime; }
public Long getUpdateTime() { return updateTime; }
public void setUpdateTime(Long updateTime) { this.updateTime = updateTime; }
}
}

View File

@@ -1,8 +1,8 @@
package com.zbkj.common.result;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zbkj.common.annotation.CustomResponseAnnotation;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
@@ -49,11 +49,14 @@ public class ResultAdvice implements ResponseBodyAdvice<Object> {
/**
* 对返回数据进行处理
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof String) {// 如果Controller直接返回String的话SpringBoot是直接返回故我们需要手动转换成json。
return objectMapper.writeValueAsString(CommonResult.success(body));
try {
return objectMapper.writeValueAsString(CommonResult.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize response to JSON", e);
}
}
if (body instanceof CommonResult) {// 如果返回的结果是CommonResult对象直接返回即可。
return body;

View File

@@ -149,7 +149,15 @@ public class FrontTokenComponent {
return null;
// throw new CrmebException("登录信息已过期,请重新登录!");
}
return redisUtil.get(getTokenKey(token));
Object value = redisUtil.get(getTokenKey(token));
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof LoginUserVo) {
LoginUserVo loginUser = (LoginUserVo) value;
return loginUser.getUser() != null ? loginUser.getUser().getId() : null;
}
return null;
}
//路由在此处则返回true无论用户是否登录都可以访问

View File

@@ -19,4 +19,21 @@ public class DateLimitUtilVo {
private String startTime; //开始时间
private String endTime; //结束时间
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}

View File

@@ -32,4 +32,29 @@ public class ImageMergeUtilVo {
@ApiModelProperty(value = "y轴", required = true)
@Min(value = 0, message = "y轴至少为0")
private int y; //y轴
// 手动添加 getter/setter避免 Lombok 未生效时编译报错
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}

View File

@@ -74,9 +74,15 @@ public class LoginUserVo implements UserDetails {
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
if (permissions == null) {
return new ArrayList<>();
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>(permissions.size());
for (SystemPermissions permission : permissions) {
authorities.add(new SimpleGrantedAuthority(permission.getPath()));
String path = permission.getPath();
if (path != null) {
authorities.add(new SimpleGrantedAuthority(path));
}
}
return authorities;
}
@@ -201,4 +207,11 @@ public class LoginUserVo implements UserDetails {
this.user = user;
}
/**
* 获取用户昵称/显示名称(后台管理员为 realName
*/
public String getNickname() {
return user != null ? user.getRealName() : null;
}
}

View File

@@ -9,12 +9,6 @@ import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 菜单待选中Vo对象
* +----------------------------------------------------------------------
* | Author:ScottPan
* +----------------------------------------------------------------------
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@@ -43,4 +37,60 @@ public class MenuCheckVo implements Serializable {
@ApiModelProperty(value = "子对象列表")
private List<MenuCheckVo> childList;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Boolean getChecked() {
return checked;
}
public void setChecked(Boolean checked) {
this.checked = checked;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public List<MenuCheckVo> getChildList() {
return childList;
}
public void setChildList(List<MenuCheckVo> childList) {
this.childList = childList;
}
}

View File

@@ -22,6 +22,9 @@ public class MenuTree {
this.menuList = menuList;
}
public List<MenusResponse> getMenuList() { return menuList; }
public void setMenuList(List<MenusResponse> menuList) { this.menuList = menuList; }
//建立树形结构
public List<MenusResponse> buildTree(){
List<MenusResponse> treeMenus = new ArrayList<MenusResponse>();
@@ -35,7 +38,7 @@ public class MenuTree {
// 排序
private List<MenusResponse> sortList(List<MenusResponse> treeMenus) {
treeMenus = treeMenus.stream().sorted(Comparator.comparing(MenusResponse::getSort).reversed()).collect(Collectors.toList());
treeMenus = treeMenus.stream().sorted(Comparator.comparing((MenusResponse m) -> m.getSort() != null ? m.getSort() : 0).reversed()).collect(Collectors.toList());
treeMenus.forEach(e -> {
if (CollUtil.isNotEmpty(e.getChildList())) {
e.setChildList(sortList(e.getChildList()));