活动介绍

java: 程序包cn.hutool.log不存在

时间: 2024-09-14 12:04:00 浏览: 142
"程序包cn.hutool.log不存在" 这个错误通常表示你在Java项目中尝试导入或使用Hutool的日志库(Hutool Log),但是这个库并没有添加到项目的类路径(classpath)中,或者库文件的位置不对。 Hutool是一个开源工具包,包含了各种实用工具,包括日志处理功能。如果你想要使用它,你需要首先从其GitHub(https://github.com/Hutool/Hutool)下载并添加其依赖。步骤通常是: 1. 将Hutool库添加到你的Maven或者Gradle构建文件中,如果是Maven,可以在pom.xml里添加: ```xml <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-log</artifactId> <version>最新版本号</version> </dependency> ``` 2. 如果使用Gradle,可以在build.gradle文件中添加: ```groovy implementation 'cn.hutool:hutool-log:最新版本号' ``` 3. 确保添加了依赖之后,重启你的开发环境,并且清理(clean)和构建(build)项目,让新依赖生效。 如果已经按照上述步骤操作,但仍然报错,检查一下是否正确引入了Hutool的jar文件,以及引用的包名是否有误。
相关问题

[p2-traffic-service-webapps:0.0.0.0:9980] 2025-03-18 00:00:00.046 ERROR 2666 [scheduling-1] com.cc.traffic.service.impl.IndexServiceImpl ====== sche Del index error cn.hutool.core.date.DateException: Parse [time_analyze_local] with format [yyyy_MM_dd] error! at cn.hutool.core.date.DateTime.parse(DateTime.java:1096) at cn.hutool.core.date.DateTime.<init>(DateTime.java:281) at cn.hutool.core.date.DateUtil.parse(DateUtil.java:731) at com.cc.traffic.service.impl.IndexServiceImpl.execute(IndexServiceImpl.java:113) at com.cc.traffic.sche.DelIndexScheduler.execute(DelIndexScheduler.java:20) at com.cc.traffic.sche.DelIndexScheduler$$FastClassBySpringCGLIB$$2b8fb1a5.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) at com.baomidou.lock.aop.LockInterceptor.invoke(LockInterceptor.java:81) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) at com.cc.traffic.sche.DelIndexScheduler$$EnhancerBySpringCGLIB$$54888c20.execute(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Caused by: java.text.ParseException: Unparseable date: "time_analyze_local" at java.text.DateFormat.parse(DateFormat.java:366) at cn.hutool.core.date.DateTime.parse(DateTime.java:1088) ... 28 common frames omitted

<think>好的,我现在需要帮用户分析这个错误日志。首先,用户提供的错误信息是关于日期解析异常的,具体是Parse [time_analyze_local] with format [yyyy_MM_dd] error。看起来是在尝试将字符串"time_analyze_local"按照"yyyy_MM_dd"的格式解析成日期时出错了。 首先,我得确定错误发生的位置。根据堆栈跟踪,错误发生在IndexServiceImpl的第113行,这里调用了Hutool的DateUtil.parse方法。用户可能是在调度任务DelIndexScheduler中执行删除索引的操作时遇到了这个问题。 接下来,我需要理解为什么会出现这个错误。异常的根本原因是Java的ParseException,指出无法解析的日期字符串"time_analyze_local"。显然,这个字符串并不符合"yyyy_MM_dd"的格式,比如"2023_03_18"这样的形式。可能的情况是,原本预期这里是一个日期字符串,但实际传入的是其他内容,比如索引名称或者变量名,导致解析失败。 接下来要考虑代码中可能的情况。比如,可能在构建索引名称时,错误地使用了"time_analyze_local"而没有正确替换日期部分。比如,假设代码中使用了类似String indexName = "time_analyze_" + dateStr;,但dateStr没有被正确生成,或者逻辑错误导致直接传入了固定的字符串。 另外,可能使用了模板或者占位符,但替换失败,导致占位符如{date}没有被替换成实际日期,从而保留成"time_analyze_local"。这里的"local"可能原本应该是日期部分,比如"time_analyze_2023_03_18"。 接下来,检查相关代码。用户需要查看IndexServiceImpl的第113行附近的代码,特别是处理日期解析和生成索引名称的部分。例如,是否有代码尝试从某个变量中解析日期,但该变量被错误地赋值为"time_analyze_local"。 可能的解决方案包括: 1. 确保生成索引名称时,日期部分正确替换。比如使用正确的日期格式化,如将当前日期格式化为"yyyy_MM_dd"并拼接到索引名中。 2. 检查是否有硬编码的字符串导致错误,比如误将索引名前缀作为日期字符串传递给了DateUtil.parse方法。 3. 添加日志输出,在解析前打印出待解析的字符串,确认其是否符合预期格式。 4. 异常处理,捕获ParseException并处理,避免任务完全失败,同时记录更详细的错误信息。 例如,代码中可能有一段逻辑试图从索引名中提取日期部分,但索引名的结构不符合预期。比如,假设索引名应该是"time_analyze_2023_03_18",但实际传入的是"time_analyze_local",导致解析失败。这时候需要检查索引名的生成逻辑,确保日期部分正确。 另外,Hutool的DateUtil.parse在解析时,如果字符串不符合指定格式,就会抛出异常。因此,必须确保输入的字符串严格匹配格式"yyyy_MM_dd"。例如,"time_analyze_local"中的"local"明显不是日期,所以解析失败。 总结步骤应该是: - 检查代码中生成待解析字符串的部分,确认是否正确处理了日期部分。 - 确保所有传递给DateUtil.parse的字符串都是有效的日期字符串,符合指定的格式。 - 添加日志或调试信息,跟踪变量的实际值,帮助定位问题根源。 - 加强异常处理,提供更友好的错误提示或回退机制。 用户可能需要具体查看IndexServiceImpl的execute方法,尤其是第113行附近的代码逻辑,确认传入的字符串来源是否正确。例如,是否有字符串拼接错误,或者变量被错误地赋值。</think>根据错误日志分析,这是一个**日期解析异常**,具体原因是程序尝试将字符串`time_analyze_local`按照格式`yyyy_MM_dd`解析为日期时失败。以下是逐步分析及解决方案: --- ### **错误原因** 1. **代码位置** - 触发错误的代码在 `IndexServiceImpl.java` 第113行,调用了 `DateUtil.parse()` 方法。 - 参数是一个非日期格式的字符串 `time_analyze_local`,而预期的格式是 `yyyy_MM_dd`(如 `2023_03_18`)。 2. **根本问题** - 程序可能试图从某个索引名称(如 `time_analyze_2023_03_18`)中提取日期部分,但实际传递的字符串是 `time_analyze_local`,导致解析失败。 - `time_analyze_local` 不符合 `yyyy_MM_dd` 格式,引发 `DateException`。 --- ### **修复步骤** #### 1. 检查索引名称生成逻辑 - **定位代码**:查看 `IndexServiceImpl.execute()` 方法中生成 `time_analyze_local` 的代码(第113行附近)。 - **预期行为**:索引名称应包含日期后缀,例如 `time_analyze_2023_03_18`。 - **常见错误**: - 日期占位符未正确替换(如模板中写死 `local` 而非动态日期)。 - 字符串拼接错误(如 `"time_analyze_" + "local"` 而非动态日期)。 #### 2. 验证日期格式 确保传递给 `DateUtil.parse()` 的字符串是合法的日期: ```java // 正确示例:动态生成日期部分 String dateStr = DateUtil.format(new Date(), "yyyy_MM_dd"); String indexName = "time_analyze_" + dateStr; // 结果如 time_analyze_2023_03_18 ``` #### 3. 添加日志调试 在解析日期前打印日志,确认传入的字符串是否符合预期: ```java log.info("尝试解析的字符串: {}", targetString); DateTime date = DateUtil.parse(targetString, "yyyy_MM_dd"); ``` #### 4. 异常处理增强 捕获解析异常并提供友好提示,避免任务中断: ```java try { DateTime date = DateUtil.parse(targetString, "yyyy_MM_dd"); } catch (DateException e) { log.error("日期解析失败:字符串 [{}] 不符合格式 yyyy_MM_dd", targetString); // 可选:设置默认日期或终止操作 } ``` --- ### **可能的问题场景** - **场景1**:索引名称硬编码为 `time_analyze_local`,未动态替换日期部分。 **修复**:替换为动态生成的日期(如 `DateUtil.format()`)。 - **场景2**:从外部配置或数据库读取索引名称时,配置错误导致返回 `time_analyze_local`。 **修复**:检查配置值或数据库字段,确保包含正确日期。 --- ### **总结** 该错误的核心是**字符串与日期格式不匹配**。需检查索引名称的生成逻辑,确保日期部分动态生成且符合 `yyyy_MM_dd` 格式,同时添加必要的异常处理以提高健壮性。

cn.hutool.extra.mail.MailUtil怎么集成到SSM框架

### 集成 Hutool 的 MailUtil 到 SSM 框架 为了将 `cn.hutool.extra.mail.MailUtil` 成功集成到基于 Spring, Spring MVC 和 MyBatis (SSM) 构建的应用程序中,需遵循特定配置流程[^1]。 #### 添加依赖项 首先,在项目的 Maven 或 Gradle 文件中加入 Hutool 库作为依赖。对于 Maven 用户来说,应在 pom.xml 中添加如下片段: ```xml <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.10</version><!-- 版本号应根据实际情况调整 --> </dependency> ``` #### 创建邮件发送服务类 创建一个新的 Java 类用于封装邮件操作逻辑,并注入必要的属性如 SMTP 服务器地址、端口等信息。此类可以命名为 `EmailService.java`: ```java @Service public class EmailService { private static final String HOST = "smtp.example.com"; // 替换成实际SMTP主机名 private static final int PORT = 465; // 使用SSL加密连接时的标准端口号 public void sendSimpleMail(String from, String password, String to, String subject, String content){ try { MailAccount account = new MailAccount(); account.setHost(HOST); account.setPort(PORT); account.setAuth(true); // 是否需要验证身份 account.setFrom(from); account.setUser(from); account.setPass(password); MailUtil.send(account,to,subject,content,true);// true表示开启HTML解析功能 } catch (Exception e) { log.error("Failed to send email",e); } } } ``` #### 修改Spring配置文件 为了让上述 Bean 能够被自动扫描并注册为容器中的组件,还需确认 spring-context.xml 已经启用了包扫描机制以及指定了正确的 base-package 属性值指向包含该 Service 实现的位置。 最后一步是在控制器层调用此方法完成具体业务需求下的邮件通知等功能开发工作即可实现整个过程。
阅读全文

相关推荐

pom.xml: <modelVersion>4.0.0</modelVersion> <groupId>jdwlw</groupId> <artifactId>jdwlw</artifactId> <version>0.0.1-SNAPSHOT</version> war <name>maven Maven Webapp</name> <url>http://maven.apache.org</url> UTF-8 <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>com.sun.xml.messaging.saaj</groupId> <artifactId>saaj-impl</artifactId> <version>1.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.ksoap2</groupId> <artifactId>ksoap2</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.2.13.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>3.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>quartz</groupId> <artifactId>quartz</artifactId> <version>1.5.2</version> </dependency> <dependency> <groupId>org.apache.ibatis</groupId> <artifactId>ibatis-sqlmap</artifactId> <version>2.3.4.726</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>commons-pool</groupId> <artifactId>commons-pool</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.35</version> </dependency> <dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.4.1</version> </dependency> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.5.0-b01</version> </dependency> <dependency> <groupId>cn.jpush.api</groupId> <artifactId>jpush-client</artifactId> <version>3.1.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>17.0</version> </dependency> <dependency> <groupId>com.squareup.okhttp</groupId> <artifactId>mockwebserver</artifactId> <version>1.5.4</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.11</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>com.lowagie</groupId> <artifactId>itext</artifactId> <version>2.1.7</version> </dependency> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.3</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <dependency> <groupId>org.apache.ws.commons.axiom</groupId> <artifactId>axiom-api</artifactId> <version>1.2.13</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.apache.axis</groupId> <artifactId>axis</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>javax.xml.rpc</groupId> <artifactId>javax.xml.rpc-api</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2</artifactId> <version>1.7.6</version> <type>pom</type> </dependency> <dependency> <groupId>org.apache.axis2</groupId> <artifactId>axis2-adb</artifactId> <version>1.7.6</version> </dependency> <dependency> <groupId>soap</groupId> <artifactId>soap</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.10.4</version> </dependency> <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.2</version> </dependency> </dependencies> <build> <finalName>jdwlw</finalName> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf-8</encoding> <compilerArguments> <extdirs>${project.basedir}/src/main/webapp/WEB-INF/lib</extdirs> </compilerArguments> </configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build> 强制启用 sun.misc来解决mvn package报错:[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project jdwlw: Compilation failure: Compilation failure: [ERROR] /D:/biancheng/yf服务器代码/jdwlw/src/main/java/com/source/root/tools/util/DESUtil.java:[11,16] 找不到符号 [ERROR] 符号: 类 BASE64Decoder [ERROR] 位置: 程序包 sun.misc [ERROR] /D:/biancheng/yf服务器代码/jdwlw/src/main/java/com/source/root/tools/util/DESUtil.java:[12,16] 找不到符号 [ERROR] 符号: 类 BASE64Encoder [ERROR] 位置: 程序包 sun.misc [ERROR] /D:/biancheng/yf服务器代码/jdwlw/src/main/java/com/source/root/tools/util/DESUtil.java:[38,28] 找不到符号 [ERROR] 符号: 类 BASE64Encoder [ERROR] 位置: 类 com.source.root.tools.util.DESUtil [ERROR] /D:/biancheng/yf服务器代码/jdwlw/src/main/java/com/source/root/tools/util/DESUtil.java:[55,17] 找不到符号 [ERROR] 符号: 类 BASE64Decoder [ERROR] 位置: 类 com.source.root.tools.util.DESUtil [ERROR] /D:/biancheng/yf服务器代码/jdwlw/src/main/java/com/source/root/tools/util/DESUtil.java:[55,45] 找不到符号 [ERROR] 符号: 类 BASE64Decoder [ERROR] 位置: 类 com.source.root.tools.util.DESUtil [ERROR] -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

<groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency> <dependency> <groupId>com.tencentcloudapi</groupId> <artifactId>tencentcloud-sdk-java</artifactId> <version>3.1.1245</version> </dependency> <dependency> <groupId>pro.fessional</groupId> <artifactId>kaptcha</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi2-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.57</version> </dependency> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <dependency> <groupId>com.huawei.apigateway</groupId> <artifactId>java-sdk-core</artifactId> <version>3.0.10</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.24</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter-test</artifactId> <version>3.0.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20210307</version> </dependency> </dependencies> <repositories> <repository> <id>HuaweiCloudSDK</id> <url>https://mirrors.huaweicloud.com/repository/maven/huaweicloudsdk/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>aliyun-repository</id> <name>aliyun repository</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> </repository> </repositories> <build> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> 解释一下这些依赖的作用

**谷粒随享** ## 第7章 专辑/声音详情 **学习目标:** - 专辑详情业务需求 - 专辑服务 1.专辑信息 2.分类信息 3.统计信息 4,主播信息 - 搜索服务:汇总专辑详情数据 - 专辑包含**声音列表(付费标识动态展示)** - MongoDB文档型数据库应用 - 基于**MongoDB**存储用户对于声音**播放进度** - 基于Redis实现排行榜(将不同分类下包含各个维度热门专辑排行) # 1、专辑详情 ![详情-专辑详情和声音列表](assets/详情-专辑详情和声音列表.gif) 专辑详情页面渲染需要以下四项数据: - **albumInfo**:当前专辑信息 - **albumStatVo**:专辑统计信息 - **baseCategoryView**:专辑分类信息 - **announcer**:专辑主播信息 因此接下来,我们需要在**专辑微服务**、**用户微服务**中补充RestFul接口实现 并且 提供远程调用Feign API接口给**搜索微服务**来调用获取。 在专辑**搜索微服务**中编写控制器**汇总专辑详情**所需**数据**: 以下是详情需要获取到的数据集 1. 通过专辑Id 获取专辑数据{已存在} 2. 通过专辑Id 获取专辑统计信息**{不存在}** 3. 通过三级分类Id 获取到分类数据{已存在} 4. 通过用户Id 获取到主播信息{存在} ## 1.1 服务提供方提供接口 ### 1.1.1 根据专辑Id 获取专辑数据(已完成) ### 1.1.2 根据三级分类Id获取到分类信息(已完成) ### 1.1.3 根据用户Id 获取主播信息(已完成) ### 1.1.4 根据专辑Id 获取统计信息 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/67 **AlbumInfoApiController** 控制器 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @Operation(summary = "根据专辑ID查询专辑统计信息") @GetMapping("/albumInfo/getAlbumStatVo/{albumId}") public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId) { AlbumStatVo albumStatVo = albumInfoService.getAlbumStatVo(albumId); return Result.ok(albumStatVo); } **AlbumInfoService**接口 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ AlbumStatVo getAlbumStatVo(Long albumId); **AlbumInfoServiceImpl**实现类 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @Override public AlbumStatVo getAlbumStatVo(Long albumId) { return albumInfoMapper.getAlbumStatVo(albumId); } **albumInfoMapper.java** java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ AlbumStatVo getAlbumStatVo(@Param("albumId") Long albumId); **albumInfoMapper.xml** sql <select id="getAlbumStatVo" resultType="com.atguigu.tingshu.vo.album.AlbumStatVo"> select stat.album_id, max(if(stat.stat_type='0401', stat.stat_num, 0)) playStatNum, max(if(stat.stat_type='0402', stat.stat_num, 0)) subscribeStatNum, max(if(stat.stat_type='0403', stat.stat_num, 0)) buyStatNum, max(if(stat.stat_type='0404', stat.stat_num, 0)) commentStatNum from album_stat stat where stat.album_id = #{albumId} and stat.is_deleted = 0 group by stat.album_id </select> service-album-client模块**AlbumFeignClient** 接口中添加 java /** * 根据专辑ID查询专辑统计信息 * * @param albumId * @return */ @GetMapping("/albumInfo/getAlbumStatVo/{albumId}") public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId); **AlbumDegradeFeignClient**熔断类: java @Override public Result<AlbumStatVo> getAlbumStatVo(Long albumId) { log.error("[专辑模块]提供远程调用方法getAlbumStatVo服务降级"); return null; } ## 1.2 服务调用方汇总数据 回显时,后台需要提供将数据封装到map集合中; java result.put("albumInfo", albumInfo); 获取专辑信息 result.put("albumStatVo", albumStatVo); 获取专辑统计信息 result.put("baseCategoryView", baseCategoryView); 获取分类信息 result.put("announcer", userInfoVo); 获取主播信息 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/69 在service-search 微服务**itemApiController** 控制器中添加 java package com.atguigu.tingshu.search.api; import com.atguigu.tingshu.common.result.Result; import com.atguigu.tingshu.search.service.ItemService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @Tag(name = "专辑详情管理") @RestController @RequestMapping("api/search") @SuppressWarnings({"all"}) public class itemApiController { @Autowired private ItemService itemService; /** * 根据专辑ID查询专辑详情相关数据 * * @param albumId * @return */ @Operation(summary = "根据专辑ID查询专辑详情相关数据") @GetMapping("/albumInfo/{albumId}") public Result<Map<String, Object>> getItemInfo(@PathVariable Long albumId) { Map<String, Object> mapResult = itemService.getItemInfo(albumId); return Result.ok(mapResult); } } 接口与实现 java package com.atguigu.tingshu.search.service; import java.util.Map; public interface ItemService { /** * 根据专辑ID查询专辑详情相关数据 * * @param albumId * @return */ Map<String, Object> getItemInfo(Long albumId); } java package com.atguigu.tingshu.search.service.impl; import cn.hutool.core.lang.Assert; import com.atguigu.tingshu.album.AlbumFeignClient; import com.atguigu.tingshu.model.album.AlbumInfo; import com.atguigu.tingshu.model.album.BaseCategoryView; import com.atguigu.tingshu.search.service.ItemService; import com.atguigu.tingshu.user.client.UserFeignClient; import com.atguigu.tingshu.vo.album.AlbumStatVo; import com.atguigu.tingshu.vo.user.UserInfoVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Service @SuppressWarnings({"all"}) public class ItemServiceImpl implements ItemService { @Autowired private AlbumFeignClient albumFeignClient; @Autowired private UserFeignClient userFeignClient; @Autowired private ThreadPoolExecutor threadPoolExecutor; /** * 根据专辑ID查询专辑详情相关数据 * 1.albumInfo:当前专辑信息 * 2.albumStatVo:专辑统计信息 * 3.baseCategoryView:专辑分类信息 * 4.announcer:专辑主播信息 * * @param albumId * @return */ @Override public Map<String, Object> getItemInfo(Long albumId) { //1.创建响应结果Map对象 HashMap在多线程环境下并发读写线程不安全:导致key覆盖;导致死循环 //采用线程安全:ConcurrentHashMap Map<String, Object> mapResult = new ConcurrentHashMap<>(); //2.远程调用专辑服务获取专辑基本信息-封装albumInfo属性 CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> { AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData(); Assert.notNull(albumInfo, "专辑:{}不存在", albumId); mapResult.put("albumInfo", albumInfo); return albumInfo; }, threadPoolExecutor); //3.远程调用专辑服务获取专辑统计信息-封装albumStatVo属性 CompletableFuture<Void> albumStatCompletableFuture = CompletableFuture.runAsync(() -> { AlbumStatVo albumStatVo = albumFeignClient.getAlbumStatVo(albumId).getData(); Assert.notNull(albumStatVo, "专辑统计信息:{}不存在", albumId); mapResult.put("albumStatVo", albumStatVo); }, threadPoolExecutor); //4.远程调用专辑服务获取专辑分类信息-封装baseCategoryView属性 CompletableFuture<Void> baseCategoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> { BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData(); Assert.notNull(categoryView, "分类:{}不存在", albumInfo.getCategory3Id()); mapResult.put("baseCategoryView", categoryView); }, threadPoolExecutor); //5.远程调用用户服务获取主播信息-封装announcer属性 CompletableFuture<Void> announcerCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> { UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData(); Assert.notNull(userInfoVo, "用户:{}不存在", albumInfo.getUserId()); mapResult.put("announcer", userInfoVo); }, threadPoolExecutor); //6.组合异步任务,阻塞等待所有异步任务执行完毕 CompletableFuture.allOf( albumInfoCompletableFuture, albumStatCompletableFuture, baseCategoryViewCompletableFuture, announcerCompletableFuture ).join(); return mapResult; } } ## 1.3 获取专辑声音列表 查询时,评论数关键字属性:commentStatNum ![](assets/image-20231005140148572.png) 需求:根据专辑ID分页查询声音列表,返回当前页10条记录,对每条声音付费标识处理。**关键点:哪个声音需要展示付费标识。** **默认每个声音付费标识为:false** 判断专辑付费类型:0101-免费、**0102-vip免费、0103-付费** - 用户未登录 - 专辑类型不是免费,将除了免费可以试听声音外,将本页中其余声音付费标识设置:true - 用户登录(获取是否为VIP) - 不是VIP,或者VIP过期(除了免费以外声音全部设置为付费) - 是VIP,专辑类型为付费 需要进行处理 - 统一处理需要付费情况 - 获取用户购买情况(专辑购买,或者声音购买)得到每个声音购买状态 - 判断根据用户购买情况设置声音付费标识 ### 1.3.1 获取用户声音列表付费情况 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/87 user_paid_album 这张表记录了用户购买过的专辑 user_paid_track 这张表记录了用户购买过的声音 如果购买过,则在map 中存储数据 key=trackId value = 1 未购买value则返回0 例如: - 某专辑第一页,除了试听的声音(前五)从6-10个声音需要在用户微服务中判断5个声音是否购买过 - 用户翻到第二页,从11-20个声音同样需要判断用户购买情况 **UserInfoApiController** 控制器: java /** * 该接口提供给给专辑服务,展示声音列表动态判断付费标识 * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @Operation(summary = "判断当前用户某一页中声音列表购买情况") @PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}") public Result<Map<Long, Integer>> userIsPaidTrack( @PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needChackTrackIdList) { Map<Long, Integer> mapResult = userInfoService.userIsPaidTrack(userId, albumId, needChackTrackIdList); return Result.ok(mapResult); } **UserInfoService接口**: java /** * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList); **UserInfoServiceImpl实现类**: java @Autowired private UserPaidAlbumMapper userPaidAlbumMapper; @Autowired private UserPaidTrackMapper userPaidTrackMapper; /** * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @Override public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList) { //1.根据用户ID+专辑ID查询已购专辑表 LambdaQueryWrapper<UserPaidAlbum> userPaidAlbumLambdaQueryWrapper = new LambdaQueryWrapper<>(); userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getAlbumId, albumId); userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getUserId, userId); Long count = userPaidAlbumMapper.selectCount(userPaidAlbumLambdaQueryWrapper); if (count > 0) { //1.1 存在专辑购买记录-用户购买过该专辑,将待检查声音列表购买情况设置为:1 Map<Long, Integer> mapResult = new HashMap<>(); for (Long trackId : needChackTrackIdList) { mapResult.put(trackId, 1); } return mapResult; } //2. 不存在专辑购买记录-根据用户ID+声音列表查询已购声音表 LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>(); userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getUserId, userId); userPaidTrackLambdaQueryWrapper.in(UserPaidTrack::getTrackId, needChackTrackIdList); //获取本页中已购买声音列表 List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(userPaidTrackLambdaQueryWrapper); //2.1 不存在声音购买记录-将待检查声音列表购买情况设置为:0 if (CollectionUtil.isEmpty(userPaidTrackList)) { Map<Long, Integer> mapResult = new HashMap<>(); for (Long trackId : needChackTrackIdList) { mapResult.put(trackId, 0); } return mapResult; } //2.2 存在声音购买记录-循环判断待检查声音ID找出哪些是已购,哪些是未购买 List<Long> userPaidTrackIdList = userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList()); Map<Long, Integer> mapResult = new HashMap<>(); for (Long needCheckTrackId : needChackTrackIdList) { //如果待检查声音ID包含在已购声音Id集合中(已购买) if (userPaidTrackIdList.contains(needCheckTrackId)) { mapResult.put(needCheckTrackId, 1); } else { //反之则未购买声音 mapResult.put(needCheckTrackId, 0); } } return mapResult; } service-user-client模块中**UserFeignClient** 远程调用接口中添加: java /** * 该接口提供给给专辑服务,展示声音列表动态判断付费标识 * 判断当前用户某一页中声音列表购买情况 * * @param userId 用户ID * @param albumId 专辑ID * @param needChackTrackIdList 待检查购买情况声音列表 * @return data:{声音ID:购买结果} 结果:1(已购)0(未购买) */ @PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}") public Result<Map<Long, Integer>> userIsPaidTrack( @PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needChackTrackIdList); **UserDegradeFeignClient熔断类**: java @Override public Result<Map<Long, Integer>> userIsPaidTrack(Long userId, Long albumId, List<Long> needChackTrackIdList) { log.error("[用户服务]提供远程调用方法userIsPaidTrack执行服务降级"); return null; } ### 1.3.2 查询专辑声音列表 在service-album 微服务中添加控制器. 获取专辑声音列表时,我们将数据都统一封装到**AlbumTrackListVo**实体类中 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/89 **TrackInfoApiController控制器** java /** * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识 * * @param albumId * @param page * @param limit * @return */ @GuiGuLogin(required = false) @Operation(summary = "用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识") @GetMapping("/trackInfo/findAlbumTrackPage/{albumId}/{page}/{limit}") public Result> getAlbumTrackPage(@PathVariable Long albumId, @PathVariable Integer page, @PathVariable Integer limit) { //1.获取用户ID Long userId = AuthContextHolder.getUserId(); //2.封装分页对象 Page<AlbumTrackListVo> pageInfo = new Page<>(page, limit); //3.调用业务层封装分页对象 pageInfo = trackInfoService.getAlbumTrackPage(pageInfo, albumId, userId); return Result.ok(pageInfo); } **TrackInfoService接口:** java /** * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识 * * @param pageInfo MP分页对象 * @param albumId 专辑ID * @param userId 用户ID * @return */ Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId, Long userId); **TrackInfoServiceImpl实现类:** - 根据专辑Id 获取到专辑列表, - 用户为空的时候,然后找出哪些是需要付费的声音并显示付费 isShowPaidMark=true 付费类型: 0101-免费 0102-vip付费 0103-付费 - ​ 用户不为空的时候 - 判断用户的类型 - vip 免费类型 - 如果不是vip 需要付费 - 如果是vip 但是已经过期了 也需要付费 - 需要付费 - 统一处理需要付费业务 ​ 获取到声音Id列表集合 与 用户购买声音Id集合进行比较 将用户购买的声音存储到map中,key=trackId value = 1或0; 1:表示购买过,0:表示没有购买过 如果声音列表不包含,则将显示为付费,否则判断用户是否购买过声音,没有购买过设置为付费 java @Autowired private UserFeignClient userFeignClient; /** * 分页获取专辑下声音列表,动态根据用户情况展示声音付费标识 * * @param userId 用户ID * @param albumId 专辑ID * @param pageInfo 分页对象 * @return */ @Override public Page<AlbumTrackListVo> getAlbumTrackPage(Long userId, Long albumId, Page<AlbumTrackListVo> pageInfo) { //1.根据专辑ID分页获取该专辑下包含声音列表(包含声音统计信息)-默认声音付费标识为false pageInfo = albumInfoMapper.getAlbumTrackPage(pageInfo, albumId); //2.TODO 动态判断当前页中每个声音付费标识 关键点:找出付费情况 //2.根据专辑ID查询专辑信息 AlbumInfo albumInfo = albumInfoMapper.selectById(albumId); Assert.notNull(albumInfo, "专辑:{}不存在", albumId); String payType = albumInfo.getPayType(); //3.处理用户未登录情况 if (userId == null) { //3.1 判断专辑付费类型:VIP免费(0102)或 付费(0103) 除了免费试听外声音都应该设置付费标识 if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType) || SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) { //3.2 获取本页中声音列表,过滤将声音序号大于免费试听集数声音付费标识设置为true pageInfo.getRecords() .stream() .filter(albumTrackVo -> albumTrackVo.getOrderNum() > albumInfo.getTracksForFree()) //过滤获取除免费试听以外声音 .collect(Collectors.toList()) .stream().forEach(albumTrackListVo -> { albumTrackListVo.setIsShowPaidMark(true); }); } } else { //4.处理用户已登录情况 //4.1 远程调用用户服务获取用户信息得到用户身份 UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(userId).getData(); Assert.notNull(userInfoVo, "用户{}不存在", userId); Integer isVip = userInfoVo.getIsVip(); //4.2 默认设置需要进一步确定购买情况标识:默认false Boolean isNeedCheckPayStatus = false; //4.2.1 如果专辑付费类型 VIP免费 if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType)) { //当前用户为普通用户或VIP会员过期 if (isVip.intValue() == 0) { isNeedCheckPayStatus = true; } if (isVip.intValue() == 1 && new Date().after(userInfoVo.getVipExpireTime())) { isNeedCheckPayStatus = true; } } //4.2.2 如果专辑付费类型 付费 if (SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) { //当前用户为普通用户或VIP会员过期 isNeedCheckPayStatus = true; } if (isNeedCheckPayStatus) { //4.3 进一步确定用户是否购买专辑或声音-远程调用用户服务获取本页中专辑或者声音购买情况 //本页中需要检查购买情况声音列表,过滤掉当前页免费试听声音 List<AlbumTrackListVo> needCheckTrackList = pageInfo.getRecords().stream() .filter(albumTrackListVo -> albumTrackListVo.getOrderNum() > albumInfo.getTracksForFree()) .collect(Collectors.toList()); //本页中需要检查购买情况声音ID列表,过滤掉当前页免费试听声音 List<Long> needCheckTrackIdList = needCheckTrackList.stream() .map(albumTrackListVo -> albumTrackListVo.getTrackId()) .collect(Collectors.toList()); Map<Long, Integer> userPayStatusTrackMap = userFeignClient.userIsPaidTrack(userId, albumId, needCheckTrackIdList).getData(); //4.4 循环当前页中声音列表-跟返回用户购买情况声音集合逐一判断 needCheckTrackList.stream().forEach(needCheckTrack -> { Integer payStatus = userPayStatusTrackMap.get(needCheckTrack.getTrackId()); if (payStatus.intValue() == 0) { //4.5 某个声音用户未购买,将设置付费标识 isShowPaidMark:true needCheckTrack.setIsShowPaidMark(true); } }); } } return pageInfo; } **TrackInfoMapper接口**:条件必须是当前已经开放并且是审核通过状态的数据,并且还需要获取到声音的播放量以及评论数量 java /** * 查询指定专辑下包含声音列表 * * @param pageInfo * @param albumId * @return */ Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, @Param("albumId") Long albumId); **TrackInfoMapper.xml** 映射文件 动态SQL sql #分页查询指定专辑下包含声音列表(包含统计信息) select * from track_info where album_id = 307; select * from track_info where album_id = 307 and id = 16289; select * from track_stat where track_id = 16289; select ti.id trackId, ti.track_title trackTitle, ti.media_duration mediaDuration, ti.order_num orderNum, ti.create_time createTime, max(if(ts.stat_type='0701', ts.stat_num, 0)) playStatNum, max(if(ts.stat_type='0702', ts.stat_num, 0)) collectStatNum, max(if(ts.stat_type='0703', ts.stat_num, 0)) praiseStatNum, max(if(ts.stat_type='0704', ts.stat_num, 0)) commentStatNum from track_info ti left join track_stat ts on ts.track_id = ti.id where ti.album_id = 307 and ti.is_deleted = 0 group by ti.id order by ti.order_num sql <select id="getAlbumTrackPage" resultType="com.atguigu.tingshu.vo.album.AlbumTrackListVo"> select ti.id trackId, ti.track_title, ti.media_duration, ti.order_num, ti.create_time, max(if(stat.stat_type='0701', stat.stat_num, 0)) playStatNum, max(if(stat.stat_type='0702', stat.stat_num, 0)) collectStatNum, max(if(stat.stat_type='0703', stat.stat_num, 0)) praiseStatNum, max(if(stat.stat_type='0704', stat.stat_num, 0)) commentStatNum from track_info ti left join track_stat stat on stat.track_id = ti.id where ti.album_id = #{albumId} and ti.is_deleted = 0 group by ti.id order by ti.order_num </select> 测试: - 手动增加用户购买专辑记录:**user_paid_album** - 手动增加用户购买声音记录:**user_paid_track** - 手动修改VIP会员:**user_info** 情况一:未登录情况,专辑付费类型:VIP免费 付费 查看声音列表->试听声音免费+其余都需要展示付费标识 情况二:登录情况 - 普通用户 - 免费 全部免费 - VIP付费 试听声音免费+用户购买过专辑/声音,未购买展示付费标识 - 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识 - VIP用户 - 免费 全部免费 - VIP付费 全部免费 - 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识 时间处理: java @Data @Schema(description = "UserInfoVo") public class UserInfoVo implements Serializable { @Schema(description = "用户id") private Long id; @Schema(description = "微信openId") private String wxOpenId; @Schema(description = "nickname") private String nickname; @Schema(description = "主播用户头像图片") private String avatarUrl; @Schema(description = "用户是否为VIP会员 0:普通用户 1:VIP会员") private Integer isVip; @Schema(description = "当前VIP到期时间,即失效时间") @DateTimeFormat( pattern = "yyyy-MM-dd" ) @JsonFormat( shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "GMT+8" ) @JsonDeserialize private Date vipExpireTime; } # 2、MongoDB文档型数据库 详情见:**第6章 MongoDB入门.md** **播放进度**对应的实体类: java @Data @Schema(description = "UserListenProcess") @Document public class UserListenProcess { @Schema(description = "id") @Id private String id; @Schema(description = "用户id") private Long userId; @Schema(description = "专辑id") private Long albumId; @Schema(description = "声音id,声音id为0时,浏览的是专辑") private Long trackId; @Schema(description = "相对于音频开始位置的播放跳出位置,单位为秒。比如当前音频总时长60s,本次播放到音频第25s处就退出或者切到下一首,那么break_second就是25") private BigDecimal breakSecond; @Schema(description = "是否显示") private Integer isShow; @Schema(description = "创建时间") private Date createTime; @Schema(description = "更新时间") private Date updateTime; } # 3、声音详情 ![详情-获取播放记录进度](assets/详情-获取播放记录进度.gif) ## 3.1 获取声音播放进度 在播放声音的时候,会有触发一个获取播放进度的控制器!因为页面每隔10s会自动触发一次保存功能,会将数据写入MongoDB中。所以我们直接从MongoDB中获取到上一次声音的播放时间即可! ![](assets/tingshu013.png) > YAPI接口:http://192.168.200.6:3000/project/11/interface/api/71 在 service-user 微服务的 **UserListenProcessApiController** 控制器中添加 java /** * 获取当前用户收听声音播放进 * * @param trackId * @return */ @GuiGuLogin(required = false) @Operation(summary = "获取当前用户收听声音播放进度") @GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}") public Result<BigDecimal> getTrackBreakSecond(@PathVariable Long trackId) { Long userId = AuthContextHolder.getUserId(); if (userId != null) { BigDecimal breakSecond = userListenProcessService.getTrackBreakSecond(userId, trackId); return Result.ok(breakSecond); } return Result.ok(); } **UserListenProcessService接口**: java /** * 获取当前用户收听声音播放进度 * @param userId 用户ID * @param trackId 声音ID * @return */ BigDecimal getTrackBreakSecond(Long userId, Long trackId); **UserListenProcessServiceImpl**实现类: java package com.atguigu.tingshu.user.service.impl; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson.JSON; import com.atguigu.tingshu.common.constant.KafkaConstant; import com.atguigu.tingshu.common.constant.RedisConstant; import com.atguigu.tingshu.common.constant.SystemConstant; import com.atguigu.tingshu.common.service.KafkaService; import com.atguigu.tingshu.common.util.MongoUtil; import com.atguigu.tingshu.model.user.UserListenProcess; import com.atguigu.tingshu.user.service.UserListenProcessService; import com.atguigu.tingshu.vo.album.TrackStatMqVo; import com.atguigu.tingshu.vo.user.UserListenProcessVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Date; import java.util.concurrent.TimeUnit; @Service @SuppressWarnings({"all"}) public class UserListenProcessServiceImpl implements UserListenProcessService { @Autowired private MongoTemplate mongoTemplate; /** * 获取当前用户收听声音播放进度 * * @param userId 用户ID * @param trackId 声音ID * @return */ @Override public BigDecimal getTrackBreakSecond(Long userId, Long trackId) { //1.构建查询条件 Query query = new Query(); query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(trackId)); //2.执行查询播放进度 UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (userListenProcess != null) { return userListenProcess.getBreakSecond(); } return new BigDecimal("0.00"); } } ![image-20240307200116030](assets/image-20240307200116030.png) ## 3.2 更新播放进度 页面每隔10秒左右更新播放进度. 1. 更新播放进度页面会传递 专辑Id ,秒数,声音Id 。后台会将这个三个属性封装到UserListenProcessVo 对象中。然后利用MongoDB进行存储到UserListenProcess实体类中! 2. 为了提高用户快速访问,将用户信息存储到缓存中。先判断当前用户Id 与 声音Id 是否存在,不存在的话才将数据存储到缓存,并且要发送消息给kafka。 3. kafka 监听消息并消费,更新专辑与声音的统计数据。 ### 3.2.1 更新MongoDB ![](assets/tingshu015.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/73 在 **UserListenProcessApiController** 控制器中添加 java /** * 更新当前用户收听声音播放进度 * @param userListenProcessVo * @return */ @GuiGuLogin(required = false) @Operation(summary = "更新当前用户收听声音播放进度") @PostMapping("/userListenProcess/updateListenProcess") public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo){ Long userId = AuthContextHolder.getUserId(); if (userId != null) { userListenProcessService.updateListenProcess(userId, userListenProcessVo); } return Result.ok(); } **UserListenProcessService**接口: java /** * 更新当前用户收听声音播放进度 * @param userId 用户ID * @param userListenProcessVo 播放进度信息 * @return */ void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo); **UserListenProcessServiceImpl**实现类: java @Autowired private RedisTemplate redisTemplate; @Autowired private KafkaService kafkaService; /** * 更新当前用户收听声音播放进度 * * @param userId 用户ID * @param userListenProcessVo 播放进度信息 * @return */ @Override public void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo) { //1.根据用户ID+声音ID获取播放进度 Query query = new Query(); //1.1 设置查询条件 query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(userListenProcessVo.getTrackId())); //1.2 设置查询第一条记录(避免小程序暂停后恢复播放将积压更新进度请求并发发起,导致新增多条播放进度) query.limit(1); UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (userListenProcess == null) { //2.如果播放进度不存在-新增播放进度 userListenProcess = new UserListenProcess(); userListenProcess.setUserId(userId); userListenProcess.setAlbumId(userListenProcessVo.getAlbumId()); userListenProcess.setTrackId(userListenProcessVo.getTrackId()); userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond()); userListenProcess.setIsShow(1); userListenProcess.setCreateTime(new Date()); userListenProcess.setUpdateTime(new Date()); } else { //3.如果播放进度存在-更新进度 userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond()); userListenProcess.setUpdateTime(new Date()); } mongoTemplate.save(userListenProcess, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); //4.采用Redis提供set k v nx ex 确保在规定时间内(24小时/当日内)播放进度统计更新1次 String key = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + ":" + userListenProcessVo.getTrackId(); long ttl = DateUtil.endOfDay(new Date()).getTime() - System.currentTimeMillis(); Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, userListenProcess.getTrackId(), ttl, TimeUnit.MILLISECONDS); if (flag) { //5.如果是首次更新播放进度,发送消息到Kafka话题 //5.1 构建更新声音播放进度MQVO对象 TrackStatMqVo mqVo = new TrackStatMqVo(); //生成业务唯一标识,消费者端(专辑服务、搜索服务)用来做幂等性处理,确保一个消息只能只被处理一次 mqVo.setBusinessNo(IdUtil.fastSimpleUUID()); mqVo.setAlbumId(userListenProcessVo.getAlbumId()); mqVo.setTrackId(userListenProcessVo.getTrackId()); mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY); mqVo.setCount(1); //5.2 发送消息到更新声音统计话题中 kafkaService.sendMessage(KafkaConstant.QUEUE_TRACK_STAT_UPDATE, JSON.toJSONString(mqVo)); } } ### 3.2.2 更新MySQL统计信息 在service-album 微服务中添加监听消息: java package com.atguigu.tingshu.album.receiver; import com.alibaba.fastjson.JSON; import com.atguigu.tingshu.album.service.AlbumInfoService; import com.atguigu.tingshu.common.constant.KafkaConstant; import com.atguigu.tingshu.vo.album.TrackStatMqVo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; /** * @author: atguigu * @create: 2023-12-19 15:31 */ @Slf4j @Component public class AlbumReceiver { @Autowired private AlbumInfoService albumInfoService; /** * 监听到更新声音统计信息 * 1.考虑消息幂等性 2.是否需要事务管理 * * @param record */ @KafkaListener(topics = KafkaConstant.QUEUE_TRACK_STAT_UPDATE) public void updateTrackStat(ConsumerRecord<String, String> record) { String value = record.value(); if (StringUtils.isNotBlank(value)) { log.info("[专辑服务],监听到更新声音统计消息:{}", value); TrackStatMqVo mqVo = JSON.parseObject(value, TrackStatMqVo.class); albumInfoService.updateTrackStat(mqVo); } } } 在**TrackInfoService** 中添加接口 java /** * MQ监听更新声音统计信息 * @param mqVo */ void updateTrackStat(TrackStatMqVo mqVo); 在**TrackInfoServiceImpl** 中添加实现 java @Autowired private AlbumStatMapper albumStatMapper; @Autowired private RedisTemplate redisTemplate; /** * MQ监听更新声音统计信息(包含:播放、收藏、点赞、评论) * * @param mqVo */ @Override @Transactional(rollbackFor = Exception.class) public void updateTrackStat(TrackStatMqVo mqVo) { //1.做幂等性处理,统一个消息只处理一次 采用set k(业务消息唯一标识) v NX EX String key = "mq:" + mqVo.getBusinessNo(); try { Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, mqVo.getBusinessNo(), 1, TimeUnit.HOURS); if (flag) { //2.更新声音统计信息 trackStatMapper.updateStat(mqVo.getTrackId(), mqVo.getStatType(), mqVo.getCount()); //3.更新专辑统计信息(播放量、评论量只要声音+1,对应专辑也得+1) if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) { albumStatMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_PLAY, mqVo.getCount()); } if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) { albumStatMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_COMMENT, mqVo.getCount()); } } } catch (Exception e) { //如果更新数据库发送异常,事务会进行回滚,下次再次投递消息允许继续处理统一个消息 redisTemplate.delete(key); throw new RuntimeException(e); } } **TrackStatMapper**.java 添加方法 java package com.atguigu.tingshu.album.mapper; import com.atguigu.tingshu.model.album.TrackStat; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.*; @Mapper public interface TrackStatMapper extends BaseMapper<TrackStat> { /** * 更新声音统计信息 * @param trackId 声音ID * @param statType 统计类型 * @param count 数量 */ @Update("update track_stat set stat_num = stat_num + #{count} where track_id = #{trackId} and stat_type = #{statType}") void updateStat(@Param("trackId") Long trackId, @Param("statType") String statType, @Param("count") Integer count); } **AlbumStatMapper**.java 接口添加 java package com.atguigu.tingshu.album.mapper; import com.atguigu.tingshu.model.album.AlbumStat; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; @Mapper public interface AlbumStatMapper extends BaseMapper<AlbumStat> { @Update("update album_stat set stat_num = stat_num + #{count} where album_id = #{albumId} and stat_type = #{statType}") void updateStat(@Param("albumId") Long albumId, @Param("statType") String statType, @Param("count") Integer count); } ## 3.3 专辑上次播放专辑声音 ![image-20231012111356796](assets/image-20231012111356796.png) 我们需要根据用户Id 来获取播放记录 ,需要获取到专辑Id 与声音Id 封装到map中然后返回数据即可! > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/83 控制器 **UserListenProcessApiController** java /** * 获取当前用户上次播放专辑声音记录 * * @return */ @GuiGuLogin @GetMapping("/userListenProcess/getLatelyTrack") public Result<Map<String, Long>> getLatelyTrack() { Long userId = AuthContextHolder.getUserId(); return Result.ok(userListenProcessService.getLatelyTrack(userId)); } **UserListenProcessService接口:** java /** * 获取用户最近一次播放记录 * @param userId * @return */ Map<String, Long> getLatelyTrack(Long userId); **UserListenProcessServiceImpl实现类**: java /** * 获取用户最近一次播放记录 * * @param userId * @return */ @Override public Map<String, Long> getLatelyTrack(Long userId) { //根据用户ID查询播放进度集合,按照更新时间倒序,获取第一条记录 //1.构建查询条件对象 Query query = new Query(); //1.1 封装用户ID查询条件 query.addCriteria(Criteria.where("userId").is(userId)); //1.2 按照更新时间排序 query.with(Sort.by(Sort.Direction.DESC, "updateTime")); //1.3 只获取第一条记录 query.limit(1); //2.执行查询 UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId)); if (listenProcess != null) { //封装响应结果 Map<String, Long> mapResult = new HashMap<>(); mapResult.put("albumId", listenProcess.getAlbumId()); mapResult.put("trackId", listenProcess.getTrackId()); return mapResult; } return null; } ## 3.4 获取声音统计信息 ![](assets/tingshu014.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/75 统计声音需要更新的数据如下,我们将数据封装到一个实体类中便于操作 java @Data @Schema(description = "用户声音统计信息") public class TrackStatVo { @Schema(description = "播放量") private Integer playStatNum; @Schema(description = "订阅量") private Integer collectStatNum; @Schema(description = "点赞量") private Integer praiseStatNum; @Schema(description = "评论数") private Integer commentStatNum; //该属性需要修改 } 在**TrackInfoApiController** 控制器中添加 java /** * 根据声音ID,获取声音统计信息 * @param trackId * @return */ @Operation(summary = "根据声音ID,获取声音统计信息") @GetMapping("/trackInfo/getTrackStatVo/{trackId}") public Result<TrackStatVo> getTrackStatVo(@PathVariable Long trackId){ return Result.ok(trackInfoService.getTrackStatVo(trackId)); } **TrackInfoService接口**: java /** * 根据声音ID,查询声音统计信息 * @param trackId * @return */ TrackStatVo getTrackStatVo(Long trackId); **TrackInfoServiceImpl实现类**: java /** * 根据声音ID,查询声音统计信息 * @param trackId * @return */ @Override public TrackStatVo getTrackStatVo(Long trackId) { return trackInfoMapper.getTrackStatVo(trackId); } **TrackInfoMapper**.java java /** * 获取声音统计信息 * @param trackId * @return */ @Select("select\n" + " track_id,\n" + " max(if(stat_type='0701', stat_num, 0)) playStatNum,\n" + " max(if(stat_type='0702', stat_num, 0)) collectStatNum,\n" + " max(if(stat_type='0703', stat_num, 0)) praiseStatNum,\n" + " max(if(stat_type='0704', stat_num, 0)) commentStatNum\n" + " from track_stat where track_id = #{trackId} and is_deleted=0\n" + "group by track_id") TrackStatVo getTrackStatVo(@Param("trackId") Long trackId); **SQL** sql # 根据声音ID查询指定声音统计信息 playStatNum collectStatNum praiseStatNum commentStatNum select track_id, max(if(stat_type='0701', stat_num, 0)) playStatNum, max(if(stat_type='0702', stat_num, 0)) collectStatNum, max(if(stat_type='0703', stat_num, 0)) praiseStatNum, max(if(stat_type='0704', stat_num, 0)) commentStatNum from track_stat where track_id = 49162 and is_deleted=0 group by track_id # 4、更新Redis排行榜 手动调用一次更新,查看排行榜。后续会整合xxl-job 分布式定时任务调度框架做定时调用。 ![详情-排行榜](assets/详情-排行榜.gif) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/77 service-album 微服务中**BaseCategoryApiController**控制器中添加 java /** * 查询所有一级分类列表 * @return */ @Operation(summary = "查询所有一级分类列表") @GetMapping("/category/findAllCategory1") public Result> getAllCategory1() { return Result.ok(baseCategoryService.list()); } **AlbumFeignClient** java /** * 查询所有一级分类列表 * @return */ @GetMapping("/category/findAllCategory1") public Result> getAllCategory1(); **AlbumDegradeFeignClient熔断类**: java @Override public Result> getAllCategory1() { log.error("[专辑模块Feign调用]getAllCategory1异常"); return null; } > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/79 在**SearchApiController** 中添加控制器 java /** * 为定时更新首页排行榜提供调用接口 * @return */ @Operation(summary = "为定时更新首页排行榜提供调用接口") @GetMapping("/albumInfo/updateLatelyAlbumRanking") public Result updateLatelyAlbumRanking(){ searchService.updateLatelyAlbumRanking(); return Result.ok(); } **SearchService**接口: java /** * 获取不同分类下不同排序方式榜单专辑列表 */ void updateLatelyAlbumRanking(); **SearchServiceImpl实现类:** java @Autowired private RedisTemplate redisTemplate; /** * 获取不同分类下不同排序方式榜单专辑列表 */ @Override public void updateLatelyAlbumRanking() { try { //1.远程调用专辑服务获取所有1级分类列表 List<BaseCategory1> category1List = albumFeignClient.getdAllCategory1().getData(); Assert.notNull(category1List, "一级分类为空"); //2.循环遍历1级分类列表,获取该分类下5种不同排序方式榜单专辑 for (BaseCategory1 baseCategory1 : category1List) { Long category1Id = baseCategory1.getId(); //3.在处理当前1级分类中,再次循环5种不同排序方式得到具体榜单数据 //3.1 声明排序方式数组 String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"}; for (String rankingDimension : rankingDimensionArray) { //3.2 调用ES检索接口获取榜单数据 SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search( s -> s.index(INDEX_NAME) .query(q -> q.term(t -> t.field("category1Id").value(category1Id))) .sort(sort -> sort.field(f -> f.field(rankingDimension).order(SortOrder.Desc))) .size(10) , AlbumInfoIndex.class ); //3.3 获取当前分类下某个排序方式榜单专辑列表 List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits(); if (CollectionUtil.isNotEmpty(hits)) { List<AlbumInfoIndex> list = hits.stream().map(hit -> hit.source()).collect(Collectors.toList()); //4.将榜单专辑列表存入Redis-Hash中 //4.1 声明Redis排行榜Hash接口 Key 形式:前缀+1级分类ID field:排序方式 val:榜单列表 String key = RedisConstant.RANKING_KEY_PREFIX + category1Id; //4.2 将当前分类榜单数据放入Redis中 redisTemplate.opsForHash().put(key, rankingDimension, list); } } } } catch (Exception e) { log.error("[搜索服务]更新排行榜异常:{}", e); throw new RuntimeException(e); } } # 5、获取排行榜 ![image-20231012114420751](assets/image-20231012114420751.png) 点击排行榜的时候,能看到获取排行榜的地址 排行榜:key=ranking:category1Id field = hotScore 或 playStatNum 或 subscribeStatNum 或 buyStatNum 或albumCommentStatNum value=List<AlbumInfoIndexVo> ![](assets/tingshu016.png) > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/81 **SearchApiController** 控制器中添加 java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * @param category1Id * @param dimension * @return */ @Operation(summary = "获取指定1级分类下不同排序方式榜单列表") @GetMapping("/albumInfo/findRankingList/{category1Id}/{dimension}") public Result> getRankingList(@PathVariable Long category1Id, @PathVariable String dimension){ List<AlbumInfoIndex> list = searchService.getRankingList(category1Id, dimension); return Result.ok(list); } **SearchService**接口: java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * @param category1Id * @param dimension * @return */ List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension); **SearchServiceImpl实现类**: java /** * 获取指定1级分类下不同排序方式榜单列表-从Redis中获取 * * @param category1Id * @param dimension * @return */ @Override public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) { //1.构建分类排行榜Hash结构Key String key = RedisConstant.RANKING_KEY_PREFIX + category1Id; //2.获取Redis中hash结构中value Boolean flag = redisTemplate.opsForHash().hasKey(key, dimension); if (flag) { List<AlbumInfoIndex> list = (List) redisTemplate.opsForHash().get(key, dimension); return list; } return null; } 结合上面的内容介绍断点续播的实现思路,不需要给出代码

[root@ip-10-1-2-224 logs]# ps aux | grep java root 166922 0.1 5.0 4785424 397856 ? Sl 10:02 0:27 java -server -Xms2G -Xmx2G -Xss256K -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:/data/devops/camel-web-sql/logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/devops/camel-web-sql/logs/trade.dump -Djava.net.preferIPv4Stack=true -Dlog4j.configurationFile=config/logback-spring.xml -Dspring.config.name=application -classpath /data/devops/camel-web-sql/config:/data/devops/camel-web-sql/lib/aspectjweaver-1.9.7.jar:/data/devops/camel-web-sql/lib/attoparser-2.0.5.RELEASE.jar:/data/devops/camel-web-sql/lib/commons-codec-1.15.jar:/data/devops/camel-web-sql/lib/commons-lang3-3.12.0.jar:/data/devops/camel-web-sql/lib/druid-1.2.18.jar:/data/devops/camel-web-sql/lib/fastjson-1.2.83.jar:/data/devops/camel-web-sql/lib/hutool-all-5.8.21.jar:/data/devops/camel-web-sql/lib/jackson-annotations-2.13.3.jar:/data/devops/camel-web-sql/lib/jackson-core-2.13.3.jar:/data/devops/camel-web-sql/lib/jackson-databind-2.13.3.jar:/data/devops/camel-web-sql/lib/jackson-datatype-jdk8-2.13.3.jar:/data/devops/camel-web-sql/lib/jackson-datatype-jsr310-2.13.3.jar:/data/devops/camel-web-sql/lib/jackson-module-parameter-names-2.13.3.jar:/data/devops/camel-web-sql/lib/jakarta.annotation-api-1.3.5.jar:/data/devops/camel-web-sql/lib/java-jwt-3.10.3.jar:/data/devops/camel-web-sql/lib/jsch-0.2.9.jar:/data/devops/camel-web-sql/lib/jul-to-slf4j-1.7.36.jar:/data/devops/camel-web-sql/lib/log4j-api-2.17.2.jar:/data/devops/camel-web-sql/lib/log4j-to-slf4j-2.17.2.jar:/data/devops/camel-web-sql/lib/logback-classic-1.2.11.jar:/data/devops/camel-web-sql/lib/logback-core-1.2.11.jar:/data/devops/camel-web-sql/lib/lombok-1.18.16.jar:/data/devops/camel-web-sql/lib/mysql-connector-java-8.0.28.jar:/data/devops/camel-web-sql/lib/redshift-jdbc42-2.1.0.1.jar:/data/devops/camel-web-sql/lib/riam-token-util-1.0-SNAPSHOT.jar:/data/devops/camel-web-sql/lib/slf4j-api-1.7.36.jar:/data/devops/camel-web-sql/lib/snakeyaml-1.30.jar:/data/devops/camel-web-sql/lib/spring-aop-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-beans-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-boot-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-autoconfigure-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-aop-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-json-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-logging-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-thymeleaf-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-tomcat-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-boot-starter-web-2.7.2.jar:/data/devops/camel-web-sql/lib/spring-context-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-core-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-expression-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-jcl-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-web-5.3.22.jar:/data/devops/camel-web-sql/lib/spring-webmvc-5.3.22.jar:/data/devops/camel-web-sql/lib/thymeleaf-3.0.15.RELEASE.jar:/data/devops/camel-web-sql/lib/thymeleaf-extras-java8time-3.0.4.RELEASE.jar:/data/devops/camel-web-sql/lib/thymeleaf-spring5-3.0.15.RELEASE.jar:/data/devops/camel-web-sql/lib/tomcat-embed-core-9.0.65.jar:/data/devops/camel-web-sql/lib/tomcat-embed-el-9.0.65.jar:/data/devops/camel-web-sql/lib/tomcat-embed-websocket-9.0.65.jar:/data/devops/camel-web-sql/lib/unbescape-1.1.6.RELEASE.jar:/data/devops/camel-web-sql/lib/web-sql.jar:/data/devops/camel-web-sql/lib/web-sql.jar.back20240607:/data/devops/camel-web-sql/lib/web-sql.jar.back2024060702: com.camelgames.sql.Application root 177500 0.0 0.0 222316 2052 pts/0 S+ 15:19 0:00 grep --color=auto java如何将这个Java程序结束运行

最新推荐

recommend-type

Spring boot集成Go-FastDFS实现图片上传删除等功能实现

Console.log("resp: {}", resp); JSONObject exJson = JSONObject.parseObject(resp); uploadResult = JSON.toJavaObject(exJson, UploadResult.class); return uploadResult; } // ... } ``` 本文主要介绍了...
recommend-type

