Commit 955b441b by Jony.L

商品展示 初步提交

parent fb777398
......@@ -144,6 +144,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
</dependencies>
</project>
package com.luhu.computility.framework.common.core.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @Author: jony
* @Date : 2025/7/30 16:52
* @VERSION v1.0
*/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key)
{
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
......@@ -34,11 +34,11 @@ public class ComputilityWebAutoConfiguration implements WebMvcConfigurer {
@Resource
private WebProperties webProperties;
/**
* 应用名
*/
@Value("${spring.application.name}")
private String applicationName;
// /**
// * 应用名
// */
// @Value("${spring.application.name}")
// private String applicationName;
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
......@@ -58,16 +58,16 @@ public class ComputilityWebAutoConfiguration implements WebMvcConfigurer {
&& antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包
}
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogCommonApi apiErrorLogApi) {
return new GlobalExceptionHandler(applicationName, apiErrorLogApi);
}
// @Bean
// @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
// public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogCommonApi apiErrorLogApi) {
// return new GlobalExceptionHandler(applicationName, apiErrorLogApi);
// }
@Bean
public GlobalResponseBodyHandler globalResponseBodyHandler() {
return new GlobalResponseBodyHandler();
}
// @Bean
// public GlobalResponseBodyHandler globalResponseBodyHandler() {
// return new GlobalResponseBodyHandler();
// }
@Bean
@SuppressWarnings("InstantiationOfUtilityClass")
......
......@@ -18,6 +18,7 @@ import com.luhu.computility.framework.common.biz.infra.logger.dto.ApiErrorLogCre
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
......@@ -49,7 +50,7 @@ import static com.luhu.computility.framework.common.exception.enums.GlobalErrorC
* @author 芋道源码
*/
@RestControllerAdvice
@AllArgsConstructor
//@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {
......@@ -60,15 +61,22 @@ public class GlobalExceptionHandler {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
private final String applicationName;
private final ApiErrorLogCommonApi apiErrorLogApi;
public GlobalExceptionHandler(
@Value("${spring.application.name}") String applicationName, // 直接注入配置值
ApiErrorLogCommonApi apiErrorLogApi
) {
this.applicationName = applicationName;
this.apiErrorLogApi = apiErrorLogApi;
}
/**
* 处理所有异常,主要是提供给 Filter 使用
* 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
*
* @param request 请求
* @param ex 异常
* @param ex 异常
* @return 通用返回
*/
public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
......@@ -110,7 +118,7 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求参数缺失
*
* <p>
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
*/
@ExceptionHandler(value = MissingServletRequestParameterException.class)
......@@ -121,7 +129,7 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求参数类型错误
*
* <p>
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
......@@ -168,16 +176,16 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求参数类型错误
*
* <p>
* 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
if(ex.getCause() instanceof InvalidFormatException) {
if (ex.getCause() instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
}else {
} else {
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
}
......@@ -204,7 +212,7 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求地址不存在
*
* <p>
* 注意,它需要设置如下两个配置项:
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
* 2. spring.mvc.static-path-pattern 为 /statics/**
......@@ -226,7 +234,7 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求方法不正确
*
* <p>
* 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
......@@ -237,7 +245,7 @@ public class GlobalExceptionHandler {
/**
* 处理 Spring Security 权限不足的异常
*
* <p>
* 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
*/
@ExceptionHandler(value = AccessDeniedException.class)
......@@ -249,7 +257,7 @@ public class GlobalExceptionHandler {
/**
* 处理业务异常 ServiceException
*
* <p>
* 例如说,商品库存不足,用户手机号已存在。
*/
@ExceptionHandler(value = ServiceException.class)
......@@ -300,7 +308,7 @@ public class GlobalExceptionHandler {
// 执行插入 errorLog
apiErrorLogApi.createApiErrorLogAsync(errorLog);
} catch (Throwable th) {
log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
}
}
......
......@@ -17,6 +17,13 @@
</description>
<dependencies>
<dependency>
<groupId>com.luhu</groupId>
<artifactId>computility-module-product</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.luhu</groupId>
<artifactId>computility-module-system</artifactId>
......
package com.luhu.computility.module.biz.controller.app.index.vo;
import lombok.Data;
/**
* 计算资源首页 Response VO
*
* @Author: jony
* @Date : 2025/8/5 14:41
* @VERSION v1.0
*/
@Data
public class ComputeRespVO {
private String model;
private String cpu;
private String gpu;
private String memory;
private String storage;
private String term;
private String publicPrice;
}
package com.luhu.computility.module.biz.controller.client.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: jony
* @Date : 2025/8/1 09:51
* @VERSION v1.0
*/
@Schema(description = "客户端 - 首页二级菜单接口返回 DTO")
@Data
public class MenuDTO implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String value;
}
......@@ -61,6 +61,14 @@ public interface ProductSpuService {
ProductSpuDO getSpu(Long id, boolean includeDeleted);
/**
* 根据categoryId获得商品 SPU 列表
*
* @param categoryId
* @return
*/
List<ProductSpuDO> getSpuListByCategoryId(Long categoryId);
/**
* 获得商品 SPU 列表
*
* @param ids 编号数组
......
......@@ -198,6 +198,14 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
@Override
public List<ProductSpuDO> getSpuListByCategoryId(Long categoryId){
if (ObjectUtil.isEmpty(categoryId)) {
return Collections.emptyList();
}
return productSpuMapper.selectList("category_id", categoryId);
}
@Override
public List<ProductSpuDO> getSpuList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
......
......@@ -113,6 +113,12 @@
<groupId>com.anji-plus</groupId>
<artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
<scope>compile</scope>
</dependency>
</dependencies>
......
package com.luhu.computility.module.system.service.dict;
import cn.hutool.core.util.StrUtil;
import com.luhu.computility.framework.common.core.redis.RedisCache;
import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.framework.common.util.date.LocalDateTimeUtils;
import com.luhu.computility.framework.common.util.object.BeanUtils;
import com.luhu.computility.framework.common.util.spring.SpringUtils;
import com.luhu.computility.framework.dict.core.DictFrameworkUtils;
import com.luhu.computility.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
import com.luhu.computility.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;
import com.luhu.computility.module.system.dal.dataobject.dict.DictDataDO;
import com.luhu.computility.module.system.dal.dataobject.dict.DictTypeDO;
import com.luhu.computility.module.system.dal.mysql.dict.DictDataMapper;
import com.luhu.computility.module.system.dal.mysql.dict.DictTypeMapper;
import com.google.common.annotations.VisibleForTesting;
import com.luhu.computility.module.system.util.dict.DictUtils;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.luhu.computility.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.luhu.computility.module.system.enums.ErrorCodeConstants.*;
......@@ -32,6 +42,25 @@ public class DictTypeServiceImpl implements DictTypeService {
@Resource
private DictTypeMapper dictTypeMapper;
@Resource
private DictDataMapper dictDataMapper;
/**
* 项目启动时,初始化字典到缓存
*/
@PostConstruct
public void init(){
loadingDictCache();
}
private void loadingDictCache(){
List<DictDataDO> dictDataList = dictDataMapper.selectListByStatusAndDictType(0, null);
Map<String, List<DictDataDO>> dictDataMap = dictDataList.stream().collect(Collectors.groupingBy(DictDataDO::getDictType));
for(Map.Entry<String,List<DictDataDO>> entry : dictDataMap.entrySet()){
DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(DictDataDO::getSort)).collect(Collectors.toList()));
}
}
@Override
public PageResult<DictTypeDO> getDictTypePage(DictTypePageReqVO pageReqVO) {
return dictTypeMapper.selectPage(pageReqVO);
......
package com.luhu.computility.module.system.util.dict;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONArray;
import com.luhu.computility.framework.common.core.redis.RedisCache;
import com.luhu.computility.framework.common.util.spring.SpringUtils;
import com.luhu.computility.module.system.dal.dataobject.dict.DictDataDO;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 字典工具类
*
* @Author: jony
* @Date : 2025/7/31 09:55
* @VERSION v1.0
*/
public class DictUtils {
/**
* 分隔符
*/
public static final String SEPARATOR = ",";
/**
* 设置字典缓存
*
* @param key 参数键
* @param dictDatas 字典数据列表
*/
public static void setDictCache(String key, List<DictDataDO> dictDatas) {
SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas);
}
/**
* 获取字典缓存
*
* @param key 参数键
* @return dictDatas 字典数据列表
*/
public static List<DictDataDO> getDictCache(String key) {
// JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
// if (ObjectUtil.isNotNull(arrayCache)) {
// return arrayCache.toJavaList(DictDataDO.class);
// }
// return null;
Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (cacheObj == null) {
return null;
}
List<DictDataDO> dictList;
if (cacheObj instanceof JSONArray) {
// 新数据:JSONArray 直接转 List
dictList = ((JSONArray) cacheObj).toJavaList(DictDataDO.class);
} else if (cacheObj instanceof ArrayList) {
dictList = (List<DictDataDO>) cacheObj;
} else {
// 其他未知类型,返回空列表(避免报错)
dictList = new ArrayList<>();
}
return dictList;
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @return 字典标签
*/
public static String getDictLabel(String dictType, String dictValue) {
return getDictLabel(dictType, dictValue, SEPARATOR);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @return 字典值
*/
public static String getDictValue(String dictType, String dictLabel) {
return getDictValue(dictType, dictLabel, SEPARATOR);
}
/**
* 根据字典类型和字典值获取字典标签
*
* @param dictType 字典类型
* @param dictValue 字典值
* @param separator 分隔符
* @return 字典标签
*/
public static String getDictLabel(String dictType, String dictValue, String separator) {
StringBuilder propertyString = new StringBuilder();
List<DictDataDO> datas = getDictCache(dictType);
if (ObjectUtil.isNotNull(datas)) {
if (StringUtils.containsAny(separator, dictValue)) {
for (DictDataDO dict : datas) {
for (String value : dictValue.split(separator)) {
if (value.equals(dict.getValue())) {
propertyString.append(dict.getLabel()).append(separator);
break;
}
}
}
} else {
for (DictDataDO dict : datas) {
if (dictValue.equals(dict.getValue())) {
return dict.getLabel();
}
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 根据字典类型和字典标签获取字典值
*
* @param dictType 字典类型
* @param dictLabel 字典标签
* @param separator 分隔符
* @return 字典值
*/
public static String getDictValue(String dictType, String dictLabel, String separator) {
StringBuilder propertyString = new StringBuilder();
List<DictDataDO> datas = getDictCache(dictType);
if (StringUtils.containsAny(separator, dictLabel) && ObjectUtil.isNotEmpty(datas)) {
for (DictDataDO dict : datas) {
for (String label : dictLabel.split(separator)) {
if (label.equals(dict.getLabel())) {
propertyString.append(dict.getValue()).append(separator);
break;
}
}
}
} else {
for (DictDataDO dict : datas) {
if (dictLabel.equals(dict.getLabel())) {
return dict.getValue();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 删除指定字典缓存
*
* @param key 字典键
*/
public static void removeDictCache(String key) {
SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key));
}
/**
* 清空字典缓存
*/
public static void clearDictCache() {
// Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*");
SpringUtils.getBean(RedisCache.class).deleteObject("*");
}
/**
* 设置cache key
*
* @param configKey 参数键
* @return 缓存键key
*/
public static String getCacheKey(String configKey) {
// return CacheConstants.SYS_DICT_KEY + configKey;
return "sys_dict:" + configKey;
}
}
package com.luhu.computility.server;
import com.luhu.computility.module.system.dal.mysql.dict.DictDataMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 项目的启动类
......@@ -13,7 +15,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @author 芋道源码
*/
@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${computility.info.base-package}
@SpringBootApplication(scanBasePackages = {"${computility.info.base-package}.server", "${computility.info.base-package}.module"})
@SpringBootApplication(scanBasePackages = {"${computility.info.base-package}.server", "${computility.info.base-package}.module", "${computility.info.base-package}.framework"})
public class ComputilityServerApplication {
public static void main(String[] args) {
......@@ -21,7 +23,7 @@ public class ComputilityServerApplication {
// 如果你碰到启动的问题,请认真阅读 https://doc.iocoder.cn/quick-start/ 文章
// 如果你碰到启动的问题,请认真阅读 https://doc.iocoder.cn/quick-start/ 文章
SpringApplication.run(ComputilityServerApplication.class, args);
ConfigurableApplicationContext run = SpringApplication.run(ComputilityServerApplication.class, args);
// new SpringApplicationBuilder(ComputilityServerApplication.class)
// .applicationStartup(new BufferingApplicationStartup(20480))
// .run(args);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment