文章目录
字典是什么
-
案例分析
- 如下图:有一个User实体类,他的sex和position_id是需要翻译的属性;
- sex为0,表示该用户为“男”,sex为1,表示该用户为”女“;position为1表示该用户职位为总裁,2表示为主管;这些字段的”0,1,2"等值会被翻译成我们可以理解的值”总裁“,”主管“等值
-
所以我们如何实现这一功能的呢:建立一种对应关系,这种关系建立需要以下信息
- 类的属性(哪个字段需要翻译:sex)
- dict_Table(去哪张表找翻译数据:sys_dict_item)
- dict_value(什么字段找key值的对应:dict_value)
- dictText(翻译后的数据是哪一列:dict_text)
Java实现
1. 实现的技术说明
- redis:如果用户请求太过频繁,数据字典查询会对数据库造成压力,所以需要缓存到redis,查询的性能更强
- mysql:数据存储到sql数据库中
- SpringBoot+mybatis_plus(mybatis也可以)
2.首先定义注解@Dict
package com.happy_retail_management.common.aspect.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "id")
* 或者dicCode=“sex”
*/
@Target(ElementType.FIELD)//修饰属性
@Retention(RetentionPolicy.RUNTIME)
public @interface Dict {
/**
* 数据code
* @return String
*/
String dicCode();
/**
* 数据Text
*/
String dicText() default "";
/**
* 数据字典
*/
String dicTable() default "";
}
3. 定义DictModel实体类->用与保存字典
package com.happy_retail_management.system.vo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 字典类
*/
@Data
@EqualsAndHashCode(callSuper =false)
@Accessors(chain = true)//运行链式调用
@JsonIgnoreProperties(ignoreUnknown=true)
public class DictModel {
private static final long serialVersionUID=1L;
/**
* 字典value
*/
private String value;
/**
* 字典文本
*/
private String text;
public DictModel(){
}
public DictModel(String value,String text){
this.value = value;
this.text = text;
}
}
4. 数据库定义sys_dict_item表
-
表单样式:
-
建表语句
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_dict_item -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_item`; CREATE TABLE `sys_dict_item` ( `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键', `dict_code` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '字典名', `item_text` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '字典显示字段', `item_value` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '字典查询字段', `item_description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
5. 定义实体类->以Product为例
-
使用上面的@Dict注解,在注解上提供信息,有以下两种注解
@Dict(dictCode="sex")
表示去system_dict_item表中查寻dictCode=sex,dictValue=?(需要查询的属性值)数据项,并翻译为dict_text数据@Dict(dicTable="vendor",dicCode = "vendor_id" ,dicText = "vendor_name")
表示去vender表查询vendor_id=?(该属性值)数据项,并且翻译为vendor_name的内容
package com.happy_retail_management.demo.product.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.fasterxml.jackson.annotation.JsonFormat; import com.happy_retail_management.common.aspect.annotation.Dict; import lombok.Data; import org.apache.ibatis.annotations.Result; import java.io.Serializable; import java.util.Date; @Data public class Product { @TableId(type = IdType.AUTO) private String productId; private String productName; @TableField("product_UPC") private String productUPC; private Double productPrice; private String productInventory; private String productUnits; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date productDate; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date expirationDate; private Double restockPrice; @Dict(dicTable="store",dicCode = "store_id" ,dicText = "store_name") private String storeId; @Dict(dicTable = "product_type",dicCode = "product_type_id",dicText = "product_type_name") private Integer productTypeId; @Dict(dicTable="brand",dicCode = "brand_id" ,dicText = "brand_name") private String brandId; @Dict(dicTable="vendor",dicCode = "vendor_id" ,dicText = "vendor_name") private String vendorId; }
6. 定义字典的切面
package com.happy_retail_management.common.aspect;
import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.happy_retail_management.common.api.vo.Result;
import com.happy_retail_management.common.aspect.annotation.Dict;
import com.happy_retail_management.common.utils.oConvertUtils;
import com.happy_retail_management.modules.system.service.ISysDictService;
import com.happy_retail_management.system.vo.DictModel;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Aspect
@Slf4j
@Component
public class AutoDict {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ISysDictService iSysDictService;
/**
* 定义切点
*/
@Pointcut(value = "execution(public * com.happy_retail_management..*.*Controller.*(..))||@annotation(com.happy_retail_management.common.aspect.annotation.AutoDict)")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long time1=System.currentTimeMillis();
Object proceed = pjp.proceed();
long time2=System.currentTimeMillis();
log.debug("获取Result结果耗费时间:"+(time2-time1)+"ms");
long time3=System.currentTimeMillis();
Object result = parseDictText(proceed);
long time4=System.currentTimeMillis();
log.debug("处理Result添加字典耗费时间:"+(time4-time3)+"ms");
return result;
}
/**
* 有两种字典,一种字典为
* 字典注入实现 通过对实体类添加注解@dict 来标识需要的字典内容,字典分为单字典code即可 ,table字典 code table text配合使用
* 示例为SysUser 字段为sex 添加了注解@Dict(dicCode = "sex") 会在字典服务立马查出来对应的text 然后在请求list的时候将这个字典text,已字段名称加_dictText形式返回到前端
* 例输入当前返回值的就会多出一个sex_dictText字段
* {
* sex:1,
* sex_dictText:"男"
* }
* @Dict(dictTable = "sys_user", dicText = "realname", dicCode = "id")
* 如果是查询表单
* @param result
* @return
*/
private Object parseDictText(Object result) {
if(result instanceof Result){
if(((Result) result).getData() instanceof IPage){
List<JSONObject> items = new ArrayList<>();
//1.筛选出嫁了Dict的字段列表
List<Field> dictFieldList = new ArrayList<>();
//数据字典列表,key=字典code,value=数据列表
Map<String,List<String>> dataListMap = new HashMap<>(5);
//取出结果集
List<Object> records=((IPage)((Result) result).getData()).getRecords();
//检查是否有字典字段
Boolean hasDict = checkHasDict(records);
if(!hasDict){
return result;
}
//进入字典翻译切面
for(Object record:records){
String json="{}";
try{
json = objectMapper.writeValueAsString(record);
}catch (Exception e){
log.error("json解析失败:"+e.getMessage());
}
//这里将record->json->JsonObject主要因为有可能类中@JsonFormat问题
JSONObject item = JSONObject.parseObject(json, Feature.OrderedField);
//遍历所有字段,把字典code取出来,放入Map中
for(Field field:oConvertUtils.getAllFields(record)){
//取出item对应的field值
String value = item.getString(field.getName());
if(oConvertUtils.isEmpty(value)){
continue;
}
if(field.getAnnotation(Dict.class)!=null){
if(!dictFieldList.contains(field)){
dictFieldList.add(field);
}
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dicTable();
List<String> dataList;
String dictCode = code;
if(!(table==null||"".equals(table))){//非空
//处理成为表名,表value,表key形式
dictCode = String.format("%s,%s,%s",table,text,code);
}
dataList = dataListMap.computeIfAbsent(dictCode,k->new ArrayList<>());
//list的去重添加
this.listAddAllDeduplicate(dataList, Arrays.asList(value.split(",")));
}
}
items.add(item);
}
//step2 调用翻译方法,一次性翻译
Map<String,List<DictModel>> translText = this.tranalateAllDict(dataListMap);
//step3 将翻译结果填充刀返回的结果中
for(JSONObject record:items){
for(Field field:dictFieldList){
String code = field.getAnnotation(Dict.class).dicCode();
String text = field.getAnnotation(Dict.class).dicText();
String table = field.getAnnotation(Dict.class).dicTable();
String fieldDictCode = code;
if(!StringUtils.isEmpty(table)){
fieldDictCode=String.format("%s,%s,%s",table,text,code);
}
String value=record.getString(field.getName());
if(oConvertUtils.isNotEmpty(value)){
List<DictModel> dictModels = translText.get(fieldDictCode);
if(dictModels==null||dictModels.size()==0){
continue;
}
String textVale=this.translDictText(dictModels,value);
record.put(field.getName()+ "_dictText",textVale);
}
}
}
((IPage) ((Result) result).getData()).setRecords(items);
// return result;
}
}
return result;
}
/**
* list去重复添加
*/
private void listAddAllDeduplicate(List<String> dataList, List<String> addList) {
// 筛选出dataList中没有的数据
List<String> filterList = addList.stream().filter(i -> !dataList.contains(i)).collect(Collectors.toList());
dataList.addAll(filterList);
}
/**
* 一次性将所有的字典都翻译了
* 1.所有的普通数据字典的所有数据只执行一次SQL
* 2.表字典相同的所有数据只执行一次SQL
*/
private Map<String,List<DictModel>> tranalateAllDict(Map<String, List<String>> dataListMap){
Map<String,List<DictModel>> translText = new HashMap<>();
//需要翻译的数据,有写可以从redis缓存中获取,就不走Sql查询
List<String> needTranslData = new ArrayList<>();
//step1,先通过redis中获取缓存字典数据
for(String dictCode:dataListMap.keySet()){
List<String> dataList = dataListMap.get(dictCode);
if(dataList.size()==0){
break;
}
//表字典需要翻译的数据
List<String> needTranslDataTable = new ArrayList<>();
for(String s:dataList){
String data = s.trim();
if(data.length() == 0){
continue;
}
if(dictCode.contains(",")){
String keyString = String.format("reatil:cache:dictTable::SimpleKey [%s,%s]",dictCode,data);
if(redisTemplate.hasKey(keyString)){
try{
String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode,k->new ArrayList<>());
list.add(new DictModel(data,text));
}catch(Exception e){
log.warn(e.getMessage());
}
} else if (!needTranslDataTable.contains(data)) {
//去重添加
needTranslDataTable.add(data);
}
}else {
String keyString = String.format("reatil:cache:dict::%s,%s",dictCode,data);
if (redisTemplate.hasKey(keyString)) {
try {
String text = oConvertUtils.getString(redisTemplate.opsForValue().get(keyString));
List<DictModel> list = translText.computeIfAbsent(dictCode, k -> new ArrayList<>());
list.add(new DictModel(data, text));
} catch (Exception e) {
log.warn(e.getMessage());
}
} else if (!needTranslData.contains(data)) {
// 去重添加
needTranslData.add(data);
}
}
}
//step2 调用数据库的翻译表字典
if(needTranslDataTable.size()>0){
String[] arr = dictCode.split(",");
String table=arr[0],text=arr[1],code=arr[2];
// String values=String.join(",",needTranslDataTable);
//将其翻译成List集合
List<DictModel> texts = iSysDictService.translateDictFromTableByKeys(table,text,code,needTranslDataTable);
List<DictModel> list=translText.computeIfAbsent(dictCode,k->new ArrayList<>());
list.addAll(texts);
//做redis缓存
for(DictModel dict:texts){
String redisKey = String.format("reatil:cache:dictTable::SimpleKey [%s,%s]",dictCode,dict.getValue());
try{
//保留5分钟
redisTemplate.opsForValue().set(redisKey,dict.getText(),300, TimeUnit.SECONDS);
}catch(Exception e){
log.warn(e.getMessage(), e);
}
}
}
}
//step3 调用数据库进行普通字典
if(needTranslData.size()>0){
List<String> dictCodeList=Arrays.asList((dataListMap.keySet().toArray(new String[]{})));
// 将不包含逗号的字典code筛选出来,因为带逗号的是表字典,而不是普通的数据字典,其实已经做了区分的了
List<String> filterDictCodes = dictCodeList.stream().filter(key -> !key.contains(",")).collect(Collectors.toList());
// String dictCodes = String.join(",", filterDictCodes);
// String values = String.join(",", needTranslData);
Map<String,List<DictModel>> manyDict = iSysDictService.queryManyDictByKeys(filterDictCodes, needTranslData);
for(String dictCode: manyDict.keySet()){
List<DictModel> list = translText.computeIfAbsent(dictCode,k->new ArrayList<>());
List<DictModel> newList = manyDict.get(dictCode);
list.addAll(newList);
//做redis缓存
for(DictModel dict:newList){
String redisKey = String.format("reatil:cache:dict::%s,%s",dictCode,dict.getValue());
try{
redisTemplate.opsForValue().set(redisKey,dict.getText());
}catch(Exception e){
log.warn(e.getMessage(),e);
}
}
}
}
return translText;
}
/**
* 字典值替换文本
*
* @param dictModels
* @param values
* @return
*/
private String translDictText(List<DictModel> dictModels, String values) {
List<String> result = new ArrayList<>();
// 允许多个逗号分隔,允许传数组对象
String[] splitVal = values.split(",");
for (String val : splitVal) {
String dictText = val;
for (DictModel dict : dictModels) {
if (val.equals(dict.getValue())) {
dictText = dict.getText();
break;
}
}
result.add(dictText);
}
return String.join(",", result);
}
/**
* 检查返回结果集中是否包含Dict注解
*/
private boolean checkHasDict(List<Object> records){
if(oConvertUtils.isNotEmpty(records)&&records.size()>0){//数据非空
for(Field field:oConvertUtils.getAllFields(records.get(0))) {
if (oConvertUtils.isNotEmpty(field.getAnnotation(Dict.class))) {
return true;
}
}
}
return false;
}
}
-
补充oConvertUtils的工具类的内容
/** * 获取类的所有属性,包括父类 * * @param object * @return */ public static Field[] getAllFields(Object object) { Class<?> clazz = object.getClass(); List<Field> fieldList = new ArrayList<>(); while (clazz != null) { fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()))); clazz = clazz.getSuperclass(); } Field[] fields = new Field[fieldList.size()]; fieldList.toArray(fields); return fields; } public static boolean isEmpty(Object object) { if (object == null) { return (true); } if ("".equals(object)) { return (true); } if ("null".equals(object)) { return (true); } return (false); } public static boolean isNotEmpty(Object object) { if (object != null && !"".equals(object) && !object.equals("null")) { return (true); } return (false); } /** * 处理空值和两边的空格 */ public static String getString(Object object) { if (isEmpty(object)) { return ""; } return (object.toString().trim()); }
7. 定义数据库查询的包
定义ISysDictService和ISysDictMapper以及对应的实现类,用于访问数据库;主要实现
-
ISysDictService接口
package com.happy_retail_management.modules.system.service; import com.happy_retail_management.system.vo.DictModel; import java.util.List; import java.util.Map; public interface ISysDictService { public List<DictModel> translateDictFromTableByKeys(String table, String text, String code, List<String> keys); public Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys); }
-
ISysDictServiceImpl
package com.happy_retail_management.modules.system.service.impl; import com.happy_retail_management.modules.system.mapper.SysDictMapper; import com.happy_retail_management.modules.system.service.ISysDictService; import com.happy_retail_management.system.vo.DictModel; import com.happy_retail_management.system.vo.DictModelMany; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class ISysDictServiceImpl implements ISysDictService { @Autowired private SysDictMapper sysDictMapper; /** * 查询外间 * @param table 表名 * @param text 显示字段名 * @param code 存储字段名 * @param keys 存储字段值 作为查询条件in * @return */ @Override public List<DictModel> translateDictFromTableByKeys(String table, String text, String code, List<String> keys) { String filterSql=""; //这里不做注入检查 return sysDictMapper.queryTableDictByKeysAndFilterSql(table, text, code, filterSql, keys); } public Map<String, List<DictModel>> queryManyDictByKeys(List<String> dictCodeList, List<String> keys){ List<DictModelMany> list = sysDictMapper.queryManyDictByKeys(dictCodeList, keys); Map<String, List<DictModel>> dictMap = new HashMap(5); for(DictModelMany dict:list){ List<DictModel> dictItemList = dictMap.computeIfAbsent(dict.getDictCode(),i->new ArrayList<>()); dictItemList.add(new DictModel(dict.getValue(),dict.getText())); } //需不需要引入Jave枚举类的字典呢? return dictMap; } }
-
SysDictMapper
package com.happy_retail_management.modules.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.happy_retail_management.modules.system.entity.SysDict; import com.happy_retail_management.system.vo.DictModel; import com.happy_retail_management.system.vo.DictModelMany; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface SysDictMapper extends BaseMapper<SysDict> { /** * 可通过多个字典code查询翻译文本 * @param dictCodeList 多个字典code * @param keys 数据列表 * @return */ List<DictModelMany> queryManyDictByKeys(@Param("dictCodeList") List<String> dictCodeList, @Param("keys") List<String> keys); /** * 查询字典表的数据 * @param table 表名 * @param text 显示字段名 * @param code 存储字段名 * @param filterSql 条件sql * @param codeValues 存储字段值 作为查询条件in * @return */ List<DictModel> queryTableDictByKeysAndFilterSql(@Param("table") String table, @Param("text") String text, @Param("code") String code, @Param("filterSql") String filterSql, @Param("codeValues") List<String> codeValues); }
-
SysDictMapper,xml
<?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.happy_retail_management.modules.system.mapper.SysDictMapper"> <!-- 查询表字典的数据, 支持设置过滤条件和code值 精确匹配查询【已加入SQL注入check】 --> <select id="queryTableDictByKeysAndFilterSql" parameterType="String" resultType="com.happy_retail_management.system.vo.DictModel"> select ${text} as "text", ${code} as "value" from ${table} where ${code} IN ( <foreach item="key" collection="codeValues" separator=","> #{key} </foreach> ) <if test="filterSql != null and filterSql != ''"> and ${filterSql} </if> </select> <!-- 通过字典code获取字典数据,可批量查询 --> <select id="queryManyDictByKeys" parameterType="String" resultType="com.happy_retail_management.system.vo.DictModelMany"> SELECT item.dict_code, item.item_text AS "text", item.item_value AS "value" FROM sys_dict_item item WHERE item.dict_code IN ( <foreach item="dictCode" collection="dictCodeList" separator=","> #{dictCode} </foreach> ) AND item.item_value IN ( <foreach item="key" collection="keys" separator=","> #{key} </foreach> ) </select> </mapper>
8. RedisTemple缓存乱码问题解决
- 当使用redisTemple存在乱码问题,说明实体类的序列化会变成二进制数据存入数据库中,这会导致乱码乱码;
- 或者我梦将RedisTemple的类型直接改为StringRedisTemple,其余都不变即可;
package com.happy_retail_management.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//首先解决key的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
//解决value的序列化方式
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//序列化时将类的数据类型存入json,以便反序列化的时候转换成正确的类型
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 解决jackson2无法反序列化LocalDateTime的问题
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
9. 测试Controller
-
测试的Controller的list接口
@RequestMapping("/list") public Result<IPage<Product>> list(@RequestBody Product product, @RequestParam(name="pageNo", defaultValue="1") Integer pageNo, @RequestParam(name="pageSize", defaultValue="10") Integer pageSize, HttpServletRequest request){ QueryWrapper<Product> queryWrapper = QueryGenerator.initQueryWrapper(product,request.getParameterMap()); Page<Product> page = new Page<>(pageNo,pageSize); IPage<Product>pageList =productService.page(page,queryWrapper); //如果在customerService类里面写baseMapper.selectPag(page,queryWrqpper) return Result.success(pageList); }
-
发送请求看到返回数据
可以看到下面的成功被翻译了