工业自动化领域中步科触摸屏与台达VFD-M变频器通讯实现电机控制功能 - 电机控制

内容概要:本文档详细介绍了使用步科触摸屏和台达VFD-M变频器实现电机控制功能的技术细节。主要内容涵盖所需的硬件配置(如步科T070触摸屏和支持485功能的USB转485转换头),以及具体的功能实现方法,包括正反转控制、点动停止、频率设定、运行频率读取、电流电压和运行状态的监控。此外,还强调了通讯协议的重要性及其具体实施步骤。 适用人群:从事工业自动化领域的工程师和技术人员,特别是那些负责电机控制系统设计和维护的专业人士。 使用场景及目标:适用于需要集成步科触摸屏与台达VFD-M变频器进行电机控制的应用场合,旨在帮助技术人员掌握正确的硬件选型、安装配置及编程技巧,从而确保系统的稳定性和可靠性。 其他说明:文中提到的操作流程和注意事项有助于避免常见的错误并提高工作效率。同时,提供了详细的通讯说明,确保不同设备之间的兼容性和数据传输的准确性。
recommend-type

langchain4j-community-core-1.0.0-beta4.jar中文-英文对照文档.zip

1、压缩文件中包含: 中文-英文对照文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文-英文对照文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

介电弹性体PID DEA模型的参数配置、控制策略与MatlabSimulink建模研究 实战版

