Commit 9cbd32ff by renyizhao

保存协议

parent cc25ba7c
......@@ -91,6 +91,13 @@
<artifactId>computility-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- 充值协议 PDF 生成:HTML 模板渲染 + Flying Saucer 转 PDF -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.5.1</version>
</dependency>
</dependencies>
</project>
......@@ -87,4 +87,7 @@ public class MemberRechargeRespVO {
@Schema(description = "人工处理时间")
private LocalDateTime manualProcessTime;
@Schema(description = "充值协议 PDF 地址")
private String agreementPdfUrl;
}
......@@ -5,6 +5,7 @@ import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRec
import com.luhu.computility.module.member.controller.admin.recharge.vo.MemberRechargeRespVO;
import com.luhu.computility.module.member.dal.dataobject.recharge.MemberRechargeDO;
import com.luhu.computility.module.member.service.recharge.MemberRechargeService;
import com.luhu.computility.module.member.service.recharge.RechargeAgreementPdfService;
import com.luhu.computility.module.pay.api.wpgj.CommonWpgjPayApi;
import com.luhu.computility.module.pay.api.wpgj.dto.CommonWpgjCreateReqDTO;
import com.luhu.computility.module.pay.api.wpgj.vo.CommonWpgjPayOrderSubmitRespVO;
......@@ -29,6 +30,7 @@ import static com.luhu.computility.framework.security.core.util.SecurityFramewor
public class AppRechargeController {
private final MemberRechargeService rechargeService;
private final RechargeAgreementPdfService agreementPdfService;
private final CommonWpgjPayApi commonWpgjPayApi;
/**
......@@ -47,6 +49,9 @@ public class AppRechargeController {
String transactionId = "R" + System.currentTimeMillis() + userId;
Long rechargeId = rechargeService.createRecharge(userId, amount, quota, "wpgj", transactionId);
// 1.1 生成充值协议 PDF(失败已在内部吞掉,不影响主流程)
agreementPdfService.generateAndSavePdf(rechargeId);
// 2. 调用旺铺聚合支付
try {
CommonWpgjCreateReqDTO createReqDTO = new CommonWpgjCreateReqDTO();
......
......@@ -96,4 +96,9 @@ public class MemberRechargeDO extends BaseDO {
*/
private LocalDateTime manualProcessTime;
/**
* 充值协议 PDF 地址(每次充值生成一份,存对象存储)
*/
private String agreementPdfUrl;
}
......@@ -507,6 +507,7 @@ public class MemberRechargeServiceImpl implements MemberRechargeService {
vo.setManualOperatorId(recharge.getManualOperatorId());
vo.setManualOperatorName(adminUser != null ? adminUser.getNickname() : null);
vo.setManualProcessTime(recharge.getManualProcessTime());
vo.setAgreementPdfUrl(recharge.getAgreementPdfUrl());
return vo;
}
......
package com.luhu.computility.module.member.service.recharge;
import com.luhu.computility.module.infra.api.file.FileApi;
import com.luhu.computility.module.member.dal.dataobject.recharge.MemberRechargeDO;
import com.luhu.computility.module.member.dal.dataobject.user.MemberUserDO;
import com.luhu.computility.module.member.dal.mysql.recharge.MemberRechargeMapper;
import com.luhu.computility.module.member.service.user.MemberUserService;
import com.lowagie.text.pdf.BaseFont;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
/**
* 充值协议 PDF 生成服务
*
* <p>每次创建充值记录后生成一份协议 PDF(不阻塞充值主流程):
* <ol>
* <li>从 HTML 模板渲染动态字段(甲方昵称、协议生效日期)</li>
* <li>用 Flying Saucer 把 HTML 转成 PDF 字节流</li>
* <li>通过 {@link FileApi} 上传到对象存储</li>
* <li>把返回的 URL 回写到 {@code member_recharge.agreement_pdf_url}</li>
* </ol>
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class RechargeAgreementPdfService {
/** HTML 模板里甲方昵称占位符 */
private static final String PLACEHOLDER_USER_NICKNAME = "${userNickname}";
/** HTML 模板里协议生效日期占位符 */
private static final String PLACEHOLDER_AGREEMENT_DATE = "${agreementDate}";
/** HTML 模板里字体栈占位符(实际加载的字体 family name 在首位) */
private static final String PLACEHOLDER_FONT_FAMILY = "${fontFamilyStack}";
/** 兜底字体栈(实际加载的字体 family name 会被插入到第一位) */
private static final String FALLBACK_FONT_STACK =
"\"Source Han Sans CN\", \"WenQuanYi Micro Hei\", sans-serif";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final MemberRechargeMapper rechargeMapper;
private final MemberUserService memberUserService;
private final FileApi fileApi;
/**
* 生成并保存协议 PDF。
* 任何异常都会被吞掉并记录日志——不阻塞充值主流程。
*
* @param rechargeId 充值记录 ID
* @return PDF URL,失败返回 {@code null}
*/
public String generateAndSavePdf(Long rechargeId) {
try {
// 1. 取充值记录 + 用户昵称
MemberRechargeDO recharge = rechargeMapper.selectById(rechargeId);
if (recharge == null) {
log.warn("[充值协议PDF] 充值记录不存在,rechargeId={}", rechargeId);
return null;
}
MemberUserDO user = memberUserService.getUser(recharge.getUserId());
String userNickname = (user != null && user.getNickname() != null) ? user.getNickname() : "";
// 协议生效日期 = 订单创建时间
String agreementDate = recharge.getCreateTime() != null
? recharge.getCreateTime().format(DATE_FORMATTER)
: LocalDate.now().format(DATE_FORMATTER);
// 2. 尝试加载中文字体,读出真实 family name 用于 HTML font-family
LoadedFont loadedFont = loadChineseFont();
String fontFamilyStack = loadedFont == null
? FALLBACK_FONT_STACK
: "\"" + loadedFont.familyName + "\", " + FALLBACK_FONT_STACK;
// 3. 读模板 + 替换占位符
String html = loadTemplate()
.replace(PLACEHOLDER_USER_NICKNAME, escapeHtml(userNickname))
.replace(PLACEHOLDER_AGREEMENT_DATE, escapeHtml(agreementDate))
.replace(PLACEHOLDER_FONT_FAMILY, fontFamilyStack);
// 4. HTML → PDF
byte[] pdfBytes = renderHtmlToPdf(html, loadedFont);
// 4. 上传到对象存储
String directory = "recharge/agreement/"
+ recharge.getUserId() + "/"
+ LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
String fileName = "recharge_agreement_" + rechargeId + ".pdf";
String url = fileApi.createFile(pdfBytes, fileName, directory, "application/pdf");
log.info("[充值协议PDF] 生成成功 rechargeId={} url={}", rechargeId, url);
// 5. 回写 URL
MemberRechargeDO update = new MemberRechargeDO();
update.setId(rechargeId);
update.setAgreementPdfUrl(url);
rechargeMapper.updateById(update);
return url;
} catch (Exception e) {
// 按要求:失败不阻塞充值,仅日志告警
log.error("[充值协议PDF] 生成失败 rechargeId={}", rechargeId, e);
return null;
}
}
// ============ 私有工具方法 ============
/** 读取 classpath 下的 HTML 协议模板 */
private String loadTemplate() {
ClassPathResource resource = new ClassPathResource("templates/recharge-agreement.html");
try (InputStream in = resource.getInputStream()) {
byte[] bytes = in.readAllBytes();
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new IllegalStateException("读取充值协议模板失败", e);
}
}
/** 用 Flying Saucer 把 XHTML 渲染为 PDF 字节流 */
private byte[] renderHtmlToPdf(String html, LoadedFont font) {
File tempFontFile = null;
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ITextRenderer renderer = new ITextRenderer();
if (font != null) {
// 把字体写到临时文件,再用 addFont 注册。
// ⚠️ 重要:iText 在 layout() / createPDF() 阶段仍会按路径重读字体文件做嵌入,
// 所以这里只能依赖 deleteOnExit() 在 JVM 退出时清理,绝对不能立刻 delete()。
tempFontFile = File.createTempFile("pdf-font-", "-" + font.fileName);
tempFontFile.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(tempFontFile)) {
fos.write(font.bytes);
}
ITextFontResolver resolver = renderer.getFontResolver();
// Flying Saucer 的 addFont 3 参形式会把第 2 参当 encoding 传给 BaseFont,
// "Noto Serif CJK SC" 不是合法 encoding 会抛 UnsupportedEncodingException。
// 必须用 4 参形式:path / "Identity-H" encoding / embedded / alias(family name)
resolver.addFont(tempFontFile.getAbsolutePath(), BaseFont.IDENTITY_H, true, font.familyName);
log.info("[充值协议PDF] 已注册中文字体 family={} (file={})",
font.familyName, font.fileName);
}
renderer.setDocumentFromString(html);
renderer.layout();
renderer.createPDF(out);
return out.toByteArray();
} catch (Exception e) {
// 带上原始异常类型和 message,便于排查
throw new IllegalStateException("HTML 转 PDF 失败: cause=" + e.getClass().getSimpleName()
+ ": " + e.getMessage(), e);
}
}
/**
* 加载中文字体。
* 先从 classpath:/fonts/ 找 NotoSansSC 或 NotoSansCJKsc;
* 用 {@link BaseFont} 解析,提取真实的 family name(避免文件名误导)。
*
* @return 加载成功返回 {@link LoadedFont},失败返回 {@code null}
*/
private LoadedFont loadChineseFont() {
String[] candidates = {"NotoSansSC-Regular.otf", "NotoSansCJKsc-Regular.otf",
"NotoSansSC-Regular.ttf", "NotoSansCJKsc-Regular.ttf"};
for (String name : candidates) {
ClassPathResource resource = new ClassPathResource("fonts/" + name);
if (!resource.exists()) {
continue;
}
try (InputStream in = resource.getInputStream()) {
byte[] bytes = in.readAllBytes();
// 用 OpenPDF 的 BaseFont 解析,读出真实 family name
File tempProbe = File.createTempFile("font-probe-", "-" + name);
tempProbe.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(tempProbe)) {
fos.write(bytes);
}
try {
BaseFont bf = BaseFont.createFont(tempProbe.getAbsolutePath(),
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
String familyName = extractFamilyName(bf);
log.info("[充值协议PDF] 找到字体文件 {} 实际 family={}", name, familyName);
return new LoadedFont(name, familyName, bytes);
} finally {
if (!tempProbe.delete()) tempProbe.deleteOnExit();
}
} catch (Exception e) {
log.warn("[充值协议PDF] 加载字体 {} 失败:{}", name, e.getMessage());
}
}
log.warn("[充值协议PDF] 未找到中文字体(resources/fonts/NotoSansSC-Regular.otf 等),"
+ "PDF 中的中文可能显示为方块,请参考 resources/fonts/README.md 添加字体");
return null;
}
/** 从 OpenPDF BaseFont 提取 family name(取第一组记录的第 4 字段:0=platform,1=encoding,2=lang,3=name) */
private String extractFamilyName(BaseFont bf) {
try {
String[][] names = bf.getFamilyFontName();
if (names != null) {
for (String[] record : names) {
if (record != null && record.length >= 4 && record[3] != null
&& !record[3].isEmpty()) {
return record[3];
}
}
}
} catch (Exception ignore) {
}
// fallback 到 PostScript 名称
return bf.getPostscriptFontName();
}
/** 已加载字体信息 */
private static class LoadedFont {
final String fileName;
final String familyName;
final byte[] bytes;
LoadedFont(String fileName, String familyName, byte[] bytes) {
this.fileName = fileName;
this.familyName = familyName;
this.bytes = bytes;
}
}
/** 简单 HTML 转义,避免 nickname 含特殊字符破坏模板 */
private String escapeHtml(String s) {
if (s == null) return "";
return s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
}
# 中文字体
## 用途
`RechargeAgreementPdfService` 使用 Flying Saucer 把 HTML 协议模板渲染成 PDF 时,
中文字符需要指定一个支持中文的字体,否则 PDF 里中文会显示为方块 □。
## 部署说明
请将一个支持简体中文的 TTF/OTF 字体放在本目录下,命名为:
```
NotoSansSC-Regular.otf
```
推荐使用 **Noto Sans SC**(Google 开源,OFL 协议,可商用),下载地址:
- GitHub: <https://github.com/googlefonts/noto-cjk/releases>
- 找到 `NotoSansSC-Regular.otf`(约 9MB,包含完整简体中文字形)
> ⚠️ 不要上传到 git 仓库,bin 体积大;放服务器部署目录的 `resources/fonts/`
> 或将本目录加入 `.gitignore`(建议)。
## 加载逻辑
`RechargeAgreementPdfService#loadChineseFont()` 启动时会从 classpath:`/fonts/` 加载
`NotoSansSC-Regular.otf`(找不到时也接受 `NotoSansCJKsc-Regular.otf`),加载失败会
**记录 warn 日志但不会中断**,此时 PDF 中的中文字符会以方块显示(不影响主流程)。
## 备选字体
如果不想用 Noto Sans SC,也可以替换为以下任一开源字体(改对应文件名即可):
- 思源黑体(Source Han Sans / Noto Sans CJK)
- 思源宋体(Source Han Serif / Noto Serif CJK)
- 霞鹜文楷(LXGW WenKai)
- 文泉驿微米黑(WenQuanYi Micro Hei)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>面向科创园区的普惠算力公共服务平台服务协议</title>
<style type="text/css">
@page {
size: A4;
margin: 2cm 2cm 2cm 2cm;
}
body {
font-family: ${fontFamilyStack};
font-size: 11pt;
line-height: 1.7;
color: #303133;
}
h1 {
text-align: center;
font-size: 18pt;
margin: 0 0 24pt 0;
font-weight: 700;
}
p {
margin: 6pt 0;
text-align: justify;
}
p.indent {
text-indent: 2em;
}
p.meta {
margin-top: 18pt;
}
strong {
font-weight: 700;
}
.signature {
margin-top: 36pt;
}
.signature p {
margin: 4pt 0;
}
</style>
</head>
<body>
<h1>面向科创园区的普惠算力公共服务平台服务协议</h1>
<p>本协议由以下双方于充值确认之日签署:</p>
<p>甲方:${userNickname}</p>
<p>乙方:面向科创园区的普惠算力公共服务平台</p>
<p class="indent">欢迎您使用面向科创园区的普惠算力公共服务平台(以下简称"普惠平台")算力服务。请您在注册、充值或使用本平台服务前,仔细阅读本《面向科创园区的普惠算力公共服务平台服务协议》(以下简称"本协议")的全部内容。您点击"同意"或实际使用本平台服务,即视为您已充分阅读、理解并接受本协议的全部条款,本协议即构成对双方具有法律约束力的文件。</p>
<p class="indent">甲乙双方本着平等自愿、诚实信用的原则,就甲方向乙方采购普惠平台相关服务事宜,经友好协商,达成如下协议,以资共同遵守。</p>
<p><strong>第一条 定义与解释</strong></p>
<p>1.1 除非本协议另有约定,下列词语具有如下含义:</p>
<p>(1)普惠平台:指乙方运营的以园区新型公共算力基础设施为载体,提供基础算力服务与AI增值服务的综合性算力服务平台。</p>
<p>(2)算力服务:指乙方通过平台向甲方提供的各类算力相关服务,包括但不限于大模型词元(Token)服务、高性能算力服务、AIGC智能内容创作服务、云存储/网络配套增值服务及平台整体授权合作服务。</p>
<p>(3)词元(Token):指大模型处理文本的基本单位,模型推理服务按实际消耗的词元数量进行计费。</p>
<p>(4)账户余额:指甲方在平台账户中充值后剩余的可用于消费的资金金额。</p>
<p>(5)服务订单:指甲方通过平台下单并确认的具体服务需求,包括服务类型、规格、数量、期限等内容。</p>
<p>1.2 本协议中的标题仅为阅读方便而设,不影响本协议任何条款的含义或解释。</p>
<p><strong>第二条 服务内容</strong></p>
<p>2.1 服务范围</p>
<p class="indent">平台仅向用户提供商用及个人用途的算力服务(租赁、销售等)。算力仅作为平台内部算力服务的计价单位,不具备法定货币、虚拟货币、金融理财、投资升值、炒作交易、兑换法币等任何功能。用户确认并同意,不会将算力用于任何违法违规或超出本协议约定的用途。乙方服务范围:</p>
<p>(1)大模型词元(Token)服务:乙方通过标准API接口统一对接智谱、豆包、通义千问、DeepSeek等国内主流闭源及开源大模型,采用自研AI网关实现统一API、统一计价、智能路由调度,向甲方提供涵盖文本大模型、视频生成模型、图像生成模型等模型推理词元服务。</p>
<p>(2)高性能算力服务:乙方向甲方提供裸金属服务器(含多型号GPU裸金属服务器)、小型工作站租赁或销售服务,以及文件存储、对象存储等配套服务。</p>
<p>(3)AIGC智能内容创作服务:乙方基于AI大模型能力,向甲方提供文本生成、图像生成、视频生成、语音合成等智能内容创作服务。</p>
<p>(4)云存储/网络等配套增值服务:乙方向甲方提供云存储、网络带宽、安全防护等配套增值服务,保障甲方算力业务的稳定运行。</p>
<p>(5)平台整体授权合作业务:乙方根据甲方需求,提供平台整体技术授权、定制化部署及运营支持等合作服务。</p>
<p>2.2 具体服务内容、规格、价格以甲方下单时平台展示的产品详情及服务订单为准。</p>
<p><strong>第三条 服务费用与充值</strong></p>
<p>3.1 计费方式</p>
<p>3.1.1 大模型词元(Token)服务采用按量计费方式,按甲方实际调用消耗的词元数量实时结算,具体单价以平台公示的各模型定价为准。</p>
<p>3.1.2 高性能算力服务按服务类型分别计费:</p>
<p>(1)裸金属服务器租赁服务按实际使用时长计费,具体单价以各型号服务器公示价格为准;</p>
<p>(2)小型工作站等终端产品按销售价格一次性计费,具体以产品公示价格为准;</p>
<p>(3)存储服务按实际使用容量和时长计费,具体单价以公示价格为准。</p>
<p>3.1.3 AIGC智能内容创作服务、云存储/网络配套增值服务及平台整体授权合作服务的计费方式,以具体服务订单约定为准。</p>
<p>3.2 充值规则</p>
<p>3.2.1 甲方使用平台服务前,需通过平台充值渠道向其账户充入资金。充值成功后,资金即时到账,可用于抵扣平台服务消费。</p>
<p>3.2.2 甲方确认:充值行为视为甲方同意本协议全部条款,充值资金一经到账,本协议即对双方产生法律约束力。</p>
<p>3.2.3 甲方账户余额不足以支付当前服务消费时,乙方有权暂停提供服务,直至甲方完成充值。</p>
<p>3.2.4 除法律规定或本协议另有约定外,甲方充值的资金不予退还。</p>
<p>3.3 发票</p>
<p>甲方如需开具发票,可向乙方提出申请;乙方在收到需求并核实消费金额后,按实际消费金额向甲方开具相应发票。开票类型、抬头及相关信息以甲方提交的申请为准。</p>
<p><strong>第四条 双方权利义务</strong></p>
<p>4.1 甲方权利义务</p>
<p>4.1.1 甲方有权按照本协议及服务订单约定使用乙方提供的算力服务。</p>
<p>4.1.2 甲方有权查询其账户余额、消费记录及服务使用情况。</p>
<p>4.1.3 甲方应按时足额支付服务费用,确保账户余额充足。</p>
<p>4.1.4 甲方应遵守国家法律法规及相关监管规定,不得利用乙方服务从事任何违法违规活动,包括但不限于:</p>
<p>(1)危害国家安全、泄露国家秘密、颠覆国家政权、破坏国家统一;</p>
<p>(2)制作、复制、发布、传播含有违法违规内容的信息;</p>
<p>(3)侵犯他人知识产权、商业秘密或其他合法权益;</p>
<p>(4)未经授权侵入他人计算机系统、窃取数据或从事其他网络攻击行为;</p>
<p>(5)利用大模型生成虚假信息、恶意内容或从事其他违反公序良俗的行为。</p>
<p>4.1.5 甲方应妥善保管其平台账号及密码,因甲方保管不善导致的账号被盗用、资金损失等后果,由甲方自行承担。</p>
<p>4.1.6 甲方应对其使用服务产生的所有行为及后果负责,因甲方使用服务不当造成的任何损失或第三方索赔,由甲方自行承担。</p>
<p>4.2 乙方权利义务</p>
<p>4.2.1 乙方应按照本协议及服务订单约定向甲方提供相应的算力服务。</p>
<p>4.2.2 乙方应保障平台系统的稳定运行,如因系统维护需暂停服务,应提前通过平台公告等方式通知甲方。</p>
<p>4.2.3 乙方有权按照本协议约定收取服务费用,并在甲方欠费时暂停服务。</p>
<p>4.2.4 乙方有权对甲方使用服务的行为进行监督,如发现甲方存在违法违规或违反本协议约定的行为,有权立即暂停或终止服务,并保留追究甲方法律责任的权利。</p>
<p>4.2.5 乙方应建立健全数据安全管理制度,采取必要的技术措施保障甲方数据安全。</p>
<p>4.2.6 乙方上游合作资源(包括但不限于大模型服务、算力资源等)发生调整时,有权相应调整平台服务内容及价格,但应提前通过平台公告等方式通知甲方。</p>
<p><strong>第五条 数据安全与隐私保护</strong></p>
<p>5.1 乙方应严格遵守相关法律法规,采取必要的技术和管理措施,保障甲方数据安全。</p>
<p>5.2 甲方确认并同意:因提供服务所需,乙方可能对甲方输入的提示词、生成内容及相关使用数据进行必要的处理,但不得用于与提供服务无关的其他用途。</p>
<p>5.3 乙方不得擅自向第三万披露甲方的个人信息、业务数据及使用记录,但下列情形除外:</p>
<p>(1)获得甲方明确同意的;</p>
<p>(2)为履行本协议所必需的;</p>
<p>(3)根据法律法规、监管要求或司法机关、行政机关的要求必须提供的;</p>
<p>(4)为保护乙方或第三方合法权益所必需的。</p>
<p>5.4 甲方应确保其输入平台的数据及内容不侵犯任何第三方的合法权益,不违反法律法规规定。因甲方数据或内容违法违规造成的任何后果,由甲方自行承担。</p>
<p><strong>第六条 知识产权</strong></p>
<p>6.1 乙方平台及相关技术、软件、文档等的知识产权归乙方或相关权利人所有,甲方不得擅自复制、修改、传播、出售或进行其他任何形式的侵权使用。</p>
<p>6.2 甲方使用AIGC智能内容创作服务生成的内容,其知识产权归属按照相关法律法规及具体服务规则确定。甲方应确保其使用生成内容的行为符合法律法规规定,不得侵犯任何第三方的合法权益。</p>
<p>6.3 甲方在使用服务过程中向乙方提供的反馈、建议等,乙方有权无偿使用,用于产品优化及服务改进。</p>
<p><strong>第七条 保密条款</strong></p>
<p>7.1 双方对在本协议履行过程中知悉的对方商业秘密、技术信息、经营信息及其他未公开信息负有保密义务,未经对方书面同意,不得向任何第三方披露。</p>
<p>7.2 本保密义务不因本协议的终止而终止,保密期限为自信息知悉之日起三年。</p>
<p>7.3 下列信息不属于保密信息:</p>
<p>(1)已公开的信息;</p>
<p>(2)接收方在披露前已合法知悉的信息;</p>
<p>(3)接收方从有权披露的第三方合法获得的信息;</p>
<p>(4)接收方独立开发的信息。</p>
<p><strong>第八条 违约责任</strong></p>
<p>8.1 任何一方违反本协议约定,应承担违约责任,赔偿对方因此遭受的直接经济损失。</p>
<p>8.2 甲方违反本协议第4.1.4条约定,从事违法违规活动的,乙方有权立即终止服务,扣除账户剩余资金,并保留追究甲方法律责任的权利。</p>
<p>8.3 因乙方原因导致服务中断或无法正常提供的,乙方应按实际中断时间相应延长服务期限或退还相应费用,但因不可抗力、甲方原因或第三方原因导致的除外。</p>
<p>8.4 因上游合作方原因导致服务内容、价格或可用性发生变化的,乙方不承担违约责任,但应及时通知甲方并协助甲方调整服务方案。</p>
<p><strong>第九条 不可抗力</strong></p>
<p>9.1 因不可抗力导致本协议无法履行或部分无法履行的,遭遇不可抗力一方应及时通知对方,并在合理期限内提供相关证明。双方应根据不可抗力的影响,协商决定部分履行、延期履行或解除协议,遭遇不可抗力一方不承担违约责任。</p>
<p>9.2 本协议所称不可抗力,是指不能预见、不能避免并不能克服的客观情况,包括但不限于自然灾害、战争、政府行为、网络基础设施故障、电力中断、上游服务商重大故障等。</p>
<p><strong>第十条 协议的变更与解除</strong></p>
<p>10.1 本协议的变更须经双方协商一致,并以书面形式确认。乙方有权根据业务发展需要调整本协议内容,调整后的协议将在平台公示,甲方继续使用服务视为同意调整后的协议。</p>
<p>10.2 有下列情形之一的,乙方有权单方解除本协议:</p>
<p>(1)甲方严重违反本协议约定,经乙方通知后仍未改正的;</p>
<p>(2)甲方利用服务从事违法违规活动的;</p>
<p>(3)甲方账户连续180天无任何活动且余额为零的;</p>
<p>(4)因不可抗力导致本协议无法继续履行的。</p>
<p>10.3 协议解除后,甲方应立即停止使用服务。除法律规定或本协议另有约定外,甲方账户剩余资金不予退还。</p>
<p><strong>第十一条 争议解决</strong></p>
<p>11.1 本协议的签订、履行、解释及争议解决均适用中华人民共和国法律。</p>
<p>11.2 因本协议引起的或与本协议有关的任何争议,双方应首先通过友好协商解决;协商不成的,任何一方均有权向乙方所在地有管辖权的人民法院提起诉讼。</p>
<p><strong>第十二条 其他条款</strong></p>
<p>12.1 本协议自甲方点击"同意"并完成充值之日起生效。</p>
<p>12.2 本协议未尽事宜,双方可另行协商并签订补充协议。补充协议与本协议具有同等法律效力。</p>
<p>12.3 如本协议中的任何条款被认定为无效或不可执行,不影响其他条款的效力。</p>
<p>12.4 乙方未行使或延迟行使本协议项下的任何权利,不构成对该权利的放弃。</p>
<p>12.5 甲方确认:在点击"同意"前,已仔细阅读并充分理解本协议全部条款,特别是涉及甲方权利义务限制、责任免除及争议解决的条款,乙方已对相关条款进行了充分提示和说明。</p>
<div class="signature">
<p>甲方(客户)确认:我已仔细阅读并完全同意本协议全部条款,自愿充值并使用平台服务。</p>
<p>乙方(平台运营方):面向科创园区的普惠算力公共服务平台</p>
<p>协议生效日期:${agreementDate}</p>
</div>
</body>
</html>
-- ============================================================
-- 会员充值协议 PDF:新增 agreement_pdf_url 字段
-- 适用数据库:MySQL 5.7+ / 8.0
-- 适用表: member_recharge
-- 创建时间: 2026-07-22
-- ============================================================
ALTER TABLE `member_recharge`
ADD COLUMN `agreement_pdf_url` VARCHAR(512) NULL DEFAULT NULL
COMMENT '充值协议 PDF 地址(每次充值生成一份,存对象存储)'
AFTER `remark`;
-- 验证:
-- SHOW COLUMNS FROM `member_recharge` LIKE 'agreement_pdf_url';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment