架构设计
- README
- 建设数据中台、业务中台、物联中台,三中台的必要性
- 关于xx实验室信息化系统建设的必要性
- 人人框架微服务版开发环境和安装部署
- 人人框架代码生成器安装部署
- 软件增加license管理
- 各应用平台注册地址
- 信息系统安全定级说明
- 运维支持考试题
- 高并发性能优化设置
- create-springboot-project
- springboot 集成 mybatis
- springboot 集成mybatis-plus
- springboot 集成mybatis-plus通用CRUD
- deepseek 环境搭建
- docker搭建deepseek运行环境和open-webui
- deepseek和dify环境搭建
- deepseek嵌入工作流实现直接驱动业务的探索
- 网站采集工具firecrawl
- deepseek提示词小技巧
- deepseek开发流程
- 通义千问&&deepseek模型对比
README
建设数据中台、业务中台、物联中台,三中台的必要性
在过去的时间里,我们建设了大量的软件信息化系统、硬件基础设施。在设计和建设时,信息化系统按使用部门的要求建设,重点解决使用部门的痛点,软、硬件协作、独立完成一项工作,软硬件系统作为一个重要工具存在。
随着时间的推移,信息化系统由不同的厂家、在不通的时间、采用不同技术构建,系统和系统之间存在数据不通、技术障碍等因素不能协作;功能存在重复,相同的数据在各信息化系统存在不一致的版本。
信息化系统犹如一个个高大的烟囱,可以独立处理业务功能,却无法组合和发挥更大价值,大量数据闲置,未形成数据资产。信息化停留在工具层面,未能对顶层决策带来有效的支撑,信息化系统呈孤岛态势。
在此背景下,在新的信息化建设规划中,我们提出,在有条件的情况下,优先建设数据中台、业务中台、物联中台,夯实基础,避免过去遇到的问题。 数据中台:通过多种技术手段,直接读取数据库、API采集、导入等多种方式,将同构、异构的数据孤岛,重新汇聚入湖(数据湖);通过对数据的比对、清洗、转换、标准化提炼,形成新的有价值数据;使用数据目录共享和对外发布数据,让其他系统可复用;通过对数据标准和源头的确定,使各系统的数据同源(口径一致)。通过对数据标准的制定,让数据交换共享更便利、不受时间、技术的限制。
因为有了全貌、全量的数据,使信息化系统从工具层面,升华到可决策、可视化、可经营、可管理层面。让信息化系统发挥更大的价值。
业务中台:通过对业务系统的共性功能提取,形成可复用的能力;对共性业务能力集中管理;对共性业务能力目录盘点,识别出既有能力,缺失能力,对信息能力查漏补缺;检索共性业务目录,在新的信息化系统规划阶段,既能有效控制建设范围,避免重新建设,降低新建信息化系统的范围,提高新建信息化的速度,最终达到降本增效的目的。
物联中台:通过对多种设备的抽象、模板、接入、管理,将物联设备产生的数据,抽象到数据中台,将物联能力抽象到业务中台。物联设备不再是独立的存在,将设备数字化后使其拥有更多的能力;根据多种策略,进行联动、告警、控制,实时反映设备状况,使设备也能成为生产经营决策的重要组成部分。
关于xx实验室信息化系统建设的必要性
当前xxx实验室弱电与智能设备已经初步安装完成。实验室做为一个特殊的区域,在有效持续运营方面还存在信息化管理手段缺失。
- 实验室运营方: 实验室的主要资产为实验设备,主要使用单位为科研机构,主要工作内容课题研究,主要输出成果为科研成果。对科研设备的管理,提高设备的利用率,科研过程中的规范管理,安全生产,提高实验室的运作效率、保护科研设备。 实验室管理系统:包括项目管理、实验室预约,安全管理、危化品管理,耗材管理、实验器材共享平台。 科研孵化平台:拉通科研机构与社会力量,将科研结果转换为商用成果,是实验室运营方持续经营的必要手段。 服务共享平台:将实验室的多家科研机构成果共享、协作,共同完成科研任务。
- 主管部门 掌握辖区科研进度,成果转换情况,实验室运作情况。及时根据运营情况调整科研政策。需要相关的数据决策与支撑。 数据中台:通过多种技术手段,直接读取数据库、API采集、导入等多种方式,将同构、异构的数据,从多个专业信息化系统,重新汇聚入湖(数据湖);通过对数据的比对、清洗、转换、标准化提炼,形成新的有价值数据;使用数据目录共享和对外发布数据,让其他系统(单位)可复用;通过对数据标准和源头的确定,使各系统的数据同源(口径一致)。通过对数据标准的制定,让数据交换共享更便利、不受时间、技术的限制。 因为有了全貌、全量的数据,使信息化系统从工具层面,升华到可决策、可视化、可经营、可管理层面。 领导驾驶舱:通过对关键数据指标、关键运营指标、关键设备指标等,进行图形化展示,通过对数字孪生叠加,使整个实验室运营情况可视化,掌握整个实验室的整体运作情况。通过多维度的指标分析,使实验室运行情况可决策、可经营。
- 物业管理方 作为楼宇、设备的维护方,为实验室提供运行保障。需要对物业服务过程优化、协调等。帮助物业管理公司提高工作效率,降低运营成本,提升服务质量,增强客户满意度。让物业管理变得更加智能化、高效化、便捷化。 物业的日常管理包括:人员管理、车辆管理、设备管理、巡更巡检、访客管理、安防告警、房租水电合同管理、保洁任务。通过标准化的管理模型,建立自身知识库,积累和整合自身的经验和优势,提升自身的核心竞争力。 清晰的业务流程明确各岗位职责,规范的业务操作提高各个业务环节的工作效率——软件系统的实施能最大限度的减少管理漏洞,减少人为因素的干预,并提高企业运营和管理效率,支持企业商业目标的实现,从而从根本上提高企业的行业竞争性
人人框架微服务版开发环境和安装部署
renren微服务框架需要jdk 17 ,nodejs 18+ 请注意版本选择
安装mysql数据库
先安mysql装数据库,并创建两个database,一个用于nacos,一个用于项目,初始化nacos数据结构和数据。
nacos-server-2.2.3 数据库初始化脚本:nacos-2.2.3-mysql-schema.sql
安装部署nacos
官方网址:https://nacos.io/zh-cn/docs/what-is-nacos.html
github地址:https://github.com/alibaba/nacos
github数据库初始化脚本:https://github.com/alibaba/nacos/blob/master/distribution/conf/mysql-schema.sql
# 镜像
# 根据需要,开放8848和9848端口
nacos/nacos-server:v2.2.3
# 设置环境变量
# 系统(集群)启动方式 ,cluster:集群,standalone:单机
MODE: standalone
# 数据库名称
MYSQL_SERVICE_DB_NAME = renren_cloud_nacos
# 数据地址
MYSQL_SERVICE_HOST = 192.168.0.10
# 数据库密码
MYSQL_SERVICE_PASSWORD = <your password>
# 数据库端口
MYSQL_SERVICE_PORT: 33306
# 数据用用户名
MYSQL_SERVICE_USER: root
# 主机模式,ip:ip地址,host:主机名
PREFER_HOST_MODE: ip
# 数据库类型
SPRING_DATASOURCE_PLATFORM: mysql
安装部署redis
version: "3"
services:
redis:
image: redis:6.2.6
restart: always # 自动重启
ports:
- 56301:6379
command: redis-server --appendonly yes --requirepass <your password>
编写Dockerfile
- java项目,包括:renren-admin-server,renren-gateway
FROM openjdk:17
EXPOSE 8080
# VOLUME /tmp
ADD target/renren-admin-server.jar /app.jar
CMD ["java","-jar","/app.jar"]
-
VUE前端项目,包括web-admin
-
nginx.conf
server {
listen 80;
#listen 443 ssl;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/log/host.access.log main;
#ssl_certificate /home/ssl/server.crt;
#ssl_certificate_key /home/ssl/server.key;
root /usr/share/nginx/html;
index index.html;
location / {
# 不缓存首页,解决VUE单页面发版后不生效
add_header Cache-Control "no-cache no-store must-revalidate proxy-revalidate,max-age=0";
add_header Last-Modified $date_gmt;
# 这个有顺序,需要加在后面
etag off;
}
}
- Dockerfile
FROM nginx:latest
EXPOSE 80
COPY ./dist /usr/share/nginx/html
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
-
解决浏览器版本缓存不刷新
-
编写push.sh文件
#!/bin/bash
# 登录
docker login -u <your name> -p <your password> swr.cn-south-1.myhuaweicloud.com
# 打包
docker build -t swr.cn-south-1.myhuaweicloud.com/vp-park/park-baseline/<your image name>:v1.0 ./
# 推送
docker push swr.cn-south-1.myhuaweicloud.com/vp-park/park-baseline//<your image name>:v1.0
编写统一配置文件
在k8s新建ConfigMap,然后再java服务中引用配置:增加环境变量,选ConfigMap,选择需要的configMap名称
# 服务地址
nacos_host = nacos-server
# 名字空间
nacos_namespace = public
# 端口
nacos_port = 8848
进一操作请参考rancher使用手册
上传nacos配置文件
打开nacos管理界⾯(http://localhost:8848/nacos) ,初始⽤户名nacos,密码nacos,登录之后,如下所示:
导⼊nacos配置⽂件,配置⽂件在项⽬⾥,⽂件名为:【~/doc/nacos/nacos_config.zip】,如下所示:
在nacos⾥,还需要修改datasource.yaml,如:redis、MySQL信息,如下所示:
安装部署代码生成器
安装部署代码生成器:人人框架代码生成器安装部署
人人框架代码生成器安装部署
代码生成器能生成基础的单表、实体、列表、CURD,减少毫无意义的工作,还能保证基础代码的一致性,强制需要使用。
代码生成器生成的代码路径存放运本机磁盘,需要在本机同时运行基础项目、代码生成器项目、前端项目;后端开发人员不会运行前端项目,前端开发人员不会运行java项目。会造成一定的麻烦。
比较好的做法是将代码生成器部署到服务器给大家共享,再用web版本的vscode浏览、将代码复制回来粘贴到项目中。
安装部署代码生成器
源代码位置:/renren-module/renren-devtools
- 编写dockerfile
# vi Dockerfile
FROM openjdk:17
EXPOSE 8080
# VOLUME /tmp
ADD target/renren-devtools.jar /app.jar
CMD ["java","-jar","/app.jar"]
- 编写push.sh
#!/bin/bash
# 登录
docker login -u <your name> -p <your password> swr.cn-south-1.myhuaweicloud.com
# 打包
docker build -t swr.cn-south-1.myhuaweicloud.com/vp-park/park-weihai/renren-devtools-server:v1.0 ./
# 推送
docker push swr.cn-south-1.myhuaweicloud.com/vp-park/park-weihai/renren-devtools-server:v1.0
- 导入代码生成器初始化数据库脚本
源代码位置:/renren-module/renren-devtools/db/mysql.sql
设置代码生成器生
注意代码生成路径 ,如果是容器运行,请将 /data 目录挂载到磁盘或者NFS
- 导入模板
模板位置:/renren-module/renren-devtools/db/template 将模板粘贴到对应项
- 生成代码
- 设置代码包相关信息
注意代码生成路径 ,如果是容器运行,请将 /data 目录挂载到磁盘或者NFS
运行code-server
code-server具有极高的权限,开发完成后应该删除相关部署、设置复杂密码
version: "2.1"
services:
code-server:
image: swr.cn-south-1.myhuaweicloud.com/vp-whdev/all-in-devops/vscode-server:latest
container_name: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- PASSWORD=password # web gui password
- DEFAULT_WORKSPACE=/data # 打开 web gui 时默认打开文件夹
volumes:
- /path/to/appdata/config:/data # 将前面设置的代码生成器映射到 code-server
ports:
- 8443:8443
restart: unless-stopped
# 设置兖州后需要登录,登录页位于网站根目录,需要为 code-server 单独设置一个域名
浏览code-server将代码复制到项目
软件增加license管理
需要增加2个文件,建议目录放置在config目录
FilterConfig.java
全局过滤器配置 ,对需要拦截的路径进行配置
package cn.vppark.whdev.license_test.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Filter配置
*
* @author Mark sunlightcs@gmail.com
*/
@Configuration
public class FilterConfig {
@Value("${app.license:}")
private String license;
@Bean
public FilterRegistrationBean licenseFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new LicenseFilter(license));
registration.addUrlPatterns("/*");
registration.setName("licenseFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
}
LicenseFilter.java
package cn.vppark.whdev.license_test.config;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
/**
* @Description: 证书校验
* @date: 2024年4月9日 下午12:39:48
* @Copyright:
*/
public class LicenseFilter implements Filter {
private String license;
/**
* 公钥
*/
private final String publicKey = "your public key";
public LicenseFilter(String license) {
this.license = license;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//设置返回参数
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html; charset=UTF-8");
try {
if (this.license == null || license.isEmpty()) {
// 优先使用配置文件的证书,因为调试运行的时候,会清空target目录,导致证书文件丢失
this.license = IOUtils.toString(new FileInputStream(getLicensePath()), StandardCharsets.UTF_8);
}
//解密证书
String licenseContent = verifyLicense(this.license);
JSONObject json = JSONObject.parseObject(licenseContent);
//获取过期时间
Date expire = json.getDate("expire");
httpServletResponse.setHeader("license-expire", expire.toString());
//判断证书是否过期
if (expire.getTime() < System.currentTimeMillis()) {
httpServletResponse.getWriter().write("软件授权过期!");
return;
}
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
} catch (FileNotFoundException e) {
httpServletResponse.getWriter().write("证书文件丢失,请联系管理员!");
e.printStackTrace();
return;
} catch (Exception e) {
httpServletResponse.getWriter().write("证书文件已损坏,请联系管理员!");
e.printStackTrace();
return;
}
chain.doFilter(request, response);
}
/**
* @throws
* @Description: 根据公钥解密字符串
* @param: licenseContent 证书内容
*/
public String verifyLicense(String licenseContent) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey))));
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(licenseContent));
String decryptedString = new String(decryptedBytes);
// 打印解密后的证书内容
System.out.println("licenseContent: " + decryptedString);
return decryptedString;
}
/**
* @throws
* @Description: 获取license完整路径
*/
private String getLicensePath() throws URISyntaxException {
URL classUrl = this.getClass().getProtectionDomain().getCodeSource().getLocation();
URI uri = classUrl.toURI();
Path path;
if (uri.getScheme().equals("jar")) {
// 如果是一个JAR URL,我们需要找到JAR文件本身
String jarPath = uri.getSchemeSpecificPart();
jarPath = jarPath.substring(0, jarPath.indexOf('!'));
path = Paths.get(new URI(jarPath)).getParent();
} else {
// 如果不是JAR URL,则可能是直接从文件系统加载的类
path = Paths.get(uri);
}
String licensePath = path.resolve("license/license").toString();
// 打印证书路径
System.out.println("licensePath: " + licensePath);
return licensePath;
}
}
各应用平台注册地址
信息系统安全定级说明
一、确定安全保护等级
依据《信息安全等级保护管理办法》(公通字 [2007] 43 号)《关于开展全国重要信息系统安全等级保护定级工作的通知》 (公信安[2007]861 号)、《信息安全技术网络安全等级保护定级指南》(GB/T22240-2020) 等文件规定和国家政务外网的统一要求,所建设的需要信息系统的安全保护等级。
二、安全等级保护等级确定流程
确定信息系统安全保护等级的一般流程如下:
- 第一、确定作为定级对象的信息系统;
- 第二、确定业务信息安全受到破坏时所侵害的客体;
- 第三、根据不同的受侵害客体,从多个方面综合评定业务信息安全被破坏对客体的侵害程度;
- 第四、得到业务信息安全保护等级;
- 第五、确定系统服务安全受到破坏时所侵害的客体;
- 第六、根据不同的受侵害客体,从多个方面综合评定系统服务安全被破坏对客体的侵害程度;
- 第七、得到系统服务安全保护等级;
- 第八、将业务信息安全保护等级和系统服务安全保护等级的较高者确定为定级对象的安全保护等级。
信息安全保护等级矩阵表如下所示:
三、定级对象受侵害客体
定级对象受到破坏时所侵害的客体包括国家安全、社会秩序、公众利益以及公民、法人和其他组织的合法权益。
-
1、受侵害的客体及对客体的侵害程度
- (1) 侵害国家安全主要是指影响国家政权稳固和国防实力、民族团结和社会安定、重要的安全保卫工作、经济竞争力和科技实力等。
- (2) 侵害社会秩序主要是指影响国家机关社会管理和公共服务的工作秩序、各种类型的经济活动秩序等。
- (3) 影响公共利益主要是指影响公共设施、公开信息资源、公共服务等方面。
- (4) 影响公民、法人和其他组织的合法权益是指由法律确认的并受法律保护的公民、法人和其他组织所享有的一定的社会权利和利。
-
2、不同危害后果的三种危害程度
- (1)一般损害:工作职能受到局部影响,业务能力有所降低但不影响主要功能的执行,出现较轻的法律问题,较低的财产损失,有限的社会不良影响,对其他组织和个人造成较低损害。
- (2) 严重损害:工作职能受到严重影响,业务能力显著下降目严重影响主要功能执行,出现较严重的法律问题,较高的财产损失,较大范围的社会不良影响,对其他组织和个人造成较严重损害。
- (3) 特别严重损害: 工作职能受到特别严重影响或丧失行使能力,业务能力严重下降且或功能无法执行,出现极其严重的法律问题.极高的财产损失,大范围的社会不良影响,对其他组织和个人造成非常严重损害。
运维支持考试题
运维知识考试题
- 1.如何打包docker镜像;
- 2.如何启动一组docker镜像;
- 3.如何查看docker容器的日志;
- 4.Docker默认会将容器镜像下载到/var/lib/docker目录,随着版本的发布、获取新版本镜像,会导致系统磁盘空间不足,如何有效避免这一必现的主机存储不足问题。
- 5.docker容器启动的时候,会自动创建一个docker桥接网络,根据每台主机的性能不同,docker桥接网络的数量可能超过主机限制而无法继续创建容器,在超过限制后如何继续创建docker容器;
- 6.使用docker后,每次重新启动docker容器,其内部数据会被重置,如何实现docker容器内应用数据的持久化放置业务数据丢失;
- 7.当前K8S集群有一台节点坏了,如何合理的退出节点和加入节点;
- 8.解释一下k8s中的deploy,pod,server,configmap,secrets;
- 9.K8s部署容器的时候,使用的镜像是私有镜像仓库、仓库有密码有认证时,如何正确拉取到镜像;
- 10.某些容器需要调用更多的系统能力,在K8S网络该如何设置;
- 11.某个应用因为管理要求,仅能在某个主机节点调度,请问在K8S网络如何调度;
- 12.K8s默认采用http访问后端,当前有一个后端必须要用https访问,请问能正确访问到后端,当前k8s集群使用nginx-ingress;
- 13.在K8S部署好的应用上传文件的时候,时提示文件太大,当前使用nginx-ingress;
- 14.如何统一设置k8s网络的https的证书,而不是在每个ingress单独设置https证书,当前使用nginx-ingress;
- 15.K8s集群内部各组件使用https证书通讯,k8s内置的https证书默认为一年,请问如何续期k8s集群组件的https证书
- 16.我们通常使用ping来确定主机是否在线,但是linux的ping会持续,主机监控系统因为ping没有返回,而无法确定主机是否在线,请问应该如何让监控系统正确得到ping的返回结果;
- 17.如何确定k8s集群是否正常;
- 18.如何批量查询、删除K8S集群中的不正常pod;
- 19.如何测试磁盘写入、读取速度;
- 20.在某些自建机房中,禁止了网络环路,而导致不能直接在内部使用服务公布的域名访问,而内部服务发现与调用又仅支持域名访问,请问要如何访问;
- 21.某个正常访问的服务,突然发生了外部网络不可达错误,请简要描述排查步骤;
- 22.当前有一个k8s 部署运行不起来,连pod都没有创建,请问如何排除容器运行不起来的原因;
- 23.如何对容器做jvm限制;
- 24.当前有一个Java开发的项目,根据前面的容器打包方式已经打包为docker镜像并部署到了k8s网络,研发人员告知,当前项目下需要使用prod配置文件,请问如何在不更改docker镜像的情况下指定当前应用程序的运行环境配置文件。
- 25.K8s内有两个项目,如何在这两个项目之间进行服务调用,而不是通过对外公布的服务又从外网接入损失流量和性能。
- 26.K8s容器网络ping不通;
- 27.Nginx的local或proxy_pass有带有 反斜杠(/)和不带反斜杠(/)二者之间有什么区别;
- 28.有一个后端服务,后端没有做身份认证,现找不到服务厂商维护、没有源代码,现在因为管理要求,需要对这个服务做身份认证,请问如何管理;
- 29.当前在linux排除问题,但是后端服务限制了近能使用域名访问,该如何排查;
- 30.linux常用的主机性能查看命令例如、磁盘、内存、CPU、网络等命令;
- 31.如何设置linux服务器密码过期策略(180天);
- 32.如何查看mysql的主要性能指标参数;
- 33.如何确定和使用mysql的慢日志;
- 34.如何确定和使用mysql的性能分析模式分析mysql优化项;
- 35.如何设置mysql的密码过期策略(180天);
- 36.如何备份和恢复mysql数据库,并只做一个定时任务来定期备份数据库;
- 37.解释一下华为云的elb的实例、监听器、转发器、后端组分别代表的什么意思;
高并发性能优化设置
tomcat设置
server:
tomcat:
max-connections: 10000 # 最大连接数,默认值为8192,一般情况下可以不用修改
threads:
max: 1000 # 处理请求最大线程数,默认值是200,既tomcat的默认并发是200
min-spare: 100 # 处理请求的最小空闲线程,即使没有请求需要处理,也保留,以便于快速响应连接请求
accept-count: 100 # 额外的阻塞的处理请求数,当所有线程都在使用,还可以额外接受的请求,并放入请求队列,待有可用线程后立刻处理,一般不超过最大线程数的10%
-
max-connections: 10000:这个参数设置了Tomcat服务器允许的最大连接数。这意味着Tomcat在同一时间最多可以处理10000个客户端连接。这个值应该根据服务器的硬件资源(如CPU、内存)和网络带宽来调整,以确保服务器能够高效稳定地运行。
-
threads: 用于处理请求的线程池的参数。
- max: 1000:线程池中的最大线程数。意味着Tomcat可以同时启动最多1000个线程来处理客户端请求。线程数数量需要考虑服务器的硬件资源。
- min-spare: 100:线程池中保持空闲的最小线程数。即使当前没有那么多请求需要处理,Tomcat也会保持至少100个线程处于空闲状态,以便快速响应突然增加的请求量。
accept-count: 100:当所有可用的处理线程都在使用时,Tomcat能够接受的额外请求的数量,并将这些请求放入队列中等待处理。意味着如果所有的处理线程都在忙,Tomcat还可以接受额外的100个请求,但这些请求需要等待直到有线程变得可用。
连接数与线程数,根据TCP原理,是先建立连接,再处理请求,如果在连接超时后还没有处理请求,则断开连接,例如常见的timeout,socket closed
nginx设置
主要是开启gzip压缩,减少流量
# 开启gzip压缩
gzip on;
# 压缩哪些文件类型
gzip_types text/plain text/css application/json text/javascript application/javascript;
# 最小压缩大小,小于这个大小不压缩,单位是字节
gzip_min_length 1000;
# 压缩率,1-9,数字越大压缩的越好,但是也越消耗CPU
gzip_comp_level 6;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁止IE6使用gzip,因为这些浏览器不支持gzip压缩
gzip_disable "MSIE [1-6]\.";
# 在特定条件下对代理服务器的响应进行压缩
gzip_proxied expired no-cache no-store private auth;
# 缓冲区数量和大小,设置了16个8KB的缓冲区
gzip_buffers 16 8k;
# 最小http版本,低于这个版本不压缩
gzip_http_version 1.1;
Springboot程序内优化
- 按需返回,返回过多的数据占用内存和带宽
- 嵌套的对象和数组,反序列化的时候嵌套对象占用开销
- 独立的中间件主机:主机资源端口有限,如果全部在一台主机,会占用主机端口和文件描述
jmeter自身优化
- 从jmeter.bat打开软件,而不是直接打开jar
- 修改jmeter.bat增加应用程序堆栈
修改HEAP大小,一般为主机内存的一半
if not defined HEAP (
rem See the unix startup file for the rationale of the following parameters,
rem including some tuning recommendations
set HEAP=-Xms1g -Xmx8g -XX:MaxMetaspaceSize=512m
)
从bat启动时为英文修改
create-springboot-project
创建项目目录结构
# 创建项目结构
mkdir -p src/main/java
mkdir -p src/main/resources
mkdir -p src/test/java
mkdir -p src/test/resources
# 创建第一个包
mkdir -p src/main/java/com/iovhm/hello
# 创建4个环境的配置文件
touch src/main/resources/application.yml
touch src/main/resources/application-dev.yml
touch src/main/resources/application-tet.yml
touch src/main/resources/application-prod.yml
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--如果项目没有层级关系,请删除下面的节点-->
<!--<parent>-->
<!--<groupId>cn.vpclub</groupId>-->
<!--<artifactId>spring-boot-starters</artifactId>-->
<!--<version>1.4.15</version>-->
<!--</parent>-->
<!--如果项目没有层级关系,请删除上面的节点-->
<!---项目组,必须-->
<groupId>com.iovhm</groupId>
<!--项目唯一ID,必须-->
<artifactId>hello-springboot</artifactId>
<!--项目版本,必须-->
<version>1.0.0</version>
<!--生成类型,必须,可选参数pom,jar,一般情况下使用jar-->
<packaging>jar</packaging>
<!---属性设置,项目属性不是必须的,但是建议写上便于精确控制-->
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.test.skip>true</maven.test.skip>
<spring-boot.version>3.4.1</spring-boot.version>
</properties>
<!-- 统一spring boot 版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--spring boot 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!--mybatis支持-->
<!--如果使用 mybatis plus 请将 mybatis 支持 去掉, mybatis plus 会自动处理依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<!-- mybatils plus 支持-->
<!--如果使用 mybatis plus 请将 mybatis 支持 去掉, mybatis plus 会自动处理依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
<!--lombok支持-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>
<!--redis支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- hu-tool工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建Application.java
package com.iovhm.hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
创建HelloController.java
package com.iovhm.hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// 全局访问路径
@RequestMapping("/")
// 标记为本类为Controller类
@RestController
public class HomeController {
// 在全局访问路径的基础上,成员方法的访问路径
@RequestMapping("/")
public String hello() {
return "hello,world";
}
}
如果没有配置数据库,可能导致程序无法运行,可以先禁用数据库自动装配
# application.yml
spring:
autoconfigure:
# 阻止Spring Boot自动配置数据源
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
下一章
springboot 集成mybatis-plus https://iovhm.com/book/books/bbcbf/page/springboot-mybatis-plus
springboot 集成 mybatis
创建项目
create-springboot-project: https://iovhm.com/book/books/bbcbf/page/create-springboot-project
更多的时候我们使用mybatis-plus,本章可以直接跳过
springboot 集成mybatis-plus https://iovhm.com/book/books/bbcbf/page/springboot-mybatis-plus
集成数据库
注意:如果你是按create-springboot-project创建的项目,需要删除配置
# application.yml
spring:
autoconfigure:
# 阻止Spring Boot自动配置数据源
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
约定与名词解释
- entity(实体),与数据表,代表一张真实的数据表
- DAO(数据访问对象),访问数据库的接口或实例,在spring-boot中,有时候有喜欢取名为Mapper
- DTO(数据传输对象),含业务领域的数据(既DTO包含entity的字段,但是应该多于entity),不包含业务逻辑,负责在系统与系统之间、分层与分层之间传递数据。
确定项目结构
好的结构易于项目维护
增加pom依赖
<!--mysql支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--mybatis支持-->
<!--如果使用 mybatis plus 请将 mybatis 支持 去掉, mybatis plus 会自动处理依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
增加数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:33306/dataway?charset=utf8mb4&serverTimezone=Asia/Shanghai
username: <root>
password: <password>
driver-class-name: com.mysql.cj.jdbc.Driver
创建数据库
创建Entity(实体)与数据库结构保持一致
在实体上增加注解 @Data
@Data
public class UserEntity {
private Integer id;
private String name;
private Integer age;
private String email;
}
创建DTO
由于DTO主要负责业务数据传输,DTO可以先直接继承于Entity,再根据业务需要增加字段,注意 @Data
注解
@Data
public class UserDTO extends UserEntity {
}
创建Dao或者Mapper
注意 @Mapper
注解,Mapper是用来操作数据库的,使用的数据结构应该是Entity,虽然用DTO也可以使用,但还是应该遵守编程规范
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user")
List<UserEntity> selectList();
}
编写Controller
// 全局访问路径
@RequestMapping("/")
// 标记为本类为Controller类
@RestController
@Slf4j
public class HomeController {
private final UserMapper userMapper;
public HomeController(UserMapper userMapper) {
this.userMapper = userMapper;
}
@RequestMapping("/")
public Object hello() {
List list =userMapper.selectList();
return list;
}
}
修改Application
注意 @MapperScan("com.iovhm.dataway.**.dao")
@SpringBootApplication
@MapperScan("com.iovhm.dataway.**.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
springboot 集成mybatis-plus
创建项目
create-springboot-project: https://iovhm.com/book/books/bbcbf/page/create-springboot-project
集成数据库
注意:如果你是按create-springboot-project创建的项目,需要删除配置
# application.yml
spring:
autoconfigure:
# 阻止Spring Boot自动配置数据源
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
约定与名词解释
- entity(实体),与数据表,代表一张真实的数据表
- DAO(数据访问对象),访问数据库的接口或实例,在spring-boot中,有时候有喜欢取名为Mapper
- DTO(数据传输对象),含业务领域的数据(既DTO包含entity的字段,但是应该多于entity),不包含业务逻辑,负责在系统与系统之间、分层与分层之间传递数据。
确定项目结构
增加pom依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-bom</artifactId>
<version>3.5.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--mysql支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--mybatis支持-->
<!--如果使用 mybatis plus 请将 mybatis 支持 去掉, mybatis plus 会自动处理依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.10</version>
</dependency>
增加数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:33306/dataway?charset=utf8mb4&serverTimezone=Asia/Shanghai
username: <root>
password: <password>
driver-class-name: com.mysql.cj.jdbc.Driver
创建数据库
创建Entity(实体)与数据库结构保持一致
注意 @Data 和 @TableName("user") 注解
@Data
@TableName("user")
public class UserEntity {
private Integer id;
private String name;
private Integer age;
private String email;
}
创建DTO
由于DTO主要负责业务数据传输,DTO可以先直接继承于Entity,再根据业务需要增加字段,注意 @Data 注解
@Data
public class UserDTO extends UserEntity {
}
创建DAO
注意继承于Mybatis-plus的BaseMapper,以及Mapper里面的Entity。DAO是用来操作数据库的类,使用的数据结构应该是Entity,虽然继承于EEntity的DTO也可以使用,但还是应该遵守编程规范
@Mapper
public interface UserDao extends BaseMapper<UserEntity> {
}
创建Service
public interface UserService {
List<UserDTO> selectList();
List<UserDTO> selectList2();
}
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final UserDao userDao;
public UserServiceImpl(UserMapper userMapper, UserDao userDao) {
this.userMapper = userMapper;
this.userDao = userDao;
}
@Override
public List<UserDTO> selectList() {
List list = userDao.selectList(null);
return list;
}
@Override
public List<UserDTO> selectList2() {
return userMapper.selectList();
}
}
创建Controller
// 全局访问路径
@RequestMapping("/")
// 标记为本类为Controller类
@RestController
@Slf4j
public class HomeController {
private UserService userService;
public HomeController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/")
public Object hello() {
List list = userService.selectList();
List list2 = userService.selectList2();
return Map.of("list", list, "list2", list2);
}
}
修改Application
注意 @MapperScan("com.iovhm.dataway.**.dao")
@SpringBootApplication
@MapperScan("com.iovhm.dataway.**.dao")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
springboot 集成mybatis-plus通用CRUD
CRUD
mybatis-plust提供了通用CRUD类 IService ,可以直接继承改接口并实现ServiceImpl既可以实现通用CURD
实现自定义的CURD基础类
根据研发规范,IService返回的数据结构是Entity类型,与规范要求的业务代码应该返回DTO不符,可以实现自定义基类来解决
创建并实现自定义基类
/*
* @Description:自定义CRUD基类
* @Author: donglietao@163.com
* @ENT: 实体类
* @DTO: DTO类
*/
public interface CrudService<ENT, DTO> {
List<DTO> list(@Param("ew") Wrapper<ENT> queryWrapper);
}
主要是用到了BaseMap<ENT>类型
/*
* @description: CrudServiceImpl
* @author: donglietao@163.com
* */
@Service
@Slf4j
public class CrudServiceImpl<ENT, DTO> implements CrudService<ENT, DTO> {
// 主要是这个地方,可以使用泛型创建mapper
@Autowired
protected BaseMapper<ENT> baseMapper;
private Class<DTO> getDTOClass() {
return (Class<DTO>) ReflectionKit.getSuperClassGenericType(this.getClass(), CrudService.class, 1);
}
public List<DTO> list(@Param("ew") Wrapper<ENT> queryWrapper) {
List<ENT> list = baseMapper.selectList(queryWrapper);
if (list == null) {
return null;
}
// 此处可以简化,将方法抽离到工具类
List<DTO> listDTO = new ArrayList<>(list.size());
Class<DTO> dtoClass = getDTOClass();
for (ENT ent : list) {
try {
DTO dto = dtoClass.newInstance();
BeanUtils.copyProperties(ent, dto);
listDTO.add(dto);
} catch (Exception ex) {
log.error("CrudServiceImpl.list() error:", ex);
}
}
return listDTO;
}
}
在自定义服务类上实现通用CRUD基类
public interface UserService extends CrudService<UserEntity, UserDTO> {
}
@Service
public class UserServiceImpl extends CrudServiceImpl<UserEntity, UserDTO> implements UserService {
}
进一步简化代码
类型转换工具类
将转换类独立为工具类
@Slf4j
public class ConvertUtils {
public static <T> T sourceToTarget(Object source, Class<T> target) {
if (source == null) {
return null;
}
T targetObject = null;
try {
targetObject = target.newInstance();
BeanUtils.copyProperties(source, targetObject);
} catch (Exception e) {
log.error("convert error ", e);
}
return targetObject;
}
public static <T> List<T> sourceToTarget(Collection<?> sourceList, Class<T> target) {
if (sourceList == null) {
return null;
}
List targetList = new ArrayList<>(sourceList.size());
try {
for (Object source : sourceList) {
T targetObject = target.newInstance();
BeanUtils.copyProperties(source, targetObject);
targetList.add(targetObject);
}
} catch (Exception e) {
log.error("convert error ", e);
}
return targetList;
}
}
使用类型转换工具简化调用
@Service
@Slf4j
public class CrudServiceImpl<ENT, DTO> implements CrudService<ENT, DTO> {
@Autowired
protected BaseMapper<ENT> baseMapper;
private Class<DTO> getDTOClass() {
return (Class<DTO>) ReflectionKit.getSuperClassGenericType(this.getClass(), CrudService.class, 1);
}
public List<DTO> list(@Param("ew") Wrapper<ENT> queryWrapper) {
List<ENT> list = baseMapper.selectList(queryWrapper);
// 使用转换工具简化调用
List<DTO> listDTO = ConvertUtils.sourceToTarget(list, getDTOClass());
return listDTO;
}
}
deepseek 环境搭建
这篇文章是入门文章,没有什么实用价值,仅是将运行deepseek的环境运行起来了。
主要软件
- ollama:模型运行平台,https://ollama.com/download
- open-webui:简单的chat页面,https://github.com/open-webui/open-webui
- dify :模型编排平台,https://github.com/langgenius/dify
- docker
- python
- 3.11:open-webui需要的运行时版本
- 科学上网:https://iovhm.com/book/books/cee63/page/9872e
ollama可以直接在容器运行
不用担心在容器运行会有性能损失,经过多年对docker的实践,除了网络方面会有损失外,其他方面并没有损失,可以将网络设置为host模式来规避bridge网络的性能损失。
相反,如果在主机直接运行,各种版本的依赖,新版本的升级,环境更加容易出问题。
version: "3"
services:
ollama:
image: harbor.iovhm.com/hub/ollama/ollama:0.5.12
container_name: ollama
restart: always
privileged: true
ports:
- "11434:11434"
volumes:
- ./ollama:/root/.ollama
networks:
- vpclub-bridge
安装模型运行平台ollama
模型运行平台下载:https://ollama.com/download
模型备份
每次下载模型都需要很久,可以将模型备份出来
进入到用户文件夹下面,例如 C:\Users\admin.ollama\models , 将模型复制出来,复制到新的机器对应的目录(未验证)
下载和运行模型
# 显示所有命令行参数
ollama
# 所有的命令行
serve Start ollama
create Create a model from a Modelfile
show Show information for a model
run Run a model
stop Stop a running model
pull Pull a model from a registry
push Push a model to a registry
list List models
ps List running models
cp Copy a model
rm Remove a model
help Help about any command
# 运行服务,如果服务没有运行,可以用这个启动服务,非必须,一般安装完成后都会自动运行
ollama serve
# 验证安装
ollama -v
# 下载模型
ollama pull deepseek-r1:1.5b
# 运行模型
# 如果本地没有这个模型,则会自动下载并运行
# 运行后,此时会出现一个对话窗口,可以进行输入文字进行对话
# 键入/bye 或者ctrl+d可以退出对话窗口,模型在后台运行
# 退出对话框后,如果需要再次进入对话框,可以在此run模型
ollama run deepseek-r1:1.5b
# 显示已经安装的模型列表
ollama list
# 显示所有在运行的模型列表
ollama ps
# 显示模型信息
ollama show deepseek-r1:1.5b
使用API对话
默认安装没有修改配置的话,ollama 运行在11343端口,可以使用命令行或者postmain测试
这种方式太简陋,要实现的内容太多,不推荐
curl -X POST -H "Content-Type: application/json" \
-d '{"model": "deepseek-r1:1.5b", "prompt": "你好,世界!"}' \
http://localhost:11434/api/generate
使用open-webui
这是一个简单的兼容多个模型的、兼容openai接口调用方式的可视化界面
下载地址:https://github.com/open-webui/open-webui
文档地址:https://docs.openwebui.com/
可以直接使用docker运行,docker的安装方式自行百度,优先推荐使用docker安装
# GPU版
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:cuda
# CPU版
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
# pip 安装
pip install open-webui -i https://pypi.tuna.tsinghua.edu.cn/simple
# python运行
# 此时系统运行在 http://localhost:8080
open-webui serve
详细设置请看:https://iovhm.com/book/books/bbcbf/page/deepseek-docker
docker搭建deepseek运行环境和open-webui
这篇文章没有什么实用价值,仅作为快速体验ollama运行环境。或者当做一个附加工具,用来管理和查看ollama环境。
docker-compose.yaml
这个脚本将同时启动ollama 和 open-webui
version: "3"
services:
ollama:
image: harbor.iovhm.com/hub/ollama/ollama:0.5.12
container_name: ollama
restart: always
privileged: true
ports:
- "11434:11434"
volumes:
- ./ollama:/root/.ollama
networks:
- vpclub-bridge
# docker-compose --profile open-webui up -d
open-webui:
# CPU版
image: harbor.iovhm.com/public/open-webui/open-webui:main
# GPU版
# image: harbor.iovhm.com/public/open-webui/open-webui:main-gpu
container_name: open-webui
restart: always
privileged: true
ports:
- "3000:8080"
volumes:
- ./open-webui:/app/backend/data
environment:
# 如果你的 ollama 服务器不在本机,请修改此地址,如果OLLAMA_BASE_URLS被设置,则使用OLLAMA_BASE_URLS
# - OLLAMA_BASE_URL=http://ollama:11434
# 如果你的 ollama 服务器不在本机,请修改此地址,可以使用分号分割多个地址提供负载均衡能力
- OLLAMA_BASE_URLS=http://ollama:11434
# 关闭 openai api 否则会因为连不上openai而卡界面
- ENABLE_OPENAI_API=false
# 禁用自带的 Arena Model模型(竞技场模型)
- ENABLE_EVALUATION_ARENA_MODELS=false
# 关闭社区分享功能
- ENABLE_COMMUNITY_SHARING=false
# 离线模式,不自动从Internet下载模型(非必须,有魔法的话不需要设置)
# - HF_HUB_OFFLINE=true
# 默认的语义向量模型引擎(非必须)
# - RAG_EMBEDDING_ENGINE=ollama
# 默认的语义向量模型(非必须)
# - RAG_EMBEDDING_MODEL=nomic-embed-text:latest
networks:
- vpclub-bridge
networks:
vpclub-bridge:
external:
name: vpclub-bridge
测试一下服务
打开浏览器,输入ollama的服务地址, http://ip:11434 ,界面提示:Ollama is running ,则表示ollama安装成功了
输入open-webui的地址,http://ip:3000 , 则可以看到open-webui的地址。
但是这个时候可能是白屏,什么都看不到;或者好不容易刷出来界面,设置好用户名密码进入系统后,也是白屏,此时应该去查看open-webui容器的出错提示,多半都是被墙拉取不到镜像的原因。那你需要魔法。
界面打不开,或者进去了也很卡问题解决
- 打开open-webui等很久,不出来界面,白屏
因为软件有很多外部依赖要下载,如果被墙,下载不到、就一直卡住,可以先不使用openai和不使用内置的语义向量模型模型
# 修改open-webui的环境变量设置
# 关闭 openai api 否则会因为连不上openai而卡界面
- ENABLE_OPENAI_API=false
# 默认的RAG引擎(非必须)
- RAG_EMBEDDING_ENGINE=ollama
# 默认的语义向量模型(非必须)
- RAG_EMBEDDING_MODEL=nomic-embed-text:latest
进入软件后,每次刷新都要等很久
关闭使用openai外部链接,修改语义向量模型
修改语义向量模型
进入到ollama容器下载模型
# 安装完成后进入ollma容器
docker exec -it ollama /bin/bash
# 查看ollama版本
ollama -v
# 下载deepseek模型
ollama run deepseek-r1:7b
# 语义向量模型
ollama pull nomic-embed-text
查看系统当前都安装了什么模型
配置知识库
点击上传文件,建议的文件类型为markdown,使用 #### 进行段落区分,如果保存知识库出错,那是因为没有设置正确的语义向量模型
为模型关联知识库,或者在聊天界面使用 # 引用知识库
在聊天界面使用# 引用知识库
那我们就可以开始体验deepseek能力啦,来看看deepseek静静的装B
deepseek和dify环境搭建
dify是一个用于构建AI应用的模型编排软件,开箱即用,可以通过拖拉拽的形式,快速组合出一个AI应用,支持接入各厂商的云上模型,也支持接入本地ollama引擎运行的模型。
准备工作
- 需要魔法:https://iovhm.com/book/books/cee63/page/9872e
- 镜像代理:https://iovhm.com/book/books/k8s/page/harbordockerdocker
- 需要升级docker-compose版本:https://github.com/docker/compose/releases
- 安装ollama并下载模型:https://iovhm.com/book/books/bbcbf/page/deepseek
- 下载dify源代码:https://github.com/langgenius/dify
安装ollama并下载模型
version: "3"
services:
ollama:
image: harbor.iovhm.com/hub/ollama/ollama:0.5.12
container_name: ollama
restart: always
privileged: true
ports:
- "11434:11434"
volumes:
- ./ollama:/root/.ollama
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# capabilities: [gpu]
# count: all
networks:
- vpclub-bridge
# 下载最少2个模型
# ollama pull deepseek-r1
# ollama pull bge-m3
下载dify源代码,进入到docker目录,修改被墙的docker镜像地址使用魔法地址
下载到源代码后,进入到docker目录,打开docker-compose.yaml,里面总共有26个服务,将镜像地址修改为私有仓库。如果并不打算二次开发和在服务器运行,只需要将docker目录上传到服务器,不需要把dify的所有源代码全部上传。
真正有用的服务只有10个,其他的是各种不同类型的向量数据库
将如下10个服务的镜像地址修改为镜像代理地址,使用docker-compose up -d 即可以将软件运行起来。其他服务是各种不同类型/厂家的向量数据库,根据自己的需要才启动,只有使用 docker-compose --profile=xxxx up -d 才会启动特定的服务。不用担心启动了太多的服务。
为了保持和官方版本升级时候的兼容性,不建议直接修改docker-compose.yaml,比喻把镜像下载回来了重命名一下。
- api
- worker
- web
- db
- redis
- sandbox , 一些模型可以调用代码,用于运行代码的沙箱容器
- plugin_daemon , 开发插件用的
- ssrf_proxy , 一个用来防止SSRF_PROXY攻击的代理软件
- nginx , 入口nginx
- weaviate,向量数据库
env配置文件
官方指导是将 .env.example 复制一个后改名为 .env , 但是需配置项太多,从头看到尾很需要时间,在此我摘抄了一个极简的 .env ,实际上不提供任何 .env文件,也可以运行。如果你的默认的80和443端口被占用,那就需要提供 .env 进行配置更改。
官方文档:https://docs.dify.ai/zh-hans/getting-started/install-self-hosted/environments
CONSOLE_API_URL=
CONSOLE_WEB_URL=
SERVICE_API_URL=
APP_API_URL=
APP_WEB_URL=
FILES_URL=
# 对外公布的服务端口
EXPOSE_NGINX_PORT=80
EXPOSE_NGINX_SSL_PORT=443
# 是否开启检查版本策略,若设置为 false,则不调用 https://updates.dify.ai 进行版本检查。
# 由于目前国内无法直接访问基于 CloudFlare Worker 的版本接口,
# 设置该变量为空,可以屏蔽该接口调用
CHECK_UPDATE_URL=
# 向量数据库配置
VECTOR_STORE=weaviate
# Weaviate 端点地址,如:http://weaviate:8080
WEAVIATE_ENDPOINT=http://weaviate:8080
# 连接 Weaviate 使用的 api-key 凭据
WEAVIATE_API_KEY=WVF5YThaHlkYwhGUSmCRgsX3tD5ngdN8pkih
接入ollama并添加模型
进入dify后,点击右上角自己的用户名图标,点击设置,进行模型供应商接入
查看已经增加的模型
增加模型,需要增加2个模型,一个是LLM模型,一个是Text Embeding模型,模型需要先到ollama下载好。
- deepseek-r1 , LLM模型
- nomic-embed-text ,文本嵌入模型,对中文的支持不太好
- bge-m3 ,文本嵌入模型,支持超过100种语言(建议)
后面的其他参数不知道怎么填,可以使用默认值。
创建知识库
可以使用word、markdown等软件将编写好文档后上传,文档要求是需要有分段关系,既标题->正文,有一定的逻辑关系,如果你不介意,可以用wps ai将文章内容更正得更正式。
创建一个新的知识库,并上传文档
对知识库进行设置
设置检索方式和嵌入式文本模型
创建一个新的应用
在工作室标签下,选择创建空白应用,选择你要创建的应用类型
对应用进行设置
应用的更多设置,比喻开场白,连续提问等。
调试体验一下
此时AI的回复特别生硬,需要修改提示词。
提示词设置
一个好的提示词,对AI的影响非常大,deepseek帮助文档给出了一些参考建议
https://api-docs.deepseek.com/zh-cn/prompt-library/
还是不知道怎么写?那我们蒸馏一下,让deepseek帮忙生成一个.
如果觉得curl 调用不方便,也可以使用postman工具进行deepseek api调用,此时你需要一个deepseek的api key。
curl --location 'https://api.deepseek.com/chat/completions' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer sk-xxxxxxxxxxxxxxxx' \
--data '{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "你是一位大模型提示词生成专家,请根据用户的需求编写一个智能助手的提示词,来指导大模型进行内容生成,要求:1. 以 Markdown 格式输出\n 2. 贴合用户需求,描述智能助手的定位、能力、知识储备\n 3. 提示词应清晰、精确、易于理解,在保持质量的同时,尽可能简洁\n 4. 使用清晰、简洁的语言回复提问,确保用户容易理解\n 5.使用友好且专业的语气与用户交流,如“您好,关于您的这个问题,我可以为您详细解答。"},
{"role": "user", "content": "请帮我生成一个'\''智慧园区智能问答助手'\''的提示词"}
],
"stream": false
}'
蒸馏一下deepseek,用魔法打败魔法
到deepseek注册一个账号:https://www.deepseek.com/
创建一个api key
充值10块钱,就可以用啦。顺便吐槽一下,老说程序员工资太高,但是你们看到别人背后的持续学习成本了吗?5-10年就技术更新一次,知识全部作废,熬夜写文档,买key,买机器,这些成本你们算过吗?你们某些行业,一本小破书就可以用到退休。就别整天BBB了。
看下deepseek蒸馏生成的提示词,我们此基础上稍微修改下。
# 威海智慧谷智慧园区智能问答助手小智提示词
## 定位
我叫小智,我是威海智慧谷智慧园区智能问答助手,是一个专为园区管理、企业员工及访客设计的智能交互平台。旨在通过自然语言处理技术,提供即时、准确的园区相关信息和服务支持。
## 能力
1. **信息查询**:能够快速响应关于园区设施、服务、活动等信息的查询。
2. **导航指引**:提供园区内的导航服务,包括建筑物位置、会议室预订等。
3. **问题解答**:解答关于园区政策、安全规定、技术支持等常见问题。
4. **服务预约**:协助用户进行会议室预订、设备租赁等服务预约。
5. **反馈收集**:收集用户对园区服务的反馈和建议,帮助园区管理方优化服务。
## 知识储备
1. **园区信息**:包括园区地图、设施介绍、服务项目等。
2. **政策法规**:园区相关的政策、规定及安全指南。
3. **技术支持**:常见技术问题的解决方案和操作指南。
4. **服务流程**:各类服务的预约流程、使用指南等。
## 交互示例
- **用户**:最近的咖啡厅在哪里?
- **助手**:贵宾,您好,园区内最近的咖啡厅位于A栋一楼,营业时间为早上8点到晚上8点。您可以通过园区导航系统找到具体位置。希望我的服务能帮助您。如有任何问题,欢迎随时咨询。
- **用户**:预订一个会议室
- **助手**:贵宾,您好,园区内可预订的会议室有A栋101、B栋202和C栋303,您可以通过园区APP或前台进行预订。希望我的服务能帮助您。如有任何问题,欢迎随时咨询。
- **用户**:...漏水...灯不亮了...门打不开了
- **助手**:贵宾,您好,感谢您的反馈,如果你需要报修,请在智慧谷app提交工单,收到您的工单后,我们会立即安排维修部门上门维修。希望我的服务能帮助您。如有任何问题,欢迎随时咨询。
## 提示词
- **查询信息**:请告诉我关于...
- **导航指引**:我需要找到...
- **问题解答**:关于...的问题,我需要帮助。
- **服务预约**:我想预订...
- **反馈收集**:我有一些建议/反馈...
再来体验一下
此时AI回复已经很客气啦!
记得要点右上角的发布按钮哦。找了半天都没找到保存按钮,直接刷新或者关闭页面,可能导致部分内容没有保存
发布应用
点击左上角的应用图标,会弹出来dify已经嵌入好的聊天页面,如果页面风格你不太满意,可以使用API对接自己的定制页面。
deepseek嵌入工作流实现直接驱动业务的探索
背景交代
市面上有很多问答式的AI产品,回复的内容确实很有参考意义,但是开放式的AI的上下文没有关联业务,还是需要先复制出来再修改一遍。
不过研发人员的ide插件可靠度就很高了,基本上都能运行。
那么问题来了,客户他只有一个一次性的需求,还需要研发吗,特别是领导们喜欢提稀奇古怪的问题,一通改下来,最后说还是第一个好,浪费时间还不给钱
生成式大屏逻辑分析
- 分析用户的输入,例如统计今天成交了多少订单(实际业务肯定比这个要求复杂),不过我们今天是入门尝试,暂不选择很复杂的例子
- 从知识库查找业务上下游的表、字段关系
- 将用户输入与表关系(业务上下文),全部传递给deepseek
- 使用deepseek生成sql语句,驱动http接口执行动态SQL(不安全,不可靠)
- 那分两步进行,先返回SQL语句,再到大屏设计器里面去绑定语句,这样的话客户自己也可以定制一些大屏了。
开整
在dify里面创建一个工作流应用
在工作流面板上右键,增加各种节点
如我们前面提到的,我们总共需要2个节点来完成,在加上一个开始和一个结束,总共需要四个节点
选中每一种不同类型的节点,都有这个节点的单独属性设置。
开始节点,增加一个输入字段,接受外部的输入
增加一个知识库检索节点,拖拽建立关系,并接受上一个节点的输出,作为本节点的输入
创建一个LLM模型,将知识库的输入绑定的LLM的上下文
重点,提示词和知识库
- 提示词的重要性我们前面讲过了,可能需要反复尝试
# MYSQL语法生成助手
## 定位
分析用户的输入,生成MYSQL代码
## 能力
- 分析用户输入,提取有用的内容
- 请输出MYSQL代码,不输出其他内容
- 只从用户输入中查找数据表、数据字段的映射关系,如果没有找到对应的数据映射关系,则不进行输出
- 只输出最正确内容的唯一一条
- 只输出select语句,对于delete,update,drop等语句不输出
- 只有用户指定的字段才进行查询输出,不要随便轻易输出*,除非用户没有限定条件
- 限定mysql5.7兼容语法,不要输出其他版本的语法
## 示例
- **用户**:查询用户
- **助手**:select * from users
- **用户**:查询年龄在20岁以上的女性用户,输出姓名,身份证号码
- **助手**:select name,idcard from users where age>=20 and sex=1
- 引用的业务内容上下文,也就是知识库,这个时候就需要精心编辑了,如果你有更好的更简洁的办法,请告诉我,例如直接导入sql表结构
为了验证确实采纳了业务规则,而不是通用回答,我特意把表名增加了前缀vp_xxx
# 表名:vp_xxx_user(用户表)
## 字段:
- id(用户ID,主键)
- name(用户名)
- email(用户邮箱)
- idcard(身份证)
# 表名:vp_xxx_order(订单表)
## 字段:
- order_id(订单ID,主键)
- user_id(用户ID,关联用户表)
- amount(订单金额)
- img_url(图片地址)
# 表名:vp_xxx_customer(客户表)
## 字段:
- id(用户ID,主键)
- name(用户名)
- email(用户邮箱)
- idcard(身份证)
最后一个节点是结束节点,既把一串流程下来的结果输出。
还可以追踪一下每一步的执行情况
验证一下
输入:查询所有年龄在20岁以上的客户,返回姓名和身份证。注意客户与用户的区别
输入:查询所有年龄在20岁以上的用户,返回姓名和身份证。注意客户与用户的区别
输入:查询2024年9月的物业合同。这个表在知识库并不存在,输出了错误的语句,提示词还需要调优
输入:查询2024年9月的订单,返回了正确的结果。
后记
这个例子非常简单,不足以说明能或者不能满足直接驱动业务,但是最少是一种尝试,清晰的知识库、良好的提示词与约束限定、反复的调优,应该是可以满足AI直接驱动业务的。
网站采集工具firecrawl
参考
反正就是一个很牛逼的网站爬取工具,支持纯JS网站,也就是现在流行的VUE等没有html的网站,原理是集成了一个无头chrome浏览器,等页面渲染了才爬取。
特性
- 整站爬取、单个页面爬取、纯JS网站爬取
- 提取为LLM支持的markdown格式,当然了,直接爬取HTML是基本操作
- 只抓取main页面,无意义的重复内容
相关文档和参考地址
- 官方帮助:https://docs.firecrawl.dev/introduction
- 网上的资料很多是V0版本的,但是现在firecrawl已经升级到V版本啦,而且v0版本将在2025年4月1日下线:https://docs.firecrawl.dev/v1-welcome
- github源代码地址:https://github.com/mendableai/firecrawl
安装
下载源代码后,docker-compose build 生成镜像,再使用docker-compose up -d 运行
playwright-service
这是处理纯JS网站的服务,例如现在几乎所有的网站都是动态生成的,所以这个服务是必须的
极简.env文件,其实不要也可以跑
# 核心配置
NUM_WORKERS_PER_QUEUE=8
PORT=3002
HOST=0.0.0.0
REDIS_URL=redis://redis:6379
REDIS_RATE_LIMIT_URL=redis://redis:6379
PLAYWRIGHT_MICROSERVICE_URL=http://playwright-service:3000/html
# 数据库及其他可选配置
USE_DB_AUTHENTICATION=false
API调用,现在GPT很强大,不懂的文GPT吧
- 整站爬取
curl -X POST http://localhost:3002/v1/crawl \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"url": "https://docs.firecrawl.dev",
"limit": 100,
"scrapeOptions": {
"formats": ["markdown", "html"]
}
}'
- 获取爬取状态、或者说是爬取的结果
curl -X GET http://localhost:3002/v1/crawl/<jobid>
- 抓取单个 URL
curl -X POST http://localhost:3002/v1/scrape \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"url": "https://docs.firecrawl.dev",
"formats": ["markdown", "html"]
}'
- 获取网站地图
curl -X POST http://localhost:3002/v1/map \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"url": "https://firecrawl.dev"
}'
``
- 执行搜索
```shell
curl -X POST http://localhost:3002/v1/search \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"query": "AI tools",
"limit": 5,
"scrapeOptions": {
"formats": ["markdown"]
}
}'
- 结构化数据
curl -X POST http://localhost:3002/v1/extract \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"url": "https://example.com",
"extract": {
"schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"price": {"type": "number"}
}
}
}
}'
- 批量抓取`
curl -X POST http://localhost:3002/v1/batch/scrape \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{
"urls": ["https://example1.com", "https://example2.com"],
"options": {
"formats": ["markdown"]
}
}'
deepseek提示词小技巧
提示词对模型的影响非常大,如果个人的写作习惯,描述的并不规范,建议大致意思写好后,放到wps使用更正式风格更正一下
。deepseek模型的提示词设计一般分为三个区块
# 威海智慧谷智慧园区智能问答助手
## 定位
我叫小智,是威海智慧谷智慧园区智能问答助手,是一个专为园区管理、企业员工及访客设计的智能交互平台。旨在通过自然语言处理技术,提供即时、准确的园区相关信息和服务支持。
## 知识储备
- 放在<context></context>XML标签内的内容是你的知识储备。
- 你的回答应当精确、简洁,并易于理解。
- 若您无法提供准确的答案,请直接回复“对不起,这个问题我不会回答”,随后立即终止对话,切勿添加任何无关内容。
## 交互示例
- **用户**:最近的咖啡厅在哪里?
- **助手**:贵宾,您好,园区内最近的咖啡厅位于A栋一楼,营业时间为早上8点到晚上8点。您可以通过园区导航系统找到具体位置。希望我的服务能帮助您。如有任何问题,欢迎随时咨询。
- 回答的时候不要添加额外的内容,以便于后续程序化处理
## 知识储备
若您无法提供准确的答案,请直接回复“对不起,这个问题我不会回答”,随后立即终止对话,切勿添加任何无关内容。
- 限定知识范围
## 知识储备
放在<context></context>XML标签内的内容是你的知识储备。
- 友好的语气回答
## 交互示例
- **用户**:最近的咖啡厅在哪里?
- **助手**:贵宾,您好,园区内最近的咖啡厅位于A栋一楼,营业时间为早上8点到晚上8点。您可以通过园区导航系统找到具体位置。希望我的服务能帮助您。如有任何问题,欢迎随时咨询。
deepseek开发流程
deepseek的工作路径
根据公开资料显示,LLM的工作方式如下。
- 人工部分:
采集和创建知识库,可以是文档,结构化数据等,需要进行段落分割,存放到向量数据库。
- 程序部分
可以是dify等一类的平台,也可以是使用OpenaAI、http接口调用。初创和小公司推荐使用dify。
- 模型运行部分
这个就不用说了,全国会搞模型的也没几个,可以调ollama运行的本地模型,也可以调用云上模型。
- 问题,向量化归哪个环节呢
向量化是归于人工部分还是程序部分呢,知识库被向量化的好坏,对模型有阵非常大的影响,我们可以选择手工向量化,也可以使用程序自动向量化
学习路径
既然我们已经弄清楚了deepseek的工作路径,第一步要做的事情就是准备知识库。
采集知识
import requests
import json
bas_url = "http://localhost:3002/v1/scrape"
headers = {"Content-Type": "application/json"}
req_data = {
"url": "http://www.eweihai.gov.cn/art/2025/3/10/art_159136_5310185.html",
"formats": ["markdown", "links"],
"includeTags": [".page-bd.article-bd"],
"onlyMainContent": True,
}
response = requests.post(bas_url, headers=headers, data=json.dumps(req_data))
print(response.json())
构建知识库
编程方式连接到ollama运行模型
import openai
base_url = "http://192.168.0.11:11434/v1"
api_key = "sk-"
## 阻塞式
response = client.chat.completions.create(
model="deepseek-r1:1.5b",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
stream=False,
)
print(response.choices[0].message.content)
## 流式
# response = client.chat.completions.create(
# model="deepseek-r1:1.5b",
# messages=[
# {"role": "system", "content": "You are a helpful assistant."},
# {"role": "user", "content": "Hello!"},
# ],
# stream=True,
# )
# for chunk in response:
# # 检查块中是否有内容
# if chunk.choices and chunk.choices[0].delta.content:
# print(chunk.choices[0].delta.content)
通义千问&&deepseek模型对比
- 知识库:威海小智问答
- 问题:如何办理停车月卡
耗时与效果
结论:客服助手选择 deepseek-v3 和 qwen-plus , 另外,deepseek-chat应该就是deepseek-v3