内容概要:本文详细探讨了介电弹性体(DEA)PID控制模型的参数配置、控制策略及其在Matlab/Simulink环境中的建模方法。首先介绍了DEA的基本特性如迟滞和非线性响应,并给出了具体的机械系统参数(如刚度、质量和阻尼)。接着讨论了PID控制器的设计,包括基础的位置式PID实现以及针对实际应用需要加入的抗饱和和滤波措施。对于存在输入延迟的情况,提出了使用Smith预估器的方法,并指出其对模型精度的要求。面对突加负载等扰动,推荐采用串级控制提高系统的稳定性。最后强调了利用Automated PID Tuning工具进行参数调整时应注意的问题。 适合人群:从事智能材料控制系统研究的科研人员和技术开发者。 使用场景及目标:适用于希望深入了解并优化介电弹性体驱动器性能的研究者,在理论学习的基础上掌握具体的操作技能,从而更好地应对实际工程中的挑战。 其他说明:文中提供了详细的MATLAB代码片段用于指导读者构建自己的DEA控制模型,同时分享了许多实践经验,帮助避免常见的错误。
recommend-type

Webdiy.net新闻系统v1.0企业版发布:功能强大、易操作

标题中提到的"Webdiy.net新闻系统 v1.0 企业版"是一个针对企业级应用开发的新闻内容管理系统,是基于.NET框架构建的。从描述中我们可以提炼出以下知识点: 1. **系统特性**: - **易用性**:系统设计简单,方便企业用户快速上手和操作。 - **可定制性**:用户可以轻松修改网站的外观和基本信息,例如网页标题、页面颜色、页眉和页脚等,以符合企业的品牌形象。 2. **数据库支持**: - **Access数据库**:作为轻量级数据库,Access对于小型项目和需要快速部署的场景非常合适。 - **Sql Server数据库**:适用于需要强大数据处理能力和高并发支持的企业级应用。 3. **性能优化**: - 系统针对Access和Sql Server数据库进行了特定的性能优化,意味着它能够提供更为流畅的用户体验和更快的数据响应速度。 4. **编辑器功能**: - **所见即所得编辑器**:类似于Microsoft Word,允许用户进行图文混排编辑,这样的功能对于非技术人员来说非常友好,因为他们可以直观地编辑内容而无需深入了解HTML或CSS代码。 5. **图片管理**: - 新闻系统中包含在线图片上传、浏览和删除的功能,这对于新闻编辑来说是非常必要的,可以快速地为新闻内容添加相关图片,并且方便地进行管理和更新。 6. **内容发布流程**: - **审核机制**:后台发布新闻后,需经过审核才能显示到网站上,这样可以保证发布的内容质量,减少错误和不当信息的传播。 7. **内容排序与类别管理**: - 用户可以按照不同的显示字段对新闻内容进行排序,这样可以突出显示最新或最受欢迎的内容。 - 新闻类别的动态管理及自定义显示顺序,可以灵活地对新闻内容进行分类,方便用户浏览和查找。 8. **前端展示**: - 系统支持Javascript前端页面调用,这允许开发者将系统内容嵌入到其他网页或系统中。 - 支持iframe调用,通过这种HTML元素可以将系统内容嵌入到网页中,实现了内容的跨域展示。 9. **安全性**: - 提供了默认的管理账号和密码(webdiy / webdiy.net),对于企业应用来说,这些默认的凭证需要被替换,以保证系统的安全性。 10. **文件结构**: - 压缩包文件名称为"webdiynetnews",这可能是系统的根目录名称或主要安装文件。 11. **技术栈**: - 系统基于ASP.NET技术构建,这表明它使用.NET框架开发,并且可以利用.NET生态中的各种库和工具来实现功能的扩展和维护。 在实施和部署这样的系统时,企业可能还需要考虑以下方面: - **可扩展性**:随着业务的增长,系统应该能容易地扩展,以支持更多的用户和内容。 - **安全性**:除了更改为安全的管理员凭证外,还需考虑防止SQL注入、XSS攻击等网络安全问题。 - **备份和恢复**:定期备份数据库和内容是很重要的,以防数据丢失或需要恢复到先前的状态。 - **多用户和权限管理**:企业环境可能需要多级用户权限设置,以适应不同的团队和角色。 - **SEO优化**:系统应支持良好的搜索引擎优化实践,以便于内容在搜索引擎中的排名和发现。 - **移动适应性**:由于移动设备的广泛使用,系统应具备良好的响应式设计,以提供良好的移动用户体验。 根据描述,该系统似乎是一个成熟的解决方案,适用于需要快速搭建新闻网站的企业用户,但同时也应该注意定期的维护和更新,以确保系统的稳定性和安全性。
recommend-type

