From 29152e56a430cf01c06a41e45618e7a863f646df Mon Sep 17 00:00:00 2001
From: huliyong <2783385703@qq.com>
Date: Thu, 23 Oct 2025 19:58:07 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat(=E5=B0=8F=E7=A8=8B=E5=BA=8F=E6=96=87?=
=?UTF-8?q?=E7=AB=A0):=20=E6=96=B0=E5=A2=9E=E5=B0=8F=E7=A8=8B=E5=BA=8F?=
=?UTF-8?q?=E6=96=87=E7=AB=A0=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加小程序文章管理的完整功能模块,包括:
1. API接口定义文件(AppletArticle.api.ts)
2. 数据模型和表单配置(AppletArticle.data.ts)
3. 表单组件(AppletArticleForm.vue)
4. 模态框组件(AppletArticleModal.vue)
5. 列表页面(AppletArticleList.vue)
实现文章的增删改查、批量操作、导入导出等功能
---
.../src/views/applet/article/AppletArticle.api.ts | 64 +++++++
.../src/views/applet/article/AppletArticle.data.ts | 77 ++++++++
.../src/views/applet/article/AppletArticleList.vue | 206 +++++++++++++++++++++
.../article/components/AppletArticleForm.vue | 70 +++++++
.../article/components/AppletArticleModal.vue | 99 ++++++++++
5 files changed, 516 insertions(+)
create mode 100644 jeecgboot-vue3/src/views/applet/article/AppletArticle.api.ts
create mode 100644 jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
create mode 100644 jeecgboot-vue3/src/views/applet/article/AppletArticleList.vue
create mode 100644 jeecgboot-vue3/src/views/applet/article/components/AppletArticleForm.vue
create mode 100644 jeecgboot-vue3/src/views/applet/article/components/AppletArticleModal.vue
diff --git a/jeecgboot-vue3/src/views/applet/article/AppletArticle.api.ts b/jeecgboot-vue3/src/views/applet/article/AppletArticle.api.ts
new file mode 100644
index 0000000..433950b
--- /dev/null
+++ b/jeecgboot-vue3/src/views/applet/article/AppletArticle.api.ts
@@ -0,0 +1,64 @@
+import {defHttp} from '/@/utils/http/axios';
+import { useMessage } from "/@/hooks/web/useMessage";
+
+const { createConfirm } = useMessage();
+
+enum Api {
+ list = '/appletArticle/appletArticle/list',
+ save='/appletArticle/appletArticle/add',
+ edit='/appletArticle/appletArticle/edit',
+ deleteOne = '/appletArticle/appletArticle/delete',
+ deleteBatch = '/appletArticle/appletArticle/deleteBatch',
+ importExcel = '/appletArticle/appletArticle/importExcel',
+ exportXls = '/appletArticle/appletArticle/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) =>
+ defHttp.get({url: Api.list, params});
+
+/**
+ * 删除单个
+ */
+export const deleteOne = (params,handleSuccess) => {
+ return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
+ handleSuccess();
+ });
+}
+/**
+ * 批量删除
+ * @param params
+ */
+export const batchDelete = (params, handleSuccess) => {
+ createConfirm({
+ iconType: 'warning',
+ title: '确认删除',
+ content: '是否删除选中数据',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: () => {
+ return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
+ handleSuccess();
+ });
+ }
+ });
+}
+/**
+ * 保存或者更新
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+ let url = isUpdate ? Api.edit : Api.save;
+ return defHttp.post({url: url, params});
+}
diff --git a/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts b/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
new file mode 100644
index 0000000..ea351e5
--- /dev/null
+++ b/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
@@ -0,0 +1,77 @@
+import {BasicColumn} from '/@/components/Table';
+import {FormSchema} from '/@/components/Table';
+import { rules} from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+import { getWeekMonthQuarterYear } from '/@/utils';
+//列表数据
+export const columns: BasicColumn[] = [
+ {
+ title: '标题',
+ align:"center",
+ dataIndex: 'title'
+ },
+ {
+ title: '排序',
+ align:"center",
+ dataIndex: 'sort'
+ },
+ {
+ title: '上架',
+ align:"center",
+ dataIndex: 'status',
+ customRender:({text}) => {
+ return render.renderSwitch(text, [{text:'是',value:'Y'},{text:'否',value:'N'}])
+ },
+ },
+];
+//查询数据
+export const searchFormSchema: FormSchema[] = [
+];
+//表单数据
+export const formSchema: FormSchema[] = [
+ {
+ label: '标题',
+ field: 'title',
+ component: 'Input',
+ },
+ {
+ label: '排序',
+ field: 'sort',
+ component: 'InputNumber',
+ },
+ {
+ label: '上架',
+ field: 'status',
+ component: 'JSwitch',
+ componentProps:{
+ },
+ },
+ {
+ label: '内容',
+ field: 'content',
+ component: 'JEditor',
+ },
+ // TODO 主键隐藏字段,目前写死为ID
+ {
+ label: '',
+ field: 'id',
+ component: 'Input',
+ show: false
+ },
+];
+
+// 高级查询数据
+export const superQuerySchema = {
+ title: {title: '标题',order: 0,view: 'text', type: 'string',},
+ sort: {title: '排序',order: 1,view: 'number', type: 'number',},
+ status: {title: '上架',order: 2,view: 'switch', type: 'string',},
+};
+
+/**
+* 流程表单调用这个方法获取formSchema
+* @param param
+*/
+export function getBpmFormSchema(_formData): FormSchema[]{
+ // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
+ return formSchema;
+}
\ No newline at end of file
diff --git a/jeecgboot-vue3/src/views/applet/article/AppletArticleList.vue b/jeecgboot-vue3/src/views/applet/article/AppletArticleList.vue
new file mode 100644
index 0000000..78f1a9c
--- /dev/null
+++ b/jeecgboot-vue3/src/views/applet/article/AppletArticleList.vue
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+ 新增
+ 导出
+ 导入
+
+
+
+
+
+
+ 删除
+
+
+
+ 批量操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecgboot-vue3/src/views/applet/article/components/AppletArticleForm.vue b/jeecgboot-vue3/src/views/applet/article/components/AppletArticleForm.vue
new file mode 100644
index 0000000..4e98b11
--- /dev/null
+++ b/jeecgboot-vue3/src/views/applet/article/components/AppletArticleForm.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecgboot-vue3/src/views/applet/article/components/AppletArticleModal.vue b/jeecgboot-vue3/src/views/applet/article/components/AppletArticleModal.vue
new file mode 100644
index 0000000..34c8f5e
--- /dev/null
+++ b/jeecgboot-vue3/src/views/applet/article/components/AppletArticleModal.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From f2ef5b523af50274f723a4bf8d1067ed1e35a108 Mon Sep 17 00:00:00 2001
From: huliyong <2783385703@qq.com>
Date: Thu, 23 Oct 2025 20:01:52 +0800
Subject: [PATCH 2/2] =?UTF-8?q?feat(=E5=B0=8F=E7=A8=8B=E5=BA=8F=E6=96=87?=
=?UTF-8?q?=E7=AB=A0):=20=E6=96=B0=E5=A2=9E=E5=B0=8F=E7=A8=8B=E5=BA=8F?=
=?UTF-8?q?=E6=96=87=E7=AB=A0=E6=A8=A1=E5=9D=97=E5=8F=8A=E7=9B=B8=E5=85=B3?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增小程序文章模块,包括文章管理、列表展示、详情查看功能
添加文章TTS语音合成支持,包括长文本TTS任务生成和回调处理
实现文章内容的多音色语音合成功能
优化TTS回调接口,增强异常处理和日志记录
更新pom.xml配置,设置maven-compiler-plugin为Java 9
修复AppletTtsCache实体类字段命名问题
---
.../jeecgboot-boot-applet/pom.xml | 12 +
.../controller/AppletApiIndexController.java | 16 ++
.../applet/controller/AppletApiTTSController.java | 146 ++++++++++-
.../applet/service/AppletApiIndexService.java | 5 +
.../modules/applet/service/AppletApiTTService.java | 15 ++
.../service/impl/AppletApiIndexServiceImpl.java | 36 +++
.../service/impl/AppletApiTTServiceImpl.java | 287 +++++++++++++++++----
.../controller/AppletArticleController.java | 207 +++++++++++++++
.../demo/appletArticle/entity/AppletArticle.java | 77 ++++++
.../appletArticle/mapper/AppletArticleMapper.java | 17 ++
.../mapper/xml/AppletArticleMapper.xml | 5 +
.../service/IAppletArticleService.java | 14 +
.../service/impl/AppletArticleServiceImpl.java | 19 ++
.../appletArticle/uniapp/AppletArticleForm.vue | 101 ++++++++
.../appletArticle/uniapp/AppletArticleList.vue | 44 ++++
.../appletArticle/uniapp3/AppletArticleData.ts | 22 ++
.../appletArticle/uniapp3/AppletArticleForm.vue | 253 ++++++++++++++++++
.../appletArticle/uniapp3/AppletArticleList.vue | 148 +++++++++++
.../demo/appletArticle/vue3/AppletArticle.api.ts | 64 +++++
.../demo/appletArticle/vue3/AppletArticle.data.ts | 77 ++++++
.../demo/appletArticle/vue3/AppletArticleList.vue | 210 +++++++++++++++
.../V20251023_1__menu_insert_AppletArticle.sql | 26 ++
.../vue3/components/AppletArticleForm.vue | 70 +++++
.../vue3/components/AppletArticleModal.vue | 99 +++++++
.../demo/appletTtsCache/entity/AppletTtsCache.java | 2 +-
.../src/views/applet/article/AppletArticle.data.ts | 10 +-
26 files changed, 1907 insertions(+), 75 deletions(-)
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/controller/AppletArticleController.java
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/entity/AppletArticle.java
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/AppletArticleMapper.java
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/xml/AppletArticleMapper.xml
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/IAppletArticleService.java
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/impl/AppletArticleServiceImpl.java
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleForm.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleList.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleData.ts
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleForm.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleList.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.api.ts
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.data.ts
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticleList.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/V20251023_1__menu_insert_AppletArticle.sql
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleForm.vue
create mode 100644 jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleModal.vue
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml
index 58e624c..69374d3 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/pom.xml
@@ -3,6 +3,18 @@
4.0.0
jeecgboot-boot-applet
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 9
+ 9
+
+
+
+
jeecg-boot-module
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiIndexController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiIndexController.java
index 4154c96..ee8ff3e 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiIndexController.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiIndexController.java
@@ -8,6 +8,7 @@ import org.jeecg.common.api.vo.Result;
import org.jeecg.config.shiro.IgnoreAuth;
import org.jeecg.modules.applet.service.AppletApiConfigService;
import org.jeecg.modules.applet.service.AppletApiIndexService;
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
import org.jeecg.modules.demo.appletBanner.entity.AppletBanner;
import org.jeecg.modules.demo.appletConfig.entity.AppletConfig;
import org.jeecg.modules.demo.appletLink.entity.AppletLink;
@@ -49,6 +50,7 @@ public class AppletApiIndexController {
List list = appletApiIndexService.getBanner();
return Result.OK(list);
}
+
/**
* 轮播图详情
*
@@ -87,4 +89,18 @@ public class AppletApiIndexController {
appletApiIndexService.linkSignup(appletRegistration);
return Result.OK();
}
+
+ @IgnoreAuth
+ @Operation(summary = "查询文章列表", description = "查询文章列表")
+ @GetMapping(value = "/articleList")
+ public Result> articleList() {
+ return Result.OK(appletApiIndexService.articleList());
+ }
+
+ @IgnoreAuth
+ @Operation(summary = "查询文章详情", description = "查询文章详情")
+ @GetMapping(value = "/articleDetail")
+ public Result articleDetail(String id) {
+ return Result.OK(appletApiIndexService.articleDetail(id));
+ }
}
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java
index e8f515d..552c0f2 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/controller/AppletApiTTSController.java
@@ -17,6 +17,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
+import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
@@ -122,23 +123,142 @@ public class AppletApiTTSController {
// }
@Operation(summary = "接收长文本TTS回调", description = "接收腾讯云回调并保存音频与状态")
- @PostMapping(value = "/long/callback")
+ @PostMapping(value = "/long/callback", consumes = {"application/json", "application/x-www-form-urlencoded", "text/plain"})
@IgnoreAuth
- public Result ttsCallback(@RequestBody Map payload) {
+ public Result ttsCallback(HttpServletRequest request) {
try {
- log.info("回调内容: {}", payload);
- String taskId = (String) payload.get("TaskId");
- Boolean success = payload.get("Success") instanceof Boolean ? (Boolean) payload.get("Success") : null;
- String audioBase64 = (String) payload.get("Audio");
- Integer sampleRate = payload.get("SampleRate") instanceof Number ? ((Number) payload.get("SampleRate")).intValue() : null;
- String codec = (String) payload.get("Codec");
- String message = (String) payload.get("Message");
-
- boolean ok = appletApiTTService.handleTtsCallback(taskId, audioBase64, sampleRate, codec, success != null && success, message);
+ String contentType = request.getContentType();
+ log.info("回调请求 Content-Type: {}", contentType);
+
+ String raw = null;
+
+ // 直接从 InputStream 读取原始字节,避免 Spring 的参数解析
+ try (java.io.InputStream is = request.getInputStream()) {
+ byte[] bytes = is.readAllBytes();
+ raw = new String(bytes, java.nio.charset.StandardCharsets.UTF_8);
+ log.info("原始请求体长度: {} 字节", bytes.length);
+ }
+
+ if (raw == null || raw.trim().isEmpty()) {
+ log.error("请求体为空");
+ return Result.error("请求体为空");
+ }
+
+ log.info("原始请求体前200字符: {}", raw.length() > 200 ? raw.substring(0, 200) + "..." : raw);
+
+ String json = raw;
+
+ // 处理 form-urlencoded 格式
+ if (contentType != null && contentType.contains("application/x-www-form-urlencoded")) {
+ // 查找 data= 参数
+ int dataIdx = raw.indexOf("data=");
+ if (dataIdx >= 0) {
+ String encoded = raw.substring(dataIdx + 5);
+
+ // 处理可能存在的其他参数(用 & 分隔)
+ int ampIdx = encoded.indexOf('&');
+ if (ampIdx > 0) {
+ encoded = encoded.substring(0, ampIdx);
+ }
+
+ try {
+ // 多次 URL 解码,处理可能的双重编码
+ json = java.net.URLDecoder.decode(encoded, "UTF-8");
+
+ // 如果解码后仍然包含 %,可能需要再次解码
+ if (json.contains("%")) {
+ try {
+ String doubleDecoded = java.net.URLDecoder.decode(json, "UTF-8");
+ if (doubleDecoded.trim().startsWith("{")) {
+ json = doubleDecoded;
+ }
+ } catch (Exception e) {
+ log.warn("二次URL解码失败,使用一次解码结果: {}", e.getMessage());
+ }
+ }
+
+ log.info("URL解码后的JSON前200字符: {}", json.length() > 200 ? json.substring(0, 200) + "..." : json);
+ } catch (Exception decodeEx) {
+ log.warn("URL解码失败,使用原始编码数据: {}", decodeEx.getMessage());
+ json = encoded;
+ }
+ }
+ } else if (raw.startsWith("data=")) {
+ // 处理直接以 data= 开头的情况
+ String encoded = raw.substring(5);
+ try {
+ json = java.net.URLDecoder.decode(encoded, "UTF-8");
+ log.info("直接data=格式解码后: {}", json.length() > 200 ? json.substring(0, 200) + "..." : json);
+ } catch (Exception decodeEx) {
+ log.warn("直接data=格式解码失败: {}", decodeEx.getMessage());
+ json = encoded;
+ }
+ }
+
+ // 解析 JSON
+ com.alibaba.fastjson.JSONObject obj = null;
+
+ // 清理可能的前后空白字符和引号
+ json = json.trim();
+ if (json.startsWith("\"") && json.endsWith("\"")) {
+ json = json.substring(1, json.length() - 1);
+ }
+
+ if (json.startsWith("{")) {
+ try {
+ obj = com.alibaba.fastjson.JSON.parseObject(json);
+ } catch (Exception parseEx) {
+ log.error("JSON解析失败: {}, JSON内容: {}", parseEx.getMessage(), json);
+ // 尝试提取 JSON 部分
+ int start = json.indexOf('{');
+ int end = json.lastIndexOf('}');
+ if (start >= 0 && end > start) {
+ try {
+ obj = com.alibaba.fastjson.JSON.parseObject(json.substring(start, end + 1));
+ } catch (Exception e2) {
+ log.error("提取JSON部分后仍解析失败: {}", e2.getMessage());
+ }
+ }
+ }
+ } else {
+ // 尝试查找 JSON 部分
+ int start = json.indexOf('{');
+ int end = json.lastIndexOf('}');
+ if (start >= 0 && end > start) {
+ try {
+ obj = com.alibaba.fastjson.JSON.parseObject(json.substring(start, end + 1));
+ } catch (Exception parseEx) {
+ log.error("提取JSON部分解析失败: {}", parseEx.getMessage());
+ }
+ }
+ }
+
+ if (obj == null) {
+ log.error("回调解析失败,无法提取有效JSON,原始内容: {}", json);
+ return Result.error("回调解析失败,非JSON格式");
+ }
+
+ log.info("成功解析回调JSON,TaskId: {}", obj.getString("TaskId"));
+
+ String taskId = obj.getString("TaskId");
+ Long status = obj.getLong("Status");
+ String statusStr = obj.getString("StatusStr");
+ String resultUrl = obj.getString("ResultUrl");
+ com.alibaba.fastjson.JSONArray subtitles = obj.getJSONArray("Subtitles");
+ String subtitlesJson = subtitles != null ? subtitles.toJSONString() : null;
+
+ // 清理URL中的反引号等异常字符
+ if (resultUrl != null) {
+ resultUrl = resultUrl.replace("`", "").trim();
+ log.info("清理后的ResultUrl: {}", resultUrl);
+ }
+
+ boolean ok = appletApiTTService.handleLongTextTtsCallback(taskId, resultUrl, status, statusStr, subtitlesJson);
return ok ? Result.OK(true) : Result.error("回调处理失败");
+
} catch (Exception e) {
- log.error("回调解析失败: {}", e.getMessage(), e);
- return Result.error("回调解析失败: " + e.getMessage());
+ log.error("回调处理异常: {}", e.getMessage(), e);
+ return Result.error("回调处理异常: " + e.getMessage());
}
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiIndexService.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiIndexService.java
index 9d45b8c..749adaa 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiIndexService.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiIndexService.java
@@ -1,5 +1,6 @@
package org.jeecg.modules.applet.service;
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
import org.jeecg.modules.demo.appletBanner.entity.AppletBanner;
import org.jeecg.modules.demo.appletLink.entity.AppletLink;
import org.jeecg.modules.demo.appletRegistration.entity.AppletRegistration;
@@ -37,4 +38,8 @@ public interface AppletApiIndexService {
* @param appletRegistration
*/
void linkSignup(AppletRegistration appletRegistration);
+
+ List articleList();
+
+ AppletArticle articleDetail(String id);
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiTTService.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiTTService.java
index 28d8fe1..7f253ee 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiTTService.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/AppletApiTTService.java
@@ -6,6 +6,7 @@ import org.jeecg.modules.demo.appletTtsTimbre.entity.AppletTtsTimbre;
import org.jeecg.modules.demo.appletTtsCache.entity.AppletTtsCache;
import java.util.List;
+import java.util.Map;
public interface AppletApiTTService {
/**
@@ -27,6 +28,11 @@ public interface AppletApiTTService {
*/
String createLongTextTtsTask(String text, Float speed, Integer voiceType, Float volume, String codec, String callbackUrl);
+ /**
+ * 为文章内容遍历所有启用音色创建长文本TTS任务(异步)
+ */
+ void generateLongTextForArticleContentAllTimbres(String content);
+
/**
* 按页面创建长文本TTS任务(异步),仅接收页面ID与音色ID
*/
@@ -46,4 +52,13 @@ public interface AppletApiTTService {
* 接收腾讯云TTS异步回调并处理结果,保存到缓存并更新状态
*/
boolean handleTtsCallback(String taskId, String audioBase64, Integer sampleRate, String codec, boolean success, String message);
+
+ /**
+ * 接收腾讯云长文本TTS异步回调并处理结果,保存ResultUrl和字幕到缓存
+ */
+ boolean handleLongTextTtsCallback(String taskId, String resultUrl, Long status, String statusStr, String subtitlesJson);
+
+ Map selectMapByHtml(String content);
+
+
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiIndexServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiIndexServiceImpl.java
index 7754723..6b9ac1b 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiIndexServiceImpl.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiIndexServiceImpl.java
@@ -3,6 +3,9 @@ package org.jeecg.modules.applet.service.impl;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.util.AppletUserUtil;
import org.jeecg.modules.applet.service.AppletApiIndexService;
+import org.jeecg.modules.applet.service.AppletApiTTService;
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
+import org.jeecg.modules.demo.appletArticle.service.IAppletArticleService;
import org.jeecg.modules.demo.appletBanner.entity.AppletBanner;
import org.jeecg.modules.demo.appletBanner.service.IAppletBannerService;
import org.jeecg.modules.demo.appletCoursePage.entity.AppletCoursePage;
@@ -14,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
+import java.util.Map;
@Service
public class AppletApiIndexServiceImpl implements AppletApiIndexService {
@@ -27,6 +31,12 @@ public class AppletApiIndexServiceImpl implements AppletApiIndexService {
@Autowired
private IAppletRegistrationService appletRegistrationService;
+ @Autowired
+ private IAppletArticleService appletArticleService;
+
+ @Autowired
+ private AppletApiTTService appletApiTTService;
+
/***
* 轮播图
* @return
@@ -87,4 +97,30 @@ public class AppletApiIndexServiceImpl implements AppletApiIndexService {
appletRegistrationService.save(appletRegistration);
}
+
+ @Override
+ public List articleList() {
+ return appletArticleService
+ .lambdaQuery()
+ .orderByDesc(AppletArticle::getSort, AppletArticle::getCreateTime)
+ .eq(AppletArticle::getStatus, "Y")
+ .select(AppletArticle::getTitle, AppletArticle::getId)
+ .list();
+ }
+
+ @Override
+ public AppletArticle articleDetail(String id) {
+
+ AppletArticle byId = appletArticleService.getById(id);
+
+ if (byId == null){
+ return null;
+ }
+
+ //查询音频
+ Map map = appletApiTTService.selectMapByHtml(byId.getContent());
+ byId.setAudios(map);
+
+ return byId;
+ }
}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java
index 1297b2f..b253dfa 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/applet/service/impl/AppletApiTTServiceImpl.java
@@ -26,6 +26,7 @@ import org.jeecg.modules.demo.appletCoursePage.entity.AppletCoursePage;
import org.jeecg.modules.demo.appletCoursePage.service.IAppletCoursePageService;
import org.jeecg.modules.demo.appletCoursePageWord.entity.AppletCoursePageWord;
import org.jeecg.modules.demo.appletCoursePageWord.service.IAppletCoursePageWordService;
+import org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog;
import org.jeecg.modules.demo.appletTtsPlayLog.service.IAppletTtsPlayLogService;
import org.jeecg.modules.demo.appletTtsTimbre.entity.AppletTtsTimbre;
import org.jeecg.modules.demo.appletTtsTimbre.service.IAppletTtsTimbreService;
@@ -35,11 +36,11 @@ import org.jeecg.modules.applet.util.AudioDurationUtil;
import java.io.ByteArrayInputStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Service;
-import java.util.List;
-import java.util.UUID;
-import java.util.Base64;
+import java.util.*;
import java.net.URL;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
@@ -65,6 +66,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
private IAppletCoursePageService appletCoursePageService;
@Autowired
private IAppletCoursePageWordService appletCoursePageWordService;
+ @Autowired
+ private RedisTemplate redisTemplate;
public TtsVo textToVoice(String text, Float speed, Integer voiceType, Float volume, String codec) {
@@ -78,23 +81,23 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 1. 先查询缓存数据库,看是否已经有相同参数的音频
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AppletTtsCache::getText, text)
- .eq(AppletTtsCache::getVoiceType, voiceType)
- .eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null)
- .eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null)
- .eq(AppletTtsCache::getSuccess, "Y");
-
+ .eq(AppletTtsCache::getVoiceType, voiceType)
+ .eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null)
+ .eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null)
+ .eq(AppletTtsCache::getSuccess, "Y");
+
AppletTtsCache existingCache = appletTtsCacheService.getOne(queryWrapper);
-
+
if (existingCache != null) {
// 缓存命中,直接返回缓存的音频ID
log.info("TTS缓存命中,直接返回缓存音频,audioId: {}", existingCache.getAudioId());
-
+
// 记录播放日志
long endTime = System.currentTimeMillis();
double elapsedTime = (endTime - startTime) / 1000.0;
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null,
- speed != null ? speed.doubleValue() : null, elapsedTime, true, existingCache.getId());
-
+ speed != null ? speed.doubleValue() : null, elapsedTime, true, existingCache.getId());
+
return TtsVo.builder()
.url(existingCache.getAudioId())
.time(existingCache.getDuration())
@@ -103,7 +106,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 2. 缓存未命中,调用腾讯云TTS接口生成音频
log.info("TTS缓存未命中,调用腾讯云接口生成音频");
-
+
// 创建认证对象
Credential cred = new Credential(secretId, secretKey);
@@ -160,7 +163,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 3. 将音频数据上传到OSS
byte[] audioData = java.util.Base64.getDecoder().decode(audioBase64);
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav";
-
+
// 使用ByteArrayInputStream上传到OSS
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData);
String audioUrl = OssBootUtil.upload(inputStream, "tts/" + fileName);
@@ -200,7 +203,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 记录成功的TTS调用日志
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null,
- speed != null ? speed.doubleValue() : null, elapsedTime, true, cacheId);
+ speed != null ? speed.doubleValue() : null, elapsedTime, true, cacheId);
log.info("TTS调用成功,文本长度: {}, 耗时: {}秒", text != null ? text.length() : 0, elapsedTime);
return TtsVo.builder()
@@ -210,7 +213,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
} else {
// 记录失败的TTS调用日志
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null,
- speed != null ? speed.doubleValue() : null, elapsedTime, false, null);
+ speed != null ? speed.doubleValue() : null, elapsedTime, false, null);
log.warn("TTS返回的音频数据为空");
return null;
@@ -223,22 +226,98 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 记录失败的TTS调用日志
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null,
- speed != null ? speed.doubleValue() : null, elapsedTime, false, null);
+ speed != null ? speed.doubleValue() : null, elapsedTime, false, null);
log.error("调用腾讯云TTS接口失败: {}", e.getMessage(), e);
return null;
}
}
+ @Override
+ public boolean handleLongTextTtsCallback(String taskId, String resultUrl, Long status, String statusStr, String subtitlesJson) {
+ // Redis锁的key
+ String lockKey = "tts_callback_lock:" + taskId;
+ // 锁的过期时间(秒)
+ int lockExpireTime = 300; // 5分钟
+
+ try {
+ // 尝试获取Redis分布式锁
+ Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", lockExpireTime, TimeUnit.SECONDS);
+ if (!lockAcquired) {
+ log.warn("TTS回调处理已在进行中,跳过重复处理,taskId: {}", taskId);
+ return false;
+ }
+
+ log.info("获取TTS回调处理锁成功,开始处理,taskId: {}", taskId);
+
+ // 检查数据库中是否已经处理完成
+ AppletTtsCache cache = appletTtsCacheService
+ .lambdaQuery()
+ .eq(AppletTtsCache::getTaskId, taskId)
+ .one();
+ if (cache == null) {
+ log.error("未找到对应的TTS缓存记录,taskId: {}", taskId);
+ return false;
+ }
+
+ // 如果已经处理完成(成功状态且state=1),直接返回
+ if ("Y".equals(cache.getSuccess()) && cache.getState() != null && cache.getState() == 1) {
+ log.info("TTS任务已处理完成,无需重复处理,taskId: {}, audioId: {}", taskId, cache.getAudioId());
+ return true;
+ }
+
+ // 保存字幕信息
+ if (subtitlesJson != null && !subtitlesJson.isEmpty()) {
+ cache.setSubtitlesJson(subtitlesJson);
+ }
+
+ // 判断是否成功 (Status == 2 表示成功)
+ if (status != null && status == 2) {
+ // 使用统一的音频处理方法
+ AudioProcessResult audioResult = processAndUploadAudio(resultUrl, taskId);
+
+ cache.setAudioId(audioResult.getAudioUrl());
+ if (audioResult.getDuration() != null) {
+ cache.setDuration(audioResult.getDuration());
+ }
+ cache.setSuccess("Y");
+ cache.setState(1);
+ } else {
+ cache.setSuccess("N");
+ cache.setState(0);
+ log.warn("TTS任务失败,taskId: {}, status: {}, statusStr: {}", taskId, status, statusStr);
+ }
+
+ cache.setUpdateTime(new java.util.Date());
+ appletTtsCacheService.updateById(cache);
+
+ log.info("长文本TTS回调处理完成,taskId: {}, success: {}, status: {}", taskId, cache.getSuccess(), status);
+ return true;
+ } catch (Exception e) {
+ log.error("处理长文本TTS回调失败: {}", e.getMessage(), e);
+ return false;
+ } finally {
+ // 释放Redis锁
+ try {
+ redisTemplate.delete(lockKey);
+ log.info("释放TTS回调处理锁,taskId: {}", taskId);
+ } catch (Exception e) {
+ log.error("释放TTS回调处理锁失败,taskId: {}, error: {}", taskId, e.getMessage());
+ }
+ }
+ }
+
@Override
public String createLongTextTtsTask(String text, Float speed, Integer voiceType, Float volume, String codec, String callbackUrl) {
try {
- // 先检查是否存在生成中的任务(相同文本与参数)
+ // 将文章内容去除HTML并计算哈希,用于缓存唯一标识
+ String normalized = stripHtml(text);
+ String textHash = String.valueOf(normalized.hashCode());
+
+ // 先检查是否存在生成中的任务(相同文本哈希与音色)
LambdaQueryWrapper generatingWrapper = new LambdaQueryWrapper<>();
- generatingWrapper.eq(AppletTtsCache::getText, text)
+ generatingWrapper.eq(AppletTtsCache::getText, textHash)
.eq(AppletTtsCache::getVoiceType, voiceType)
- .eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null)
- .eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null)
.eq(AppletTtsCache::getState, 0);
AppletTtsCache generating = appletTtsCacheService.getOne(generatingWrapper);
@@ -249,10 +328,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 再检查是否已有成功缓存
LambdaQueryWrapper successWrapper = new LambdaQueryWrapper<>();
- successWrapper.eq(AppletTtsCache::getText, text)
+ successWrapper.eq(AppletTtsCache::getText, textHash)
.eq(AppletTtsCache::getVoiceType, voiceType)
- .eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null)
- .eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null)
.eq(AppletTtsCache::getSuccess, "Y")
.eq(AppletTtsCache::getState, 1);
AppletTtsCache successCache = appletTtsCacheService.getOne(successWrapper);
@@ -285,7 +362,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
// 启用时间戳字幕
try {
req.getClass().getMethod("setEnableSubtitle", Boolean.class).invoke(req, Boolean.TRUE);
- } catch (Exception ignore) {}
+ } catch (Exception ignore) {
+ }
if (callbackUrl != null && !callbackUrl.isEmpty()) {
req.setCallbackUrl(callbackUrl);
}
@@ -296,10 +374,10 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
throw new JeecgBootException("创建长文本TTS任务失败,未返回任务ID");
}
- // 写入缓存,标记生成中
+ // 写入缓存,标记生成中(text字段保存哈希)
AppletTtsCache cache = new AppletTtsCache();
cache.setTaskId(taskId);
- cache.setText(text);
+ cache.setText(textHash);
cache.setVoiceType(voiceType);
cache.setVolume(volume != null ? volume.doubleValue() : null);
cache.setSpeed(speed != null ? speed.doubleValue() : null);
@@ -308,7 +386,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
cache.setCreateTime(new java.util.Date());
appletTtsCacheService.save(cache);
- log.info("长文本TTS任务创建成功,taskId: {}", taskId);
+ log.info("长文本TTS任务创建成功,taskId: {},textHash: {}", taskId, textHash);
return taskId;
} catch (Exception e) {
log.error("创建长文本TTS任务失败: {}", e.getMessage(), e);
@@ -352,32 +430,21 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
appletTtsCacheService.updateById(cache);
return cache;
} else if (status == 2) {
- // 成功,处理音频
- byte[] audioData = null;
- if (audioUrl != null && !audioUrl.isEmpty()) {
- audioData = downloadBytes(audioUrl);
- }
-
- if (audioData != null) {
- String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav";
- ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData);
- String uploadedUrl = OssBootUtil.upload(inputStream, "tts/" + fileName);
- cache.setAudioId(uploadedUrl != null ? uploadedUrl : audioUrl);
+ // 成功,使用统一的音频处理方法
+ AudioProcessResult audioResult = processAndUploadAudio(audioUrl, taskId);
- Double realDuration = AudioDurationUtil.calculateDuration(audioData);
- if (realDuration != null) {
- cache.setDuration(realDuration);
- }
- } else {
- cache.setAudioId(audioUrl);
+ cache.setAudioId(audioResult.getAudioUrl());
+ if (audioResult.getDuration() != null) {
+ cache.setDuration(audioResult.getDuration());
}
// 解析时间戳
String timestampsJson = null;
try {
timestampsJson = JSON.toJSONString(resp.getData().getSubtitles());
- cache.setTimestamps(timestampsJson);
- } catch (Exception ignore) {}
+ cache.setSubtitlesJson(timestampsJson);
+ } catch (Exception ignore) {
+ }
cache.setSuccess("Y");
cache.setState(1);
@@ -538,7 +605,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
String text = obj.getString("content");
return text != null ? text : "";
}
- } catch (Exception ignore) {}
+ } catch (Exception ignore) {
+ }
// 作为纯文本返回
return content;
}
@@ -607,6 +675,31 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
}
}
+ @Override
+ public Map selectMapByHtml(String content) {
+ HashMap map = new HashMap<>();
+
+ int i = stripHtml(content).hashCode();
+ String textHash = String.valueOf(i);
+
+ List list = appletTtsTimbreService
+ .lambdaQuery()
+ .select(AppletTtsTimbre::getVoiceType)
+ .list();
+
+ for (AppletTtsTimbre timbre : list) {
+ AppletTtsCache one = appletTtsCacheService.lambdaQuery()
+ .eq(AppletTtsCache::getText, textHash)
+ .eq(AppletTtsCache::getVoiceType, timbre.getVoiceType())
+ .select(AppletTtsCache::getAudioId)
+ .one();
+
+ map.put(timbre.getVoiceType(), one != null ? one.getAudioId() : null);
+ }
+
+ return map;
+ }
+
private byte[] downloadBytes(String url) {
try (InputStream in = new URL(url).openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buf = new byte[8192];
@@ -620,16 +713,14 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
return null;
}
}
-
+
/**
* 保存TTS播放日志
*/
- private void savePlayLog(String userId, String text, Integer voiceType, Double volume,
- Double speed, Double elapsedTime, boolean success, String cacheId) {
+ private void savePlayLog(String userId, String text, Integer voiceType, Double volume,
+ Double speed, Double elapsedTime, boolean success, String cacheId) {
try {
- org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog playLog =
- new org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog();
-
+ AppletTtsPlayLog playLog = new AppletTtsPlayLog();
playLog.setUserId(userId);
playLog.setVoicetype(voiceType);
playLog.setVolume(volume);
@@ -640,7 +731,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
playLog.setCreateTime(new java.util.Date());
appletTtsPlayLogService.save(playLog);
-
+
log.debug("TTS播放日志保存成功,用户: {}, 结果: {}, 缓存ID: {}", userId, success ? "成功" : "失败", cacheId);
} catch (Exception e) {
log.error("保存TTS播放日志失败: {}", e.getMessage(), e);
@@ -653,4 +744,88 @@ public class AppletApiTTServiceImpl implements AppletApiTTService {
return appletTtsTimbreService.list();
}
-}
+ @Override
+ public void generateLongTextForArticleContentAllTimbres(String content) {
+ try {
+ List timbres = appletTtsTimbreService
+ .lambdaQuery()
+ .eq(AppletTtsTimbre::getStatus, "Y")
+ .select(AppletTtsTimbre::getVoiceType)
+ .list();
+ String callbackUrl = TtscallbackUrl;
+ for (AppletTtsTimbre timbre : timbres) {
+ try {
+ createLongTextTtsTask(content, null, timbre.getVoiceType(), null, "wav", callbackUrl);
+ } catch (Exception ex) {
+ log.warn("创建文章内容长文本TTS任务失败 voiceType {}: {}", timbre.getVoiceType(), ex.getMessage());
+ }
+ }
+ } catch (Exception e) {
+ log.error("遍历音色创建文章长文本TTS任务失败: {}", e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 统一的音频处理方法:下载音频、上传到OSS、计算时长
+ *
+ * @param audioUrl 音频URL
+ * @param taskId 任务ID(用于日志)
+ * @return AudioProcessResult 包含上传后的URL和音频时长
+ */
+ private AudioProcessResult processAndUploadAudio(String audioUrl, String taskId) {
+ if (audioUrl == null || audioUrl.isEmpty()) {
+ log.warn("音频URL为空,taskId: {}", taskId);
+ return new AudioProcessResult(null, null);
+ }
+
+ try {
+ // 下载音频数据
+ byte[] audioData = downloadBytes(audioUrl);
+ if (audioData == null || audioData.length == 0) {
+ log.error("音频下载失败或数据为空,taskId: {}, audioUrl: {}", taskId, audioUrl);
+ return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底
+ }
+
+ // 生成文件名并上传到OSS
+ String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav";
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData);
+ String uploadedUrl = OssBootUtil.upload(inputStream, "tts/" + fileName);
+
+ if (uploadedUrl == null) {
+ log.error("音频上传到OSS失败,taskId: {}", taskId);
+ return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底
+ }
+
+ // 计算音频时长
+// Double audioDuration = AudioDurationUtil.calculateDuration(audioData);
+
+ log.info("音频处理成功,taskId: {}, uploadedUrl: {}, duration: {}", taskId, uploadedUrl, null);
+ return new AudioProcessResult(uploadedUrl, null);
+
+ } catch (Exception e) {
+ log.error("音频处理失败,taskId: {}, audioUrl: {}, error: {}", taskId, audioUrl, e.getMessage());
+ return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底
+ }
+ }
+
+ /**
+ * 音频处理结果封装类
+ */
+ private static class AudioProcessResult {
+ private final String audioUrl;
+ private final Double duration;
+
+ public AudioProcessResult(String audioUrl, Double duration) {
+ this.audioUrl = audioUrl;
+ this.duration = duration;
+ }
+
+ public String getAudioUrl() {
+ return audioUrl;
+ }
+
+ public Double getDuration() {
+ return duration;
+ }
+ }
+}
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/controller/AppletArticleController.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/controller/AppletArticleController.java
new file mode 100644
index 0000000..b9288aa
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/controller/AppletArticleController.java
@@ -0,0 +1,207 @@
+package org.jeecg.modules.demo.appletArticle.controller;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.common.system.query.QueryRuleEnum;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
+import org.jeecg.modules.demo.appletArticle.service.IAppletArticleService;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecgframework.poi.excel.ExcelImportUtil;
+import org.jeecgframework.poi.excel.def.NormalExcelConstants;
+import org.jeecgframework.poi.excel.entity.ExportParams;
+import org.jeecgframework.poi.excel.entity.ImportParams;
+import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
+import org.jeecg.common.system.base.controller.JeecgController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.servlet.ModelAndView;
+import com.alibaba.fastjson.JSON;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.jeecg.common.aspect.annotation.AutoLog;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.jeecg.modules.applet.service.AppletApiTTService;
+ /**
+ * @Description: 小程序文章
+ * @Author: jeecg-boot
+ * @Date: 2025-10-23
+ * @Version: V1.0
+ */
+@Tag(name="小程序文章")
+@RestController
+@RequestMapping("/appletArticle/appletArticle")
+@Slf4j
+public class AppletArticleController extends JeecgController {
+ @Autowired
+ private IAppletArticleService appletArticleService;
+
+ @Autowired
+ private AppletApiTTService appletApiTTService;
+
+ /**
+ * 分页列表查询
+ *
+ * @param appletArticle
+ * @param pageNo
+ * @param pageSize
+ * @param req
+ * @return
+ */
+ //@AutoLog(value = "小程序文章-分页列表查询")
+ @Operation(summary="小程序文章-分页列表查询")
+ @GetMapping(value = "/list")
+ public Result> queryPageList(AppletArticle appletArticle,
+ @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
+ @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
+ HttpServletRequest req) {
+
+
+ QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(appletArticle, req.getParameterMap());
+ Page page = new Page(pageNo, pageSize);
+ IPage pageList = appletArticleService.page(page, queryWrapper);
+ return Result.OK(pageList);
+ }
+
+ /**
+ * 添加
+ *
+ * @param appletArticle
+ * @return
+ */
+ @AutoLog(value = "小程序文章-添加")
+ @Operation(summary="小程序文章-添加")
+ @RequiresPermissions("appletArticle:applet_article:add")
+ @PostMapping(value = "/add")
+ public Result add(@RequestBody AppletArticle appletArticle) {
+ appletArticleService.save(appletArticle);
+
+ // 触发长文本TTS生成:遍历所有启用音色,对content进行转换
+ try {
+ String content = appletArticle.getContent();
+ if (content != null && !content.trim().isEmpty()) {
+ appletApiTTService.generateLongTextForArticleContentAllTimbres(content);
+ }
+ } catch (Exception e) {
+ log.warn("文章添加后触发长文本TTS失败: {}", e.getMessage());
+ }
+
+ return Result.OK("添加成功!");
+ }
+
+ /**
+ * 编辑
+ *
+ * @param appletArticle
+ * @return
+ */
+ @AutoLog(value = "小程序文章-编辑")
+ @Operation(summary="小程序文章-编辑")
+ @RequiresPermissions("appletArticle:applet_article:edit")
+ @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
+ public Result edit(@RequestBody AppletArticle appletArticle) {
+ appletArticleService.updateById(appletArticle);
+
+ // 触发长文本TTS生成:遍历所有启用音色,对content进行转换
+ try {
+ String content = appletArticle.getContent();
+ if (content != null && !content.trim().isEmpty()) {
+ appletApiTTService.generateLongTextForArticleContentAllTimbres(content);
+ }
+ } catch (Exception e) {
+ log.warn("文章编辑后触发长文本TTS失败: {}", e.getMessage());
+ }
+
+ return Result.OK("编辑成功!");
+ }
+
+ /**
+ * 通过id删除
+ *
+ * @param id
+ * @return
+ */
+ @AutoLog(value = "小程序文章-通过id删除")
+ @Operation(summary="小程序文章-通过id删除")
+ @RequiresPermissions("appletArticle:applet_article:delete")
+ @DeleteMapping(value = "/delete")
+ public Result delete(@RequestParam(name="id",required=true) String id) {
+ appletArticleService.removeById(id);
+ return Result.OK("删除成功!");
+ }
+
+ /**
+ * 批量删除
+ *
+ * @param ids
+ * @return
+ */
+ @AutoLog(value = "小程序文章-批量删除")
+ @Operation(summary="小程序文章-批量删除")
+ @RequiresPermissions("appletArticle:applet_article:deleteBatch")
+ @DeleteMapping(value = "/deleteBatch")
+ public Result deleteBatch(@RequestParam(name="ids",required=true) String ids) {
+ this.appletArticleService.removeByIds(Arrays.asList(ids.split(",")));
+ return Result.OK("批量删除成功!");
+ }
+
+ /**
+ * 通过id查询
+ *
+ * @param id
+ * @return
+ */
+ //@AutoLog(value = "小程序文章-通过id查询")
+ @Operation(summary="小程序文章-通过id查询")
+ @GetMapping(value = "/queryById")
+ public Result queryById(@RequestParam(name="id",required=true) String id) {
+ AppletArticle appletArticle = appletArticleService.getById(id);
+ if(appletArticle==null) {
+ return Result.error("未找到对应数据");
+ }
+ return Result.OK(appletArticle);
+ }
+
+ /**
+ * 导出excel
+ *
+ * @param request
+ * @param appletArticle
+ */
+ @RequiresPermissions("appletArticle:applet_article:exportXls")
+ @RequestMapping(value = "/exportXls")
+ public ModelAndView exportXls(HttpServletRequest request, AppletArticle appletArticle) {
+ return super.exportXls(request, appletArticle, AppletArticle.class, "小程序文章");
+ }
+
+ /**
+ * 通过excel导入数据
+ *
+ * @param request
+ * @param response
+ * @return
+ */
+ @RequiresPermissions("appletArticle:applet_article:importExcel")
+ @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
+ public Result> importExcel(HttpServletRequest request, HttpServletResponse response) {
+ return super.importExcel(request, response, AppletArticle.class);
+ }
+
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/entity/AppletArticle.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/entity/AppletArticle.java
new file mode 100644
index 0000000..777c378
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/entity/AppletArticle.java
@@ -0,0 +1,77 @@
+package org.jeecg.modules.demo.appletArticle.entity;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.math.BigDecimal;
+import java.util.Map;
+
+import com.baomidou.mybatisplus.annotation.*;
+import org.jeecg.common.constant.ProvinceCityArea;
+import org.jeecg.common.util.SpringContextUtils;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.jeecgframework.poi.excel.annotation.Excel;
+import org.jeecg.common.aspect.annotation.Dict;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @Description: 小程序文章
+ * @Author: jeecg-boot
+ * @Date: 2025-10-23
+ * @Version: V1.0
+ */
+@Data
+@TableName("applet_article")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description="小程序文章")
+public class AppletArticle implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**主键*/
+ @TableId(type = IdType.ASSIGN_ID)
+ @Schema(description = "主键")
+ private java.lang.String id;
+ /**创建人*/
+ @Schema(description = "创建人")
+ private java.lang.String createBy;
+ /**创建日期*/
+ @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "创建日期")
+ private java.util.Date createTime;
+ /**更新人*/
+ @Schema(description = "更新人")
+ private java.lang.String updateBy;
+ /**更新日期*/
+ @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+ @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+ @Schema(description = "更新日期")
+ private java.util.Date updateTime;
+ /**所属部门*/
+ @Schema(description = "所属部门")
+ private java.lang.String sysOrgCode;
+ /**标题*/
+ @Excel(name = "标题", width = 15)
+ @Schema(description = "标题")
+ private java.lang.String title;
+ /**排序*/
+ @Excel(name = "排序", width = 15)
+ @Schema(description = "排序")
+ private java.lang.Integer sort;
+ /**上架*/
+ @Excel(name = "上架", width = 15,replace = {"是_Y","否_N"} )
+ @Schema(description = "上架")
+ private java.lang.String status;
+ /**内容*/
+ @Excel(name = "内容", width = 15)
+ @Schema(description = "内容")
+ private java.lang.String content;
+
+ @TableField(exist = false)
+ private Map audios;
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/AppletArticleMapper.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/AppletArticleMapper.java
new file mode 100644
index 0000000..371a77d
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/AppletArticleMapper.java
@@ -0,0 +1,17 @@
+package org.jeecg.modules.demo.appletArticle.mapper;
+
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * @Description: 小程序文章
+ * @Author: jeecg-boot
+ * @Date: 2025-10-23
+ * @Version: V1.0
+ */
+public interface AppletArticleMapper extends BaseMapper {
+
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/xml/AppletArticleMapper.xml b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/xml/AppletArticleMapper.xml
new file mode 100644
index 0000000..47d0077
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/mapper/xml/AppletArticleMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/IAppletArticleService.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/IAppletArticleService.java
new file mode 100644
index 0000000..9df3a75
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/IAppletArticleService.java
@@ -0,0 +1,14 @@
+package org.jeecg.modules.demo.appletArticle.service;
+
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @Description: 小程序文章
+ * @Author: jeecg-boot
+ * @Date: 2025-10-23
+ * @Version: V1.0
+ */
+public interface IAppletArticleService extends IService {
+
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/impl/AppletArticleServiceImpl.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/impl/AppletArticleServiceImpl.java
new file mode 100644
index 0000000..190f0d9
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/service/impl/AppletArticleServiceImpl.java
@@ -0,0 +1,19 @@
+package org.jeecg.modules.demo.appletArticle.service.impl;
+
+import org.jeecg.modules.demo.appletArticle.entity.AppletArticle;
+import org.jeecg.modules.demo.appletArticle.mapper.AppletArticleMapper;
+import org.jeecg.modules.demo.appletArticle.service.IAppletArticleService;
+import org.springframework.stereotype.Service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+/**
+ * @Description: 小程序文章
+ * @Author: jeecg-boot
+ * @Date: 2025-10-23
+ * @Version: V1.0
+ */
+@Service
+public class AppletArticleServiceImpl extends ServiceImpl implements IAppletArticleService {
+
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleForm.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleForm.vue
new file mode 100644
index 0000000..3bda3db
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleForm.vue
@@ -0,0 +1,101 @@
+
+
+
+
+ 返回
+ 小程序文章
+
+
+
+
+
+
+
+
+
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleList.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleList.vue
new file mode 100644
index 0000000..8f5234f
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp/AppletArticleList.vue
@@ -0,0 +1,44 @@
+
+
+
+
+ 返回
+ 小程序文章
+
+
+
+
+
+
+
+
+
+
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleData.ts b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleData.ts
new file mode 100644
index 0000000..6209b9a
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleData.ts
@@ -0,0 +1,22 @@
+import { render } from '@/common/renderUtils';
+//列表数据
+export const columns = [
+ {
+ title: '标题',
+ align:"center",
+ dataIndex: 'title'
+ },
+ {
+ title: '排序',
+ align:"center",
+ dataIndex: 'sort'
+ },
+ {
+ title: '上架',
+ align:"center",
+ dataIndex: 'status',
+ customRender:({text}) => {
+ return render.renderSwitch(text, [{text:'是',value:'Y'},{text:'否',value:'N'}])
+ },
+ },
+];
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleForm.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleForm.vue
new file mode 100644
index 0000000..d54fad8
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleForm.vue
@@ -0,0 +1,253 @@
+
+{
+layout: 'default',
+style: {
+navigationStyle: 'custom',
+navigationBarTitleText: '小程序文章',
+},
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleList.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleList.vue
new file mode 100644
index 0000000..3b287fa
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/uniapp3/AppletArticleList.vue
@@ -0,0 +1,148 @@
+
+{
+layout: 'default',
+style: {
+navigationBarTitleText: '小程序文章',
+navigationStyle: 'custom',
+},
+}
+
+
+
+
+
+
+
+
+
+
+ {{ cItem.title }}
+ {{ item[cItem.dataIndex] }}
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.api.ts b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.api.ts
new file mode 100644
index 0000000..433950b
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.api.ts
@@ -0,0 +1,64 @@
+import {defHttp} from '/@/utils/http/axios';
+import { useMessage } from "/@/hooks/web/useMessage";
+
+const { createConfirm } = useMessage();
+
+enum Api {
+ list = '/appletArticle/appletArticle/list',
+ save='/appletArticle/appletArticle/add',
+ edit='/appletArticle/appletArticle/edit',
+ deleteOne = '/appletArticle/appletArticle/delete',
+ deleteBatch = '/appletArticle/appletArticle/deleteBatch',
+ importExcel = '/appletArticle/appletArticle/importExcel',
+ exportXls = '/appletArticle/appletArticle/exportXls',
+}
+/**
+ * 导出api
+ * @param params
+ */
+export const getExportUrl = Api.exportXls;
+/**
+ * 导入api
+ */
+export const getImportUrl = Api.importExcel;
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) =>
+ defHttp.get({url: Api.list, params});
+
+/**
+ * 删除单个
+ */
+export const deleteOne = (params,handleSuccess) => {
+ return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
+ handleSuccess();
+ });
+}
+/**
+ * 批量删除
+ * @param params
+ */
+export const batchDelete = (params, handleSuccess) => {
+ createConfirm({
+ iconType: 'warning',
+ title: '确认删除',
+ content: '是否删除选中数据',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: () => {
+ return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
+ handleSuccess();
+ });
+ }
+ });
+}
+/**
+ * 保存或者更新
+ * @param params
+ */
+export const saveOrUpdate = (params, isUpdate) => {
+ let url = isUpdate ? Api.edit : Api.save;
+ return defHttp.post({url: url, params});
+}
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.data.ts b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.data.ts
new file mode 100644
index 0000000..ea351e5
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticle.data.ts
@@ -0,0 +1,77 @@
+import {BasicColumn} from '/@/components/Table';
+import {FormSchema} from '/@/components/Table';
+import { rules} from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+import { getWeekMonthQuarterYear } from '/@/utils';
+//列表数据
+export const columns: BasicColumn[] = [
+ {
+ title: '标题',
+ align:"center",
+ dataIndex: 'title'
+ },
+ {
+ title: '排序',
+ align:"center",
+ dataIndex: 'sort'
+ },
+ {
+ title: '上架',
+ align:"center",
+ dataIndex: 'status',
+ customRender:({text}) => {
+ return render.renderSwitch(text, [{text:'是',value:'Y'},{text:'否',value:'N'}])
+ },
+ },
+];
+//查询数据
+export const searchFormSchema: FormSchema[] = [
+];
+//表单数据
+export const formSchema: FormSchema[] = [
+ {
+ label: '标题',
+ field: 'title',
+ component: 'Input',
+ },
+ {
+ label: '排序',
+ field: 'sort',
+ component: 'InputNumber',
+ },
+ {
+ label: '上架',
+ field: 'status',
+ component: 'JSwitch',
+ componentProps:{
+ },
+ },
+ {
+ label: '内容',
+ field: 'content',
+ component: 'JEditor',
+ },
+ // TODO 主键隐藏字段,目前写死为ID
+ {
+ label: '',
+ field: 'id',
+ component: 'Input',
+ show: false
+ },
+];
+
+// 高级查询数据
+export const superQuerySchema = {
+ title: {title: '标题',order: 0,view: 'text', type: 'string',},
+ sort: {title: '排序',order: 1,view: 'number', type: 'number',},
+ status: {title: '上架',order: 2,view: 'switch', type: 'string',},
+};
+
+/**
+* 流程表单调用这个方法获取formSchema
+* @param param
+*/
+export function getBpmFormSchema(_formData): FormSchema[]{
+ // 默认和原始表单保持一致 如果流程中配置了权限数据,这里需要单独处理formSchema
+ return formSchema;
+}
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticleList.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticleList.vue
new file mode 100644
index 0000000..cd0231d
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/AppletArticleList.vue
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+ 新增
+ 导出
+ 导入
+
+
+
+
+
+
+ 删除
+
+
+
+ 批量操作
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/V20251023_1__menu_insert_AppletArticle.sql b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/V20251023_1__menu_insert_AppletArticle.sql
new file mode 100644
index 0000000..2a33648
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/V20251023_1__menu_insert_AppletArticle.sql
@@ -0,0 +1,26 @@
+-- 注意:该页面对应的前台目录为views/appletArticle文件夹下
+-- 如果你想更改到其他目录,请修改sql中component字段对应的值
+
+
+INSERT INTO sys_permission(id, parent_id, name, url, component, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_route, is_leaf, keep_alive, hidden, hide_tab, description, status, del_flag, rule_flag, create_by, create_time, update_by, update_time, internal_or_external)
+VALUES ('2025102304348630020', NULL, '小程序文章', '/appletArticle/appletArticleList', 'appletArticle/AppletArticleList', NULL, NULL, 0, NULL, '1', 0.00, 0, NULL, 1, 0, 0, 0, 0, NULL, '1', 0, 0, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0);
+
+-- 权限控制sql
+-- 新增
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630021', '2025102304348630020', '添加小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:add', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
+-- 编辑
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630022', '2025102304348630020', '编辑小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:edit', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
+-- 删除
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630023', '2025102304348630020', '删除小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:delete', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
+-- 批量删除
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630024', '2025102304348630020', '批量删除小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:deleteBatch', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
+-- 导出excel
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630025', '2025102304348630020', '导出excel_小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:exportXls', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
+-- 导入excel
+INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external)
+VALUES ('2025102304348630026', '2025102304348630020', '导入excel_小程序文章', NULL, NULL, 0, NULL, NULL, 2, 'appletArticle:applet_article:importExcel', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2025-10-23 16:34:02', NULL, NULL, 0, 0, '1', 0);
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleForm.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleForm.vue
new file mode 100644
index 0000000..4e98b11
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleForm.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleModal.vue b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleModal.vue
new file mode 100644
index 0000000..34c8f5e
--- /dev/null
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletArticle/vue3/components/AppletArticleModal.vue
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletTtsCache/entity/AppletTtsCache.java b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletTtsCache/entity/AppletTtsCache.java
index cb54901..2a9a5ee 100644
--- a/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletTtsCache/entity/AppletTtsCache.java
+++ b/jeecg-boot/jeecg-boot-module/jeecgboot-boot-applet/src/main/java/org/jeecg/modules/demo/appletTtsCache/entity/AppletTtsCache.java
@@ -99,5 +99,5 @@ public class AppletTtsCache implements Serializable {
private java.lang.Integer state;
@Schema(description = "时间戳")
- private java.lang.String timestamps;
+ private java.lang.String subtitlesJson;
}
diff --git a/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts b/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
index ea351e5..97dc4a1 100644
--- a/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
+++ b/jeecgboot-vue3/src/views/applet/article/AppletArticle.data.ts
@@ -1,8 +1,8 @@
-import {BasicColumn} from '/@/components/Table';
-import {FormSchema} from '/@/components/Table';
-import { rules} from '/@/utils/helper/validator';
-import { render } from '/@/utils/common/renderUtils';
-import { getWeekMonthQuarterYear } from '/@/utils';
+import {BasicColumn} from '/src/components/Table';
+import {FormSchema} from '/src/components/Table';
+import { rules} from '/src/utils/helper/validator';
+import { render } from '/src/utils/common/renderUtils';
+import { getWeekMonthQuarterYear } from '/src/utils';
//列表数据
export const columns: BasicColumn[] = [
{