This commit is contained in:
2026-02-13 23:38:38 +08:00
commit 0207414c50
551 changed files with 35558 additions and 0 deletions

833
项目/ai零代码.md Normal file
View File

@@ -0,0 +1,833 @@
# 后端初始化
## 初始化
springweb
lombok
mysqldriver
把apprication.properited 改成yml格式
然后加入
```yml
spring:
application:
name: zds-ai 项目名字
server:
port: 2778
servlet:
context-path: /api 后端访问的路径吧
```
### 依赖整合
hutool knife4j
```xml
<!-- hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
<!-- knife4j接口文档--> -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
```
改xml配置
```yml
# springdoc-openapi
springdoc:
group-configs:
- group: 'default'
packages-to-scan: com.zds.zdsai.controller
# knife4j
knife4j:
enable: true
setting:
language: zh_cn
```
### 写模板代码 自定义异常
已经在包中 用就可以了
### 全局跨域配置(样板代码)
直接在config包中 样板代码
```java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("*");
}
}
```
# 前端初始化
emm
使用nvm 来管理node.js
安装ant design
```shell
npm i --save ant-design-vue@4.x
```
全局装配
```typescript
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Antd)
app.mount('#app')
```
安装
axios
```
npm install axios
```
```ts
import axios from 'axios'
import { message } from 'ant-design-vue'
// 创建 Axios 实例
const myAxios = axios.create({
baseURL: 'http://localhost:8123/api',
timeout: 60000,
withCredentials: true,
})
// 全局请求拦截器
myAxios.interceptors.request.use(
function (config) {
// Do something before request is sent
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
},
)
// 全局响应拦截器
myAxios.interceptors.response.use(
function (response) {
const { data } = response
// 未登录
if (data.code === 40100) {
// 不是获取用户信息的请求,并且用户目前不是已经在用户登录页面,则跳转到登录页面
if (
!response.request.responseURL.includes('user/get/login') &&
!window.location.pathname.includes('/user/login')
) {
message.warning('请先登录')
window.location.href = `/user/login?redirect=${window.location.href}`
}
}
return response
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error)
},
)
export default myAxios
```
## openapi 前端代码生成器
[@umijs/openapi - npm](https://www.npmjs.com/package/@umijs/openapi) 官网自己去学把
```
npm i --save-dev @umijs/openapi
npm i --save-dev tslib
```
```
export default {
requestLibPath: "import request from '@/request'",
schemaPath: 'http://localhost:2778/api/v3/api-docs',
serversPath: './src',
}
```
# 用户登录
<img src="https://pic.code-nav.cn/course_picture/1608440217629360130/ahLctL73Pp9LatVp.svg" alt="img" style="zoom: 80%;" />
接口权限
1.未登录也可以使用
2登录用户才能使用
3未登录也可以使用但是登录用户能进行更多操作比如登录后查看全文
4仅管理员才能使用
### mybatis flex
生成器 不用学看官方就行
然后就是说 这个
entity是数据库与实体类的连接
### @MapperScan
可能扫描到mapper包
### Dto就是业务代码互相传递的类
Vo是脱敏的类
### 用户登录接口
```java
@Override
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
//校验参数
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
}
if (userAccount.length() < 4) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过知短");
}
if (userPassword.length() < 8) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过知短");
}
//加密密码
String encryptPassword = getEncryptPassword(userPassword);
//查询用户
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encryptPassword);
User user = this.mapper.selectOneByQuery(queryWrapper);
ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
//记录用户的登录态
request.getSession().setAttribute(USER_LOGIN_STATE, user);
return this.getLoginUserVO(user);
}
```
**request.getSession().setAttribute(USER_LOGIN_STATE, user);** 很重要的 种session
## 注解(重要)
### 0先引入依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
```
### 1在annotation包下 创建注解类
```java
@Target(ElementType.METHOD) //在方法上的注解
@Retention(RetentionPolicy.RUNTIME) //注解的策略
public @interface AuthCheck {
/**
* 必须有某个角色
*/
String mustRole() default "";
}
```
### 2 创建一个切面 在aop包里面
```java
package com.zds.zdsai.aop;
import com.zds.zdsai.annotation.AuthCheck;
import com.zds.zdsai.exception.BusinessException;
import com.zds.zdsai.exception.ErrorCode;
import com.zds.zdsai.model.entity.User;
import com.zds.zdsai.model.enums.UserRoleEnum;
import com.zds.zdsai.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 执行拦截
*
* @param joinPoint 切入点
* @param authCheck 权限校验注解
*/
//切点 -->你在什么时候拦截这个方法
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
String mustRole = authCheck.mustRole();
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 当前登录用户
User loginUser = userService.getLoginUser(request);
UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
// 不需要权限,放行
if (mustRoleEnum == null) {
return joinPoint.proceed();
}
// 以下为:必须有该权限才通过
// 获取当前用户具有的权限
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
// 没有权限,拒绝
if (userRoleEnum == null) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 要求必须有管理员权限,但用户没有管理员权限,拒绝
if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 通过权限校验,放行
return joinPoint.proceed();
}
}
```
**@Aspect**来定义这是一个切面
**@annotation(authcheck)** --》就是通知 只有在有authcheck注解的 才能进行权限校验
**joinPoint.proceed()**; 放行
## 前端id与后端id传值不一致
因为后端的int值太tmd大 而前端的js的不支持那么大的 所以会少那么一些
加个样板代码
```java
/**
* Spring MVC Json 配置
*/
@JsonComponent
public class JsonConfig {
/**
* 添加 Long 转 json 精度丢失的配置
*/
@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(module);
return objectMapper;
}
}
```
## 前端
前端想要获取用户的登陆数据 是不是每次刷新页面 就要写一个fetch函数来获取呢
不要 我们直接写一个引入pinna
> pinna
```ts
import { useLoginUserStore } from '@/stores/loginUser.ts'
const loginUserStore = useLoginUserStore()
loginUserStore.fetchLoginUser()
```
获取用户
# Ai大模型接入
## langchain4j
> 引入依赖
```xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
<version>1.1.0-beta7</version>
</dependency>
```
> application.xml
```yml
# AI
langchain4j:
open-ai:
chat-model:
base-url: https://api.deepseek.com
api-key: <Your API Key>
model-name: deepseek-chat
log-requests: true
log-responses: true
```
### 创建接口AiCodeGeneratorService
```java
package com.zds.zdsai.ai;
import dev.langchain4j.service.SystemMessage;
/**
* AI代码生成服务
*/
public interface AiCodeGeneratorService {
/**
* 生成HTML代码
*
* @param userMessage 用户输入
* @return 代码
*/
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
String generateCode(String userMessage);
/**
* 生成多文件代码
*
* @param userMessage 用户输入
* @return 代码
*/
@SystemMessage(fromResource = "prompt/codegen-multi-file-system-prompt.txt")
String generateMultiFileCode(String userMessage);
}
```
写提示词 当然 如果词的长度少 你可以不用建立txt
然后就是 写一个代理工厂 来创建实例
```java
package com.zds.zdsai.ai;/*
*@auther 郑笃实
*@version 1.0
*
*/
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
//Ai服务创建工厂
public class AiCodeGeneratorServiceFactory {
//指定使用的大模型
@Resource
private ChatModel chatModel;
/**
* 创建AI 代码生成器代码服务
* @return
*/
@Bean
public AiCodeGeneratorService createAiCodeGeneratorService() {
return AiServices.create(AiCodeGeneratorService.class, chatModel);
}
}
```
是这样的 所以及就获得了一个String的 返回结果
但是问题来了你该如何将String给用户看直接看也行 或许
我们可以将String转化为JSON 让ai按照格式生成相应key 这样我们就可以 更好的展示相关的内容了
```java
package com.zds.zdsai.ai.model;
import lombok.Data;
//Html代码结果
@Data
public class HtmlCodeResult {
/**
* Html代码
*/
private String htmlCode;
//描述
private String description;
}
```
![image-20251111194720859](F:\记录\项目\image-20251111194720859-1762861641318-1.png)
然后改一下接口的返回值 这样就好了
- method: POST
- url: https://api.deepseek.com/chat/completions
- headers: [Authorization: Beare...c6], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
"model" : "deepseek-chat",
"messages" : [ {
"role" : "system",
"content" : "你是一位资深的 Web 前端开发专家,精通 HTML、CSS 和原生 JavaScript。你擅长构建响应式、美观且代码整洁的单页面网站。\r\n\r\n你的任务是根据用户提供的网站描述生成一个完整、独立的单页面网站。你需要一步步思考并最终将所有代码整合到一个 HTML 文件中。\r\n\r\n约束:\r\n1. 技术栈: 只能使用 HTML、CSS 和原生 JavaScript。\r\n2. 禁止外部依赖: 绝对不允许使用任何外部 CSS 框架、JS 库或字体库。所有功能必须用原生代码实现。\r\n3. 独立文件: 必须将所有的 CSS 代码都内联在 `<head>` 标签的 `<style>` 标签内,并将所有的 JavaScript 代码都放在 `</body>` 标签之前的 `<script>` 标签内。最终只输出一个 `.html` 文件,不包含任何外部文件引用。\r\n4. 响应式设计: 网站必须是响应式的,能够在桌面和移动设备上良好显示。请优先使用 Flexbox 或 Grid 进行布局。\r\n5. 内容填充: 如果用户描述中缺少具体文本或图片,请使用有意义的占位符。例如,文本可以使用 Lorem Ipsum图片可以使用 https://picsum.photos 的服务 (例如 `<img src=\"https://picsum.photos/800/600\" alt=\"Placeholder Image\">`)。\r\n6. 代码质量: 代码必须结构清晰、有适当的注释,易于阅读和维护。\r\n7. 交互性: 如果用户描述了交互功能 (如 Tab 切换、图片轮播、表单提交提示等),请使用原生 JavaScript 来实现。\r\n8. 安全性: 不要包含任何服务器端代码或逻辑。所有功能都是纯客户端的。\r\n9. 输出格式: 你的最终输出必须包含 HTML 代码块,可以在代码块之外添加解释、标题或总**结性文字。格式如下:\r\n\r\n```html\r\n... HTML 代码 ...\r\n\r\n"
}, {
"role" : "user",
"content" : "做个程序员鱼皮的工作记录小工具\nYou must answer strictly in the following JSON format: {\n\"htmlCode\": (type: string),\n\"description\": (type: string)\n}"
} ],**
"stream" : false
}
> 但是 啊 但是 如果ai生成的文字太大 以至于 Stirng类型装不下 或者ai不听你的 json不按照格式来 怎么办呢??、
1. 设置maxtoken
2.严格限制
```
langchain4j:
open-ai:
chat-model:
strict-json-schema: true
response-format: json_object
```
3. 加上注释 Description("HTML代码")
这样 我们发送这个model的时候 就可以带上描述 这样ai可以更好的理解
## 设计模式 门面模式
先建立枚举类 **CodeGenTypeEnum**
然后再core包下面写一个文件存储类FileSaver 就是将代码存到文件中
![image-20251111205105080](F:\记录\项目\image-20251111205105080.png)
先写出来了 门面 门面调用aiCodeGeneratorService 和 Filesave
**AiCodeGeneratorFacade**
```java
package com.zds.zdsai.core;
import com.zds.zdsai.ai.AiCodeGeneratorService;
import com.zds.zdsai.ai.model.HtmlCodeResult;
import com.zds.zdsai.ai.model.MultiFileCodeResult;
import com.zds.zdsai.exception.BusinessException;
import com.zds.zdsai.exception.ErrorCode;
import com.zds.zdsai.model.enums.CodeGenTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.io.File;
/**
* AI 代码生成外观类,组合生成和保存功能
*/
@Service
public class AiCodeGeneratorFacade {
@Resource
private AiCodeGeneratorService aiCodeGeneratorService;
/**
* 统一入口:根据类型生成并保存代码
*
* @param userMessage 用户提示词
* @param codeGenTypeEnum 生成类型
* @return 保存的目录
*/
public File generateAndSaveCode(String userMessage, CodeGenTypeEnum codeGenTypeEnum) {
if (codeGenTypeEnum == null) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "生成类型为空");
}
return switch (codeGenTypeEnum) {
case HTML -> generateAndSaveHtmlCode(userMessage);
case MULTI_FILE -> generateAndSaveMultiFileCode(userMessage);
default -> {
String errorMessage = "不支持的生成类型:" + codeGenTypeEnum.getValue();
throw new BusinessException(ErrorCode.SYSTEM_ERROR, errorMessage);
}
};
}
/**
* 生成 HTML 模式的代码并保存
*
* @param userMessage 用户提示词
* @return 保存的目录
*/
private File generateAndSaveHtmlCode(String userMessage) {
HtmlCodeResult result = aiCodeGeneratorService.generateHtmlCode(userMessage);
return CodeFileSaver.saveHtmlCodeResult(result);
}
/**
* 生成多文件模式的代码并保存
*
* @param userMessage 用户提示词
* @return 保存的目录
*/
private File generateAndSaveMultiFileCode(String userMessage) {
MultiFileCodeResult result = aiCodeGeneratorService.generateMultiFileCode(userMessage);
return CodeFileSaver.saveMultiFileCodeResult(result);
}
}
```
## SSE结构化流式输出
等待全部返回太麻烦了 于是我们选择 SSE结构化流式输出
> langchain4j +Reactor
![image-20251111212329732](F:\记录\项目\image-20251111212329732.png)
首先我们应该 修改工厂 符合流式
```java
package com.zds.zdsai.ai;/*
*@auther 郑笃实
*@version 1.0
*
*/
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.StreamingChatModel;
import dev.langchain4j.service.AiServices;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//Ai服务创建工厂
@Configuration
public class AiCodeGeneratorServiceFactory {
//指定使用的大模型
@Resource
private ChatModel chatModel;
@Resource
private StreamingChatModel streamingChatModel;
/**
* 创建AI 代码生成器代码服务
* @return
*/
@Bean
public AiCodeGeneratorService createAiCodeGeneratorService() {
return AiServices.builder(AiCodeGeneratorService.class)
.chatModel(chatModel)//哪个chatmodel
.streamingChatModel(streamingChatModel)//哪个streamingChatModel
.build();
}
}
```
然后再在**AiCodeGeneratorService** 中 添加两个适合流式的代码
```java
/**
* 生成HTML代码
*
* @param userMessage 用户输入
* @return 代码
*/
@SystemMessage(fromResource = "prompt/codegen-html-system-prompt.txt")
Flux<String> generateHtmlCodeStream(String userMessage);
/**
* 生成多文件代码
*
* @param userMessage 用户输入
* @return 代码
*/
@SystemMessage(fromResource = "prompt/codegen-multi-file-system-prompt.txt")
Flux<String> generateMultiFileCodeStream(String userMessage);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

243
项目/公司.md Normal file
View File

@@ -0,0 +1,243 @@
11.27
- [x] 提交测试
- [x] 开发中心 产品详情 测试设备 后端接口 郑亚琦\李文斌
1. 前端给我传SN码
2. 我把SN码插入产品测试设备关联表
3. 前端需要什么 todo 更新数据库!!!
- | 设备名称 | 设备编码 | MCU固件 | 备注 | 在线状态 | 最后在线时间 | 操作 |
| :------------: | :-------: | :-----: | :--: | :------: | :----------: | :--: |
| `product_code` | device_sn | | | | | |
- [x] 开发中心 调试设备开发 设备坐标 后端接口 郑亚琦\李文斌
- [x] 开发中心 研发分组,研发分组设备列表 后台接口 郑亚琦\李文斌
- [x] 新增设备
- [x] 批量导入
- [ ] 批量导出//报错
- [x] 批量验证
- [x] 批量删除
- [x] 按照设备编码搜
- [x] 按照产品名称搜
- [x] dd批量转移
<img src="F:\记录\image-20251202135729648.png" alt="image-20251202135729648" style="zoom:33%;" />
- [x] 开发中心 固件升级 任务范围 全部设备\指定分组\指定设备 指定设备内是否勾选测试设备 郑亚琦\李文斌
- 只显示测试设备
- [ ] 开发中心 固件升级 新增升级任务后, 扫描生产的设备表(依赖条件 分组/指定设备/依赖版本/依赖型号 版本<=升级版本), 插入升级明细表 郑亚琦\李文斌
- [ ] 开发中心 固件升级 升级任务表明细 数字版本 郑亚琦\李文斌
//12.5代办
1.开发者中心->固件升级->升级任务->编辑接口 添加指定分组的字段 ++
2.研发分组->设备列表->根据产品代号productCode查询设备 +++
3.开发者中心->固件升级->升级任务->编辑接口->指定设备->对应的List接口->添加是否测试设备字段 +++
4.研发分组->设备导出->报错 +6++
5.开发者中心->固件升级->升级任务->编辑接口 添加指定分组id,指定分组名称的字段 ????
6.ccapp 添加设备sn去掉空格 +++
//12.8
12.9
- [x] 关于设备info的返回 导致查询失败
- [x] 升级未完成不能升级
- [x] 升级可以暂停 但是也只有 1==待执行2=进行中 的可以暂停 其他的不能暂停
- [ ] 数字版本只能升级 不能下降
- [x] 重复插入 没有明确说要不要
- [x] 该分组下的设备 与任务产品不同会报错
- [x] 产品代码 A-Z和a-z 0-9 --》数据库问题
- [x] 找到升级成功/未成功的标识 TaskStatus
- [x] 研发分组列表 插入重复和空格和换行
- [x] 任务升级暂停 可以是 整个任务暂停
- [x] 也可以是单个的设备在这个任务中暂停
12.10
- [ ] 20251210 admin 数据中心 - 项目管理 20251210 郑亚琦
- [ ]
- [ ] 20251210 admin 数据中心 - 测站管理 20251210 郑亚琦
- [ ] 设备列表和设备审核 的MCU字段不一致
设备审核 hzPcDeviceInfo/list
设备列表 hzCcDeviceInfo/list
- [x] 依赖数字版本 数组 在里面的
- [x] 依赖型号一样的
- [x] 固件版本 默认 小于g固件版本
- [x] 传SN 返回 设备的所有的
- [x] 新增数字版本不能<=0
- [x] '模组固件数字版本', 0
- [x] MCU固件数字版本', 1
12.12
- [ ] 有一个数据对不上
- [ ] 项目所属组织
批量删除
项目管理 组织名称
测站中 设备数量和所属项目
```dw!ef@rv3GT4S5YD6U7
ProjectName string `json:"projectName" dc:"项目名称"`
```
12.16
[ ] 20251215 admin 数据中心 - 设备详情 属性展示\属性设置\属性历史\功能调用\设备日志\设备事件\子设备列表绑定\设备模拟\设备标签 20251215 开发李
- [x] 项目添加测站 项目添加设备
- [x] 1.项目 对设备 增加 删除
- [x] 2.项目对测站的增加
- [x] 3.测站对设备的增加 删除
12.17
## bug
- [ ] 在测站/项目中 对于设备的修改 会将其他的给覆盖掉
## Misson
- [ ]
## 思路
```
IotDevicePropertyLatest 获取设备最新属性数据
调用方法
service.DeviceCache().GetDeviceProperty(context.Background(), contexts.GetTenantId(ctx), req.DeviceKey, true);// 获取设备属性缓存
property, err = bcache.GetIotDeviceProperty(ctx, tenantId, deviceKey) //从cach中找
if err != nil {
return nil, err
}
if len(property) == 0 && isLoad {
// 从时序数据库查询
property2, err := pubservice.IotDeviceProperty().GetLatestProperty(ctx, tenantId, deviceKey)
```
### 从时序数据库中
// GetLatestProperty 获取设备最新的属性值
先从缓存中找 func (s *sDeviceCache) GetDevice(ctx context.Context, tenantId, deviceKey string) (device *pubmodel.IotDeviceInfoCache, err error) {
里面有func GetIotDeviceInfo(ctx context.Context, tenantId, deviceKey string) (data *pubmodel.IotDeviceInfoCache, err error) {
最底层的
‘缓存找不到 从数据库中找
```
}
tsdDb := tsdb.NewDB(tsdb.GetDefaultOption())
defer tsdDb.Close()
```
12.19
## BUG
- [ ] hzdatacenter/hzDaProjectProductRel/edit 新增后,List返回的新增的代号/名称/描述,是错误的
## TASK
- [x] 产品 一键导入产品

97
项目/智能云库.md Normal file
View File

@@ -0,0 +1,97 @@
# 后端初始化
## 跨域问题
前端与后端的端口号不一致----》跨域问题
### 解决方案
- 后端来支持跨域
- 代理 nginx 第三方脚手架
```java
package com.zds.zds_picture_backend.conifg;/*
*@auther 郑笃实
*@version 1.0
*
*/
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 CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
// 放行哪些请求方式
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// 放行哪些请求头
.allowedHeaders("*")
// 暴露哪些响应头
.exposedHeaders("*");
}
}
```
使用这个就可以让解决跨域问题
## lombok报错
[【已解决】java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have-CSDN博客](https://blog.csdn.net/weixin_36829761/article/details/136287081)
```xml
<!-- Lombok只保留一条provided由 compiler plugin 负责 annotation processing -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
```
## @MapperScan("com.zds.zds_picture_backend.mapper")
这是mybatis-plus的注解用于扫描mapper接口
mapper层是针对数据库的
## 在进行跨域以后前后端依旧不连通
那有可能是 前端发送的是HTTPS的 而后端springboot内置的tomcat是只能支持http协议的 协议不同所有可能导致前端调用后端的时候出现问题
## LoginUserVO
```java
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request)
```
为什么需要这个 ```HttpServletRequest request``` 因为我们登录账号的时候需要给他种session 所以我们需要request
## id精度问题
![image-20250925180747838](F:\记录\image-20250925180747838.png)
从后端调出来的id精度可能跟后端的精度不一致甚至可能会导致json中的和展示的不一样

View File

@@ -0,0 +1,101 @@
## 统一异常管理:
首先我定义了 自定义异常类BusinessException全局异常处理器GlobalExceptionHandler并且自定义了一些错误码ErrorCode
1. 出现业务逻辑的时候 会抛出我的自定义异常类 然后传入相应的错误码
2. 全局异常处理器会捕获这个 自定义异常类 并返回一个统一的响应结构给前端。
3. 如果是未预料到的系统异常RuntimeException同样会被 GlobalExceptionHandler 拦截返回固定的系统错误码50000和提示信息系统内部异常
## Redis + Caffeine 多级缓存:
Redis 是一种高性能的分布式 KV 存储,支持丰富的数据结构和高并发访问。它能把缓存数据存储在内存中,单节点读写 QPS 可达 10 万,常用于做分布式缓存或分布式锁。 Caffeine 是 Java 主流的本地缓存库,运行在 JVM 内部,访问速度比 Redis 更快,但仅能在单个服务实例内使用,无法在多台服务器之间共享数据。
我在云图库项目中将二者结合,形成多级缓存:
1. 用户请求先查询 Caffeine 本地缓存
2. 若本地缓存未命中,则查询 Redis 缓存
3. 若 Redis 也未命中,则回源到数据库,并将查询结果写回 Redis 与 Caffeine 流程如图:
## 以图搜图功能集成(门面模式)
门面模式Facade Pattern是一种结构型设计模式通过提供一个统一的门面接口把内部复杂的子系统接口隐藏起来从而简化客户端与子系统的交互降低耦合度。
我在云图库项目的“以图搜图”功能中就用到了门面模式:
1. 我有多个独立的爬虫或 API 调用类,分别负责获取搜图页面、解析 HTML 脚本、获取搜图结果等
2. 为了简化调用流程,我创建了一个 ImageSearchApiFacade 门面类,把这几个复杂步骤都封装成一个 searchImage 方法
3. 外部只需一行代码 ImageSearchApiFacade.searchImage(imageUrl) 就能得到最终的相似图片列表,而不必关心内部分多个 API 去爬取、解析、获取搜索结果的细节。 这样做既减少了重复代码,也让功能调用更直观,符合“高层接口简化、子系统细节隐藏”的门面模式思想。
![image-20251111215720515](F:\记录\项目\image-20251111215720515.png)
## 实时协作模块:
![image-20251111220030406](F:\记录\项目\image-20251111220030406.png)
## AI 集成模块
![image-20251111220154137](F:\记录\项目\image-20251111220154137.png)
# 朋友
## 距离算法
距离算法是一种用于度量两个字符串之间的相似度或者差异性的算法
## 分布式
Redis 是一个开源的、基于内存的 K/V 存储中间件。由于基于内存其读写性能非常高很适用于缓存。此外Redis 支持多种数据结构、各类编程语言的客户端、支持持久化数据,其生态也非常广泛。
本项目中,我使用*** Redis 分布式 Session*** 来代替 *** Tomcat 本地的 Session 存储*** ,能够在分布式多机场景下保证获取登录用户信息的一致性。用 Redis 实现分布式 Session 的优点是非常简单方便,只需要引入 Redis 和 spring-session-data-redis 依赖,然后在配置文件中指定 Redis 的地址和 session 的 store-type 为 redis即可自动生效不用自己额外编码。
## TOP N
你提到使用优先队列来减少 TOP N 运算过程中的内存占用,能否解释一下优先队列的特点和在项目中的具体应用?
在项目中,使用**优先队列**来存储从**数据库中查询出来的 TopN **最相似用户。把**`相似度作为优先级`**,淘汰相似度小于当前队列 TopN 的用户,存入相似度大于当前队列 TopN 的用户,**将队列的元素个数始终维持在 N 个**,从而减少了内存占用。
## 接口幂等性
接口幂等性:对于相同的请求,无论调用多少次,结果都应保持一致。项目中主要就是对创建队伍这种请求如果连点多次会超出每个用户规定的创建队伍最大数量
**redission **是一个基于redis的**数据网络** 提供了开箱即用的分布式锁功能
- 获取锁当客户端请求获取锁时Redisson 会向 Redis 发送一个 SETNX 命令,尝试将一个特定的键(锁的标识)设置为一个特定的值(客户端标识),并设置锁的超时时间。
- 争用锁:如果多个客户端同时尝试获取同一个锁,只有一个客户端能够成功设置键的值,其他客户端的 SETNX 命令将失败,它们会继续尝试获取锁。
- 锁超时为了防止某个客户端获取锁后发生异常导致锁永远不会被释放Redisson 设置了锁的超时时间。当锁的超时时间到达后Redisson 会自动释放锁,允许其他客户端获取锁。
- 释放锁:当客户端执行完锁保护的操作后,可以主动释放锁,这将删除锁的标识键,或者锁的自动超时也会导致锁的释放。
- 锁的可重入性Redisson 支持可重入锁,允许同一客户端多次获取同一个锁,然后多次释放锁。只有所有获取锁的次数都释放后,锁才会被完全释放。
- 锁的续期如果一个客户端在持有锁时锁的超时时间即将到期Redisson会自动为锁续期防止锁在操作过程中被自动释放。