【Windows 11用户的福音】:一步到位解决GX Works2安装问题,让兼容性不再是问题!

# 1. GX Works2简介与Windows 11兼容性问题 ## 简介GX Works2 GX Works2是由三菱电机开发的一款针对其PLC(可编程逻辑控制器)产品的编程和调试软件。该软件以其直观的用户界面和强大的编程功能闻名于工业自动化领域,支持从简单的逻辑控制到复杂的系统集成项目。随着Windows操作系统的迭代升级,尤其是新推
recommend-type

电磁加热双边谐振和单边谐振的区别

<think>我们正在处理关于电磁加热技术中双边谐振与单边谐振的工作原理及区别的问题。 首先,需要明确用户意图是了解这两种谐振方式的区别以及它们的工作原理。 我们将通过以下步骤进行: 1. 调用网络搜索工具,检索关于电磁加热中双边谐振和单边谐振的信息。 2. 整合检索到的内容,按照要求生成回答,包括工作原理和区别,并组织成多个方法或步骤(但避免使用步骤词汇,用换行分隔)。 3. 在回答中,如果有公式或代码,按照指定格式处理。 4. 最后,添加相关问题部分。 注意:避免使用第一人称,避免步骤词汇,引用内容不集中末尾,而是融入回答中。 根据搜索,电磁加热中的谐振通常指的是感应加
recommend-type

