v-model
v-model 是 Vue.js 中用于实现双向数据绑定的指令。它在表单元素(如输入框、复选框、单选按钮和选择框)中使用得非常频繁,使得开发者可以轻松地将表单元素的值与 Vue 实例中的数据进行绑定。
其对于不同的数据有不同的绑定方法:
对于输入框 (),以下的v-model 指令将输入框的值绑定到 message 变量,并且当输入框的值发生变化时,message 变量也会随之更新
<template>
<div>
<input v-model="message" placeholder="Enter something">
<p>Message is: {{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
}
}
</script>
同理对于复选框 (<input type="checkbox">
),绑定一个布尔值 checked
到复选框,当复选框的状态改变时,checked
的值也会相应地改变。
<template>
<div>
<input type="checkbox" v-model="checked">
<p>Checked: {{ checked }}</p>
</div>
</template>
<script>
export default {
data() {
return {
checked: false
}
}
}
</script>
对于单选按钮 (<input type="radio">
),绑定到 picked
变量,并且根据选中的单选按钮的值来更新 picked
变量。
<template>
<div>
<input type="radio" v-model="picked" value="Option 1"> Option 1
<input type="radio" v-model="picked" value="Option 2"> Option 2
<p>Picked: {{ picked }}</p>
</div>
</template>
<script>
export default {
data() {
return {
picked: ''
}
}
}
</script>
对于选择框 (<select>
),将选中的选项值绑定到 selected
变量
<template>
<div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>Selected: {{ selected }}</p>
</div>
</template>
<script>
export default {
data() {
return {
selected: ''
}
}
}
</script>
v-model修饰符
默认情况下,v-model
会在每次 input
事件触发时更新数据。如果希望在 change
事件(失去焦点或按下回车键)之后才更新,可以使用 .lazy
修饰符。
<template>
<div>
<input v-model.lazy="message" placeholder="Enter something">
<p>Message is: {{ message }}</p>
</div>
</template>
默认情况下,v-model 会将输入框的值作为字符串。如果希望将其转换为数值,可以使用 .number 修饰符。如果输入的内容无法被 parseFloat 转换,则会保持原样。
<template>
<div>
<input v-model.number="age" type="number" placeholder="Enter age">
<p>Age is: {{ age }}</p>
</div>
</template>
默认情况下,v-model 会绑定输入框的完整值。如果希望自动去除首尾空白字符,可以使用 .trim 修饰符。
<template>
<div>
<input v-model.trim="message" placeholder="Enter something">
<p>Message is: {{ message }}</p>
</div>
</template>
自定义修饰符(通过自定义组件实现):
通过自定义组件,实现一个自动将输入内容转换为大写的修饰符
<!-- UppercaseInput.vue -->
<template>
<input :value="value" @input="updateValue">
</template>
<script>
export default {
props: ['value'],
methods: {
updateValue(event) {
const uppercasedValue = event.target.value.toUpperCase();
this.$emit('input', uppercasedValue);
}
}
}
</script>
然后进行调用:
<template>
<div>
<UppercaseInput v-model="message"></UppercaseInput>
<p>Message is: {{ message }}</p>
</div>
</template>
<script>
import UppercaseInput from './UppercaseInput.vue';
export default {
components: { UppercaseInput },
data() {
return {
message: ''
}
}
}
</script>
通过自定义组件,自动去除所有空格
<!-- NoSpacesInput.vue -->
<template>
<input :value="value" @input="updateValue">
</template>
<script>
export default {
props: ['value'],
methods: {
updateValue(event) {
const noSpacesValue = event.target.value.replace(/\s+/g, '');
this.$emit('input', noSpacesValue);
}
}
}
</script>
然后进行调用:
<template>
<div>
<NoSpacesInput v-model="message"></NoSpacesInput>
<p>Message is: {{ message }}</p>
</div>
</template>
<script>
import NoSpacesInput from './NoSpacesInput.vue';
export default {
components: { NoSpacesInput },
data() {
return {
message: ''
}
}
}
</script>
自定义组件中的 v-model:
在自定义组件中使用 v-model 需要进行一些额外的配置。默认情况下,v-model 在组件中相当于传递 value prop 和监听 input 事件。
<!-- CustomInput.vue -->
<template>
<input :value="value" @input="$emit('input', $event.target.value)">
</template>
<script>
export default {
props: ['value']
}
</script>
在父组件中使用 CustomInput 组件,,自定义组件 CustomInput 通过 value prop 接收值,通过 input 事件向父组件传递更新值,从而实现与父组件数据的双向绑定:
<template>
<div>
<CustomInput v-model="message"></CustomInput>
<p>Message is: {{ message }}</p>
</div>
</template>
<script>
import CustomInput from './CustomInput.vue';
export default {
components: { CustomInput },
data() {
return {
message: ''
}
}
}
</script>
CSS @keyframes规则
CSS @keyframes 规则用于创建动画。通过定义关键帧,可以指定元素在动画的各个阶段所处的样式。
基本用法:from 和 to
from 和 to 是 @keyframes 的简化写法,分别对应 0% 和 100%。
@keyframes example {
from {
background-color: red;
}
to {
background-color: yellow;
}
}
使用百分比可以定义更复杂的动画,指定在动画的特定时间点元素的样式
@keyframes example {
0% {
background-color: red;
left: 0px;
}
50% {
background-color: yellow;
left: 50px;
}
100% {
background-color: green;
left: 100px;
}
}
定义了 @keyframes 之后,需要使用 animation 或者相关的属性将动画应用到元素上
/* 定义动画 */
@keyframes example {
from {background-color: red;}
to {background-color: yellow;}
}
/* 应用动画 */
div {
width: 100px;
height: 100px;
background-color: red;
animation-name: example;
animation-duration: 4s; /* 动画持续时间 */
}
实例:
简单的颜色渐变动画
@keyframes colorChange {
from {
background-color: red;
}
to {
background-color: blue;
}
}
div {
width: 100px;
height: 100px;
animation: colorChange 2s infinite;
}
移动和旋转动画
@keyframes moveAndRotate {
0% {
transform: translateX(0) rotate(0deg);
background-color: red;
}
50% {
transform: translateX(100px) rotate(180deg);
background-color: yellow;
}
100% {
transform: translateX(0) rotate(360deg);
background-color: green;
}
}
div {
width: 100px;
height: 100px;
animation: moveAndRotate 4s infinite;
}
旋转和缩放动画
@keyframes rotateScale {
0% {
transform: rotate(0deg) scale(1);
background-color: red;
}
25% {
transform: rotate(90deg) scale(1.5);
background-color: orange;
}
50% {
transform: rotate(180deg) scale(1);
background-color: yellow;
}
75% {
transform: rotate(270deg) scale(0.5);
background-color: green;
}
100% {
transform: rotate(360deg) scale(1);
background-color: blue;
}
}
div {
width: 100px;
height: 100px;
animation: rotateScale 5s infinite;
}
文字颜色和大小动画
@keyframes textChange {
0% {
color: red;
font-size: 20px;
}
25% {
color: orange;
font-size: 30px;
}
50% {
color: yellow;
font-size: 40px;
}
75% {
color: green;
font-size: 50px;
}
100% {
color: blue;
font-size: 20px;
}
}
h1 {
animation: textChange 10s infinite;
}
弹跳动画
@keyframes bounce {
0%, 100% {
transform: translateY(0);
animation-timing-function: ease-in-out;
}
50% {
transform: translateY(-50px);
animation-timing-function: ease-in-out;
}
}
div {
width: 100px;
height: 100px;
background-color: red;
animation: bounce 2s infinite;
}
渐变背景色动画
@keyframes gradientBackground {
0% {
background: linear-gradient(0deg, red, yellow);
}
50% {
background: linear-gradient(180deg, red, yellow);
}
100% {
background: linear-gradient(360deg, red, yellow);
}
}
div {
width: 200px;
height: 200px;
animation: gradientBackground 5s infinite;
}
移动和淡入淡出动画
@keyframes moveFade {
0% {
transform: translateX(0);
opacity: 1;
}
25% {
transform: translateX(100px);
opacity: 0.75;
}
50% {
transform: translateX(200px);
opacity: 0.5;
}
75% {
transform: translateX(300px);
opacity: 0.25;
}
100% {
transform: translateX(400px);
opacity: 0;
}
}
div {
width: 100px;
height: 100px;
background-color: red;
animation: moveFade 5s infinite;
}
心跳动画
@keyframes heartbeat {
0%, 100% {
transform: scale(1);
animation-timing-function: ease-in-out;
}
25%, 75% {
transform: scale(1.3);
animation-timing-function: ease-in-out;
}
50% {
transform: scale(1.5);
animation-timing-function: ease-in-out;
}
}
div {
width: 100px;
height: 100px;
background-color: red;
border-radius: 50%;
animation: heartbeat 1.5s infinite;
}
光影效果动画
@keyframes shadowPulse {
0%, 100% {
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.5);
}
50% {
box-shadow: 0 0 20px 5px rgba(0, 0, 0, 1);
}
}
div {
width: 100px;
height: 100px;
background-color: blue;
animation: shadowPulse 2s infinite;
}
路径动画
@keyframes pathMove {
0% {
offset-distance: 0%;
}
100% {
offset-distance: 100%;
}
}
path {
stroke: black;
stroke-width: 2;
fill: none;
}
circle {
offset-path: path('M0,0 Q50,100 100,0 T200,0');
offset-distance: 0%;
animation: pathMove 4s linear infinite;
r: 5;
fill: red;
}
插值表达式与指令
插值表达式 插值表达式是 Vue.js 中用于在模板中绑定数据的一种方式,常见的插值表达式包括文本插值和属性插值
文本插值 使用双大括号 {{ }} 可以在 HTML 中绑定数据:
<div id="app">
{{ message }}
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello, Vue!'
}
});
</script>
属性插值 在 HTML 属性中使用 v-bind 指令来绑定数据:
<div id="app">
<img v-bind:src="imageSrc" alt="Vue Logo">
</div>
<script>
new Vue({
el: '#app',
data: {
imageSrc: 'https://vuejs.org/images/logo.png'
}
});
</script>
使用简写语法“ :”
<img v-bind:src="imageSrc" alt="Vue Logo">
简写为
<img :src="imageSrc" alt="Vue Logo">
内置指令
Vue.js 内置指令通过 Vue 的模板编译器在创建 Vue 实例时被解析并转换为相应的渲染函数。在运行时,这些指令会根据数据的变化来更新 DOM 元素。
Vue.js 内置指令通过 Vue 的模板编译器在创建 Vue 实例时被解析并转换为相应的渲染函数。在运行时,这些指令会根据数据的变化来更新 DOM 元素。以下是 Vue.js 所有内置指令的简述,包括其原理和逻辑。
v-bind
用途:动态地绑定一个或多个属性,或一个组件 prop。
在编译阶段,v-bind
指令被解析,属性值被转换为对应的数据绑定。
在数据变化时,Vue 会更新绑定的属性或 prop。
<a v-bind:href="url">Link</a>
<a :href="url">Link</a>
v-model
用途:在表单控件元素上创建双向数据绑定。
v-model
实际上是v-bind
和 v-on
的语法糖,它绑定 value 属性和 input 事件。
在数据变化时,更新控件的值;在用户输入时,更新数据。
<input v-model="message">
v-if
, v-else-if
, v-else
用途:根据条件渲染元素。
在编译阶段,v-if
指令被解析,并添加条件判断。
在运行时,当条件为 true 时,渲染元素;否则,移除元素。
v-else-if
和 v-else
需要与 v-if
配合使用,用于提供多条件分支。
<p v-if="type === 'A'">A</p>
<p v-else-if="type === 'B'">B</p>
<p v-else>C</p>
v-show
用途:根据条件展示或隐藏元素。
v-show
指令通过设置元素的 display 样式来控制其显示或隐藏。
在数据变化时,根据条件设置元素的 display 样式为 none 或其他显示样式。
<p v-show="isVisible">Visible</p>
v-for
用途:基于源数据多次渲染元素或模板块。
v-for
指令在编译阶段解析并生成渲染函数。
在运行时,根据源数据的变化重新渲染列表元素。
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
v-on
用途:绑定事件监听器。
在编译阶段,v-on
指令解析并生成事件绑定代码。
在运行时,事件触发时调用绑定的方法。
<button v-on:click="handleClick">Click</button>
<button @click="handleClick">Click</button>
v-cloak
用途:防止闪烁内容在 Vue 编译完成前显示。
v-cloak
指令添加一个 v-cloak
属性。
结合 CSS 使用,在 Vue 实例编译完成后移除该属性。
<style>
[v-cloak] { display: none; }
</style>
<div v-cloak>{{ message }}</div>
v-pre
用途:跳过这个元素和它的子元素的编译过程。
在编译阶段,v-pre 指令标记的元素和子元素会被跳过,不进行编译。
用于显示原始 Mustache 标签或提高性能。
<div v-pre>{{ raw }}</div>
v-once
用途:只渲染元素和组件一次。
v-once
指令在首次渲染后缓存结果,以后数据变化时不再更新。
用于优化性能。
<div v-once>{{ message }}</div>
v-html
用途:输出 HTML。
v-html
指令将内容作为 HTML 插入元素。
在编译阶段,解析绑定的内容并插入到 DOM 中。
注意:使用 v-html 可能会导致 XSS 攻击,使用时需谨慎。
<div v-html="htmlContent"></div>
v-text
用途:更新元素的文本内容。
v-text
指令将绑定的内容作为文本插入元素。
类似于 Mustache 插值,但 v-text 可以用于 HTML 属性中。
<div v-text="textContent"></div>
v-slot
用途:具名插槽。
v-slot
指令用于接收组件的具名插槽内容。
在编译阶段,插槽内容会被插入到指定位置。
<template v-slot:header>
<h1>Header Content</h1>
</template>
v-bind.sync
用途:简化双向绑定。
v-bind.sync
是 .sync 修饰符的简写,用于组件 prop 的双向绑定。
在数据变化时,更新组件 prop;在组件内部事件触发时,更新父组件的数据。
<child-component :value.sync="parentValue"></child-component>
本次系统中的用法:
<section>
<transition name="fade">
<div class="info">
<h2 v-if="formData.title">{{ formData.title }}</h2>
<h2 v-else class="placeholder">沙某某诉袁某某探望权纠纷案</h2>
</div>
</transition>
使用v-if, v-else,于条件渲染。如果 formData.title 存在,则渲染
元素并显示 formData.title;否则,渲染一个带有占位符文本的
元素,这是构成本次实验指导性案例与用户填写共存的基础。
以上是自动给出的案例,允许用户填入时将其覆盖
问卷清洗
前情提要:问卷收集需求的过程中由于考虑了各种因素,最终是发布了5分题目顺序不同但是内容相同的,包含29道选择题和1道非选择题的问卷,因此要进行数据清洗与数据合并
设计去除题号的方法:
去掉列名中所有形如“数字+、”的部分,即题号
def remove_decimal_prefix(column_name):
return re.sub(r'^\d+、', '', column_name)
正则表达式 r'^\d+、' 的含义:
^ 表示匹配字符串的开头。 \d+ 表示匹配一个或多个数字。 、匹配中文顿号 然后替换成空,统一格式
利用python pandes库定义输入与输出的dataFrame:
df1 = pd.read_excel('output/test1.xlsx')
df2 = pd.read_excel('output/test2.xlsx')
df3 = pd.read_excel('output/test3.xlsx')
df4 = pd.read_excel('output/test4.xlsx')
df5 = pd.read_excel('output/test5.xlsx')
result_df = pd.DataFrame()
pd.read_excel
是 Pandas 提供的一个函数,用于读取 Excel 文件,并将其内容转换为 DataFrame。这个函数依赖于第三方库 openpyxl 或 xlrd 来处理 Excel 文件的解析和读取。
openpyxl
:用于处理 .xlsx 文件。
xlrd:用于处理 .xls 文件(在较新版本中已经弃用对 .xlsx 文件的支持)
pd.read_excel
会打开指定路径的 Excel 文件,利用合适的解析库来读取文件内容。
函数根据提供的参数(如 sheet_name、header、names 等)来解析 Excel 文件中的数据。 默认情况下,sheet_name=0 读取第一个工作表,可以通过 sheet_name=None 读取所有工作表并返回一个字典。
解析后的数据将转换为 Pandas DataFrame 结构,方便后续的数据操作和分析。
pd.DataFrame
是 Pandas 中最核心的数据结构之一,用于创建二维、大小可变、且具有标记轴(行和列)的数据结构。DataFrame 提供了多种方法和属性来操作和分析数据。
可以从多种数据源创建 DataFrame,如字典、列表、NumPy 数组、另一个 DataFrame 或读取外部文件(如 CSV、Excel)。 在创建 DataFrame 时,可以指定行标签(index)和列标签(columns)。
DataFrame 使用 NumPy 的 ndarray 作为其底层数据结构,提供高效的内存管理和快速的数值计算。 每一列的数据可以是不同的数据类型(例如,整数、浮点数、字符串)。
提供丰富的方法来操作数据,如选择、过滤、分组、聚合、合并和重塑。 支持标签和位置两种索引方式,提供灵活的数据访问和操作。
对所有题目去除序号:
去掉列名中的题目序号
new_columns = {col: remove_decimal_prefix(col) for col in df1.columns}
df1.rename(columns=new_columns, inplace=True)
new_columns = {col: remove_decimal_prefix(col) for col in df2.columns}
df2.rename(columns=new_columns, inplace=True)
new_columns = {col: remove_decimal_prefix(col) for col in df3.columns}
df3.rename(columns=new_columns, inplace=True)
new_columns = {col: remove_decimal_prefix(col) for col in df4.columns}
df4.rename(columns=new_columns, inplace=True)
new_columns = {col: remove_decimal_prefix(col) for col in df5.columns}
df5.rename(columns=new_columns, inplace=True)
使用字典推导式创建一个新的字典 new_columns。 字典的键是原始列名。 字典的值是通过 remove_decimal_prefix 函数处理后的列名 remove_decimal_prefix(col) 函数用于去掉列名中的题目序号,例如将 1、Name 转换为 Name。
使用 rename 方法重命名列名。 columns=new_columns 参数指定列名的替换规则。 inplace=True 参数表示直接在原 DataFrame 上进行修改,而不是返回一个新的 DataFrame。
进行列合并获得最终的xlsx文件:
# Concatenate the DataFrames
merged_df = pd.concat([df1, df2, df3, df4, df5], axis=0)
merged_df.to_excel('output/merge.xlsx', index=False)
pd.concat
是 Pandas 提供的一个函数,用于沿指定轴将多个 DataFrame 或 Series 对象连接在一起。它允许在垂直或水平方向上合并数据,这在数据处理和分析中非常常见。以下是 pd.concat 的工作原理和机制。
pd.concat
接受一个包含多个 DataFrame 或 Series 的列表或字典作为输入。
axis=0:沿行方向进行合并(垂直堆叠)。默认值。
axis=1:沿列方向进行合并(水平堆叠)。
pd.concat
会尝试对齐不同 DataFrame 的索引。如果索引不匹配,可以使用 ignore_index=True 来重新生成连续的索引。
默认情况下,pd.concat
会保留各个 DataFrame 的原始索引。可以通过设置 ignore_index=True 来忽略原始索引,并生成新的索引。
join
:控制如何处理索引对齐。join='outer'(默认)表示并集,join='inner' 表示交集。
keys
:用于为每个 DataFrame 添加一个多级索引(常用于区分来源)。
最终成果:
反向地理编码
反向地理编码(Reverse Geocoding)是将地理坐标(经纬度)转换为人类可读的地址或地名的过程。这在地图应用和位置服务中非常常见,能够将用户点击地图上的位置转换为实际的地址信息
工作原理
-
获取地理坐标 反向地理编码的输入是地理坐标(纬度和经度)。这些坐标可以通过 GPS 设备、IP 地址、Wi-Fi 网络或其他定位服务获取。
-
发送请求到地理编码服务 将获取到的地理坐标发送到反向地理编码服务。常用的反向地理编码服务包括:
Google Maps Geocoding API OpenStreetMap Nominatim Mapbox Geocoding API BigDataCloud API 3. 服务端处理 反向地理编码服务接收到坐标后,会在其地理信息数据库中查找与这些坐标对应的地址信息。这个过程包括以下步骤:
空间索引:使用空间索引技术(如四叉树、R树)快速查找包含输入坐标的地理单元。 数据库查询:在地理信息数据库中查询匹配的地址记录。 最近邻搜索:如果没有直接匹配的记录,服务可能会查找最近的已知位置。 4. 返回地址信息 地理编码服务将找到的地址信息返回给客户端。这些信息通常包括:
国家:例如,中国、美国 省/州:例如,陕西省、加利福尼亚州 市/区:例如,西安市、旧金山 街道名称:例如,中山路 邮政编码:例如,710000 其他详细信息:例如,建筑物名称、门牌号 5. 解析和显示 客户端接收地址信息后,可以将其解析并显示给用户。例如,将地址显示在地图上,或者在信息窗口中展示详细地址。
本次系统基于api.bigdatacloud.net/data/revers…
其提供了多个参数:
主要参数
latitude
(纬度)
描述:需要进行反向地理编码的纬度坐标。
示例:latitude=37.7749
longitude
(经度)
描述:需要进行反向地理编码的经度坐标。 示例:longitude=-122.4194 localityLanguage (语言)
描述:返回的地址信息的语言。
默认值:en
示例:localityLanguage=zh
其他可选参数
key
描述:API密钥。
示例:key=YOUR_API_KEY
注意:在免费的API中不需要提供API密钥。
fallback
描述:是否启用IP地理位置作为回退(如果未提供坐标)。
值:true 或 false
示例:fallback=true
timezone
描述:是否在返回的数据中包含时区信息。
值:true 或 false
示例:timezone=true
include
描述:指定是否包含额外的信息。
值:可以是 localityInfo(包含本地信息)或 boundingBox(包含边界框)。
示例:include=localityInfo
async showPosition(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const response = await axios.get(`https://api.bigdatacloud.net/data/reverse-geocode-client`, {
params: {
latitude: lat,
longitude: lon,
localityLanguage: 'zh'
}
});
const data = response.data;
this.autoLocation = `${data.principalSubdivision} ${data.city}`;
if (!this.formData.location) {
this.formData.location = this.autoLocation;
}
this.mapCenter = [lat, lon];
this.markerLocation = [lat, lon];
},
首先定义异步函数,接收一个 position 对象作为参数,使用 await 关键字来等待异步操作完成。然后从 position 对象中提取 latitude(纬度)和 longitude(经度)。这些值通过浏览器的地理定位 API 获取。之后使用 axios.get 发起一个 GET 请求到 BigDataCloud 反向地理编码 API。await 关键字用于等待请求完成。请求参数包括 latitude 和 longitude,以及 localityLanguage,指定返回的地址信息应为中文(zh)。请求完成后,从响应中提取数据并存储在 data 变量中。
将 data 中的 principalSubdivision(主要行政区划)和 city(城市)拼接成一个字符串,并赋值给 this.autoLocation
。检查 this.formData.location
是否为空。如果为空,则将 this.autoLocation 赋值给 this.formData.location
,即将自动获取的地址信息填充到表单中。将 lat 和 lon 赋值给 this.mapCenter 和 this.markerLocation
,用来更新地图的中心位置和标记的位置。
其中marker标记图标由于没有设置所以如此显示
文书抽取
本次工作的文书格式抽取是基于不含有任何案例信息,不含有任何个人姓名,具体的任何真实信息,是一个待填写的文书格式框架,这样对于交互式的文书填写是极为有利的
页面结构分析:
使用浏览器控制台观察页面源代码
可以发现我们所需要的内容是detail_title和detail_txt里面的
使用python基于bs4库进行dom tree爬取:
请求页面内容:
response = requests.get(url)
response.encoding = 'UTF-8' # 设置正确的编码
解析 HTML:
soup = BeautifulSoup(response.text, 'html.parser')
提取 detail_title:
detail_title_div = soup.find('div', class_='detail_title')
detail_title = detail_title_div.get_text(strip=True) if detail_title_div else "No title found"
提取 detail_txt:
detail_txt_div = soup.find('div', class_='detail_txt')
detail_txt = detail_txt_div.get_text(strip=True) if detail_txt_div else "No text found"
存储到文件:
with open("ttttt", "w", encoding="utf-8") as f:
f.write(f"Detail Title:\n{detail_title}\n\n")
f.write(f"Detail Text:\n{detail_txt}")
在本次系统开发过程中,我们进行了广泛的研究和分析,最终决定抽取最常用的法律文书格式。这些文书格式涵盖了各种法律程序中常见的文件类型,包括但不限于:
民事再审申请书:用于申请重新审理民事案件的重要文件。 不予受理支付令申请通知书:告知申请人其支付令申请未被受理的正式通知。 民事答辩状:被告在民事诉讼中针对原告的诉讼请求进行答辩的重要文件。 民事判决书:法院对民事案件作出的正式判决文件。 民事上诉状:当事人对一审判决不服,向上一级法院提起上诉时使用的文件。 这些文书格式是经过严格筛选的,涵盖了法律实践中最常见和最具代表性的十余项文书类型,旨在为用户提供全面、标准化的文书参考。
然而,在抽取这些文书格式的过程中,我们并不仅仅是简单地提取 .string 或 .text 内容。法律文书具有高度的格式规范性,其编写和呈现必须严格按照既定的格式要求执行。因此,我们在抽取文书内容时,特别注意保留文书的格式细节,包括但不限于以下几个方面:
字体属性(font-family):法律文书通常使用特定的字体以确保其正式性和权威性。我们在抽取内容时,仔细保留了每个文书段落、标题和正文部分的字体信息,以确保文书在不同平台和环境中呈现时的一致性。
文本缩进(text-indent):许多法律文书在段落开始处需要特定的缩进格式,这不仅仅是为了美观,更是为了遵循法律文书编写的规范。我们确保在抽取过程中,所有段落的缩进信息都被准确保留。
文本格式和顺序:法律文书中的每一句话、每一个段落都有其特定的顺序和逻辑关系,这对于传达法律信息和法律意图至关重要。我们在抽取文书内容时,严格保持了原文的句与句之间的格式和顺序,确保抽取后的文书在逻辑和语义上与原文完全一致。
其他格式信息:除了字体和缩进之外,法律文书中还可能包含其他重要的格式信息,如行距、段间距、加粗、斜体等。我们在抽取过程中,也对这些格式信息进行了细致的保留和处理,确保文书的完整性和标准化。
通过上述严格而细致的抽取过程,不仅保留了法律文书的内容,还保留了其格式和风格,从而确保了文书的专业性和规范性。我们的系统可以为用户提供高质量、标准化的法律文书模板,帮助用户在法律事务处理中更加便捷和高效。
HTTP 请求
在 Vue.js 中,处理 HTTP 请求的最常见方法是使用 axios 库。Axios 是一个基于 Promise 的 HTTP 客户端,用于在浏览器和 Node.js 中进行 HTTP 请求。它的主要功能是发送异步 HTTP 请求以与服务器进行数据交互,并处理请求的响应。
Axios 是基于 Promise 的,这意味着它在处理异步操作时更为简洁和强大。每个 HTTP 请求返回一个 Promise 对象,可以使用 then 和 catch 方法处理请求的成功和失败结果。
axios.get('url')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error fetching user data:', error);
});
Axios 的配置选项,可以在全局、实例级别或请求级别进行配置:
1. url
类型:string
描述:请求的 URL。
2. method
类型:string
描述:请求使用的方法。默认是 get。
3. baseURL
类型:string
描述:将自动加在 url 前面,除非 url 是一个绝对 URL。
4. headers
类型:object
描述:自定义请求头。
5. params
类型:object
描述:请求的 URL 参数。
6. data
类型:object
描述:PUT、POST、PATCH 请求时发送的请求体数据。
7. timeout
类型:number
描述:请求的超时时间(毫秒)。
8. responseType
类型:string
描述:表示服务器响应的数据类型。可以是 json, text, blob, document, arraybuffer, stream 等。
9. auth
类型:object
描述:HTTP 基础认证的认证信息。
10. withCredentials
类型:boolean
描述:表示跨域请求时是否需要使用凭证。
11. xsrfCookieName
类型:string
描述:用作 xsrf token 的值的 cookie 名称。
12. xsrfHeaderName
类型:string
描述:携带 xsrf token 值的 HTTP 头的名称。
13. onUploadProgress
类型:function
描述:上传进度事件的处理函数。
14. onDownloadProgress
类型:function
描述:下载进度事件的处理函数。
Axios 的核心组件
-
Axios 核心 Axios 的核心模块负责处理 HTTP 请求的构建、发送和响应处理。它包括了配置管理、请求方法实现和拦截器机制。Axios 提供了 axios.all 方法用于处理并发请求,并使用 axios.spread 方法处理所有响应。Axios 支持多种 HTTP 请求方法,包括 get, post, put, delete, patch, options, head 等。每种方法都可以根据需要传递参数和数据。
-
适配器 Axios 使用适配器来处理请求的具体实现。默认的适配器是 xhr.js(用于浏览器)和 http.js(用于 Node.js)。适配器负责将抽象的请求配置转换为具体的 HTTP 请求。
-
拦截器 Axios 的拦截器机制允许在请求和响应的各个阶段插入自定义逻辑。请求拦截器在请求发送之前执行,响应拦截器在响应返回之后执行。
系统中的实际代码:
// 创建一个 Axios 实例
const apiClient = axios.create({
baseURL: 'https://api.bigdatacloud.net',
timeout: 5000, // 设置请求超时时间
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
apiClient.interceptors.request.use(
config => {
// 这里可以添加 token 或其他自定义头信息
console.log('Request sent:', config);
return config;
},
error => {
console.error('Request error:', error);
return Promise.reject(error);
}
);
// 响应拦截器
apiClient.interceptors.response.use(
response => {
console.log('Response received:', response);
return response;
},
error => {
console.error('Response error:', error);
return Promise.reject(error);
}
);
// 包装后的 API 请求方法
const getReverseGeocode = async (lat, lon, language = 'zh') => {
try {
const response = await apiClient.get('/data/reverse-geocode-client', {
params: {
latitude: lat,
longitude: lon,
localityLanguage: language
}
});
return response.data;
} catch (error) {
console.error('Error fetching reverse geocode data:', error);
throw error;
}
};
axios.create
:创建一个带有自定义配置的 Axios 实例。
baseURL
:指定了所有请求的基础 URL。
timeout
:设置请求的超时时间为 5000 毫秒(5秒)。如果请求在这个时间内没有完成,它会自动中断并抛出错误。
headers
:设置默认的请求头。在这里,设置了 Content-Type 为 application/json,表示发送的数据格式为 JSON。
apiClient.interceptors.request.use
:添加请求拦截器。
成功拦截函数:在请求发送之前执行。在这里,可以添加认证 token 或其他自定义头信息。console.log('Request sent:', config) 打印出发送的请求配置信息。
错误拦截函数:在请求错误时执行。console.error('Request error:', error) 打印出错误信息,并通过 Promise.reject(error)
将错误传递到下一个处理程序。
apiClient.interceptors.response.use
:添加响应拦截器。
成功拦截函数:在响应到达后执行。console.log('Response received:', response)
打印出接收到的响应数据。
错误拦截函数:在响应错误时执行。console.error('Response error:', error)
打印出错误信息,并通过 Promise.reject(error)
将错误传递到下一个处理程序。
apiClient.get
:使用 apiClient 实例发送 GET 请求
params
:请求的 URL 参数,包含 latitude、longitude 和 localityLanguage。
await
:等待请求完成,并将响应赋值给 response。
return response.data
:返回响应数据部分。
错误处理:使用 try...catch
结构捕获错误,如果请求失败,打印错误信息并抛出错误。
三、后端功能
后端对接接口文档
侧边栏
-
获取当前对话的有效历史记录
接口
GET /api/getHistory
参数
属性 类型 说明 id
string
当前对话的id 返回
属性 类型 说明 index
number[]
所有有效历史记录的索引 说明
“有效历史记录”是指用于AI回答上下文的对话记录
-
设定当前对话的有效历史记录
接口
POST /api/setHistory
参数
属性 类型 说明 id
string
当前对话的id history
object<item>[]
设定的对话使用历史记录 (item.index)
number
历史记录索引 (item.abstract)
string
历史记录前几个字,用于判断索引是否正确 返回
属性 类型 说明 ok
number[]
成功设定的历史记录 说明
请确保每一条记录的索引是唯一且固定的,我们会用一个自增序列来记录索引,请不要改变它
对话主页
-
对话示例
接口
GET /api/example
参数 无
返回
属性 类型 说明 question
string
示例提问 answer
string
对该提问的回答 说明
示例提问是随机而非固定的,会在示例问题集中选取一个
-
对话模板
接口
GET /api/template
参数 无
返回
属性 类型 说明 template
string
一份提问模板 -
发送问题
接口
POST /api/ask
参数
属性 类型 说明 id
string
当前对话id question
string
问题内容 返回
属性 类型 说明 answer
string[]
对此问题的回答 说明
回答只会参考已设定的历史记录作为上下文,你可以通过
setHistory
接口更改上下文 -
文本加密与解密
接口
POST /api/mask
参数
属性 类型 说明 text
string
要处理的文本 key
string
由id和时间戳生成的键值 timestamp
number
时间戳,需要与key相匹配 reality
boolean?
true为解密,false(默认)为加密 返回
属性 类型 说明 result
string
加密或解密的结果 说明
key的生成请使用提供的
easyID.js
工具函数 -
用户反馈
接口
POST /api/feedback
参数
属性 类型 说明 id
string
当前对话id say
string
反馈内容 history
number[]
参考的对话记录 返回 无
js使用docxtemplater生成word
安装依赖
npm install docxtemplater docxtemplater-image-module-free pizzip file-saver -S
说明:由于 docxtemplater 免费版生成的word不支持图片插入,所以引入社区开源免费的 docxtemplater-image-module-free 进行补充图片无法插入的问题
源码实现(renderDoc.js)
原理说明
创建一个 Word 模板
首先,你需要一个 .docx 模板文件。在这个文件中,你可以使用 {placeholder} 作为你想要在 JavaScript 中替换的占位符。例如,你可以创建一个名为 template.docx 的文件,并在其中输入以下内容:
Hello, {name}! This is a document generated for you.
使用 JavaScript 替换占位符
读取模板文件,然后创建一个 Docxtemplater 实例,并将数据应用到模板上。然后,它生成一个新的 Word 文档并将其写入到 output.docx 文件中。如果在处理过程中发生任何错误,它都会捕获并打印错误消息。
如何使用
使用以下的代码即可生成word
import {renderDoc} from 'renderDoc.js' const data = { last_name: '哈', first_name: '哈', img: '/sponsor-qrcode/qrcode-alipay.png' } renderDoc({ templateUrl: '', data, fileName: 'output' })
常用语法
`//文本
docx:
Hello {name} !
data:
{
name: "John"
}
result:
Hello John!
//图片渲染
{%img}
data:
{
"img": "/images/1.png"
}
//条件渲染
docx:
{#hasKitty}Cat’s name: {kitty}{/hasKitty}
{#hasDog}Dog’s name: {dog}{/hasDog}
data:
{
"hasKitty": true,
"kitty": "Minie"
"hasDog": false,
"dog": null
}
result:
Cat’s name: Minie
//循环渲染
docx:
{#products}
{name}, {price} $
{/products}
data:
{
"products": [
{ name: "Windows", price: 100 },
{ name: "Mac OSX", price: 200 },
{ name: "Ubuntu", price: 0 }
]
}
result:
Windows, 100 $
Mac OSX, 200 $
Ubuntu, 0 $`
注意事项
如果代码、标签和数据都没有写错,但是就是渲染不出来,请将标签换行。
有可能出现的问题:
{#hasImg} {%img} {/hasImg}
//若上述的模板图片无法渲染,则最好将其换行,如下所示:
{#hasImg}
{%img}
{/hasImg}
远程服务器连接
纯JavaScript实现的SSH2模块:用于Node.js的强大工具
在寻找一个高效且可靠的SSH解决方案时,你可能会遇到名为ssh2的纯JavaScript库。这个开源项目为Node.js提供了客户端和服务器端的SSH2模块,旨在提供无缝的远程连接和服务支持。我们在项目中应用到了这一工具。
ssh2介绍
ssh2库是由mscdex开发并维护的一个全栈SSH2解决方案,适用于Node.js环境。它的特色在于完全使用JavaScript编写,无需依赖外部C/C++扩展,这意味着它在跨平台部署上更加灵活。
技术分析
纯JavaScript实现:由于没有C/C++扩展,易于安装和在各种操作系统上运行。
全面的API:提供丰富的客户端和服务器接口,可以执行命令、建立shell会话、进行文件传输(SFTP)以及端口转发等操作。
安全与稳定性:与OpenSSH保持同步更新,确保最新的安全特性。
性能优化:利用可选的cpu-features包以提高加密算法的效率。
技术应用场景
远程服务器管理:
管理员可以安全地远程登录和管理云服务器,进行系统配置、软件安装、日志查看等操作。
通过SSH2建立的安全隧道,管理员可以远程管理和操作云数据库,如MySQL、MongoDB等。
文件传输:
SSH2的文件传输功能(如SFTP)可以用于将本地文件上传至云服务器或从云服务器下载文件,方便数据备份和共享
远程开发和调试:
开发人员可以使用SSH2远程登录到云服务器进行代码开发和调试,提高开发效率
端口转发:
动态或静态的端口转发,可用于穿透防火墙,访问内部网络资源或建立私有网络隧道。
ssh2特点:
易用性:
一些SSH2工具或模块提供了清晰的示例代码和详细的API文档,使得初学者也能快速上手。
安全性:
SSH2使用强大的加密算法来加密数据,确保在传输过程中的机密性。 提供了公钥/私钥对和基于密码的身份验证方法,增强了用户认证的安全性。 提供了消息完整性检查,确保数据在传输过程中没有被篡改。
隧道和端口转发:
SSH2支持隧道技术,可以建立安全的加密通道来传输其他协议的数据(如X11、TCP/IP等)。 支持本地端口转发、远程端口转发和动态端口转发,方便用户访问远程资源。
跨平台性:
SSH2协议是跨平台的,可以在各种操作系统上运行,包括Windows、Linux、macOS等。这使得SSH2成为了一个广泛使用的远程登录和加密通信工具。
要了解更多关于ssh2的信息和实际应用,可以查看其GitHub上的Readme文档,那里包含了完整的API说明和多个演示实例。
安装方式:
npm install ssh2
项目具体应用:
const { writeFileSync } = require("fs");
const { Client } = require("ssh2");
const client = new Client();
const python = "/home/Tom/Python/Python310/bin/python3";
const test = "/home/Tom/Project/test.py";
client.on("ready", () => {
console.log("Client :: ready");
client.exec(`${python} ${test} chat ${encodeURIComponent("你好,可以提供一些Z开头的成语吗")}`, (err, stream) => {
if (err) throw err;
stream.on("close", (code) => {
console.log("Stream :: close :: code: " + code);
client.end();
}).on("data", (data) => {
console.log("----OK----");
writeFileSync("./answer.json", data.toString());
}).stderr.on("data", (data) => {
console.log("STDERR: " + data);
});
});
}).connect({
host: "服务器地址",
port: 22,
username: "你的用户名",
password: "你的密码"
});
Flask路由
在 Flask 中,路由(Routing) 是定义 URL 模式和相应视图函数之间映射的过程。
当你访问一个特定的 URL 时,Flask 会查找与该 URL 匹配的路由,并调用相应的视图函数来处理请求。
from flask import Flask, render_template, request
app = Flask(__name__)
# 这是一个路由装饰器,它将 URL 路径 '/' 与下面的视图函数 hello_world 关联起来
@app.route('/')
def hello_world():
return 'Hello, World!'
# 另一个带有参数的路由示例
@app.route('/hello/<name>')
def hello_name(name):
return f'Hello, {name}!'
# 还可以指定请求方法(如 GET, POST 等)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
# 处理 POST 请求的逻辑
pass
else:
# 处理 GET 请求的逻辑,通常用于显示登录表单
return render_template('login.html')
if __name__ == '__main__':
app.run(debug=True)
在这个例子中:
我们从 flask 模块导入了 Flask、render_template 和 request。 使用 app.run(debug=True) 来启动 Flask 开发服务器。在开发过程中,将 debug 设置为 True 可以自动重新加载代码和显示详细的错误跟踪信息。
在这个实践项目中,我们主要使用flask路由作为一个中间件,连接服务器与AI接受一个问题将AI的回答输出。
使用如下:
@app.route('/chat', methods=['POST'])
def chat():
global history
data = request.json
query = data['input']
print(query)
response, history = model.chat(tokenizer, query, history=history)
return jsonify({'answer': response})
@app.route('/clear', methods=['GET'])
def new_chat():
global history
history = []
return jsonify({'ok': True})
node服务器调用路由的方式如下:
this.api("chat", Method.POST, async (params) => {
const input = params.input?.trim();
if (!input) return Response.error("缺少参数");
try {
const response = await fetch(this.ai("ask"), {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ input })
});
if (response.status === 200)
return Response.data(await response.json());
else return Response.error("发生错误");
} catch (e) {
console.error(e);
return Response.error("发生错误")
}
});
跨域问题
当在我们的实践中出现了问题:
Access to XMLHttpRequest at 'http://localhost:9091/api/ai/chat' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
问题来源
跨域问题
跨域问题主要出现在前后端分离的应用架构中,例如使用Spring Boot作为后端,Vue.js作为前端时,由于前后端运行在不同的端口上,因此存在跨域问题。当前端页面尝试通过Ajax等技术向后端请求数据时,浏览器会检查请求的URL是否与当前页面的URL同源,如果不同源,则会阻止该请求或限制其响应数据的访问。
同源策略
同源策略(Same-origin policy)是浏览器的一种安全策略,其目的是为了防止恶意网站窃取用户数据或进行其他恶意操作。根据同源策略,浏览器会限制一个网页上的脚本访问或操作其他来源的网页上的数据。这里的“来源”是指协议、域名和端口三者的组合。
问题详解
这个错误信息表明浏览器在尝试从 http://localhost:8080
发起 XMLHttpRequest 请求到 http://localhost:9091/api/ai/chat
时遇到了跨源资源共享(CORS)策略的阻止。具体来说,预检请求(preflight request)没有通过访问控制检查,因为响应没有 HTTP 状态码 ok
。
解决策略
服务器端设置 CORS
确保服务器正确设置了 CORS 策略,允许来自 http://localhost:8080
的请求。服务器需要在响应头中包含 Access-Control-Allow-Origin
,并且其值应该设置为请求的源,或者设置为 *
以允许所有源。
检查预检请求
CORS 策略要求对于某些类型的请求(如带有自定义头部或非简单方法的请求),浏览器会首先发送一个预检请求(OPTIONS 请求),以确定实际请求是否可以安全地继续。确保服务器正确响应了这个预检请求。
HTTP 状态码
错误信息指出预检请求没有返回 HTTP 状态码 ok
(通常是 200)。检查服务器的响应确保它返回了正确的状态码。
检查请求方法
确保您的 XMLHttpRequest 请求使用了正确的方法(如 GET、POST 等)。
检查请求头部
如果您的请求需要自定义头部,确保这些头部在预检请求和实际请求中都正确设置。
使用 Fetch API
如果可能,使用 Fetch API 代替 XMLHttpRequest,因为 Fetch API 处理 CORS 问题的方式可能更现代和友好。
浏览器扩展或插件
如果您在开发过程中遇到 CORS 问题,可以使用浏览器扩展或插件(如 CORS Everywhere)来临时绕过 CORS 限制,但这仅适用于开发环境,不应该用于生产环境。
具体解决策略
Express.js(或其他Node.js框架)
可以添加如下代码
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: 'http://localhost:8080', // 或者 '*', 允许所有来源,但生产环境中不推荐
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], // 允许的HTTP方法
credentials: true, // 如果需要携带凭证(cookies, HTTP认证及客户端SSL证明等)
}));
// ... 其他中间件和路由
app.listen(9091, () => {
console.log('Server is running on port 9091');
});
Spring Boot
你可以在你的Spring Boot应用中配置CORS支持。这可以通过在你的配置类中添加一个WebMvcConfigurer
的Bean并覆盖addCorsMappings
方法来实现:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有路径
.allowedOrigins("http://localhost:8080") // 允许来自这个源的请求
.allowedMethods("*") // 允许所有HTTP方法
.allowedHeaders("*") // 允许所有头
.allowCredentials(true); // 允许携带凭证
}
}
数据库
数据库使用mysql数据库,用于搭建基本的文书系统框架
数据库中共十二个实体,包括用户,评论,点赞,文件,菜单,角色,文书,公告等。分别对应每个实体的相应的功能。
后端连接数据库:
服务器配置 (server)
port: 9090: 这行配置了应用程序运行在9090端口上。
Spring 配置 (spring)
datasource: 这个部分包含了数据源的配置,用于连接数据库。
driver-class-name: 指定了 MySQL 数据库的 JDBC 驱动类。
url: 指定了数据库的 JDBC URL,包括数据库服务器地址、端口号、数据库名,以及时区设置(serverTimezone=GMT%2b8)。
username: 数据库的用户名。
password: 数据库的密码。
servlet: 这个部分配置了 Spring MVC 的 Servlet 参数。
multipart: 配置了文件上传的相关参数。
max-file-size: 配置了单个文件大小的最大限制为100MB。
max-request-size: 配置了请求的最大大小为100MB。
MyBatis 配置 (mybatis)
mapper-locations: 指定了 MyBatis 的 XML 映射文件的位置,这里使用了类路径下的 mapper 目录,并且扫描所有 .xml 文件。
MyBatis-Plus 配置 (mybatis-plus)
configuration: 这个部分配置了 MyBatis-Plus 的一些参数。
log-impl: 指定了 MyBatis-Plus 的日志实现类,这里使用了 StdOutImpl,意味着日志信息将被输出到控制台。
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/bs_blog?serverTimezone=GMT%2b8
username: ****
password: ****
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
mybatis:
mapper-locations: classpath:mapper/*.xml #扫描所有mybatis的xml文件
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
数据库结构:
后端采用标准的springMVC三层架构:将实体、服务、接口分开设计,前端只于后端接口对接,由此实现前后端分离,数据库配置Spring,实现用mybatis-plus框架,对数据库进行基本的增删改查以及进阶的各种模糊查询、分类查询与返回等操作。
特别案例,文书评论树的实现:
/**
* 文书评论树结构接口
* @param foreignId 博客id
* @return
*/
@GetMapping("/tree")
public Result list(@RequestParam Integer foreignId) {
List<Comment> list = commentService.list(new LambdaQueryWrapper<Comment>().eq(Comment::getBlogId,foreignId));
// 给评论赋值用户相关属性
list.stream().forEach(comment -> {
if (null != comment.getPid()){
comment.setParentName(userService.getById(comment.getUserId()).getNickname());
}
comment.setUsername(userService.getById(comment.getUserId()).getNickname());
comment.setImg(userService.getById(comment.getUserId()).getAvatarUrl());
});
// 一级评论
List<Comment> first = list.stream().filter(comment -> comment.getPid() == null).collect(Collectors.toList());
for (Comment comment : first) {
Integer pid = comment.getId();
// 二级评论
List<Comment> second = list.stream().filter(comment1 -> Objects.equals(pid, comment1.getPid())).collect(Collectors.toList());
//将子评论放到children集合里
comment.setChildren(second);
}
return Result.success(first);
}
JSPDF排版
原本的方式是使用html2canvas将html转为图片,然后放置到pdf中,但实际效果并不理想。
首先是使用了一堆乱七八糟的CSS特效,导致直接转为图像时文本样式不一,甚至布局错误,为解决这一问题只能创建一个新的div将文档复制进去,然后还不能隐藏这个多余的div(否则就会生成空白内容)。 其次是在生成图片时使用了 Web Fonts,可能会出现字体渲染不一致的问题,这是因为浏览器和 html2canvas 在处理字体时的机制不完全相同。 生成的图片会受到分辨率和清晰度的影响,特别是在需要高质量打印或显示的场景中,可能出现模糊或失真的情况,而且体积比较大。 使用 jspdf 将生成的图片放置到 PDF 中时,涉及到图片大小、位置、页面布局等方面的调整和优化,否则会导致 PDF 输出效果不理想。即使通过比例计算实现了较好的效果,在分页方面依然是灾难 图片方式基本是不可行了,那么就使用根据标签绘制文本的方式吧。
不过这并不容易,因为我们无法像写word文档一样向PDF里塞文本,只能指定坐标然后进行绘制,因此需要手动计算。此处我们只用到h2、h3和p三个标签,所以工作量不算太大。
首先中文输出就出现问题了,解决方案:给它指定一个可用的字体文件(显然需要有中文字库)
const pdf = new jsPDF();
pdf.addFont("/stzhongs.ttf", "song", "normal")
pdf.setFont("song");
对于同一种页面,大小是固定的,因此我们通过offset计算距离页面顶部的距离,并通过指定x位置与align来实现居中效果。
let offset = 20;
Array.from(element.querySelectorAll("h2,h3,p")).forEach((item) => {
let text = item.textContent.trim();
let split = "";
switch (item.tagName) {
case "H2":
pdf.setFontSize(20);
pdf.text(text, 105, offset, {align: "center"});
offset += 10;
break;
case "H3":
pdf.setFontSize(16);
pdf.text(text, 10, offset);
offset += 8;
break;
case "P":
pdf.setFontSize(12);
split = pdf.splitTextToSize(text.replace(/^\s*/mg, " "), 190);
pdf.text(split, 10, offset);
offset += 6 * split.length + 2;
break;
default:
break;
}
})
pdf.save('appeal.pdf');
使用正则表达式将文本中每行的开头的空白字符替换为全角空格(即在每行的开头添加两个全角空格实现段落首行缩进),然后将经过处理的文本按照指定的宽度(190)进行分割,根据文本的行数和行高来调整下一个文本块的垂直位置。
测试效果:
四、模型
一、模型训练与测试
相较于ChatGLM这样的已经固化的预训练模型,我选择使用ChatGLM-6B LoRA 16-bit指令微调实现法律文本分析系统,从而得到适用于中文法律文本分析的智能化系统,具体在模型的参数调整训练过程如下
1. 模型选择
针对项目实训的法律文书文本生成系统,我进行了广泛的模型测试和比较。清华技术成果转化的ChatGLM虽然是一个强大的语言模型,但在具体的法条推荐功能上存在一定局限性。而OpenAI的GPT大模型虽然功能强大,但由于国内外法律的差异,不太符合国人的法律需求。
因此,我们最终选择了ChatGLM-6B LoRA 16-bit模型进行微调来满足我们项目中的需求。该模型还具有比较高的分析性能和灵活性,可以通过微调来适应特定的法律文本分析、法条推荐和法律生成任务。
2. 模型微调过程
在微调过程中,我延用了ChatGLM官方提供的requirements,准备了环境。选择了FP16精度的ChatGLM-6B,测试发现需要至少14G的显存。微调的数据集包括现有的法律问答数据集以及真实案例构建的高质量法律文本问答系统。微调的过程中,我还尝试了多卡并行LoRA微调,以加快模型的训练速度。
3. 模型数据集准备
模型训练的数据集包括带有法律条文依据的情景问答和律所真实案情分析等等。通过结合具体法条的案情分析问答,以及法律知识解释。我构建了案例全面的数据集组合,可以更好利用数据对于模型的训练和性能提升方面的作用。
4. 模型训练细节
在模型训练过程中,我首先完成了对模型本身和训练时的参数设置及调整。通过自监督方式进行训练,模型能够自己回答所生成的问题,从而保证了回答的准确性和涵盖了法律知识的问题回答。训练数据集不断地调整和更新,以确保模型的训练效果最优。
獬豸
LawGPT_zh:中文法律大模型(獬豸)
项目简介
“獬豸”是为让所有人在遇到法律问题时能第一时间获得专业可靠的回答。它是由天津市委网信办与360集团合作打造的全国首个网络普法领域垂直大模型。它结合了数据的共建、共训、共享,旨在实现普法资源的集成和优质普法成果的共享。
本项目开源的中文法律通用模型由ChatGLM-6B LoRA 16-bit指令微调得到。数据集包括现有的法律问答数据集和基于法条和真实案例指导的self-Instruct构建的高质量法律文本问答,提高了通用语言大模型在法律领域的表现,提高了模型回答的可靠性和专业程度。 主要功能
法律咨询:模型拥有多模态交互能力与专业、全面、庞大的法律知识库,能够为用户提供更易理解的法律解释和更精准的法律指导。
网信服务:在政务服务方面实现智能化,帮助企业及机构在处理跨境数据时保持合法合规,规避风险。
普法助理:模型能担当网信工作人员的“普法助理”,利用数字人视频生成功能,24小时宣讲法律知识。
资源共享:模型的众训共享模块能有效实现资源的共享与集中管理。 技术特点
獬豸模型不仅集成了丰富的法律知识库,还通过多模态交互能力,提供了更加便捷和高效的法律咨询和服务。
它能够为企业和机构提供跨境数据处理的法律支持,帮助它们避免法律风险。
该模型的数字人视频生成功能,使得法律知识的普及和宣传更加生动和直观。 人文意义
中文法律大模型“獬豸”是一个结合了现代科技与古代文化智慧的产物,它不仅能够提供高效便捷的法律咨询和服务,还能够为网络普法工作提供有力支持。通过数字人视频生成等功能,该模型让法律知识的普及和宣传更加生动有趣,有助于提升公众的法律意识和素养。 使用简介
配置项目依赖环境
cd src
pip install -r requirements.txt
#其中peft需要本地安装
cd peft
pip install -e .
下载一系列文件
ChatGLM-6B模型参数,检索模型参数,text2vec-base-chinese模型参数
运行交互文件
输入
new chat
可以清空上下文信息
命令行交互界面实例
Human:
信用卡欠账不还后果很严重吗?
------------------------------------------------
Answer:
信用卡欠款如果不及时偿还,会对自己产生严重的影响。下面是一些可能的后果:
1.利息和罚款:如果未在规定时间内还款,则会开始收取滞纳金和利息,费用可能会非常高;
2.信用记录受损:逾期付款会影响个人的信用报告,使个人在未来申请贷款、购房和其他业务时面临困难;
3.银行起诉:若未能在指定时间内还清全额账单,可能会被银行起诉至法院要求归还债务并支付相应的诉讼费用;
4.法律风险:逾期付款可能会导致合同违约,构成违约则需要承担违约责任(包括赔偿损失);
5.失去使用信用卡的权利:如果发现自己的信用记录中存在多次逾期或其他不良记录,将会失去使用某些信用卡或贷款的机会。
因此,为了避免以上情况的发生,应合理规划好开支,按时按量偿还信用卡欠款。
数据集构建
本项目数据主要分为两个部分:1.律师和用户之间的情景对话 2.对特定法律知识的问答
獬豸数据集构成
情景对话
真实的中文律师用户问答数据,来自CirmeKgAssitant收集的200k条情景对话数据。
利用ChatGPT根据CrimeKgAssitant的问答重新生成,使得生成的回答比原回答更详细,语言组织更规范。最终得到52k单轮问答。
根据中华人民共和国法律手册上最核心的9k法律条文,利用ChatGPT联想生成具体的情景问答,从而使得生成的数据集有具体的法律依据。
知识问答
收集法律领域的教科书,经典案例等数据,自建一个法律专业知识数据库。
针对Self-Instruct的可靠性和安全性漏洞,我们使用了基于特定知识的Reliable-Self-Instruction:通过提供具体的法律知识文本,先让ChatGPT生成与该段法律知识内容与逻辑关系相关的若干问题,再通过“文本段-问题”对的方式让ChatGPT回答问题,从而使ChatGPT能够生成含有法律信息的回答,保证回答的准确性。
其中,计划法律知识数据包含民法商法、行政法、经济法、社会法、刑法等各个细分法律的条例,分析和题库。
利用中国司法考试的相关数据和资源,构建与司法考试相关的数据集。 这有助于模型更好地理解法律考试中的问题和答题技巧,提升其在法律领域的应用能力。
模型训练
训练步骤
内部测试模型:
在训练初期,可以使用如LaWGPT-7B-alpha这样的内部测试模型进行初步训练。 这个模型可能在Chinese-LLaMA-7B的基础上直接构造,并使用30万法律问答数据集进行指令精调。
其中的步骤包括以下几点:
下载训练数据集 配置好train_lora.py参数 运行train_lora.py函数(使用模型并行算法,请勿同时使用数据并行)
公开发布模型:
经过初步训练后,公开发布更为完善的模型,如Legal-Base-7B和LaWGPT-7B-beta系列模型。 这些模型在训练过程中,可能会使用到50万中文裁判文书数据、基于Chinese-LLaMA-7B模型的二次预训练以及基于更大规模的高质量法律问答数据集的指令精调。
情景对话训练的特殊性
法律语境的深入理解:
情景对话训练需要模型深入理解法律语境和术语,以便能够准确回答用户的问题。 这需要模型在训练过程中充分暴露于各种法律场景和对话中,以提升其语境理解能力。
对话逻辑的准确把握:
情景对话通常具有复杂的逻辑结构和多轮交互的特点。 在训练过程中,需要特别注意模型对对话逻辑的把握能力,以确保其能够正确理解用户的意图并给出准确的回答。
数据集的探索
1.收集模型调整训练数据
2.学习基本的数据处理手段,对源数据进行清洗、调整、再存储。完成模型微调的第一步数据集的整理
数据集来源成果:需要提取文章中的案例内容与判决情况,用于模型测试数据集的建立
3.学习vue前端设计思路,制作精美、互动性强的可视化界面
一、数据集梳理
法律文书系统数据集最常见的来源是国家法律法规数据库,这里涵盖各种类型的法条,包括但不限于刑法、民法、治安管理处罚法等数百种法律文件。但是国家法律法规数据库的一大缺陷是,文本需要重新爬取,不适合懒人使用,有两个平台很适合用于模型调试数据集。
1.北大法宝V6官网
2.中华人民共和国法律手册
获取成果:
二、数据处理
对于判决书,我们需要的只是法律案例分析和裁判结果,因此需要提取判决书中的相应文本,文本提取的基本步骤是使用正则表达式对文本进行匹配,就可以实现文本提取了。
对于问答轮,因为已经是JSON格式的文件,因此不需要额外进行处理,使用JSON即可
判决书文本提取:
import re
# 正则表达式模式,用于匹配裁判要旨和判决依据法条
judgement_purpose_pattern = re.compile(
r"\【裁判要旨\】(.*?)(?:\【|$)",
re.DOTALL
)
law_article_pattern = re.compile(
r'“(《*?》.*?第十一条第一款.*?规定的.*?)”',
re.DOTALL
)
# 读取文件内容
file_path = './data.txt'
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# 搜索裁判要旨
judgement_purpose_match = judgement_purpose_pattern.search(content)
if judgement_purpose_match:
judgement_purpose = judgement_purpose_match.group(1).strip()
print("裁判要旨:")
print(judgement_purpose)
else:
print("未找到裁判要旨。")
# 搜索判决依据法条
law_article_match = law_article_pattern.search(content)
if law_article_match:
law_article = law_article_match.group(1).strip()
print("\n判决依据法条:")
print(law_article)
else:
print("未找到判决依据法条。")
虽然每一个裁决书的格式类似,但是内容中涉及到该法律的裁决信息是难以设计,这包含着大量的文本内容变化,并不能简单用一个模板实现文本的处理。因此简单的尝试之后,我选择了学会正则表达式匹配法条的基本思路即可。
文本转化的思路:实践中,我们常常会遇到文本格式不对的问题, 但是一般来说,并没有简单而朴素的方式对文件进行格式转化,如.txt转化为.excel文件。因此这部分对文件转化给出简单的使用思路。
pdf表格转化为excel表格:
import PyPDF2
import pytesseract
import pandas as pd
import os
# 设置OCR引擎(如果需要)
# pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract' # 指定Tesseract OCR引擎的路径
# 将PDF文件转换为文本
def pdf_to_text(pdf_path):
text = ""
with open(pdf_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
num_pages = len(reader.pages)
for page in range(num_pages):
pdf_page = reader.pages[page]
text += pdf_page.extract_text()
return text
# 使用OCR识别文本
def ocr_text(image_path):
text = pytesseract.image_to_string(image_path)
return text
# 将文本保存为Excel文件
def save_text_as_excel(text, output_path):
lines = text.split('\n')
data = [line.split() for line in lines if line.strip()]
df = pd.DataFrame(data)
df.to_excel(output_path, index=False)
# 主函数
def pdf_to_excel(pdf_folder, output_folder):
pdf_files = [f for f in os.listdir(pdf_folder) if f.endswith('.pdf')]
for pdf_file in pdf_files:
pdf_path = os.path.join(pdf_folder, pdf_file)
text = pdf_to_text(pdf_path)
# 使用OCR识别文本(如果需要)
# image_path = 'image.png' # 将PDF转换为图像文件(可选)
# text = ocr_text(image_path)
excel_file = pdf_file.replace('.pdf', '.xlsx')
output_path = os.path.join(output_folder, excel_file)
save_text_as_excel(text, output_path)
print("转换完成!")
# 调用函数进行转换
pdf_folder = 'D:\Project\python\KeShiHua\Exp2\data'
output_folder = 'D:\Project\python\KeShiHua\Exp2\output'
pdf_to_excel(pdf_folder, output_folder)
三、前端设计的基本思路
用户界面(UI)设计:
清晰且一致的视觉层次结构。 使用合适的颜色、字体和图像以增强可读性和美观性。 适应不同屏幕尺寸和分辨率的设计。 用户体验(UX)设计:
直观的导航和逻辑流程。 快速响应用户操作。 为用户提供明确的视觉反馈。 可访问性(Accessibility):
遵循无障碍设计原则,使所有用户都能使用你的应用。 提供键盘可访问性、屏幕阅读器兼容性。 使用适当的ARIA(Accessible Rich Internet Applications)标签。 交互设计(Interaction Design):
按钮和链接应具有清晰的点击/触摸反馈。 动画和过渡效果应平滑且不干扰用户操作。 避免过度的动画或闪烁效果,以防引起用户不适。 表单处理:
表单输入应具有验证逻辑,确保数据准确性。 提供即时的输入反馈。 使用占位符和标签清晰地指示所需信息。 错误处理:
明确且友好的错误消息。 指导用户如何纠正错误。 避免技术性或模糊的错误描述。
模型调用的探索
KimiAi基本信息:KimiAi擅长中英文对话,能够阅读和理解多种文件格式,包括TXT、PDF、Word文档、PPT幻灯片和Excel电子表格。
主要功能:
回答各种问题,从一般知识到专业咨询。 阅读和解析你提供的文件内容。 结合互联网搜索结果来提供更加全面的回答。 理解和解释编程概念,帮助你解决技术问题。 提供教育和学习资源,辅助你的个人发展。 Kimi的一些限制,比如不能创建或提供下载链接、电子邮件附件等。
KimiAI入门使用:python脚本版
from openai import OpenAI
client = OpenAI(
api_key = "$MOONSHOT_API_KEY",
base_url = "https://api.moonshot.cn/v1",
)
completion = client.chat.completions.create(
model = "moonshot-v1-8k",
messages = [
{"role": "system", "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"},
{"role": "user", "content": "你好,我叫李雷,1+1等于多少?"}
],
temperature = 0.3,
)
print(completion.choices[0].message.content)
KimiAI工具使用,我们可以利用下面的代码,对于Kimi进行工具测试,考虑
from openai import OpenAI
client = OpenAI(
api_key = "$MOONSHOT_API_KEY",
base_url = "https://api.moonshot.cn/v1",
)
completion = client.chat.completions.create(
model = "moonshot-v1-8k",
messages = [
{"role": "system", "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"},
{"role": "user", "content": "编程判断 3214567 是否是素数。"}
],
tools = [{
"type": "function",
"function": {
"name": "CodeRunner",
"description": "代码执行器,支持运行 python 和 javascript 代码",
"parameters": {
"properties": {
"language": {
"type": "string",
"enum": ["python", "javascript"]
},
"code": {
"type": "string",
"description": "代码写在这里"
}
},
"type": "object"
}
}
}],
temperature = 0.3,
)
print(completion.choices[0].message)
创建对话窗口:
# 创建主窗口
root = tk.Tk()
root.geometry("800x800") # 例如设置为 500 像素宽,300 像素高
root.title("法律问答机器人")
# 创建一个文本输入框
entry = tk.Entry(root, width=200)
entry.pack()
# 创建一个按钮,点击时调用on_button_click函数
button = tk.Button(root, text="提交", command=on_button_click)
button.pack()
#创建一个文本输出区域
text_area = tk.Text(root, height=100, width=500)
text_area.pack()
# 运行主循环
root.mainloop()
执行效果如下图:在上面的输入框中输入自己想要咨询的问题,点击提交即可在下面显示
模型运行问题解决
在没有 root 权限的情况下,为在远程 Linux 服务器上成功部署和运行语言模型还是遇到了很多坑的,此处给出解决方案记录。
用Vim不熟练 PyCharm可以解决这个问题,借助远程开发功能,我们只要连接上服务器就能开始干活儿了。
(注意:社区版是不行的)
在工具->部署->配置中,添加一个sftp连接,由于目标是先连接上,故暂时只填入必要信息(地址、端口、用户名等)
然后在设置->项目设置->Python解释器中,添加一个基于SSH解释器(On SSH),这里我将本地项目映射到了~/Project/中而不是默认的tmp目录,然后一路success同步完成,就可以愉快的用PyCharm写代码了。
如果不出意外的话... 那就一定要出意外了!远程服务器Python版本是3.6,然后PyCharm要求至少3.7才能成功实现,再加上项目本身要求3.9以上,所以一路success是不可能的。
那就装个Python 3.10 吧,没有root只能手动编译安装了。
什么?OpenSSL开发库也没有?
解决OpenSSL apt-get是不可能的,所以只能
apt-get download libssl-dev
# 这里手动把包改了个名字
dpkg -x libssl.deb $HOME/ssl
然后在.bashrc中加入
export CPATH=$HOME/usr/include/\
:$HOME/usr/include/x86_64-linux-gnu/
export LD_LIBRARY_PATH=$HOME/usr/lib/\
:$HOME/usr/lib/x86_64-linux-gnu/\
:$HOME/ssl/lib/
export LIBRARY_PATH=$HOME/usr/lib/\
:$HOME/usr/lib/x86_64-linux-gnu/
有一些多余的东西是之前添加的,无视即可,输入openssl version,与自己的目标一致即可
解决Python
在未解决OpenSSL的情况下会导致pip出问题,但并不是添加了路径就可以了,要找到Modules/Setup文件,去掉OpenSSL那几段的注释并指定路径,然后再编译安装。
这个感觉挺玄学的,原本怎么弄不好(找不到SSL模块),但吃个饭回来重新搞就没问题了
哦还有,别忘了添加环境变量,就像这样
alias python=python3
alias pip=pip3
export PATH=/home/u2602395/usr/bin/\
:/home/u2602395/Python/Python310/bin/\
:$PATH
然后进行更新
source ~/.bashrc
我icetk呢
安装完一堆依赖后运行,提示未找到icetk,但我装了啊!尝试import icetk,哦,看起来我好像遇到了传说中的过度try-catch,是icetk导入的其他库不存在,所以报错,然后被认为是icetk不存在了。
这个没找到很好的解决办法,最后是把/usr/lib/python3.6/lib-dynload/_bz2.cpython-36m-x86_64-linux-gnu.so文件复制到用户目录的对应的Python文件夹,然后把名字中的36m改成310,就不会有_bz2导入出错了,其他组件同理。
这样虽然问题得以解决,但不确定是否会导致别的bug,不过目前没遇到,
那就是没bug
5. 测试结果
经过模型的微调和训练,我对预训练的模型进行了一系列的测试。测试结果显示,模型在案情分析和法律知识解释方面表现良好,能够准确地回答各种法律相关的问题,并且在法条推荐方面也取得了较好的效果。而且模型在各种情景下的表现均符合我们的预期,为我们的法律文书生成系统打下了基础。但是现在还需要测试在相似案例下模型对于法条的具体区分,因为在一些不常见的案例中还会出现分析错误的测试结果,目前正在添加新的训练数据集来调整模型。
二、.模型与后端接口对接
此外,我重点进行了训练及微调后的模型与后端接口的连接工作方面的探索。具体工作进展如下:
首先对后端提供的接口进行了梳理和测试,确保接口的稳定性和数据的准确性。目前先重点着眼于模型的测试结果对比,通过与北大法律网站等优秀的传统法律帮扶网站相进行比较和补充,主要在法条推荐和生成方面调参和输出格式。同时开始结合固定模板生成法律文书(以及与前后端的输出接口匹配问题)