{"id":20837420,"url":"https://github.com/xdwangwei/gulimall","last_synced_at":"2025-05-08T19:53:25.083Z","repository":{"id":38353663,"uuid":"294894315","full_name":"xdwangwei/gulimall","owner":"xdwangwei","description":"分布式项目谷粒商城，前后端分离，前端基于Vue+ElementUI，后端基于SpringBoot+MybatisPlus+Mysql+Redis+ElasticSearch，具备商城所有功能（注册（社交登录）、登录、上架、检索、购物车、订单、支付、秒杀），项目框架由renrenfast开源项目搭建。项目中使用到SpringCache，SpringSession，Rabbitmq，SpringGateway+Nginx，Openfeign，alibaba-nacos做分布式注册中心和配置中心，alibaba-seata做分布式事务控制，redisson做布式锁。","archived":false,"fork":false,"pushed_at":"2025-04-29T03:23:26.000Z","size":3291,"stargazers_count":99,"open_issues_count":0,"forks_count":21,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-29T04:30:03.505Z","etag":null,"topics":["elasticsearch","gateway","mybatisplus","mysql","nacos","nginx","redisson","seata","springboot","vue"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xdwangwei.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-09-12T07:33:28.000Z","updated_at":"2025-04-29T03:23:29.000Z","dependencies_parsed_at":"2022-08-09T03:01:50.660Z","dependency_job_id":null,"html_url":"https://github.com/xdwangwei/gulimall","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xdwangwei%2Fgulimall","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xdwangwei%2Fgulimall/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xdwangwei%2Fgulimall/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xdwangwei%2Fgulimall/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xdwangwei","download_url":"https://codeload.github.com/xdwangwei/gulimall/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253141418,"owners_count":21860538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elasticsearch","gateway","mybatisplus","mysql","nacos","nginx","redisson","seata","springboot","vue"],"created_at":"2024-11-18T01:07:28.796Z","updated_at":"2025-05-08T19:53:24.986Z","avatar_url":"https://github.com/xdwangwei.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gulimall\n分布式商城\n\n## Docker\n\n### Docker安装RabbitMQ\n```shell\ndocker run --name rabbitmq -p 5672:5672 -p 15672:15672 -d rabbitmq:3.8-management\n```\n\n### Docker安装Mysql\n```shell\n# 编写my.cnf配置文件\nvim /root/docker/mysql/conf/my.cnf\n[mysql]\n# 设置mysql客户端默认字符集\ndefault-character-set=utf8mb4\n[client]\n# 设置mysql客户端连接服务端时默认使用的端口\nport=3306\ndefault-character-set=utf8mb4\n[mysqld]\n# 设置3306端口\nport=3306\n# 允许最大连接数\nmax_connections=200\n# 允许连接失败的次数。\nmax_connect_errors=10\n# 服务端使用的字符集默认为utf8mb4\ncharacter-set-server=utf8mb4\ncollation-server=utf8mb4_unicode_ci\nskip-name-resolve\n\n# 运行容器\ndocker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root \\\n-v /root/docker/mysql/conf:/etc/mysql/conf.d \\\n-v /root/docker/mysql/data:/var/lib/mysql \\\n-v /root/docker/mysql/log:/var/log/mysql \\\n-d mysql:8.0\n```\n\n### Docker安装Redis\n```shell script\ndocker pull redis:5.0.9\nmkdir /root/docker/redis\nvim /root/docker/redis/redis.conf\n    port 6379\n    requirepass xxxx\n    appendonly yes\ndocker run -p 6379:6379 --name redis \\\n        -v /root/docker/redis/data:/data \\\n        -v /root/docker/redis/redis.conf:/etc/redis/redis.conf \\\n        -d redis:5.0.9 redis-server /etc/redis/redis.conf\n```\n### Docker安装Nginx\n```shell script\nmkdir /root/docker/nginx\nmkdir /root/docker/nginx/conf\n# 由于我们现在没有配置文件，也不知道配置什么。可以先启动一个nginx，讲他的配置文件拷贝出来\n# 再作为映射，启动真正的nginx\ndocker pull nginx:1.17.4\ndocker run --name some-nginx -d nginx:1.17.4\ndocker container cp some-nginx:/etc/nginx /root/docker/nginx/conf\n# 然后就可以删除这个容器了\ndocker docker rm -f some-nginx\n# 启动nginx\ndocker run --name nginx -p 80:80 \\\n        -v /root/docker/nginx/conf:/etc/nginx \\\n        -v /root/docker/nginx/html:/usr/share/nginx/html \\\n        -d nginx:1.17.4\n\n```\n### Docker安装ElasticSearch\n```shell script\ndocker pull elasticsearch:7.8.0\n# 做映射之前赋予文件夹相应权限，\nchmod -R 775 /root/docker/elasticsearch/data\nchmod -R 775 /root/docker/elasticsearch/logs\n\ndocker run -p 9200:9200 -p 9300:9300 --name es7.8 \\\n-e \"discovery.type=single-node\" \\\n-e ES_JAVA_OPTS=\"-Xms128m -Xmx512m\" \\\n-v /root/docker/elasticsearch/plugins:/usr/share/elasticsearch/plugins \\\n-v /root/docker/elasticsearch/data:/usr/share/elasticsearch/data \\\n-v /root/docker/elasticsearch/logs:/usr/share/elasticsearch/logs \\\n-d elasticsearch:7.8.0\n\n```\n### Docker安装Kibana\n```sehll\n# 拉取镜像\n# kibana版本必须和elasticsearch版本保持一致\ndocker pull kibana:7.8.0\n\n# 启动容器\n# YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID 正在运行的ES容器ID或name\ndocker run --link YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID:elasticsearch -p 5601:5601 {docker-repo}:{version}\ndocker run --link es7.8:elasticsearch -p 5601:5601 --name kibana -d kibana:7.8.0\n```\n### DockerElasticSearch安装IK分词器\n```sehll\n# Ik分词器版本要和ES和Kibana版本保持一致\n\n# 进入容器\ndocker exec -it es7.8 /bin/bash\n#此命令需要在容器中运行\nelasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.8.0/elasticsearch-analysis-ik-7.8.0.zip\n# 退出容器，重启容器\nexit\ndocker restart es7.8\n```\n## spring-cloud-alibaba的使用\n### 引入依赖，全套组件版本统一管理\n```xml\n\u003cproperties\u003e\n    \u003cspringcloud.alibaba.version\u003e2.2.2.RELEASE\u003c/springcloud.alibaba.version\u003e\n\u003c/properties\u003e\n\u003cdependencyManagement\u003e\n    \u003cdependencies\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003eorg.springframework.cloud\u003c/groupId\u003e\n            \u003cartifactId\u003espring-cloud-dependencies\u003c/artifactId\u003e\n            \u003cversion\u003e${spring-cloud.version}\u003c/version\u003e\n            \u003ctype\u003epom\u003c/type\u003e\n            \u003cscope\u003eimport\u003c/scope\u003e\n        \u003c/dependency\u003e\n    \u003c/dependencies\u003e\n\u003c/dependencyManagement\u003e\n```\n### nacos作为注册中心\n1. 本地启动nacos客户端，windows双击startup.bat\n若启动失败，则在当前目录下打开cmd，执行startup.bat -m standalone\n2. 引入nacos作为注册中心的依赖\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.alibaba.cloud\u003c/groupId\u003e\n    \u003cartifactId\u003espring-cloud-starter-alibaba-nacos-discovery\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n3. 在配置文件中配置注册中心地址以及服务名和端口\n```properties\n# 服务端口\nserver.port=8070\n# 服务名\nspring.application.name=service-provider\n# 注册中心地址\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\n```\n4. 启用服务注册发现配置 @EnableDiscoveryClient\n```java\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class NacosProviderApplication {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(NacosProviderApplication.class, args);\n\t}\n}\n```\n### nacos作为配置中心\n1. 本地启动nacos客户端，windows双击startup.bat\n若启动失败，则在当前目录下打开cmd，执行startup.bat -m standalone\n2. 引入nacos作为配置中心的依赖\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.alibaba.cloud\u003c/groupId\u003e\n    \u003cartifactId\u003espring-cloud-starter-alibaba-nacos-config\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n3. 在配置文件bootstrap.properties中配置注册中心地址以及服务名\n```properties\n# 服务名,配置服务名是因为服务名是默认读取的dataId的一部分\nspring.application.name=service-provider\n# 配置中心地址\nspring.cloud.nacos.config.server-addr=127.0.0.1:8848\n```\n当配置了nacos配置中心，服务启动时会去nacos配置中心加载默认命名空间(public)下默认命名的配置集，dataId的组成形式是 ${prefix}-${spring.profiles.active}.${file-extension}\n- dataId：一组配置的集合的唯一表示\n- prefix：默认值为配置文件中配置的服务名，也可以使用spring.cloud.nacos.config.prefix来自定义\n- spring.profiles.active指当前激活的环境，如dev,prod等，如果当前服务不存在多个环境，则dataI\nd将变为 ${prefix}.${file-extension}\n- file-extension：配置中心中配置项的配置格式(默认是properties,因此只要内容格式是properties就可以不加后缀)，可在配置文件中通过spring.cloud.nacos.config.file-extension指定，当前只支持yaml和properties\n4. 获取配合中心中配置项的值可使用@Value注解，若希望读取的值随着配置中心中的改变而实时更新（不用重启项目），使用@RefreshScope自动刷新\n- 若某个配置项既在application.yaml有配置，也在配置中心进行了配置，优先读取配置中心中的值\n5. 关于配置中心的几个名字概念\n- 命名空间：用来做配置隔离，区分每个dataId所属哪个命名空间，比如可以让每个服务所创建的所有dataId属于自己的命名空间\n- 配置中心中新建的dataId默认所属的命名空间都是PUBLIC，服务启动时默认去读取的dataId是服务名.properties，若自己修改了其所属命名空间，则无法读取到配置项的内容\n可以在配置文件bootstrap.properties中指定要读取哪个配置空间\n- 每个配置空间下所创建的配置集dataId，可以设置其所属组，默认组是DEFAULT_PUBLIC，可以通过这个组来进行生产、测试等多个环境下的多个配置，然后在配置文件中指定读取的是哪个组即可\n6. 读取多个配置文件\n我们可以将服务的全部配置分成多个配置文件(数据连接相关，mybatis相关，redis相关，其他)，为每部分的配置文件也都可以设置所属组，然后在配置文件中通过spring.cloud.nacos.config.extension-configs:来指定加在属于指定组的多个配置文件 \n7. 通常情况下，每个模块(微服务)会去创建自己专属的命名空间(如服务名)，然后在命名空间内创建多个dataId进行相应部分的配置管理，并可以为其指定所属组来进行不同环境下的不同配置。\n```java\n@RestController\n@RequestMapping(\"/config\")\n@RefreshScope\npublic class ConfigController {\n\n    @Value(\"${useLocalCache:false}\")\n    private boolean useLocalCache;\n\n    @RequestMapping(\"/get\")\n    public boolean get() {\n        return useLocalCache;\n    }\n}\n```\n## JSR303\n- 1)、导入 javax.validation、hibernate-validator依赖，尤其是第二个，在springboot应用中使用校验，必须导入\n- 2)、给Bean的字段添加校验注解:javax.validation.constraints，并定义自己的message提示\n  - @NotNull: CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集（size = 0）。\n  - @NotEmpty: CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0。\n  - @NotBlank: String 不是 null 且 至少包含一个字符\n- 3)、开启校验功能 使用@Valid\n  - 效果：校验错误以后会有默认的响应；\n- 4)、给校验的bean后紧跟一个BindingResult，就可以获取到校验的结果\n- 5)、分组校验（多场景的复杂校验）\n      - @NotBlank(message = \"品牌名必须提交\",groups = {AddGroup.class,UpdateGroup.class})\n      - @Validated({AddGroup.class}),给校验注解标注什么情况需要进行校验\n      - 默认没有指定分组的字段校验使用注解@Valid，在分组校验情况下，只会在@Validated({AddGroup.class})生效；\n- 6)、自定义校验\n   - 1、编写一个自定义的校验注解\n   - 2、编写一个自定义的校验器 ConstraintValidator\n   - 3、关联自定义的校验器和自定义的校验注解\n     - @Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器，适配不同类型的校验】 })\n- 统一的异常处理\n    - @ControllerAdvice\n    - 编写异常处理类，使用@ControllerAdvice。\n    - 使用@ExceptionHandler标注方法可以处理的异常。\n   \n## 分布式锁Redisson的使用\n   https://redis.io/topics/distlock\n   https://github.com/redisson/redisson\n\n   Redisson是一个在Redis的基础上实现的Java驻内存数据网格（In-Memory Data Grid）。它不仅提供了一系列的分布式的Java常用对象，还提供了许多分布式服务。\n   其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)\n   Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离（Separation of Concern），从而让使用者能够将精力更集中地放在处理业务逻辑上。\n     \n   1. 导入erdisson依赖，可去maven仓库\n   2. 编写配置类，创建 RedissonClient对象\n   3. @autowired注入RedissonClient对象\n   4. 获取锁 参数就是锁的名字\n            // 获取分布式可重入锁，最基本的锁\n           RLock lock = redissonClient.getLock(\"锁名\");\n           // 获取读写锁\n           redissonClient.getReadWriteLock(\"anyRWLock\");\n           // 信号量\n           redissonClient.getSemaphore(\"semaphore\");\n  \n      Rlock实现了juc下的lock，完全可以像使用本地锁一样使用它\n  \n   5. 以可重入锁为例\n      如果直接执行 lock.lock();\n         Redisson内部提供了一个监控锁的看门狗，它的作用是在Redisson实例被关闭前，(定时任务)不断的延长锁的有效期。\n         默认情况下，看门狗的检查锁的超时时间是30秒钟，也可以通过修改Config.lockWatchdogTimeout来另行指定\n         也就是说，先加锁，然后执行业务，锁的默认有效期是30s，业务进行期间，会通过定时任务不断将锁的有效期续至30s。直到业务代码结束\n         所以即便不手动释放锁。最终也会自动释放\n         默认是任务调度的周期是 看门狗时间 / 3  = 10s\n   \n      也可以使用 lock.lock(10, TimeUnit.SECONDS);手动指定时间\n         此时，不会有定时任务自动延期，超过这个时间后锁便自动解开了\n         需要注意的是，如果代码块中有手动解锁，但是业务执行完成之前锁的有效期到了，\n         此时执行unlock会报错：当前线程无法解锁\n         因为现在redis中的锁是另一个线程加上的，而他的删锁逻辑是lua脚本执行\n         先获取键值，判断是否是自己加的锁。如果是。则释放，lua脚本保证这是一个原子操作\n         所以，手动设置时间必须保证这个时间内业务能够执行完成\n         \n## SpringCache的使用\n\nhttps://docs.spring.io/spring-framework/docs/current/spring-framework-reference/integration.html#cache\n1. 导入依赖。\n    ```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n        \u003cartifactId\u003espring-boot-starter-cache\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n    ```\n2. CacheAutoconfiguration类导入了多种类型的缓存自动配置\n```java\nstatic class CacheConfigurationImportSelector implements ImportSelector {\n        @Override\n        public String[] selectImports(AnnotationMetadata importingClassMetadata) {\n            CacheType[] types = CacheType.values();\n            String[] imports = new String[types.length];\n            for (int i = 0; i \u003c types.length; i++) {\n                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);\n            }\n            return imports;\n        }\n\n    }\n// 缓存类型\npublic enum CacheType {\n    GENERIC,\n    JCACHE,\n    EHCACHE,\n    HAZELCAST,\n    INFINISPAN,\n    COUCHBASE,\n    REDIS,\n    CAFFEINE,\n    SIMPLE,\n    NONE;}\n// 各种类型缓存对应的自动配置类\nmappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);\nmappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);\nmappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);\nmappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);\nmappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);\nmappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);\nmappings.put(CacheType.REDIS, RedisCacheConfiguration.class);\nmappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);\nmappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);\nmappings.put(CacheType.NONE, NoOpCacheConfiguration.class);\nMAPPINGS = Collections.unmodifiableMap(mappings);\n```\n3. 每种类型的缓存自动配置类中，创建了CacheManager，根据默认配置或用户自定义配置初始化了一系列Cache\n以RedisCacheConfiguration为例\n```java\n    // 创建cacheManager。初始化cache\n    @Bean\n    RedisCacheManager cacheManager(){}\n    // 用于决定初始化cache用什么配置，如果用户自定义了RedisCacheConfiguration。就用用户的配置\n    // 否则就自己创建一个配置\n    private determineConfiguration(){\n                    CacheProperties cacheProperties,\n                    ObjectProvider\u003corg.springframework.data.redis.cache.RedisCacheConfiguration\u003e redisCacheConfiguration,\n                    ClassLoader classLoader) {\n                return redisCacheConfiguration.getIfAvailable(() -\u003e createConfiguration(cacheProperties, classLoader));\n    }\n    // 自己创建一个RedisCacheConfiguration\n    private createConfiguration(\n        CacheProperties cacheProperties, ClassLoader classLoader) {\n        Redis redisProperties = cacheProperties.getRedis();\n        // 这里就是默认策略的设置\n        org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration\n                .defaultCacheConfig();\n        // 值的序列化采用Jdk序列化\n        config = config.serializeValuesWith(\n                SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));\n        // 读取配置文件中用户指定的缓存有效期\n        if (redisProperties.getTimeToLive() != null) {\n            config = config.entryTtl(redisProperties.getTimeToLive());\n        }\n        // 读取配置文件中用户指定的缓存键的前缀\n        if (redisProperties.getKeyPrefix() != null) {\n            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());\n        }\n        // 读取配置文件中用户指定的缓存是否要缓存空值，缓存控制能够解决缓存雪崩(疯狂访问一个缓存和数据库中都没有的id，导致崩溃)\n        if (!redisProperties.isCacheNullValues()) {\n            config = config.disableCachingNullValues();\n        }\n        // 读取配置文件中用户指定的缓存是否使用指定的键前缀\n        if (!redisProperties.isUseKeyPrefix()) {\n            config = config.disableKeyPrefix();\n        }\n        return config;\n    }\n```\n4. 使用缓存\n首先在applicaition.yml中指定我们使用的是哪个类型的缓存，\n并在配置类上使用@EnableCaching启用缓存\n接下来只需要使用注解标注方法就行\n```yml\nspring.cache.type=redis\n```\n```java\n@EnableCaching\npublic class Application {\n    public static void main(String[] args) {\n    \t\tSpringApplication.run(Application.class, args);\n    }\n}\n@Cacheable: Triggers cache population: 触发将值存入缓存的操作\n@CacheEvict: Triggers cache eviction.   触发将值从缓存移除的操作\n@CachePut: Updates the cache without interfering with the method execution：触发更新缓存的操作 \n@Caching: Regroups multiple cache operations to be applied on a method：组合以上多种操作\n@CacheConfig: Shares some common cache-related settings at class-level：在类级别上共享相同的缓存配置\n```\n### @Cacheable\n```java\n    /**\n     * @Cacheable 代表当前方法的结果需要放入缓存\n     *      并且，每次访问这个方法时，会先去缓存中判断数据是否存在，若存在则直接返回缓存中数据，不会执行方法\n     *      但是我们需要指定，要将结果放入哪个缓存中，每个cacheManager管理多个cache.\n     *      我们需要指定cache的名字(相当于对缓存进行分区管理，建议按照业务进行划分)\n     *      使用cacheNames或value属性都可以。可以同时放入多个分区\n     *\n     *  存入缓存中的键名：product-category::SimpleKey []\n     *                  cacheName::默认生成的键名 [方法参数列表]\n     *      这个simplekey[]就是自己生成的键名\n     *      我们可以使用注解的key属性，指定键名，它接收一个Spel表达式\n     *          比如 #root.methodName 就是获取方法名\n     *       详细用法：https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/integration.html#cache-spel-context\n     *   指定key后，得到的键名是：product-category::getLevel1Category\n     *   \n     *  默认生成的值是采用jdk序列化，并且过期时间是-1，\n     *  如果我们想要改变默认配置，\n     *      一些最基本的配置可以在配置文件中设置：\n     *          # 是否缓存空值\n     *          spring.cache.redis.cache-null-values=true\n     *          # 键的前缀\n     *          spring.cache.redis.key-prefix=CACHE_\n     *          # 是否使用这个前缀\n     *          spring.cache.redis.use-key-prefix=true\n     *          # 缓存的有效期\n     *          spring.cache.redis.time-to-live=3600000\n\n     *  比较高级的配置，比如设置序列化策略采用json形式，就得自己编写RedisCacheConfiguration\n\n            sync 属性：默认为false。\n                如果设为true。则会为处理过程加上synchronized本地锁。也就是说在一个微服务里面，能够保证线程间互斥访问这个方法\n     *\n     * @return\n     */\n    @RequestMapping(\"/product/category/level1\")\n    @Cacheable(cacheNames = {\"product-category\"}, key = \"#root.methodName\")\n    public List\u003cCategoryEntity\u003e getLevel1Category() {\n        System.out.println(\"查询数据库\");\n        List\u003cCategoryEntity\u003e level1Categories = categoryService.getLevel1Categories();\n        return level1Categories;\n    }\n    /**\n     * 自己编写RedisCacheConfiguration\n     */\n    @EnableConfigurationProperties(CacheProperties.class)\n    @Configuration\n    public class RedisCacheConfig {\n    \n        /**\n         * 没有无参构造方法。不能直接 new RedisCacheConfiguration();\n         * 使用RedisCacheConfiguration.defaultCacheConfig();默认配置，再修改\n         * \n         * 用户的自定义配置都在 application.yml中其中spring.cache部分。\n         * 它和CacheProperties.class进行了绑定，\n         *      @ConfigurationProperties(prefix = \"spring.cache\")\n         *      public class CacheProperties {}\n         * 但是这个类它没有被作为一个bean放入容器中。\n         * 我们需要手动导入\n         * @EnableConfigurationProperties(CacheProperties.class)\n         * 这样容器中就会创建出一个 cacheProperties对象。我们可以使用方法参数直接接收\n         * @return\n         */\n        @Bean\n        public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {\n            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();\n            // 设置键的序列化策略\n            config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()));\n            // 设置值的序列化策略\n            config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));\n            // 加载配置文件中用户自定义配置\n            // 拿出redis部分\n            CacheProperties.Redis redisProperties = cacheProperties.getRedis();\n            if (redisProperties.getTimeToLive() != null) {\n                config = config.entryTtl(redisProperties.getTimeToLive());\n            }\n            if (redisProperties.getKeyPrefix() != null) {\n                config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());\n            }\n            if (!redisProperties.isCacheNullValues()) {\n                config = config.disableCachingNullValues();\n            }\n            if (!redisProperties.isUseKeyPrefix()) {\n                config = config.disableKeyPrefix();\n            }\n            return config;\n        }\n    }\n\n    /**\n     * @CacheEvict: 触发删除缓存操作\n     *      cacheNames: 指定要删除的是哪个缓存分区的缓存\n     *      key: 要删除的是这个分区中的那个缓存\n     *      allEntries: 是否删除这个分区中的所有缓存。默认false\n     * 所以。如果只指定cacheNames.不指定key。不会进行任何删除。\n     *      如果设置allEntries = true。那么不用指定key。全部删除\n     *  \n     * key。一次只能指定一个key。那如果要删除多个，但不想全部删除，就需要使用 @Caching\n     *     @Caching(\n     *             evict = {\n     *                     @CacheEvict(cacheNames = {ProductConstant.CacheName.PRODUCT_CATEGORY},\n     *                             key = \"'level1Category'\"),\n     *                     @CacheEvict(cacheNames = {ProductConstant.CacheName.PRODUCT_CATEGORY},\n     *                             key = \"'level2Category'\")\n     *             },\n     *             cacheable = {}\n     *     )\n     */\n    \n    /**\n     * @CachePut\n\n     * 最简单的保证缓存和数据库一致性的方式就是，每次执行更新操作。就将缓存删除。下次查询方法自然会将最新数据存入缓存\n     * \n     * 如果我们的更新方法没有返回值，也就是更新完就结束，那么我们使用@CacheEvit删除缓存\n     * 如果我们的更新方法有返回值，也就是更新成功后将最新数据返回，那么我们使用@CachePut将最新数据更新到缓存\n     * @return\n     */  \n```\n### Rabbitmq消息可靠投递\n    https://codeonce.cc/archives/rabbitmq-confirm\n### Feign远程调用请求头丢失\n    https://codeonce.cc/archives/feign-lost-cookie\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxdwangwei%2Fgulimall","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxdwangwei%2Fgulimall","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxdwangwei%2Fgulimall/lists"}