Merge pull request #654 from xinnan-tech/manager-api-admin

完善了管理员的接口,删除,用户分页,全部设备
This commit is contained in:
hrz
2025-04-05 14:56:37 +08:00
committed by GitHub
20 changed files with 355 additions and 26 deletions
@@ -0,0 +1,38 @@
package xiaozhi.common.page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 扩展的分页对象
* @author zjy
* @since 2025-4-2
*/
@Data
@Schema(description = "分页数据")
public class ExtendPageData<T> implements Serializable {
@Schema(description = "总记录数")
private int totalCount;
@Schema(description = "页数")
private int totalPage;
@Schema(description = "列表数据")
private List<T> list;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
* @param page 页数
*/
public ExtendPageData(List<T> list, long total, long page) {
this.list = list;
this.totalCount = (int) total;
this.totalPage = (int) page;
}
}
@@ -26,4 +26,13 @@ public class RedisKeys {
public static String getDeviceCaptchaKey(String captcha) {
return "sys:device:captcha:" + captcha;
}
/**
* 用户id的Key
*/
public static String getUserIdKey(Long userid) {
return "sys:username:id:" + userid;
}
}
@@ -2,6 +2,9 @@ package xiaozhi.common.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
@@ -60,4 +63,39 @@ public class DateUtils {
return null;
}
/**
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
* @param date
* @return
*/
public static String getShortTime(Date date) {
if (date == null) {
return null;
}
// 将 Date 转换为 Instant
LocalDateTime localDateTime = date.toInstant()
// 获取系统默认时区
.atZone(ZoneId.systemDefault())
// 转换为 LocalDateTime
.toLocalDateTime();
// 当前时间
LocalDateTime now = LocalDateTime.now();
// 时间差,单位为秒
long secondsBetween = ChronoUnit.SECONDS.between(localDateTime, now);
if (secondsBetween <= 10) {
return "刚刚";
} else if (secondsBetween < 60) {
return secondsBetween + "秒前";
} else if (secondsBetween < 60 * 60) {
return secondsBetween / 60 + "分钟前";
} else if (secondsBetween < 86400) {
return secondsBetween / 3600 + "小时前";
} else if (secondsBetween < 604800) {
return secondsBetween / 86400 + "天前";
} else {
// 超过一周,显示完整日期时间
return format(date,DATE_TIME_PATTERN);
}
}
}
@@ -22,4 +22,10 @@ public interface AgentService extends BaseService<AgentEntity> {
* 获取智能体详情
*/
AgentEntity getAgentById(String id);
/**
* 删除这个用户的所有
* @param userId
*/
void deleteAgentByUserId(String userId);
}
@@ -4,16 +4,19 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.transaction.annotation.Transactional;
import xiaozhi.common.page.PageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.agent.dao.AgentDao;
import xiaozhi.modules.agent.entity.AgentEntity;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.device.entity.DeviceEntity;
@Service
public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> implements AgentService {
@@ -62,4 +65,11 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
return super.insert(entity);
}
@Override
public void deleteAgentByUserId(String userId) {
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId);
baseDao.delete(wrapper);
}
}
@@ -0,0 +1,27 @@
package xiaozhi.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import lombok.Data;
/**
* 查询所有设备的DTO
*
* @author zjy
* @since 2025-3-21
*/
@Data
@Schema(description = "查询所有设备的DTO")
public class DevicePageUserDTO {
@Schema(description = "设备关键词")
private String keywords;
@Schema(description = "页数")
@Min(value = 0, message = "{page.number}")
private String page;
@Schema(description = "显示列数")
@Min(value = 0, message = "{limit.number}")
private String limit;
}
@@ -2,10 +2,13 @@ package xiaozhi.modules.device.service;
import java.util.List;
import xiaozhi.common.page.ExtendPageData;
import xiaozhi.modules.device.dto.DeviceBindDTO;
import xiaozhi.modules.device.dto.DevicePageUserDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
public interface DeviceService {
@@ -26,7 +29,7 @@ public interface DeviceService {
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
/**
* 获取用户设备列表
* 获取用户指定智能体的设备列表
*/
List<DeviceEntity> getUserDevices(Long userId, String agentId);
@@ -39,4 +42,25 @@ public interface DeviceService {
* 设备激活
*/
Boolean deviceActivation(String activationCode);
/**
* 删除此用户的所有设备
* @param userId 用户id
*/
void deleteByUserId(Long userId);
/**
* 获取指定用户的设备数量
* @param userId 用户id
* @return 设备数量
*/
Long selectCountByUserId(Long userId);
/**
* 分页获取全部设备信息
*
* @param dto 分页查找参数
* @return 用户列表分页数据
*/
ExtendPageData<UserShowDeviceListVO> page(DevicePageUserDTO dto);
}
@@ -8,6 +8,7 @@ import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
@@ -18,32 +19,42 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import cn.hutool.core.util.RandomUtil;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.ExtendPageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.user.UserDetail;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.DateUtils;
import xiaozhi.modules.device.dao.DeviceDao;
import xiaozhi.modules.device.dto.DeviceBindDTO;
import xiaozhi.modules.device.dto.DevicePageUserDTO;
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
import xiaozhi.modules.device.entity.DeviceEntity;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
import xiaozhi.modules.security.user.SecurityUser;
import xiaozhi.modules.sys.service.SysUserUtilService;
@Service
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
private final DeviceDao deviceDao;
private final SysUserUtilService sysUserUtilService;
private final String frontedUrl;
private final RedisTemplate<String, Object> redisTemplate;
// 添加构造函数来初始化 deviceMapper
public DeviceServiceImpl(DeviceDao deviceDao,
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
RedisTemplate<String, Object> redisTemplate) {
public DeviceServiceImpl(DeviceDao deviceDao, SysUserUtilService sysUserUtilService,
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
RedisTemplate<String, Object> redisTemplate) {
this.deviceDao = deviceDao;
this.sysUserUtilService = sysUserUtilService;
this.frontedUrl = frontedUrl;
this.redisTemplate = redisTemplate;
}
@@ -197,6 +208,46 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
baseDao.delete(wrapper);
}
@Override
public void deleteByUserId(Long userId) {
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId);
baseDao.delete(wrapper);
}
@Override
public Long selectCountByUserId(Long userId) {
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
wrapper.eq("user_id", userId);
return baseDao.selectCount(wrapper);
}
@Override
public ExtendPageData<UserShowDeviceListVO> page(DevicePageUserDTO dto) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(Constant.PAGE, dto.getPage());
params.put(Constant.LIMIT, dto.getLimit());
IPage<DeviceEntity> page = baseDao.selectPage(
getPage(params, "sort", true),
// 定义查询条件
new QueryWrapper<DeviceEntity>()
// 必须设备关键词查找
.like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords()));
// 循环处理page获取回来的数据,返回需要的字段
List<UserShowDeviceListVO> list = page.getRecords().stream().map(device -> {
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
// 把最后修改的时间,改为简短描述的时间
vo.setRecentChatTime(DateUtils.getShortTime(device.getUpdateDate()));
sysUserUtilService.assignUsername(device.getUserId(),
vo::setBindUserName);
vo.setDeviceType(device.getBoard());
return vo;
}).toList();
//计算页数
long num = page.getTotal() / Long.parseLong(dto.getPage());
return new ExtendPageData<>(list, page.getTotal(), num);
}
private DeviceReportRespDTO.ServerTime buildServerTime() {
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
TimeZone tz = TimeZone.getDefault();
@@ -0,0 +1,31 @@
package xiaozhi.modules.device.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "用户显示设备列表VO")
public class UserShowDeviceListVO {
@Schema(description = "app版本")
private String appVersion;
@Schema(description = "绑定用户名称")
private String bindUserName;
@Schema(description = "设备型号")
private String deviceType;
@Schema(description = "设备唯一标识符")
private String id;
@Schema(description = "mac地址")
private String macAddress;
@Schema(description = "开启OTA")
private Integer otaUpgrade;
@Schema(description = "最近对话时间")
private String recentChatTime;
}
@@ -17,9 +17,13 @@ import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.page.ExtendPageData;
import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
import xiaozhi.common.validator.ValidatorUtils;
import xiaozhi.modules.device.dto.DevicePageUserDTO;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
import xiaozhi.modules.sys.service.SysUserService;
import xiaozhi.modules.sys.vo.AdminPageUserVO;
@@ -37,6 +41,8 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
public class AdminController {
private final SysUserService sysUserService;
private final DeviceService deviceService;
@GetMapping("/users")
@Operation(summary = "分页查找用户")
@RequiresPermissions("sys:role:superAdmin")
@@ -45,16 +51,16 @@ public class AdminController {
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<PageData<AdminPageUserVO>> pageUser(
public Result<ExtendPageData<AdminPageUserVO>> pageUser(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
AdminPageUserDTO dto = new AdminPageUserDTO();
dto.setMobile((String) params.get("mobile"));
dto.setLimit((String) params.get(Constant.LIMIT));
dto.setPage((String) params.get(Constant.PAGE));
ValidatorUtils.validateEntity(dto);
PageData<AdminPageUserVO> page = sysUserService.page(dto);
return new Result<PageData<AdminPageUserVO>>().ok(page);
ValidatorUtils.validateEntity(dto);
ExtendPageData<AdminPageUserVO> page = sysUserService.page(dto);
return new Result<ExtendPageData<AdminPageUserVO>>().ok(page);
}
@PutMapping("/users/{id}")
@@ -70,7 +76,7 @@ public class AdminController {
@Operation(summary = "用户删除")
@RequiresPermissions("sys:role:superAdmin")
public Result<Void> delete(@PathVariable Long id) {
sysUserService.delete(new Long[] { id });
sysUserService.deleteById(id);
return new Result<>();
}
@@ -82,9 +88,14 @@ public class AdminController {
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
})
public Result<Void> pageDevice(
public Result<ExtendPageData<UserShowDeviceListVO>> pageDevice(
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
// TODO 等设备功能模块写好
return new Result<Void>().error(600, "等设备功能模块写好");
DevicePageUserDTO dto = new DevicePageUserDTO();
dto.setKeywords((String) params.get("keywords"));
dto.setLimit((String) params.get(Constant.LIMIT));
dto.setPage((String) params.get(Constant.PAGE));
ValidatorUtils.validateEntity(dto);
ExtendPageData<UserShowDeviceListVO> page = deviceService.page(dto);
return new Result<ExtendPageData<UserShowDeviceListVO>>().ok(page);
}
}
@@ -1,6 +1,7 @@
package xiaozhi.modules.sys.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import lombok.Data;
/**
@@ -10,15 +11,17 @@ import lombok.Data;
* @since 2025-3-21
*/
@Data
@Schema(description = "音色分页参数")
@Schema(description = "管理员分页用户的参数DTO")
public class AdminPageUserDTO {
@Schema(description = "手机号码")
private String mobile;
@Schema(description = "页数")
@Min(value = 0, message = "{sort.number}")
private String page;
@Schema(description = "显示列数")
@Min(value = 0, message = "{sort.number}")
private String limit;
}
@@ -1,6 +1,6 @@
package xiaozhi.modules.sys.service;
import xiaozhi.common.page.PageData;
import xiaozhi.common.page.ExtendPageData;
import xiaozhi.common.service.BaseService;
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
import xiaozhi.modules.sys.dto.PasswordDTO;
@@ -19,7 +19,11 @@ public interface SysUserService extends BaseService<SysUserEntity> {
void save(SysUserDTO dto);
void delete(Long[] ids);
/**
* 删除指定用户,且有关联的数据设备和智能体
* @param ids
*/
void deleteById(Long ids);
/**
* 验证是否允许修改密码更改
@@ -51,5 +55,5 @@ public interface SysUserService extends BaseService<SysUserEntity> {
* @param dto 分页查找参数
* @return 用户列表分页数据
*/
PageData<AdminPageUserVO> page(AdminPageUserDTO dto);
ExtendPageData<AdminPageUserVO> page(AdminPageUserDTO dto);
}
@@ -0,0 +1,19 @@
package xiaozhi.modules.sys.service;
import java.util.function.Consumer;
/**
* 定义一个系统用户工具类,避免和用户模块循环依赖
* 如用户和设备互相依赖,用户需要获取所有设备,设备又需要获取每个设备的用户名
* @author zjy
* @since 2025-4-2
*/
public interface SysUserUtilService {
/**
* 赋值用户名
* @param userId 用户id
* @param setter 赋值方法
*/
void assignUsername( Long userId, Consumer<String> setter);
}
@@ -1,6 +1,5 @@
package xiaozhi.modules.sys.service.impl;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -19,9 +18,11 @@ import lombok.AllArgsConstructor;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
import xiaozhi.common.page.PageData;
import xiaozhi.common.page.ExtendPageData;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.modules.agent.service.AgentService;
import xiaozhi.modules.device.service.DeviceService;
import xiaozhi.modules.security.password.PasswordUtils;
import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
@@ -40,6 +41,10 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
private final SysUserDao sysUserDao;
private final DeviceService deviceService;
private final AgentService agentService;
@Override
public SysUserDTO getByUsername(String username) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
@@ -87,10 +92,14 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(Long[] ids) {
public void deleteById(Long id) {
// 删除用户
baseDao.deleteBatchIds(Arrays.asList(ids));
// TODO 除了要删除用户还要删除用户关联的设备,对话,智能体。等此3个功能完善在添加
baseDao.deleteById(id);
// 删除设备
deviceService.deleteByUserId(id);
// 删除智能体
agentService.deleteById(id);
// TODO 除了要删除用户还要删除用户关联的对话
}
@Override
@@ -142,7 +151,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
}
@Override
public PageData<AdminPageUserVO> page(AdminPageUserDTO dto) {
public ExtendPageData<AdminPageUserVO> page(AdminPageUserDTO dto) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(Constant.PAGE, dto.getPage());
params.put(Constant.LIMIT, dto.getLimit());
@@ -152,16 +161,19 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
new QueryWrapper<SysUserEntity>()
// 必须按照手机号码查找
.eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile()));
// 循环处理page获取回来的数据,返回需要的字段
List<AdminPageUserVO> list = page.getRecords().stream().map(user -> {
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
adminPageUserVO.setUserid(user.getId().toString());
adminPageUserVO.setMobile(user.getUsername());
adminPageUserVO.setStatus(user.getStatus());
// TODO 2. 等设备功能写好,获取对应数据
adminPageUserVO.setDeviceCount("0");
String deviceCount = deviceService.selectCountByUserId(user.getId()).toString();
adminPageUserVO.setDeviceCount(deviceCount);
adminPageUserVO.setStatus(user.getStatus().toString());
return adminPageUserVO;
}).toList();
return new PageData<>(list, page.getTotal());
//计算页数
long num = page.getTotal() / Long.parseLong(dto.getPage());
return new ExtendPageData<>(list, page.getTotal(),num);
}
private boolean isStrongPassword(String password) {
@@ -0,0 +1,35 @@
package xiaozhi.modules.sys.service.impl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.modules.sys.dao.SysUserDao;
import xiaozhi.modules.sys.entity.SysUserEntity;
import xiaozhi.modules.sys.service.SysUserUtilService;
import java.util.function.Consumer;
@Service
@AllArgsConstructor
public class SysUserUtilServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserUtilService {
private RedisUtils redisUtils;
@Override
public void assignUsername(Long userId, Consumer<String> setter) {
String userIdKey = RedisKeys.getUserIdKey(userId);
String username = redisUtils.get(userIdKey).toString();
if(username != null){
setter.accept(username);
}else {
SysUserEntity entity = baseDao.selectById(userId);
if (entity != null) {
username = entity.getUsername();
redisUtils.set(userIdKey,username,10);
setter.accept(username);
}
}
}
}
@@ -23,4 +23,7 @@ public class AdminPageUserVO {
@Schema(description = "用户id")
private String userid;
@Schema(description = "用户状态")
private String status;
}
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
@@ -3,6 +3,8 @@ id.require=ID can not be empty
id.null=ID has to be empty
sort.number=The sort value cannot be less than 0
page.number=The page value cannot be less than 0
limit.number=The limit value cannot be less than 0
sysdict.type.require=The dictionary type cannot be empty
sysdict.name.require=The dictionary name cannot be empty
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
page.number=\u9801\u6578\u4E0D\u80FD\u5C0F\u4E8E0
limit.number=\u5217\u6578\u4E0D\u80FD\u5C0F\u4E8E0
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A