Files
StudyNote/项目/ai零代码.md
2026-02-13 23:38:38 +08:00

19 KiB
Raw Blame History

后端初始化

初始化

springweb

lombok

mysqldriver

把apprication.properited 改成yml格式

然后加入

spring:
  application:
    name: zds-ai  项目名字

server:
  port: 2778
  servlet:
    context-path: /api  后端访问的路径吧

依赖整合

hutool knife4j


        <!-- 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配置

# springdoc-openapi
springdoc:
  group-configs:
    - group: 'default'
      packages-to-scan: com.zds.zdsai.controller
# knife4j
knife4j:
  enable: true
  setting:
    language: zh_cn

写模板代码 自定义异常

已经在包中 用就可以了

全局跨域配置(样板代码)

直接在config包中 样板代码

@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

npm i --save ant-design-vue@4.x

全局装配

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
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 官网自己去学把

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

接口权限

1.未登录也可以使用 2登录用户才能使用 3未登录也可以使用但是登录用户能进行更多操作比如登录后查看全文 4仅管理员才能使用

mybatis flex

生成器 不用学看官方就行

然后就是说 这个

entity是数据库与实体类的连接

@MapperScan

可能扫描到mapper包

Dto就是业务代码互相传递的类

Vo是脱敏的类

用户登录接口

 @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先引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1在annotation包下 创建注解类

@Target(ElementType.METHOD) //在方法上的注解
@Retention(RetentionPolicy.RUNTIME) //注解的策略
public @interface AuthCheck {

    /**  
     * 必须有某个角色
     */
    String mustRole() default "";
}

2 创建一个切面 在aop包里面

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的不支持那么大的 所以会少那么一些

加个样板代码

/**
 * 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

import { useLoginUserStore } from '@/stores/loginUser.ts'

const loginUserStore = useLoginUserStore()
loginUserStore.fetchLoginUser()

获取用户

Ai大模型接入

langchain4j

引入依赖

<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

# 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

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

然后就是 写一个代理工厂 来创建实例

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 这样我们就可以 更好的展示相关的内容了

package com.zds.zdsai.ai.model;

import lombok.Data;
//Html代码结果
@Data
public class HtmlCodeResult {

    /**
     * Html代码
     */
    private String htmlCode;

    //描述
    private String description;
}

image-20251111194720859

然后改一下接口的返回值 这样就好了

  • 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

  1. 加上注释 Description("HTML代码")

    这样 我们发送这个model的时候 就可以带上描述 这样ai可以更好的理解

设计模式 门面模式

先建立枚举类 CodeGenTypeEnum

然后再core包下面写一个文件存储类FileSaver 就是将代码存到文件中

image-20251111205105080

先写出来了 门面 门面调用aiCodeGeneratorService 和 Filesave

AiCodeGeneratorFacade

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

首先我们应该 修改工厂 符合流式

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 中 添加两个适合流式的代码


    /**
     * 生成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);