diff --git a/na-core/pom.xml b/na-core/pom.xml index c8b6b78..d525d65 100644 --- a/na-core/pom.xml +++ b/na-core/pom.xml @@ -30,38 +30,113 @@ 21 + org.springframework.boot spring-boot-starter-data-jdbc + org.springframework.boot spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-jdbc + org.springframework.boot spring-boot-starter-web + com.mysql mysql-connector-j runtime + org.projectlombok lombok true + org.springframework.boot spring-boot-starter-test test + + + + com.aliyun.oss + aliyun-sdk-oss + 3.15.1 + + + + com.alibaba + fastjson + 2.0.31 + + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.7 + + + + com.alibaba + druid-spring-boot-starter + 1.2.18 + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-aop + + + + + ch.qos.logback + logback-classic + + + + + com.fasterxml.jackson.core + jackson-databind + + + + + cn.hutool + hutool-all + 5.8.20 + + + + + org.lionsoul + ip2region + 2.6.4 + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + diff --git a/na-core/src/main/java/com/nailart/NaCoreApplication.java b/na-core/src/main/java/com/nailart/NaCoreApplication.java index 2e545a9..a652758 100644 --- a/na-core/src/main/java/com/nailart/NaCoreApplication.java +++ b/na-core/src/main/java/com/nailart/NaCoreApplication.java @@ -1,11 +1,12 @@ package com.nailart; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication +@MapperScan("com.nailart.mapper") public class NaCoreApplication { - public static void main(String[] args) { SpringApplication.run(NaCoreApplication.class, args); } diff --git a/na-core/src/main/java/com/nailart/aop/RequestLogAspect.java b/na-core/src/main/java/com/nailart/aop/RequestLogAspect.java new file mode 100644 index 0000000..3f0ff59 --- /dev/null +++ b/na-core/src/main/java/com/nailart/aop/RequestLogAspect.java @@ -0,0 +1,91 @@ +package com.nailart.aop; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nailart.dto.RequestLogDTO; +import com.nailart.utils.IpAddressUtil; +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.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import jakarta.servlet.http.HttpServletRequest; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Date; + +/** + * @Description 实现日志记录 + * @Classname RequestLogDTOAspect + * @Date 2025/7/11 0:05 + * @Created by 21616 + */ +@Aspect +@Component +public class RequestLogAspect { + @Autowired + private IpAddressUtil ipAddressUtil; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final String logFilePath = "request_logs.json"; // 日志文件路径 + + @Pointcut("execution(* com.nailart.controller..*(..))") + public void requestPointcut() {} + + @Around("requestPointcut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + long startTime = System.currentTimeMillis(); + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) { + return joinPoint.proceed(); + } + HttpServletRequest request = attributes.getRequest(); + + RequestLogDTO log = new RequestLogDTO(); + log.setRequestTime(new Date()); + log.setIp(getClientIp(request)); + log.setCity(ipAddressUtil.getCityInfo(log.getIp())); + log.setUrl(request.getRequestURL().toString()); + log.setHttpMethod(request.getMethod()); + log.setMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); + + try { + return joinPoint.proceed(); + } finally { + log.setResponseTime(System.currentTimeMillis() - startTime); + // 直接将日志写入文件,不使用日志框架 + writeLogToFile(log); + } + } + + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip; + } + + private void writeLogToFile(RequestLogDTO log) { + try (PrintWriter writer = new PrintWriter(new FileWriter(logFilePath, true))) { + // 将日志对象序列化为JSON并写入文件 + writer.println(objectMapper.writeValueAsString(log)); + } catch (IOException e) { + // 处理写入异常(可以选择记录到标准输出或忽略) + System.err.println("Failed to write request log: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/na-core/src/main/java/com/nailart/config/CorsConfig.java b/na-core/src/main/java/com/nailart/config/CorsConfig.java new file mode 100644 index 0000000..1b4d379 --- /dev/null +++ b/na-core/src/main/java/com/nailart/config/CorsConfig.java @@ -0,0 +1,31 @@ +package com.nailart.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @Description 跨域处理 + * @Classname CorsConfig + * @Date 2025/7/11 0:01 + * @Created by 21616 + */ +// 案例 一 +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + //是否发送Cookie + .allowCredentials(false) + //放行哪些原始域 + .allowedOrigins("*") + .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) + .allowedHeaders("*") + .exposedHeaders("*"); + } +} diff --git a/na-core/src/main/java/com/nailart/config/MyBatisPlusConfig.java b/na-core/src/main/java/com/nailart/config/MyBatisPlusConfig.java new file mode 100644 index 0000000..9f5179d --- /dev/null +++ b/na-core/src/main/java/com/nailart/config/MyBatisPlusConfig.java @@ -0,0 +1,29 @@ +package com.nailart.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @Classname my + * @Description TODO + * @Date 2025/7/10 18:50 + * @Created by 21616 + */ +@Configuration +public class MyBatisPlusConfig { + /** + * 分页插件配置 + * + * @return MybatisPlusInterceptor + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 向MyBatis-Plus的过滤器链中添加分页拦截器,需要设置数据库类型(主要用于分页方言) + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} diff --git a/na-core/src/main/java/com/nailart/config/RedisConfig.java b/na-core/src/main/java/com/nailart/config/RedisConfig.java new file mode 100644 index 0000000..69a7f62 --- /dev/null +++ b/na-core/src/main/java/com/nailart/config/RedisConfig.java @@ -0,0 +1,37 @@ +package com.nailart.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @Description redis配置类 + * @Classname RedisConfig + * @Date 2025/7/10 22:23 + * @Created by 21616 + */ +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + + // 设置键的序列化方式 + template.setKeySerializer(new StringRedisSerializer()); + // 设置值的序列化方式 + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + + // 设置哈希键的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + // 设置哈希值的序列化方式 + template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + + template.afterPropertiesSet(); + return template; + } +} diff --git a/na-core/src/main/java/com/nailart/controller/PortfolioController.java b/na-core/src/main/java/com/nailart/controller/PortfolioController.java new file mode 100644 index 0000000..91f5935 --- /dev/null +++ b/na-core/src/main/java/com/nailart/controller/PortfolioController.java @@ -0,0 +1,131 @@ +package com.nailart.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.nailart.dataobj.PortfolioDO; +import com.nailart.enums.RespCodeEnum; +import com.nailart.service.PortfolioService; +import com.nailart.vo.RespVO; +import org.apache.ibatis.annotations.Param; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @Classname PortfolioController + * @Description 作品集控制类 + * @Date 2025/7/10 18:48 + * @Created by 21616 + */ +@RestController +@RequestMapping("/portfolio") +public class PortfolioController { + private final PortfolioService portfolioService; + + public PortfolioController(PortfolioService portfolioService) { + this.portfolioService = portfolioService; + } + + @GetMapping("/list") + public RespVO getPortfolioList(@RequestParam("page") int page, @RequestParam("size") int size) { + RespVO respVO = new RespVO(); + if (page <= 0 || size <= 0) { + respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode()); + respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage()); + return respVO; + } + try { + IPage portfolioDOIPage = portfolioService.listByPage(page, size); + respVO.setCode(RespCodeEnum.SUCCESS.getCode()); + respVO.setMsg(RespCodeEnum.SUCCESS.getMessage()); + respVO.setData(portfolioDOIPage); + } catch (Exception e) { + respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode()); + respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage()); + } + return respVO; + + } + + @PostMapping("/add") + public RespVO addPortfolio(PortfolioDO portfolioDO) { + RespVO respVO = new RespVO(); + if (portfolioDO == null) { + respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode()); + respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage()); + return respVO; + } + try { + Integer i = portfolioService.addPortfolio(portfolioDO); + respVO.setCode(RespCodeEnum.SUCCESS.getCode()); + respVO.setMsg(RespCodeEnum.SUCCESS.getMessage()); + respVO.setData(i); + } catch (Exception e) { + respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode()); + respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage()); + } + return respVO; + + + } + + @PostMapping("/update") + public RespVO updatePortfolio(PortfolioDO portfolioDO) { + RespVO respVO = new RespVO(); + if (portfolioDO == null) { + respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode()); + respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage()); + return respVO; + } + try { + Integer i = portfolioService.updatePortfolio(portfolioDO); + respVO.setCode(RespCodeEnum.SUCCESS.getCode()); + respVO.setMsg(RespCodeEnum.SUCCESS.getMessage()); + respVO.setData(i); + } catch (Exception e) { + respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode()); + respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage()); + } + return respVO; + } + + @PostMapping("/delete") + public RespVO deletePortfolio(@RequestParam("id") Integer id) { + RespVO respVO = new RespVO(); + if (id == null) { + respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode()); + respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage()); + return respVO; + } + try { + Integer i = portfolioService.deletePortfolio(id); + respVO.setCode(RespCodeEnum.SUCCESS.getCode()); + respVO.setMsg(RespCodeEnum.SUCCESS.getMessage()); + respVO.setData(i); + } catch (Exception e) { + respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode()); + respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage()); + } + return respVO; + } + + @PostMapping("/addLoves") + public RespVO addLoves(@RequestParam("id") Integer id,@RequestParam Integer changes) { + RespVO respVO = new RespVO(); + if (id == null) { + respVO.setCode(RespCodeEnum.BAD_REQUEST.getCode()); + respVO.setMsg(RespCodeEnum.BAD_REQUEST.getMessage()); + return respVO; + } + try { + Integer i = portfolioService.addLoves(id, changes); + respVO.setCode(RespCodeEnum.SUCCESS.getCode()); + respVO.setMsg(RespCodeEnum.SUCCESS.getMessage()); + respVO.setData(i); + } catch (Exception e) { + respVO.setCode(RespCodeEnum.INTERNAL_SERVER_ERROR.getCode()); + respVO.setMsg(RespCodeEnum.INTERNAL_SERVER_ERROR.getMessage()); + } + return respVO; + } +} diff --git a/na-core/src/main/java/com/nailart/dataobj/PortfolioDO.java b/na-core/src/main/java/com/nailart/dataobj/PortfolioDO.java new file mode 100644 index 0000000..416e5e8 --- /dev/null +++ b/na-core/src/main/java/com/nailart/dataobj/PortfolioDO.java @@ -0,0 +1,54 @@ +package com.nailart.dataobj; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import org.springframework.data.relational.core.mapping.Table; + +@Data +@TableName("portfolio_info") +public class PortfolioDO { + /** + * id + */ + private Long id; + /** + * 标题 + */ + private String title; + /** + * 描述 + */ + private String description; + /** + * 点赞数 + */ + private Integer loves; + /** + * 图片地址 + */ + private String imgUrl; + /** + * 图片文件名 + */ + private String imgFilename; + /** + * 标签 + */ + private String tag; + /** + * 标签样式 + */ + private String tagStyle; + /** + * 创建时间 + */ + private String createTime; + /** + * 状态 + */ + private Integer status; + /** + * 价格 + */ + private Integer amt; +} diff --git a/na-core/src/main/java/com/nailart/dto/RequestLogDTO.java b/na-core/src/main/java/com/nailart/dto/RequestLogDTO.java new file mode 100644 index 0000000..ae62731 --- /dev/null +++ b/na-core/src/main/java/com/nailart/dto/RequestLogDTO.java @@ -0,0 +1,33 @@ +package com.nailart.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * @Description 日志实体类 + * @Classname RequestLogDTO + * @Date 2025/7/11 0:04 + * @Created by 21616 + */ + + +@Data +public class RequestLogDTO { + // 请求IP + private String ip; + // IP对应的城市 + private String city; + // 请求时间 + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8") + private Date requestTime; + // 请求方法(类名+方法名) + private String method; + // 请求URL + private String url; + // HTTP方法(GET/POST等) + private String httpMethod; + // 响应时间(ms) + private long responseTime; +} diff --git a/na-core/src/main/java/com/nailart/enums/RespCodeEnum.java b/na-core/src/main/java/com/nailart/enums/RespCodeEnum.java new file mode 100644 index 0000000..e6b4595 --- /dev/null +++ b/na-core/src/main/java/com/nailart/enums/RespCodeEnum.java @@ -0,0 +1,80 @@ +package com.nailart.enums; + +import lombok.Getter; + +/** + * @Description 统一响应状态码枚举类 + * @Classname ResptCodeEnum + * @Date 2025/7/10 22:34 + * @Created by 21616 + */ +@Getter +public enum RespCodeEnum { + + // ===================== 成功状态码 ===================== + /** 成功 */ + SUCCESS(200, "操作成功"), + /** 创建成功 */ + CREATED(201, "资源创建成功"), + /** 无内容(适用于删除成功等场景) */ + NO_CONTENT(204, "操作成功,无返回内容"), + + + // ===================== 客户端错误 ===================== + /** 请求参数错误 */ + BAD_REQUEST(400, "请求参数格式错误或不完整"), + /** 未授权(未登录) */ + UNAUTHORIZED(401, "请先登录"), + /** 权限不足 */ + FORBIDDEN(403, "没有权限执行该操作"), + /** 资源不存在 */ + NOT_FOUND(404, "请求的资源不存在"), + /** 请求方法不支持 */ + METHOD_NOT_ALLOWED(405, "不支持的请求方法(如用GET访问POST接口)"), + /** 请求频率过高 */ + TOO_MANY_REQUESTS(429, "请求过于频繁,请稍后再试"), + + + // ===================== 服务器错误 ===================== + /** 服务器内部错误 */ + INTERNAL_SERVER_ERROR(500, "服务器内部错误,请联系管理员"), + /** 服务暂不可用 */ + SERVICE_UNAVAILABLE(503, "服务暂时不可用,请稍后再试"), + /** 数据库错误 */ + DATABASE_ERROR(5001, "数据库操作失败"), + /** 缓存错误 */ + CACHE_ERROR(5002, "缓存操作失败"), + + + // ===================== 业务自定义错误 ===================== + /** 数据重复(如新增已存在的记录) */ + DATA_DUPLICATE(601, "数据已存在,请勿重复添加"), + /** 数据验证失败(如字段格式不符合要求) */ + DATA_VALIDATION_FAILED(602, "数据验证失败,请检查输入"), + /** 第三方接口调用失败 */ + THIRD_PARTY_SERVICE_ERROR(603, "第三方服务调用失败"); + + + /** 状态码 */ + private final int code; + /** 描述信息 */ + private final String message; + + // 构造方法 + RespCodeEnum(int code, String message) { + this.code = code; + this.message = message; + } + + /** + * 根据状态码获取枚举实例(可选工具方法) + */ + public static RespCodeEnum getByCode(int code) { + for (RespCodeEnum resultCode : values()) { + if (resultCode.code == code) { + return resultCode; + } + } + return null; + } +} diff --git a/na-core/src/main/java/com/nailart/mapper/PortfolioMapper.java b/na-core/src/main/java/com/nailart/mapper/PortfolioMapper.java new file mode 100644 index 0000000..712d819 --- /dev/null +++ b/na-core/src/main/java/com/nailart/mapper/PortfolioMapper.java @@ -0,0 +1,9 @@ +package com.nailart.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.nailart.dataobj.PortfolioDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortfolioMapper extends BaseMapper { +} diff --git a/na-core/src/main/java/com/nailart/service/PortfolioService.java b/na-core/src/main/java/com/nailart/service/PortfolioService.java new file mode 100644 index 0000000..da696ed --- /dev/null +++ b/na-core/src/main/java/com/nailart/service/PortfolioService.java @@ -0,0 +1,17 @@ +package com.nailart.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import com.nailart.dataobj.PortfolioDO; + + +public interface PortfolioService extends IService { + IPage listByPage(int page, int size); + Integer addPortfolio(PortfolioDO portfolioDO); + + Integer updatePortfolio(PortfolioDO portfolioDO); + + Integer deletePortfolio(Integer id); + + Integer addLoves(Integer id, Integer changes); +} diff --git a/na-core/src/main/java/com/nailart/service/impl/PortfolioServiceImpl.java b/na-core/src/main/java/com/nailart/service/impl/PortfolioServiceImpl.java new file mode 100644 index 0000000..e068a47 --- /dev/null +++ b/na-core/src/main/java/com/nailart/service/impl/PortfolioServiceImpl.java @@ -0,0 +1,61 @@ +package com.nailart.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.nailart.dataobj.PortfolioDO; +import com.nailart.mapper.PortfolioMapper; +import com.nailart.service.PortfolioService; +import org.springframework.stereotype.Service; + +/** + * + * @Author: xiaowang + * @Date: 2021/5/10 14:48 + */ +@Service +public class PortfolioServiceImpl extends ServiceImpl implements PortfolioService { + + private final PortfolioMapper portfolioMapper; + + public PortfolioServiceImpl(PortfolioMapper portfolioMapper) { + this.portfolioMapper = portfolioMapper; + } + + /** + * + * @param page + * @param size + * @return + */ + @Override + public IPage listByPage(int page, int size) { + Page objectPage = new Page<>(page, size); + return portfolioMapper.selectPage(objectPage, null); + } + + @Override + public Integer addPortfolio(PortfolioDO portfolioDO) { + return portfolioMapper.insert(portfolioDO); + } + + @Override + public Integer updatePortfolio(PortfolioDO portfolioDO) { + return portfolioMapper.updateById(portfolioDO); + } + + @Override + public Integer deletePortfolio(Integer id) { + return portfolioMapper.deleteById(id); + } + + @Override + public Integer addLoves(Integer id, Integer changes) { + PortfolioDO portfolioDO = portfolioMapper.selectById(id); + if (portfolioDO == null) { + return 0; + } + portfolioDO.setLoves(portfolioDO.getLoves() + changes); + return portfolioMapper.updateById(portfolioDO); + } +} diff --git a/na-core/src/main/java/com/nailart/utils/AliyunOSSUtils.java b/na-core/src/main/java/com/nailart/utils/AliyunOSSUtils.java new file mode 100644 index 0000000..6974f08 --- /dev/null +++ b/na-core/src/main/java/com/nailart/utils/AliyunOSSUtils.java @@ -0,0 +1,313 @@ +package com.nailart.utils; + +/** + * @Classname AliyunOSSUtils + * @Description 阿里云对象存储工具类 + * @Date 2025/7/10 19:11 + * @Created by 21616 + */ + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URL; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + * 阿里云OSS操作工具类 + */ +@Component +public class AliyunOSSUtils { + + private static final Logger logger = LoggerFactory.getLogger(AliyunOSSUtils.class); + + @Value("${aliyun.oss.endpoint}") + private String endpoint; + + @Value("${aliyun.oss.access-key-id}") + private String accessKeyId; + + @Value("${aliyun.oss.access-key-secret}") + private String accessKeySecret; + + @Value("${aliyun.oss.bucket-name}") + private String bucketName; + + @Value("${aliyun.oss.file-host}") + private String fileHost; + + /** + * 上传文件到OSS + * + * @param file 上传的文件 + * @param directory 存储目录 + * @return 文件访问URL + */ + public String uploadFile(MultipartFile file, String directory) { + OSS ossClient = null; + try { + // 生成文件名 + String originalFilename = file.getOriginalFilename(); + String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); + String fileName = UUID.randomUUID().toString().replace("-", "") + fileExtension; + + // 构建完整路径 + if (directory != null && !directory.isEmpty()) { + if (!directory.endsWith("/")) { + directory = directory + "/"; + } + fileName = directory + fileName; + } + + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 上传文件 + ossClient.putObject(bucketName, fileName, new ByteArrayInputStream(file.getBytes())); + + // 返回访问URL + return fileHost + "/" + fileName; + } catch (Exception e) { + logger.error("上传文件失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 上传本地文件到OSS + * + * @param filePath 文件路径 + * @param targetFileName 目标文件名 + * @return 文件访问URL + */ + public String uploadLocalFile(String filePath, String targetFileName) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 上传文件 + ossClient.putObject(bucketName, targetFileName, new File(filePath)); + + // 返回访问URL + return fileHost + "/" + targetFileName; + } catch (Exception e) { + logger.error("上传本地文件失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 下载文件 + * + * @param objectName 对象名称 + * @param localFilePath 本地文件路径 + * @return 是否下载成功 + */ + public boolean downloadFile(String objectName, String localFilePath) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 下载OSS文件到本地文件 + ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFilePath)); + + return true; + } catch (Exception e) { + logger.error("下载文件失败: {}", e.getMessage(), e); + return false; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 删除文件 + * + * @param objectName 对象名称 + * @return 是否删除成功 + */ + public boolean deleteFile(String objectName) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 删除文件 + ossClient.deleteObject(bucketName, objectName); + + return true; + } catch (Exception e) { + logger.error("删除文件失败: {}", e.getMessage(), e); + return false; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 批量删除文件 + * + * @param objectNames 对象名称列表 + * @return 删除结果 + */ + public DeleteObjectsResult deleteFiles(List objectNames) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 批量删除文件 + DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName) + .withKeys(objectNames); + return ossClient.deleteObjects(deleteObjectsRequest); + } catch (Exception e) { + logger.error("批量删除文件失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 获取文件URL(带签名,用于临时访问) + * + * @param objectName 对象名称 + * @param expires 过期时间(秒) + * @return 带签名的URL + */ + public String getSignedUrl(String objectName, long expires) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 设置URL过期时间 + Date expiration = new Date(System.currentTimeMillis() + expires * 1000); + + // 生成签名URL + URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration); + + return url.toString(); + } catch (Exception e) { + logger.error("获取签名URL失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 判断文件是否存在 + * + * @param objectName 对象名称 + * @return 是否存在 + */ + public boolean doesObjectExist(String objectName) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 判断文件是否存在 + return ossClient.doesObjectExist(bucketName, objectName); + } catch (Exception e) { + logger.error("检查文件是否存在失败: {}", e.getMessage(), e); + return false; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 获取文件元信息 + * + * @param objectName 对象名称 + * @return 文件元信息 + */ + public ObjectMetadata getObjectMetadata(String objectName) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 获取文件元信息 + return ossClient.getObjectMetadata(bucketName, objectName); + } catch (Exception e) { + logger.error("获取文件元信息失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } + + /** + * 列出指定目录下的文件 + * + * @param prefix 前缀(目录) + * @param maxKeys 最大返回数量 + * @return 文件列表 + */ + public ListObjectsV2Result listObjects(String prefix, int maxKeys) { + OSS ossClient = null; + try { + // 创建OSSClient实例 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + + // 构造ListObjects请求 + ListObjectsV2Request listObjectsV2Request = new ListObjectsV2Request(); + listObjectsV2Request.setBucketName(bucketName); + listObjectsV2Request.setPrefix(prefix); + listObjectsV2Request.setMaxKeys(maxKeys); + + // 列出文件 + return ossClient.listObjectsV2(listObjectsV2Request); + } catch (Exception e) { + logger.error("列出文件失败: {}", e.getMessage(), e); + return null; + } finally { + // 关闭OSSClient + if (ossClient != null) { + ossClient.shutdown(); + } + } + } +} diff --git a/na-core/src/main/java/com/nailart/utils/DateUtils.java b/na-core/src/main/java/com/nailart/utils/DateUtils.java new file mode 100644 index 0000000..c6bbcc4 --- /dev/null +++ b/na-core/src/main/java/com/nailart/utils/DateUtils.java @@ -0,0 +1,199 @@ +package com.nailart.utils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +/** + * 日期处理工具类,基于Java 8+的java.time包 + */ +public class DateUtils { + // 默认日期格式 + public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; + // 默认日期时间格式 + public static final String DEFAULT_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + // 默认时间格式 + public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; + + // 日期格式化器 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT); + // 日期时间格式化器 + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATETIME_FORMAT); + // 时间格式化器 + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT); + + /** + * 获取当前日期(yyyy-MM-dd) + * @return 当前日期字符串 + */ + public static String getCurrentDate() { + return LocalDate.now().format(DATE_FORMATTER); + } + + /** + * 获取当前日期时间(yyyy-MM-dd HH:mm:ss) + * @return 当前日期时间字符串 + */ + public static String getCurrentDateTime() { + return LocalDateTime.now().format(DATETIME_FORMATTER); + } + + /** + * 获取当前时间(HH:mm:ss) + * @return 当前时间字符串 + */ + public static String getCurrentTime() { + return LocalTime.now().format(TIME_FORMATTER); + } + + /** + * 日期转字符串 + * @param date 日期 + * @param pattern 格式模式 + * @return 格式化后的日期字符串 + */ + public static String formatDate(LocalDate date, String pattern) { + return date.format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 日期时间转字符串 + * @param dateTime 日期时间 + * @param pattern 格式模式 + * @return 格式化后的日期时间字符串 + */ + public static String formatDateTime(LocalDateTime dateTime, String pattern) { + return dateTime.format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 字符串转日期 + * @param dateStr 日期字符串 + * @param pattern 格式模式 + * @return 解析后的LocalDate + */ + public static LocalDate parseDate(String dateStr, String pattern) { + return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 字符串转日期时间 + * @param dateTimeStr 日期时间字符串 + * @param pattern 格式模式 + * @return 解析后的LocalDateTime + */ + public static LocalDateTime parseDateTime(String dateTimeStr, String pattern) { + return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 计算两个日期之间的天数差 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 天数差(正数表示结束日期晚于开始日期) + */ + public static long daysBetween(LocalDate startDate, LocalDate endDate) { + return ChronoUnit.DAYS.between(startDate, endDate); + } + + /** + * 计算两个日期时间之间的小时差 + * @param startDateTime 开始日期时间 + * @param endDateTime 结束日期时间 + * @return 小时差 + */ + public static long hoursBetween(LocalDateTime startDateTime, LocalDateTime endDateTime) { + return ChronoUnit.HOURS.between(startDateTime, endDateTime); + } + + /** + * 计算两个日期时间之间的分钟差 + * @param startDateTime 开始日期时间 + * @param endDateTime 结束日期时间 + * @return 分钟差 + */ + public static long minutesBetween(LocalDateTime startDateTime, LocalDateTime endDateTime) { + return ChronoUnit.MINUTES.between(startDateTime, endDateTime); + } + + /** + * 在指定日期上增加/减少天数 + * @param date 基准日期 + * @param days 增加(正数)/减少(负数)的天数 + * @return 计算后的日期 + */ + public static LocalDate plusDays(LocalDate date, int days) { + return date.plusDays(days); + } + + /** + * 在指定日期时间上增加/减少小时 + * @param dateTime 基准日期时间 + * @param hours 增加(正数)/减少(负数)的小时 + * @return 计算后的日期时间 + */ + public static LocalDateTime plusHours(LocalDateTime dateTime, int hours) { + return dateTime.plusHours(hours); + } + + /** + * 在指定日期时间上增加/减少分钟 + * @param dateTime 基准日期时间 + * @param minutes 增加(正数)/减少(负数)的分钟 + * @return 计算后的日期时间 + */ + public static LocalDateTime plusMinutes(LocalDateTime dateTime, int minutes) { + return dateTime.plusMinutes(minutes); + } + + /** + * Date转LocalDateTime + * @param date Date对象 + * @return LocalDateTime对象 + */ + public static LocalDateTime toLocalDateTime(Date date) { + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * LocalDateTime转Date + * @param dateTime LocalDateTime对象 + * @return Date对象 + */ + public static Date toDate(LocalDateTime dateTime) { + return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * 获取一天的开始时间(00:00:00) + * @param date 日期 + * @return 当天开始时间的LocalDateTime + */ + public static LocalDateTime getStartOfDay(LocalDate date) { + return date.atStartOfDay(); + } + + /** + * 获取一天的结束时间(23:59:59) + * @param date 日期 + * @return 当天结束时间的LocalDateTime + */ + public static LocalDateTime getEndOfDay(LocalDate date) { + return LocalDateTime.of(date, LocalTime.MAX); + } + + /** + * 判断日期是否在范围内 + * @param targetDate 目标日期 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return true-在范围内;false-不在范围内 + */ + public static boolean isDateInRange(LocalDate targetDate, LocalDate startDate, LocalDate endDate) { + return !targetDate.isBefore(startDate) && !targetDate.isAfter(endDate); + } +} diff --git a/na-core/src/main/java/com/nailart/utils/IpAddressUtil.java b/na-core/src/main/java/com/nailart/utils/IpAddressUtil.java new file mode 100644 index 0000000..b9f31f3 --- /dev/null +++ b/na-core/src/main/java/com/nailart/utils/IpAddressUtil.java @@ -0,0 +1,59 @@ +package com.nailart.utils; + +import cn.hutool.core.io.resource.ResourceUtil; +import org.lionsoul.ip2region.xdb.Searcher; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @Description IP 解析工具类,使用 ip2region 实现 IP 地址解析城市功能 + * @Classname IpAddressUtil + * @Date 2025/7/11 0:04 + * @Created by 21616 + */ + +@Component +public class IpAddressUtil { + private static Searcher searcher; + + // 初始化IP数据库 + static { + try { + // 从资源文件中读取ip2region.xdb + InputStream inputStream = ResourceUtil.getStream("ip2region.xdb"); + byte[] dbContent = new byte[inputStream.available()]; + inputStream.read(dbContent); + searcher = Searcher.newWithBuffer(dbContent); + } catch (Exception e) { + throw new RuntimeException("初始化IP地址解析器失败", e); + } + } + + /** + * 根据IP获取城市信息 + * + * @param ip IP地址 + * @return 城市信息(格式:国家|区域|省份|城市|ISP) + */ + public String getCityInfo(String ip) { + if (ip == null || ip.isEmpty()) { + return "未知IP"; + } + + // 本地IP处理 + if ("127.0.0.1".equals(ip) || "localhost".equals(ip)) { + return "本地主机"; + } + + try { + String result = searcher.search(ip); + return result != null ? result : "未知地址"; + } catch (IOException e) { + return "解析失败"; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/na-core/src/main/java/com/nailart/utils/RedisUtils.java b/na-core/src/main/java/com/nailart/utils/RedisUtils.java new file mode 100644 index 0000000..6c13d7c --- /dev/null +++ b/na-core/src/main/java/com/nailart/utils/RedisUtils.java @@ -0,0 +1,665 @@ +package com.nailart.utils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.RedisStringCommands; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * Redis工具类,封装常用的Redis操作 + */ +@Component +public class RedisUtils { + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + // =============================common============================ + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据key获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete(Arrays.asList(key)); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 递增 + * + * @param key 键 + * @param delta 要增加几(大于0) + * @return 增加后的值 + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * 递减 + * + * @param key 键 + * @param delta 要减少几(大于0) + * @return 减少后的值 + */ + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递减因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false 失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false 失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return 增加后的值 + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(大于0) + * @return 减少后的值 + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return Set集合 + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) expire(key, time); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return 长度数值 + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return List集合 + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return 长度数值 + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return 值对象 + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return 操作结果 + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return 操作结果 + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) expire(key, time); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值列表 + * @return 操作结果 + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值列表 + * @param time 时间(秒) + * @return 操作结果 + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) expire(key, time); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return 操作结果 + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + Long remove = redisTemplate.opsForList().remove(key, count, value); + return remove; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + // ============================HyperLogLog============================= + + /** + * 添加元素到HyperLogLog + * + * @param key 键 + * @param values 值 + * @return 添加结果 + */ + public long pfAdd(String key, String... values) { + return redisTemplate.opsForHyperLogLog().add(key, (Object[]) values); + } + + /** + * 获取HyperLogLog的基数估算值 + * + * @param key 键 + * @return 基数估算值 + */ + public long pfCount(String key) { + return redisTemplate.opsForHyperLogLog().size(key); + } + + /** + * 合并多个HyperLogLog + * + * @param destKey 目标键 + * @param sourceKeys 源键 + * @return 合并结果 + */ + public long pfMerge(String destKey, String... sourceKeys) { + return redisTemplate.opsForHyperLogLog().union(destKey, sourceKeys); + } + + // ============================BitMap============================= + + /** + * 设置BitMap的位值 + * + * @param key 键 + * @param offset 偏移量 + * @param value 值 + * @return 设置结果 + */ + public boolean setBit(String key, long offset, boolean value) { + return redisTemplate.opsForValue().setBit(key, offset, value); + } + + /** + * 获取BitMap的位值 + * + * @param key 键 + * @param offset 偏移量 + * @return 位值 + */ + public boolean getBit(String key, long offset) { + return redisTemplate.opsForValue().getBit(key, offset); + } + + /** + * 统计BitMap中为1的位的数量 + * + * @param key 键 + * @return 为1的位的数量 + */ + public long bitCount(String key) { + return stringRedisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes())); + } + + /** + * 统计BitMap中指定范围内为1的位的数量 + * + * @param key 键 + * @param start 起始字节 + * @param end 结束字节 + * @return 为1的位的数量 + */ + public long bitCount(String key, long start, long end) { + return stringRedisTemplate.execute((RedisCallback) con -> con.bitCount(key.getBytes(), start, end)); + } + + /** + * 对多个BitMap执行位运算 + * + * @param op 运算类型 + * @param destKey 目标键 + * @param keys 源键 + * @return 运算结果的长度 + */ + public long bitOp(RedisStringCommands.BitOperation op, String destKey, String... keys) { + byte[][] bytes = new byte[keys.length][]; + for (int i = 0; i < keys.length; i++) { + bytes[i] = keys[i].getBytes(); + } + return stringRedisTemplate.execute((RedisCallback) con -> + con.bitOp(op, destKey.getBytes(), bytes)); + } +} diff --git a/na-core/src/main/java/com/nailart/vo/RespVO.java b/na-core/src/main/java/com/nailart/vo/RespVO.java new file mode 100644 index 0000000..6fcb209 --- /dev/null +++ b/na-core/src/main/java/com/nailart/vo/RespVO.java @@ -0,0 +1,16 @@ +package com.nailart.vo; + +import lombok.Data; + +/** + * @Description 所有的请求返回封装体 + * @Classname RespVO + * @Date 2025/7/10 22:32 + * @Created by 21616 + */ +@Data +public class RespVO { + private Integer code; + private String msg; + private Object data; +} diff --git a/na-core/src/main/resources/application.properties b/na-core/src/main/resources/application.properties deleted file mode 100644 index ce92952..0000000 --- a/na-core/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=na-core diff --git a/na-core/src/main/resources/application.yml b/na-core/src/main/resources/application.yml new file mode 100644 index 0000000..28535d3 --- /dev/null +++ b/na-core/src/main/resources/application.yml @@ -0,0 +1,76 @@ +server: + port: 8090 + servlet: + context-path: /api # 应用访问路径前缀 + +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource # 使用Druid连接池 + driver-class-name: com.mysql.cj.jdbc.Driver # MySQL驱动类 + url: jdbc:mysql://wcy111.top:3306/nailart?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC # 数据库连接地址 + username: root # 数据库用户名 + password: 122503wcy # 数据库密码 + druid: # Druid连接池配置 + initial-size: 5 # 初始化连接数 + min-idle: 5 # 最小空闲连接数 + max-active: 20 # 最大活跃连接数 + max-wait: 60000 # 获取连接时的最大等待时间(毫秒) + time-between-eviction-runs-millis: 60000 # 间隔多久进行一次检测,检测需要关闭的空闲连接(毫秒) + min-evictable-idle-time-millis: 300000 # 一个连接在池中最小生存的时间(毫秒) + validation-query: SELECT 1 FROM DUAL # 验证连接是否有效的SQL语句 + test-while-idle: true # 空闲时是否检测连接有效性 + test-on-borrow: false # 借出连接时是否检测有效性 + test-on-return: false # 归还连接时是否检测有效性 + pool-prepared-statements: true # 是否缓存PreparedStatement + max-pool-prepared-statement-per-connection-size: 20 # 每个连接缓存的PreparedStatement数量 + filters: stat,wall,log4j # 配置监控统计拦截的filters + connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 连接属性配置(合并SQL、慢SQL阈值) + # Redis配置 + redis: + host: wcy111.top # Redis服务器地址 + port: 6379 # Redis服务器端口 + password: 122503wcy # Redis密码 + timeout: 10000ms # 连接超时时间 + database: 0 # 数据库索引 + lettuce: # Lettuce连接池配置 + pool: + max-active: 8 # 最大连接数 + max-wait: -1ms # 最大阻塞等待时间(负数表示无限制) + max-idle: 8 # 最大空闲连接 + min-idle: 0 # 最小空闲连接 + +# MyBatis-Plus配置 +mybatis-plus: + mapper-locations: classpath:mapper/*.xml # Mapper XML文件存放路径 + type-aliases-package: com.nailart.do # 实体类别名包路径 + global-config: + db-config: + id-type: auto # 主键生成策略(AUTO=数据库ID自增) + field-strategy: not_null # 字段验证策略(NOT_NULL=非NULL判断) + table-underline: false # 表名是否使用下划线命名 + logic-delete-field: deleted # 全局逻辑删除字段名 + logic-not-delete-value: 0 # 逻辑未删除值 + logic-delete-value: 1 # 逻辑已删除值 + configuration: + map-underscore-to-camel-case: false # 是否开启下划线转驼峰命名 + cache-enabled: false # 是否开启二级缓存 + call-setters-on-nulls: true # 查询结果是否返回null字段 + jdbc-type-for-null: 'null' # 空值的JDBC类型映射 + +# 阿里云OSS配置 +aliyun: + oss: + endpoint: oss-cn-chengdu.aliyuncs.com # OSS服务端点 + access-key-id: LTAI5tLbzuS6P8rGQ4Rw9rN9 # 访问密钥ID + access-key-secret: R3zbklnTmEMks4HEiKRaxlXwBJvY8T # 访问密钥Secret + bucket-name: yuminailart # 存储桶名称 + file-host: yuminailart.oss-cn-chengdu.aliyuncs.com # 文件主机地址 + +# 日志配置 +logging: + level: + root: info # 根日志级别 + com.example.mapper: debug # Mapper接口日志级别(调试用) + file: + name: logs/application.log # 日志文件存储路径 \ No newline at end of file diff --git a/na-core/src/main/resources/ip2region.xdb b/na-core/src/main/resources/ip2region.xdb new file mode 100644 index 0000000..7052c05 Binary files /dev/null and b/na-core/src/main/resources/ip2region.xdb differ diff --git a/na-frontend/package-lock.json b/na-frontend/package-lock.json index 4587634..3fa8e1e 100644 --- a/na-frontend/package-lock.json +++ b/na-frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "na-frontend", "version": "0.1.0", "dependencies": { + "axios": "^1.10.0", "core-js": "^3.8.3", "element-ui": "^2.15.14", "less-loader": "^12.3.0", @@ -3442,6 +3443,12 @@ "babel-runtime": "6.x" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz", @@ -3490,6 +3497,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-helper-vue-jsx-merge-props": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz", @@ -3903,7 +3921,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4238,6 +4255,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz", @@ -5075,6 +5104,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -5253,7 +5291,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -5428,7 +5465,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5438,7 +5474,6 @@ "version": "1.3.0", "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5455,7 +5490,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -5464,6 +5498,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", @@ -6385,7 +6434,6 @@ "version": "1.15.9", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, "funding": [ { "type": "individual", @@ -6402,6 +6450,22 @@ } } }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", @@ -6485,7 +6549,6 @@ "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6522,7 +6585,6 @@ "version": "1.3.0", "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6547,7 +6609,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -6666,7 +6727,6 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6732,7 +6792,6 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6741,6 +6800,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hash-sum": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz", @@ -6752,7 +6826,6 @@ "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -8024,7 +8097,6 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8138,7 +8210,6 @@ "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8148,7 +8219,6 @@ "version": "2.1.35", "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -9804,6 +9874,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", diff --git a/na-frontend/package.json b/na-frontend/package.json index 7ee1f30..cb68330 100644 --- a/na-frontend/package.json +++ b/na-frontend/package.json @@ -8,6 +8,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "axios": "^1.10.0", "core-js": "^3.8.3", "element-ui": "^2.15.14", "less-loader": "^12.3.0", diff --git a/na-frontend/src/App.vue b/na-frontend/src/App.vue index 44a26c9..afeebda 100644 --- a/na-frontend/src/App.vue +++ b/na-frontend/src/App.vue @@ -6,9 +6,9 @@
- - - + + +
@@ -48,22 +48,12 @@ export default { /* 固定顶部导航栏 */ .nav-bar { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 100; height: 45px; background: #fff; } /* 固定底部TabBar */ .tab-bar { - position: fixed; - bottom: 0; - left: 0; - right: 0; - z-index: 100; height: 50px; background: #fff; } @@ -71,9 +61,7 @@ export default { /* 中间内容区:避开上下Bar的位置 */ .content-container { position: relative; - min-height: 100vh; - padding-top: 50px; - padding-bottom: 60px; + height: calc(100vh - 110px); overflow: hidden; /* 防止内容溢出 */ } diff --git a/na-frontend/src/components/NavBar.vue b/na-frontend/src/components/NavBar.vue index 63a540e..c0f5b83 100644 --- a/na-frontend/src/components/NavBar.vue +++ b/na-frontend/src/components/NavBar.vue @@ -1,6 +1,6 @@