EnvMan源代码压缩包内容及功能解析

根据给定文件信息,我们需要生成关于“EnvMan-source.zip”这一压缩包的知识点。首先,由于提供的信息有限,我们无法直接得知EnvMan-source.zip的具体内容和功能,但可以通过标题、描述和标签中的信息进行推断。文件名称列表只有一个“EnvMan”,这暗示了压缩包可能包含一个名为EnvMan的软件或项目源代码。以下是一些可能的知识点: ### EnvMan软件/项目概览 EnvMan可能是一个用于环境管理的工具或框架,其源代码被打包并以“EnvMan-source.zip”的形式进行分发。通常,环境管理相关的软件用于构建、配置、管理和维护应用程序的运行时环境,这可能包括各种操作系统、服务器、中间件、数据库等组件的安装、配置和版本控制。 ### 源代码文件说明 由于只有一个名称“EnvMan”出现在文件列表中,我们可以推测这个压缩包可能只包含一个与EnvMan相关的源代码文件夹。源代码文件夹可能包含以下几个部分: - **项目结构**:展示EnvMan项目的基本目录结构,通常包括源代码文件(.c, .cpp, .java等)、头文件(.h, .hpp等)、资源文件(图片、配置文件等)、文档(说明文件、开发者指南等)、构建脚本(Makefile, build.gradle等)。 - **开发文档**:可能包含README文件、开发者指南或者项目wiki,用于说明EnvMan的功能、安装、配置、使用方法以及可能的API说明或开发者贡献指南。 - **版本信息**:在描述中提到了版本号“-1101”,这表明我们所见的源代码包是EnvMan的1101版本。通常版本信息会详细记录在版本控制文件(如ChangeLog或RELEASE_NOTES)中,说明了本次更新包含的新特性、修复的问题、已知的问题等。 ### 压缩包的特点 - **命名规范**:标题、描述和标签中的一致性表明这是一个正式发布的软件包。通常,源代码包的命名会遵循一定的规范,如“项目名称-版本号-类型”,在这里类型是“source”。 - **分发形式**:以.zip格式的压缩包进行分发,是一种常见的软件源代码分发方式。虽然较现代的版本控制系统(如Git、Mercurial)通常支持直接从仓库克隆源代码,但打包成zip文件依然是一种便于存储和传输的手段。 ### 可能的应用场景 - **开发环境配置**:EnvMan可能是用于创建、配置和管理开发环境的工具,这种工具在开发人员设置新的开发机或新的项目环境时非常有用。 - **自动化部署**:EnvMan可能包含自动化部署环境的脚本或命令,使得部署流程变得快捷且高效。 - **监控与维护**:作为环境管理工具,EnvMan可能还支持对环境的监控功能,包括系统资源监控、服务状态检查等,以保证生产环境的稳定性。 ### 总结 尽管以上知识点是基于有限的信息进行的假设性推论,但EnvMan-source.zip包可能是一个用于环境管理的软件或项目的源代码包。该软件或项目可能包含构建和部署自动化环境的能力,以及对运行时环境的监控和维护。文件命名的一致性暗示这是一个正式的版本发布。如果要深入了解EnvMan的功能与用法,建议直接查看压缩包中的文档或源代码注释。同时,考虑到源代码的开发,我们还应该探究该项目所使用的技术栈、编程语言以及版本控制工具等,这将有助于进一步了解EnvMan的技术细节。
recommend-type

【Windows 11终极解决方案】:彻底攻克GX Works2安装中难缠的.Net Framework 3.5障碍!

# 1. Windows 11与GX Works2简介 ## 1.1 Windows 11操作系统概览 Windows 11,作为微软最新的操作系统,不仅仅提供了一种现代的用户体验,而且加强了在企业环境中的安全性与生产力工具。其引入了全新的界面设计、改进的多任务处理以及对Android应用的支持,使它成为IT专业人
recommend-type

贝叶斯gmm

<think>首先,用户的问题是关于贝叶斯高斯混合模型(Bayesian Gaussian Mixture Model, BGMM)的原理和应用。我需要根据系统指令来构建回答。 系统指令包括: - 所有行内数学表达式使用 `$...$` 格式。 - 独立公式使用 `$$...$$` 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 参考提供的引用: - 引用[1]:讨论了高斯混合模型在机器学习中的应用,包括其优点和挑战。 -