Expand EasyCode front workbench features

This commit is contained in:
王鹏
2026-05-23 11:31:38 +08:00
parent 56dbc02526
commit 2c8b74ea8a
70 changed files with 6596 additions and 638 deletions

View File

@@ -10,9 +10,10 @@ public class DeepSeekProperties
private String apiKey;
private String baseUrl = "https://api.deepseek.com";
private String model = "deepseek-chat";
private Integer timeout = 60000;
private Integer timeout = 180000;
private Integer maxTables = 12;
private Integer maxColumnsPerTable = 30;
private Boolean skipSslVerify = false;
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
@@ -26,4 +27,7 @@ public class DeepSeekProperties
public void setMaxTables(Integer maxTables) { this.maxTables = maxTables; }
public Integer getMaxColumnsPerTable() { return maxColumnsPerTable; }
public void setMaxColumnsPerTable(Integer maxColumnsPerTable) { this.maxColumnsPerTable = maxColumnsPerTable; }
public Boolean getSkipSslVerify() { return skipSslVerify; }
public void setSkipSslVerify(Boolean skipSslVerify) { this.skipSslVerify = skipSslVerify; }
public boolean isSkipSslVerify() { return Boolean.TRUE.equals(skipSslVerify); }
}

View File

@@ -0,0 +1,88 @@
package com.ruoyi.generator.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.generator.domain.SourceProject;
import com.ruoyi.generator.service.ISourceProjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/generator/sourceProject")
public class SourceProjectController extends BaseController
{
@Autowired
private ISourceProjectService sourceProjectService;
@PreAuthorize("@ss.hasPermi('generator:sourceProject:list')")
@GetMapping("/list")
public TableDataInfo list(SourceProject sourceProject)
{
startPage();
List<SourceProject> list = sourceProjectService.selectSourceProjectList(sourceProject);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:export')")
@Log(title = "源码项目", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SourceProject sourceProject)
{
List<SourceProject> list = sourceProjectService.selectSourceProjectList(sourceProject);
ExcelUtil<SourceProject> util = new ExcelUtil<SourceProject>(SourceProject.class);
util.exportExcel(response, list, "源码项目数据");
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:query')")
@GetMapping(value = "/{projectId}")
public AjaxResult getInfo(@PathVariable("projectId") Long projectId)
{
return success(sourceProjectService.selectSourceProjectByProjectId(projectId));
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:add')")
@Log(title = "源码项目", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody SourceProject sourceProject)
{
return toAjax(sourceProjectService.insertSourceProject(sourceProject));
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:edit')")
@Log(title = "源码项目", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SourceProject sourceProject)
{
return toAjax(sourceProjectService.updateSourceProject(sourceProject));
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:edit')")
@Log(title = "源码项目状态", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public AjaxResult changeStatus(@RequestBody SourceProject sourceProject)
{
return toAjax(sourceProjectService.changeSourceProjectStatus(sourceProject));
}
@PreAuthorize("@ss.hasPermi('generator:sourceProject:remove')")
@Log(title = "源码项目", businessType = BusinessType.DELETE)
@DeleteMapping("/{projectIds}")
public AjaxResult remove(@PathVariable Long[] projectIds)
{
return toAjax(sourceProjectService.deleteSourceProjectByProjectIds(projectIds));
}
}

View File

@@ -0,0 +1,63 @@
package com.ruoyi.generator.controller;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.generator.domain.SourcePurchase;
import com.ruoyi.generator.service.ISourcePurchaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/generator/sourcePurchase")
public class SourcePurchaseController extends BaseController
{
@Autowired
private ISourcePurchaseService sourcePurchaseService;
@PreAuthorize("@ss.hasPermi('generator:sourcePurchase:list')")
@GetMapping("/list")
public TableDataInfo list(SourcePurchase sourcePurchase)
{
startPage();
List<SourcePurchase> list = sourcePurchaseService.selectSourcePurchaseList(sourcePurchase);
return getDataTable(list);
}
@PreAuthorize("@ss.hasPermi('generator:sourcePurchase:export')")
@Log(title = "源码购买记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, SourcePurchase sourcePurchase)
{
List<SourcePurchase> list = sourcePurchaseService.selectSourcePurchaseList(sourcePurchase);
ExcelUtil<SourcePurchase> util = new ExcelUtil<SourcePurchase>(SourcePurchase.class);
util.exportExcel(response, list, "源码购买记录数据");
}
@PreAuthorize("@ss.hasPermi('generator:sourcePurchase:query')")
@GetMapping(value = "/{purchaseId}")
public AjaxResult getInfo(@PathVariable("purchaseId") Long purchaseId)
{
return success(sourcePurchaseService.selectSourcePurchaseByPurchaseId(purchaseId));
}
@PreAuthorize("@ss.hasPermi('generator:sourcePurchase:edit')")
@Log(title = "源码购买记录", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody SourcePurchase sourcePurchase)
{
return toAjax(sourcePurchaseService.updateSourcePurchase(sourcePurchase));
}
}

View File

@@ -0,0 +1,100 @@
package com.ruoyi.generator.domain;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
public class SourceProject extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long projectId;
@Excel(name = "访问标识")
private String slug;
@Excel(name = "项目名称")
private String projectName;
@Excel(name = "分类")
private String category;
@Excel(name = "封面标识")
private String coverText;
@Excel(name = "项目摘要")
private String summary;
private String descriptionMd;
@Excel(name = "价格")
private Long price;
@Excel(name = "浏览量")
private Long viewCount;
private String tags;
private String modules;
private String scenes;
@Excel(name = "资源名称")
private String resourceName;
private String resourceUrl;
@Excel(name = "资源版本")
private String resourceVersion;
@Excel(name = "资源大小")
private String resourceSize;
@Excel(name = "排序")
private Integer sort;
@Excel(name = "状态", readConverterExp = "0=上架,1=下架")
private String status;
private String delFlag;
private String keyword;
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public String getSlug() { return slug; }
public void setSlug(String slug) { this.slug = slug; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getCoverText() { return coverText; }
public void setCoverText(String coverText) { this.coverText = coverText; }
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getDescriptionMd() { return descriptionMd; }
public void setDescriptionMd(String descriptionMd) { this.descriptionMd = descriptionMd; }
public Long getPrice() { return price; }
public void setPrice(Long price) { this.price = price; }
public Long getViewCount() { return viewCount; }
public void setViewCount(Long viewCount) { this.viewCount = viewCount; }
public String getTags() { return tags; }
public void setTags(String tags) { this.tags = tags; }
public String getModules() { return modules; }
public void setModules(String modules) { this.modules = modules; }
public String getScenes() { return scenes; }
public void setScenes(String scenes) { this.scenes = scenes; }
public String getResourceName() { return resourceName; }
public void setResourceName(String resourceName) { this.resourceName = resourceName; }
public String getResourceUrl() { return resourceUrl; }
public void setResourceUrl(String resourceUrl) { this.resourceUrl = resourceUrl; }
public String getResourceVersion() { return resourceVersion; }
public void setResourceVersion(String resourceVersion) { this.resourceVersion = resourceVersion; }
public String getResourceSize() { return resourceSize; }
public void setResourceSize(String resourceSize) { this.resourceSize = resourceSize; }
public Integer getSort() { return sort; }
public void setSort(Integer sort) { this.sort = sort; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getDelFlag() { return delFlag; }
public void setDelFlag(String delFlag) { this.delFlag = delFlag; }
public String getKeyword() { return keyword; }
public void setKeyword(String keyword) { this.keyword = keyword; }
}

View File

@@ -0,0 +1,50 @@
package com.ruoyi.generator.domain;
import java.util.Date;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
public class SourcePurchase extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long purchaseId;
private Long projectId;
private Long userId;
@Excel(name = "订单号")
private String orderNo;
@Excel(name = "金额")
private Long amount;
@Excel(name = "支付状态", readConverterExp = "0=待支付,1=已支付,2=已取消")
private String payStatus;
@Excel(name = "资源状态", readConverterExp = "0=未发放,1=已发放")
private String resourceStatus;
@Excel(name = "购买时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date purchaseTime;
private String projectName;
public Long getPurchaseId() { return purchaseId; }
public void setPurchaseId(Long purchaseId) { this.purchaseId = purchaseId; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public Long getAmount() { return amount; }
public void setAmount(Long amount) { this.amount = amount; }
public String getPayStatus() { return payStatus; }
public void setPayStatus(String payStatus) { this.payStatus = payStatus; }
public String getResourceStatus() { return resourceStatus; }
public void setResourceStatus(String resourceStatus) { this.resourceStatus = resourceStatus; }
public Date getPurchaseTime() { return purchaseTime; }
public void setPurchaseTime(Date purchaseTime) { this.purchaseTime = purchaseTime; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
}

View File

@@ -19,6 +19,7 @@ public class FrontProject extends BaseEntity
private String backFramework;
private String generateStatus;
private String previewStatus;
private String erDiagram;
private String status;
private List<FrontProjectTable> tables;
@@ -46,6 +47,8 @@ public class FrontProject extends BaseEntity
public void setGenerateStatus(String generateStatus) { this.generateStatus = generateStatus; }
public String getPreviewStatus() { return previewStatus; }
public void setPreviewStatus(String previewStatus) { this.previewStatus = previewStatus; }
public String getErDiagram() { return erDiagram; }
public void setErDiagram(String erDiagram) { this.erDiagram = erDiagram; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public List<FrontProjectTable> getTables() { return tables; }

View File

@@ -7,6 +7,7 @@ public class DatabaseDesignResponse
private Long projectId;
private List<DatabaseTableDesign> tables;
private String sql;
private Object erDiagram;
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
@@ -14,4 +15,6 @@ public class DatabaseDesignResponse
public void setTables(List<DatabaseTableDesign> tables) { this.tables = tables; }
public String getSql() { return sql; }
public void setSql(String sql) { this.sql = sql; }
public Object getErDiagram() { return erDiagram; }
public void setErDiagram(Object erDiagram) { this.erDiagram = erDiagram; }
}

View File

@@ -0,0 +1,57 @@
package com.ruoyi.generator.domain.source;
import java.util.Date;
import java.util.List;
public class SourceProjectResponse
{
private Long projectId;
private String slug;
private String projectName;
private String category;
private String coverText;
private String summary;
private String descriptionMd;
private Long price;
private String priceLabel;
private Long viewCount;
private Date updateTime;
private List<String> tags;
private List<String> modules;
private List<String> scenes;
private boolean purchased;
private boolean resourceReady;
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public String getSlug() { return slug; }
public void setSlug(String slug) { this.slug = slug; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getCoverText() { return coverText; }
public void setCoverText(String coverText) { this.coverText = coverText; }
public String getSummary() { return summary; }
public void setSummary(String summary) { this.summary = summary; }
public String getDescriptionMd() { return descriptionMd; }
public void setDescriptionMd(String descriptionMd) { this.descriptionMd = descriptionMd; }
public Long getPrice() { return price; }
public void setPrice(Long price) { this.price = price; }
public String getPriceLabel() { return priceLabel; }
public void setPriceLabel(String priceLabel) { this.priceLabel = priceLabel; }
public Long getViewCount() { return viewCount; }
public void setViewCount(Long viewCount) { this.viewCount = viewCount; }
public Date getUpdateTime() { return updateTime; }
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public List<String> getModules() { return modules; }
public void setModules(List<String> modules) { this.modules = modules; }
public List<String> getScenes() { return scenes; }
public void setScenes(List<String> scenes) { this.scenes = scenes; }
public boolean isPurchased() { return purchased; }
public void setPurchased(boolean purchased) { this.purchased = purchased; }
public boolean isResourceReady() { return resourceReady; }
public void setResourceReady(boolean resourceReady) { this.resourceReady = resourceReady; }
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.generator.domain.source;
import java.util.Date;
public class SourcePurchaseResponse
{
private Long purchaseId;
private Long projectId;
private String projectName;
private String orderNo;
private Long amount;
private String amountLabel;
private String payStatus;
private String resourceStatus;
private Date purchaseTime;
public Long getPurchaseId() { return purchaseId; }
public void setPurchaseId(Long purchaseId) { this.purchaseId = purchaseId; }
public Long getProjectId() { return projectId; }
public void setProjectId(Long projectId) { this.projectId = projectId; }
public String getProjectName() { return projectName; }
public void setProjectName(String projectName) { this.projectName = projectName; }
public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public Long getAmount() { return amount; }
public void setAmount(Long amount) { this.amount = amount; }
public String getAmountLabel() { return amountLabel; }
public void setAmountLabel(String amountLabel) { this.amountLabel = amountLabel; }
public String getPayStatus() { return payStatus; }
public void setPayStatus(String payStatus) { this.payStatus = payStatus; }
public String getResourceStatus() { return resourceStatus; }
public void setResourceStatus(String resourceStatus) { this.resourceStatus = resourceStatus; }
public Date getPurchaseTime() { return purchaseTime; }
public void setPurchaseTime(Date purchaseTime) { this.purchaseTime = purchaseTime; }
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.generator.mapper;
import java.util.List;
import com.ruoyi.generator.domain.SourceProject;
public interface SourceProjectMapper
{
public SourceProject selectSourceProjectByProjectId(Long projectId);
public SourceProject selectSourceProjectBySlug(String slug);
public List<SourceProject> selectSourceProjectList(SourceProject sourceProject);
public List<SourceProject> selectPublicSourceProjectList(SourceProject sourceProject);
public int insertSourceProject(SourceProject sourceProject);
public int updateSourceProject(SourceProject sourceProject);
public int deleteSourceProjectByProjectIds(Long[] projectIds);
public int changeSourceProjectStatus(SourceProject sourceProject);
public int increaseViewCount(Long projectId);
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.generator.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.generator.domain.SourcePurchase;
public interface SourcePurchaseMapper
{
public SourcePurchase selectSourcePurchaseByPurchaseId(Long purchaseId);
public SourcePurchase selectPaidPurchase(@Param("userId") Long userId, @Param("projectId") Long projectId);
public List<SourcePurchase> selectSourcePurchaseList(SourcePurchase sourcePurchase);
public List<SourcePurchase> selectSourcePurchasesByUserId(Long userId);
public int insertSourcePurchase(SourcePurchase sourcePurchase);
public int updateSourcePurchase(SourcePurchase sourcePurchase);
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.generator.service;
import java.util.List;
import java.util.Map;
import com.ruoyi.generator.domain.SourceProject;
import com.ruoyi.generator.domain.SourcePurchase;
import com.ruoyi.generator.domain.source.SourceProjectResponse;
import com.ruoyi.generator.domain.source.SourcePurchaseResponse;
public interface ISourceProjectService
{
public SourceProject selectSourceProjectByProjectId(Long projectId);
public List<SourceProject> selectSourceProjectList(SourceProject sourceProject);
public int insertSourceProject(SourceProject sourceProject);
public int updateSourceProject(SourceProject sourceProject);
public int deleteSourceProjectByProjectIds(Long[] projectIds);
public int changeSourceProjectStatus(SourceProject sourceProject);
public List<SourceProjectResponse> listPublicProjects(Long userId, SourceProject query);
public SourceProjectResponse getPublicProject(Long userId, String slug, boolean increaseView);
public SourcePurchase purchase(Long userId, Long projectId);
public List<SourcePurchaseResponse> listPurchases(Long userId);
public Map<String, Object> getDownloadInfo(Long userId, Long projectId);
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.generator.service;
import java.util.List;
import com.ruoyi.generator.domain.SourcePurchase;
public interface ISourcePurchaseService
{
public SourcePurchase selectSourcePurchaseByPurchaseId(Long purchaseId);
public List<SourcePurchase> selectSourcePurchaseList(SourcePurchase sourcePurchase);
public int updateSourcePurchase(SourcePurchase sourcePurchase);
}

View File

@@ -0,0 +1,333 @@
package com.ruoyi.generator.service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.domain.SourceProject;
import com.ruoyi.generator.domain.SourcePurchase;
import com.ruoyi.generator.domain.source.SourceProjectResponse;
import com.ruoyi.generator.domain.source.SourcePurchaseResponse;
import com.ruoyi.generator.mapper.SourceProjectMapper;
import com.ruoyi.generator.mapper.SourcePurchaseMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SourceProjectServiceImpl implements ISourceProjectService
{
private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$");
@Autowired
private SourceProjectMapper sourceProjectMapper;
@Autowired
private SourcePurchaseMapper sourcePurchaseMapper;
@Override
public SourceProject selectSourceProjectByProjectId(Long projectId)
{
return sourceProjectMapper.selectSourceProjectByProjectId(projectId);
}
@Override
public List<SourceProject> selectSourceProjectList(SourceProject sourceProject)
{
return sourceProjectMapper.selectSourceProjectList(sourceProject);
}
@Override
public int insertSourceProject(SourceProject sourceProject)
{
prepareForSave(sourceProject, true);
sourceProject.setCreateTime(DateUtils.getNowDate());
return sourceProjectMapper.insertSourceProject(sourceProject);
}
@Override
public int updateSourceProject(SourceProject sourceProject)
{
prepareForSave(sourceProject, false);
sourceProject.setUpdateTime(DateUtils.getNowDate());
return sourceProjectMapper.updateSourceProject(sourceProject);
}
@Override
public int deleteSourceProjectByProjectIds(Long[] projectIds)
{
return sourceProjectMapper.deleteSourceProjectByProjectIds(projectIds);
}
@Override
public int changeSourceProjectStatus(SourceProject sourceProject)
{
sourceProject.setUpdateTime(DateUtils.getNowDate());
return sourceProjectMapper.changeSourceProjectStatus(sourceProject);
}
@Override
public List<SourceProjectResponse> listPublicProjects(Long userId, SourceProject query)
{
List<SourceProjectResponse> responses = new ArrayList<SourceProjectResponse>();
List<SourceProject> projects = sourceProjectMapper.selectPublicSourceProjectList(query);
for (SourceProject project : projects)
{
responses.add(toProjectResponse(project, hasPaid(userId, project.getProjectId())));
}
return responses;
}
@Override
public SourceProjectResponse getPublicProject(Long userId, String slug, boolean increaseView)
{
SourceProject project = sourceProjectMapper.selectSourceProjectBySlug(slug);
assertProjectPublished(project);
if (increaseView)
{
sourceProjectMapper.increaseViewCount(project.getProjectId());
project.setViewCount(Long.valueOf(defaultLong(project.getViewCount()) + 1L));
}
return toProjectResponse(project, hasPaid(userId, project.getProjectId()));
}
@Override
public SourcePurchase purchase(Long userId, Long projectId)
{
assertFrontUserLoggedIn(userId);
SourceProject project = sourceProjectMapper.selectSourceProjectByProjectId(projectId);
assertProjectPublished(project);
SourcePurchase paid = sourcePurchaseMapper.selectPaidPurchase(userId, projectId);
if (paid != null)
{
return paid;
}
SourcePurchase purchase = new SourcePurchase();
purchase.setProjectId(projectId);
purchase.setUserId(userId);
purchase.setOrderNo(buildOrderNo(userId));
purchase.setAmount(project.getPrice());
purchase.setPayStatus("1");
purchase.setResourceStatus(StringUtils.isNotEmpty(project.getResourceUrl()) ? "1" : "0");
purchase.setPurchaseTime(DateUtils.getNowDate());
purchase.setCreateTime(DateUtils.getNowDate());
sourcePurchaseMapper.insertSourcePurchase(purchase);
return purchase;
}
@Override
public List<SourcePurchaseResponse> listPurchases(Long userId)
{
assertFrontUserLoggedIn(userId);
List<SourcePurchaseResponse> responses = new ArrayList<SourcePurchaseResponse>();
List<SourcePurchase> purchases = sourcePurchaseMapper.selectSourcePurchasesByUserId(userId);
for (SourcePurchase purchase : purchases)
{
responses.add(toPurchaseResponse(purchase));
}
return responses;
}
@Override
public Map<String, Object> getDownloadInfo(Long userId, Long projectId)
{
assertFrontUserLoggedIn(userId);
SourceProject project = sourceProjectMapper.selectSourceProjectByProjectId(projectId);
assertProjectPublished(project);
SourcePurchase paid = sourcePurchaseMapper.selectPaidPurchase(userId, projectId);
if (paid == null)
{
throw new ServiceException("请先购买源码");
}
if (StringUtils.isEmpty(project.getResourceUrl()))
{
throw new ServiceException("源码资源暂未配置");
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("projectId", project.getProjectId());
result.put("projectName", project.getProjectName());
result.put("resourceName", project.getResourceName());
result.put("resourceUrl", project.getResourceUrl());
result.put("resourceVersion", project.getResourceVersion());
return result;
}
private SourceProjectResponse toProjectResponse(SourceProject project, boolean purchased)
{
SourceProjectResponse response = new SourceProjectResponse();
response.setProjectId(project.getProjectId());
response.setSlug(project.getSlug());
response.setProjectName(project.getProjectName());
response.setCategory(project.getCategory());
response.setCoverText(project.getCoverText());
response.setSummary(project.getSummary());
response.setDescriptionMd(project.getDescriptionMd());
response.setPrice(project.getPrice());
response.setPriceLabel(formatPrice(project.getPrice()));
response.setViewCount(Long.valueOf(defaultLong(project.getViewCount())));
response.setUpdateTime(project.getUpdateTime() == null ? project.getCreateTime() : project.getUpdateTime());
response.setTags(readStringList(project.getTags()));
response.setModules(readStringList(project.getModules()));
response.setScenes(readStringList(project.getScenes()));
response.setPurchased(purchased);
response.setResourceReady(StringUtils.isNotEmpty(project.getResourceUrl()));
return response;
}
private SourcePurchaseResponse toPurchaseResponse(SourcePurchase purchase)
{
SourcePurchaseResponse response = new SourcePurchaseResponse();
response.setPurchaseId(purchase.getPurchaseId());
response.setProjectId(purchase.getProjectId());
response.setProjectName(purchase.getProjectName());
response.setOrderNo(purchase.getOrderNo());
response.setAmount(purchase.getAmount());
response.setAmountLabel(formatPrice(purchase.getAmount()));
response.setPayStatus(purchase.getPayStatus());
response.setResourceStatus(purchase.getResourceStatus());
response.setPurchaseTime(purchase.getPurchaseTime());
return response;
}
private boolean hasPaid(Long userId, Long projectId)
{
return userId != null && sourcePurchaseMapper.selectPaidPurchase(userId, projectId) != null;
}
private void assertFrontUserLoggedIn(Long userId)
{
if (userId == null)
{
throw new ServiceException("前台用户未登录");
}
}
private void assertProjectPublished(SourceProject project)
{
if (project == null || !"0".equals(project.getStatus()) || "2".equals(project.getDelFlag()))
{
throw new ServiceException("源码项目未上架或不存在");
}
}
private void prepareForSave(SourceProject project, boolean inserting)
{
if (project == null || StringUtils.isEmpty(project.getProjectName()))
{
throw new ServiceException("项目名称不能为空");
}
if (StringUtils.isEmpty(project.getSlug()))
{
project.setSlug(toSlug(project.getProjectName()));
}
if (StringUtils.isEmpty(project.getCoverText()))
{
project.setCoverText(project.getSlug().toUpperCase(Locale.ENGLISH));
}
project.setTags(normalizeJsonList(project.getTags()));
project.setModules(normalizeJsonList(project.getModules()));
project.setScenes(normalizeJsonList(project.getScenes()));
if (project.getPrice() == null)
{
project.setPrice(0L);
}
if (project.getViewCount() == null)
{
project.setViewCount(0L);
}
if (project.getSort() == null)
{
project.setSort(0);
}
if (StringUtils.isEmpty(project.getStatus()))
{
project.setStatus("0");
}
if (inserting && StringUtils.isEmpty(project.getDelFlag()))
{
project.setDelFlag("0");
}
}
private String toSlug(String value)
{
String slug = StringUtils.defaultString(value).trim()
.replaceAll("[^A-Za-z0-9]+", "-")
.replaceAll("^-+|-+$", "")
.replaceAll("-{2,}", "-")
.toLowerCase(Locale.ENGLISH);
if (!SLUG_PATTERN.matcher(slug).matches())
{
return "source-" + System.currentTimeMillis();
}
return slug;
}
private List<String> readStringList(String value)
{
if (StringUtils.isEmpty(value))
{
return new ArrayList<String>();
}
try
{
List<String> list = JSON.parseArray(value, String.class);
return list == null ? new ArrayList<String>() : list;
}
catch (Exception e)
{
return splitList(value);
}
}
private String normalizeJsonList(String value)
{
return JSON.toJSONString(readStringList(value));
}
private List<String> splitList(String value)
{
List<String> list = new ArrayList<String>();
if (StringUtils.isEmpty(value))
{
return list;
}
String[] parts = value.split("[,\\n]");
for (String part : parts)
{
String item = part == null ? "" : part.trim();
if (StringUtils.isNotEmpty(item))
{
list.add(item);
}
}
return list;
}
private String formatPrice(Long cents)
{
BigDecimal amount = BigDecimal.valueOf(defaultLong(cents), 2).stripTrailingZeros();
return "¥" + amount.toPlainString();
}
private long defaultLong(Long value)
{
return value == null ? 0L : value.longValue();
}
private String buildOrderNo(Long userId)
{
String time = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
String random = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase(Locale.ENGLISH);
return "SRC" + time + userId + random;
}
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.generator.service;
import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.generator.domain.SourcePurchase;
import com.ruoyi.generator.mapper.SourcePurchaseMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class SourcePurchaseServiceImpl implements ISourcePurchaseService
{
@Autowired
private SourcePurchaseMapper sourcePurchaseMapper;
@Override
public SourcePurchase selectSourcePurchaseByPurchaseId(Long purchaseId)
{
return sourcePurchaseMapper.selectSourcePurchaseByPurchaseId(purchaseId);
}
@Override
public List<SourcePurchase> selectSourcePurchaseList(SourcePurchase sourcePurchase)
{
return sourcePurchaseMapper.selectSourcePurchaseList(sourcePurchase);
}
@Override
public int updateSourcePurchase(SourcePurchase sourcePurchase)
{
sourcePurchase.setUpdateTime(DateUtils.getNowDate());
return sourcePurchaseMapper.updateSourcePurchase(sourcePurchase);
}
}

View File

@@ -1,12 +1,16 @@
package com.ruoyi.generator.service.front;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.generator.config.DeepSeekProperties;
@@ -61,6 +65,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
normalizeAndBuildSql(response);
validator.validate(response, maxTables(), maxColumnsPerTable());
persistDatabase(userId, projectId, response);
markDatabaseGenerated(projectId);
generation.setResponsePayload(aiContent);
generation.setSuccess("1");
generation.setElapsedMs(System.currentTimeMillis() - start);
@@ -116,7 +121,13 @@ public class AiGenerateServiceImpl implements IAiGenerateService
private DatabaseDesignResponse parseResponse(String aiContent)
{
String content = stripMarkdownFence(aiContent);
DatabaseDesignResponse response = JSON.parseObject(content, DatabaseDesignResponse.class);
JSONObject json = JSON.parseObject(content);
if (json == null)
{
throw new ServiceException("AI database design is empty");
}
normalizeResponseAliases(json);
DatabaseDesignResponse response = json.toJavaObject(DatabaseDesignResponse.class);
if (response == null)
{
throw new ServiceException("AI 数据库设计为空");
@@ -124,6 +135,90 @@ public class AiGenerateServiceImpl implements IAiGenerateService
return response;
}
private void normalizeResponseAliases(JSONObject response)
{
if (response == null)
{
return;
}
JSONArray tables = response.getJSONArray("tables");
if (tables == null)
{
return;
}
for (int tableIndex = 0; tableIndex < tables.size(); tableIndex++)
{
JSONObject table = tables.getJSONObject(tableIndex);
if (table == null)
{
continue;
}
JSONArray columns = table.getJSONArray("columns");
if (columns == null)
{
continue;
}
for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++)
{
JSONObject column = columns.getJSONObject(columnIndex);
if (column == null)
{
continue;
}
normalizeFlagAlias(column, "isPk", "pk", "primaryKey", "isPrimaryKey", "primary");
normalizeFlagAlias(column, "isIncrement", "autoIncrement", "auto_increment", "increment");
normalizeFlagAlias(column, "isRequired", "required", "notNull", "not_null");
}
}
}
private void normalizeFlagAlias(JSONObject column, String canonicalName, String... aliases)
{
if (column.containsKey(canonicalName))
{
String normalized = normalizeFlagValue(column.get(canonicalName), null);
if (normalized != null)
{
column.put(canonicalName, normalized);
return;
}
}
for (String alias : aliases)
{
if (column.containsKey(alias))
{
column.put(canonicalName, normalizeFlagValue(column.get(alias), null));
return;
}
}
}
private String normalizeFlagValue(Object value, String defaultValue)
{
if (value == null)
{
return defaultValue;
}
if (value instanceof Boolean)
{
return (Boolean) value ? "1" : "0";
}
if (value instanceof Number)
{
return ((Number) value).intValue() == 0 ? "0" : "1";
}
String text = StringUtils.defaultString(String.valueOf(value)).trim().toLowerCase(Locale.ENGLISH);
if ("1".equals(text) || "true".equals(text) || "yes".equals(text) || "y".equals(text))
{
return "1";
}
if ("0".equals(text) || "false".equals(text) || "no".equals(text) || "n".equals(text))
{
return "0";
}
return defaultValue;
}
private String stripMarkdownFence(String content)
{
String text = StringUtils.defaultString(content).trim();
@@ -142,6 +237,7 @@ public class AiGenerateServiceImpl implements IAiGenerateService
{
return;
}
normalizeDatabaseNames(response);
int tableSort = 1;
for (DatabaseTableDesign table : response.getTables())
{
@@ -156,11 +252,12 @@ public class AiGenerateServiceImpl implements IAiGenerateService
{
for (DatabaseColumnDesign column : table.getColumns())
{
column.setColumnType(normalizeColumnType(column.getColumnType()));
column.setJavaType(StringUtils.defaultIfEmpty(column.getJavaType(), guessJavaType(column.getColumnType())));
column.setJavaField(StringUtils.defaultIfEmpty(column.getJavaField(), toJavaField(column.getColumnName())));
column.setIsPk(StringUtils.defaultIfEmpty(column.getIsPk(), "0"));
column.setIsIncrement(StringUtils.defaultIfEmpty(column.getIsIncrement(), "0"));
column.setIsRequired(StringUtils.defaultIfEmpty(column.getIsRequired(), "0"));
column.setIsPk(normalizeFlagValue(column.getIsPk(), "0"));
column.setIsIncrement(normalizeFlagValue(column.getIsIncrement(), "0"));
column.setIsRequired(normalizeFlagValue(column.getIsRequired(), "0"));
column.setIsInsert(StringUtils.defaultIfEmpty(column.getIsInsert(), "1"));
column.setIsEdit(StringUtils.defaultIfEmpty(column.getIsEdit(), "1"));
column.setIsList(StringUtils.defaultIfEmpty(column.getIsList(), "1"));
@@ -171,6 +268,8 @@ public class AiGenerateServiceImpl implements IAiGenerateService
columnSort++;
}
}
normalizePrimaryKey(table);
refreshColumnSort(table);
table.setCreateTableSql(ddlBuilder.build(table));
if (!validator.isSafeSql(table.getCreateTableSql()))
{
@@ -182,6 +281,286 @@ public class AiGenerateServiceImpl implements IAiGenerateService
response.setSql(StringUtils.join(ddlList, "\n\n"));
}
private void normalizeDatabaseNames(DatabaseDesignResponse response)
{
Set<String> tableNames = new HashSet<String>();
int tableIndex = 1;
for (DatabaseTableDesign table : response.getTables())
{
if (table == null)
{
continue;
}
table.setTableName(uniqueDatabaseName(table.getTableName(), "table", tableIndex, tableNames));
normalizeColumnNames(table);
tableIndex++;
}
}
private void normalizeColumnNames(DatabaseTableDesign table)
{
if (table.getColumns() == null)
{
return;
}
Set<String> columnNames = new HashSet<String>();
int columnIndex = 1;
for (DatabaseColumnDesign column : table.getColumns())
{
if (column != null)
{
column.setColumnName(uniqueDatabaseName(column.getColumnName(), "field", columnIndex, columnNames));
}
columnIndex++;
}
}
private String uniqueDatabaseName(String rawName, String fallbackPrefix, int index, Set<String> usedNames)
{
String baseName = normalizeDatabaseName(rawName, fallbackPrefix + "_" + index);
String uniqueName = baseName;
int suffix = 2;
while (usedNames.contains(uniqueName))
{
uniqueName = appendNameSuffix(baseName, "_" + suffix);
suffix++;
}
usedNames.add(uniqueName);
return uniqueName;
}
private String normalizeDatabaseName(String rawName, String fallbackName)
{
String name = StringUtils.defaultString(rawName).trim();
name = name.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
name = name.replaceAll("([a-z0-9])([A-Z])", "$1_$2");
name = name.toLowerCase(Locale.ENGLISH);
name = name.replaceAll("[^a-z0-9]+", "_");
name = name.replaceAll("_+", "_").replaceAll("^_+|_+$", "");
if (StringUtils.isBlank(name))
{
name = fallbackName;
}
if (!name.substring(0, 1).matches("[a-z]"))
{
name = fallbackName + "_" + name;
}
if (name.length() < 2)
{
name = name + "_1";
}
return trimDatabaseName(name);
}
private String appendNameSuffix(String baseName, String suffix)
{
int maxBaseLength = Math.max(2, 64 - suffix.length());
String trimmedBase = baseName.length() > maxBaseLength ? baseName.substring(0, maxBaseLength) : baseName;
trimmedBase = trimmedBase.replaceAll("_+$", "");
if (trimmedBase.length() < 2)
{
trimmedBase = "f_" + trimmedBase;
}
return trimDatabaseName(trimmedBase + suffix);
}
private String trimDatabaseName(String name)
{
if (name.length() <= 64)
{
return name;
}
String trimmed = name.substring(0, 64).replaceAll("_+$", "");
return trimmed.length() < 2 ? "f_1" : trimmed;
}
private void normalizePrimaryKey(DatabaseTableDesign table)
{
if (table.getColumns() == null || table.getColumns().isEmpty())
{
return;
}
int primaryKeyIndex = -1;
for (int i = 0; i < table.getColumns().size(); i++)
{
DatabaseColumnDesign column = table.getColumns().get(i);
if ("1".equals(column.getIsPk()))
{
if (primaryKeyIndex < 0)
{
primaryKeyIndex = i;
column.setIsRequired("1");
}
else
{
column.setIsPk("0");
column.setIsIncrement("0");
}
}
}
if (primaryKeyIndex >= 0)
{
return;
}
DatabaseColumnDesign idColumn = findColumn(table, "id");
if (idColumn == null)
{
idColumn = createIdPrimaryKeyColumn();
table.getColumns().add(0, idColumn);
}
idColumn.setIsPk("1");
idColumn.setIsIncrement("1");
idColumn.setIsRequired("1");
idColumn.setJavaType("Long");
idColumn.setJavaField("id");
}
private DatabaseColumnDesign findColumn(DatabaseTableDesign table, String columnName)
{
for (DatabaseColumnDesign column : table.getColumns())
{
if (columnName.equals(column.getColumnName()))
{
return column;
}
}
return null;
}
private DatabaseColumnDesign createIdPrimaryKeyColumn()
{
DatabaseColumnDesign column = new DatabaseColumnDesign();
column.setColumnName("id");
column.setColumnType("bigint(20)");
column.setColumnComment("ID");
column.setJavaType("Long");
column.setJavaField("id");
column.setIsPk("1");
column.setIsIncrement("1");
column.setIsRequired("1");
column.setIsInsert("0");
column.setIsEdit("0");
column.setIsList("0");
column.setIsQuery("0");
column.setQueryType("EQ");
column.setHtmlType("input");
return column;
}
private String normalizeColumnType(String columnType)
{
String type = StringUtils.defaultString(columnType).trim().toLowerCase(Locale.ENGLISH);
type = type.replace("`", "").replaceAll("\\s+", " ");
type = type.replaceAll("\\s+unsigned\\b", "");
type = type.replaceAll("\\s+(not\\s+null|null|default\\b.*|comment\\b.*|auto_increment\\b.*|primary\\s+key\\b.*)$", "");
type = type.trim();
if (StringUtils.isBlank(type))
{
return "varchar(255)";
}
if (type.startsWith("varchar") || "string".equals(type) || "str".equals(type))
{
return "varchar(" + boundedTypeNumber(type, 255, 1, 1000) + ")";
}
if (type.startsWith("char"))
{
return "char(" + boundedTypeNumber(type, 32, 1, 99) + ")";
}
if (type.startsWith("bigint") || "long".equals(type) || "long integer".equals(type) || "int64".equals(type))
{
return "bigint(20)";
}
if (type.startsWith("int") || "integer".equals(type) || type.startsWith("tinyint")
|| type.startsWith("smallint") || type.startsWith("mediumint") || "bool".equals(type)
|| "boolean".equals(type))
{
return "int(11)";
}
if (type.startsWith("decimal") || "numeric".equals(type) || "number".equals(type)
|| "float".equals(type) || "double".equals(type))
{
return normalizeDecimalType(type);
}
if ("datetime".equals(type) || "timestamp".equals(type) || "localdatetime".equals(type))
{
return "datetime";
}
if ("date".equals(type) || "localdate".equals(type))
{
return "date";
}
if ("longtext".equals(type))
{
return "longtext";
}
if ("text".equals(type) || "mediumtext".equals(type) || "json".equals(type) || "object".equals(type))
{
return "text";
}
return "varchar(255)";
}
private int boundedTypeNumber(String type, int defaultValue, int min, int max)
{
int start = type.indexOf('(');
int end = type.indexOf(')', start + 1);
if (start < 0 || end <= start)
{
return defaultValue;
}
try
{
int value = Integer.parseInt(type.substring(start + 1, end).split(",")[0].trim());
return Math.max(min, Math.min(max, value));
}
catch (NumberFormatException e)
{
return defaultValue;
}
}
private String normalizeDecimalType(String type)
{
int precision = 10;
int scale = 2;
int start = type.indexOf('(');
int end = type.indexOf(')', start + 1);
if (start >= 0 && end > start)
{
String[] parts = type.substring(start + 1, end).split(",");
try
{
precision = Integer.parseInt(parts[0].trim());
if (parts.length > 1)
{
scale = Integer.parseInt(parts[1].trim());
}
}
catch (NumberFormatException e)
{
precision = 10;
scale = 2;
}
}
precision = Math.max(1, Math.min(65, precision));
scale = Math.max(0, Math.min(30, scale));
scale = Math.min(scale, precision);
return "decimal(" + precision + "," + scale + ")";
}
private void refreshColumnSort(DatabaseTableDesign table)
{
if (table.getColumns() == null)
{
return;
}
int sort = 1;
for (DatabaseColumnDesign column : table.getColumns())
{
column.setSort(sort++);
}
}
private void persistDatabase(Long userId, Long projectId, DatabaseDesignResponse response)
{
frontProjectColumnMapper.deleteColumnsByProjectId(projectId);
@@ -198,6 +577,15 @@ public class AiGenerateServiceImpl implements IAiGenerateService
}
}
private void markDatabaseGenerated(Long projectId)
{
FrontProject project = new FrontProject();
project.setProjectId(projectId);
project.setGenerateStatus("1");
project.setPreviewStatus("0");
frontProjectMapper.updateFrontProject(project);
}
private FrontProjectGeneration newGeneration(Long projectId, Long userId, String prompt)
{
FrontProjectGeneration generation = new FrontProjectGeneration();

View File

@@ -12,9 +12,15 @@ import com.ruoyi.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@Service
public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewService
@@ -55,6 +61,24 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
return genProjectService.downloadStructure(genProject, templateType);
}
@Override
public byte[] downloadAll(Long userId, Long projectId)
{
GenProject genProject = loadGenProject(userId, projectId);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(outputStream))
{
appendZip(zip, "backend", genProjectService.downloadStructure(genProject, "backend"));
appendZip(zip, "front-web", genProjectService.downloadStructure(genProject, "frontend"));
appendZip(zip, "admin-web", genProjectService.downloadStructure(genProject, "admin_frontend"));
}
catch (IOException e)
{
throw new ServiceException("生成项目源码压缩包失败");
}
return outputStream.toByteArray();
}
@Override
public int markPreviewReady(Long userId, Long projectId)
{
@@ -83,4 +107,59 @@ public class FrontProjectPreviewServiceImpl implements IFrontProjectPreviewServi
}
return FrontProjectConverter.toGenProject(frontProject);
}
private void appendZip(ZipOutputStream targetZip, String rootFolder, byte[] sourceData) throws IOException
{
ZipInputStream sourceZip = new ZipInputStream(new ByteArrayInputStream(sourceData));
ZipEntry sourceEntry;
byte[] buffer = new byte[4096];
while ((sourceEntry = sourceZip.getNextEntry()) != null)
{
String entryName = normalizeEntryName(rootFolder, sourceEntry.getName());
if (entryName == null)
{
sourceZip.closeEntry();
continue;
}
if (sourceEntry.isDirectory())
{
if (!entryName.endsWith("/"))
{
entryName = entryName + "/";
}
targetZip.putNextEntry(new ZipEntry(entryName));
targetZip.closeEntry();
}
else
{
targetZip.putNextEntry(new ZipEntry(entryName));
int length;
while ((length = sourceZip.read(buffer)) > -1)
{
targetZip.write(buffer, 0, length);
}
targetZip.closeEntry();
}
sourceZip.closeEntry();
}
sourceZip.close();
}
private String normalizeEntryName(String rootFolder, String entryName)
{
if (entryName == null || entryName.trim().length() == 0)
{
return null;
}
String normalized = entryName.replace('\\', '/');
while (normalized.startsWith("/"))
{
normalized = normalized.substring(1);
}
if (normalized.startsWith("../") || normalized.contains("/../"))
{
throw new ServiceException("项目源码压缩包路径不合法");
}
return rootFolder + "/" + normalized;
}
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -34,6 +35,7 @@ public class FrontProjectServiceImpl implements IFrontProjectService
private static final Pattern VARCHAR_TYPE_PATTERN = Pattern.compile("^varchar\\((\\d{1,5})\\)$");
private static final Pattern CHAR_TYPE_PATTERN = Pattern.compile("^char\\((\\d{1,3})\\)$");
private static final Pattern DECIMAL_TYPE_PATTERN = Pattern.compile("^decimal\\((\\d{1,2}),(\\d{1,2})\\)$");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Autowired
private FrontProjectMapper frontProjectMapper;
@@ -129,7 +131,7 @@ public class FrontProjectServiceImpl implements IFrontProjectService
public DatabaseDesignResponse getDatabase(Long userId, Long projectId)
{
assertFrontUserLoggedIn(userId);
getProject(userId, projectId);
FrontProject project = getProject(userId, projectId);
DatabaseDesignResponse response = new DatabaseDesignResponse();
response.setProjectId(projectId);
List<DatabaseTableDesign> tables = new ArrayList<DatabaseTableDesign>();
@@ -146,6 +148,7 @@ public class FrontProjectServiceImpl implements IFrontProjectService
}
response.setTables(tables);
response.setSql(joinCreateTableSql(tables));
response.setErDiagram(readJson(project.getErDiagram()));
return response;
}
@@ -168,6 +171,7 @@ public class FrontProjectServiceImpl implements IFrontProjectService
frontProjectColumnMapper.insertFrontProjectColumn(column);
}
}
markDatabaseGenerated(projectId, writeJson(request.getErDiagram()));
return getDatabase(userId, projectId);
}
@@ -289,6 +293,48 @@ public class FrontProjectServiceImpl implements IFrontProjectService
return StringUtils.join(sqlList, "\n\n");
}
private void markDatabaseGenerated(Long projectId, String erDiagram)
{
FrontProject project = new FrontProject();
project.setProjectId(projectId);
project.setGenerateStatus("1");
project.setPreviewStatus("0");
project.setErDiagram(erDiagram);
frontProjectMapper.updateFrontProject(project);
}
private Object readJson(String json)
{
if (StringUtils.isEmpty(json))
{
return null;
}
try
{
return OBJECT_MAPPER.readValue(json, Object.class);
}
catch (Exception e)
{
return null;
}
}
private String writeJson(Object value)
{
if (value == null)
{
return null;
}
try
{
return value instanceof String ? (String) value : OBJECT_MAPPER.writeValueAsString(value);
}
catch (Exception e)
{
throw new ServiceException("ER图草稿保存失败");
}
}
private FrontProjectTable toFrontProjectTable(Long projectId, Long userId, DatabaseTableDesign source)
{
FrontProjectTable table = new FrontProjectTable();

View File

@@ -1,6 +1,17 @@
package com.ruoyi.generator.service.front;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@@ -18,6 +29,34 @@ import com.ruoyi.generator.config.DeepSeekProperties;
@Component
public class HttpDeepSeekClient implements IDeepSeekClient
{
private static final HostnameVerifier TRUST_ALL_HOSTNAME_VERIFIER = new HostnameVerifier()
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
};
private static final X509TrustManager TRUST_ALL_TRUST_MANAGER = new X509TrustManager()
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[0];
}
};
private static final String SYSTEM_PROMPT = "你是企业管理系统数据库设计助手只返回JSON。";
@Autowired
@@ -57,10 +96,58 @@ public class HttpDeepSeekClient implements IDeepSeekClient
private RestTemplate restTemplate()
{
int timeout = deepSeekProperties.getTimeout() == null ? 60000 : deepSeekProperties.getTimeout();
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout((int) TimeUnit.MILLISECONDS.toMillis(timeout));
factory.setReadTimeout((int) TimeUnit.MILLISECONDS.toMillis(timeout));
return new RestTemplate(factory);
return new RestTemplate(requestFactory(timeout));
}
SimpleClientHttpRequestFactory requestFactory(int timeout)
{
SimpleClientHttpRequestFactory factory = deepSeekProperties.isSkipSslVerify()
? trustAllRequestFactory()
: new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(timeout);
factory.setReadTimeout(timeout);
return factory;
}
private SimpleClientHttpRequestFactory trustAllRequestFactory()
{
return new TrustAllClientHttpRequestFactory(trustAllSslContext());
}
private SSLContext trustAllSslContext()
{
try
{
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { TRUST_ALL_TRUST_MANAGER }, new SecureRandom());
return sslContext;
}
catch (NoSuchAlgorithmException | KeyManagementException e)
{
throw new ServiceException("DeepSeek SSL 证书忽略配置初始化失败").setDetailMessage(e.getMessage());
}
}
private static class TrustAllClientHttpRequestFactory extends SimpleClientHttpRequestFactory
{
private final SSLContext sslContext;
private TrustAllClientHttpRequestFactory(SSLContext sslContext)
{
this.sslContext = sslContext;
}
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException
{
super.prepareConnection(connection, httpMethod);
if (connection instanceof HttpsURLConnection)
{
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory());
httpsConnection.setHostnameVerifier(TRUST_ALL_HOSTNAME_VERIFIER);
}
}
}
private String chatUrl()

View File

@@ -13,5 +13,7 @@ public interface IFrontProjectPreviewService
public byte[] download(Long userId, Long projectId, String templateType);
public byte[] downloadAll(Long userId, Long projectId);
public int markPreviewReady(Long userId, Long projectId);
}

View File

@@ -17,6 +17,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="backFramework" column="back_framework" />
<result property="generateStatus" column="generate_status" />
<result property="previewStatus" column="preview_status" />
<result property="erDiagram" column="er_diagram" />
<result property="status" column="status" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
@@ -24,7 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectFrontProjectVo">
select project_id, user_id, project_name, project_file_name, package_name, version, project_desc, industry_template, front_framework, back_framework, generate_status, preview_status, status, create_time, update_time, remark from front_project
select project_id, user_id, project_name, project_file_name, package_name, version, project_desc, industry_template, front_framework, back_framework, generate_status, preview_status, er_diagram, status, create_time, update_time, remark from front_project
</sql>
<select id="selectFrontProjectById" parameterType="Long" resultMap="FrontProjectResult">
@@ -64,6 +65,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="backFramework != null">back_framework,</if>
<if test="generateStatus != null">generate_status,</if>
<if test="previewStatus != null">preview_status,</if>
<if test="erDiagram != null">er_diagram,</if>
<if test="status != null">status,</if>
create_time,
<if test="remark != null">remark,</if>
@@ -80,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="backFramework != null">#{backFramework},</if>
<if test="generateStatus != null">#{generateStatus},</if>
<if test="previewStatus != null">#{previewStatus},</if>
<if test="erDiagram != null">#{erDiagram},</if>
<if test="status != null">#{status},</if>
sysdate(),
<if test="remark != null">#{remark},</if>
@@ -99,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="backFramework != null">back_framework = #{backFramework},</if>
<if test="generateStatus != null">generate_status = #{generateStatus},</if>
<if test="previewStatus != null">preview_status = #{previewStatus},</if>
<if test="erDiagram != null">er_diagram = #{erDiagram},</if>
<if test="status != null">status = #{status},</if>
update_time = sysdate(),
<if test="remark != null">remark = #{remark},</if>

View File

@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.generator.mapper.SourceProjectMapper">
<resultMap type="SourceProject" id="SourceProjectResult">
<id property="projectId" column="project_id" />
<result property="slug" column="slug" />
<result property="projectName" column="project_name" />
<result property="category" column="category" />
<result property="coverText" column="cover_text" />
<result property="summary" column="summary" />
<result property="descriptionMd" column="description_md" />
<result property="price" column="price" />
<result property="viewCount" column="view_count" />
<result property="tags" column="tags" />
<result property="modules" column="modules" />
<result property="scenes" column="scenes" />
<result property="resourceName" column="resource_name" />
<result property="resourceUrl" column="resource_url" />
<result property="resourceVersion" column="resource_version" />
<result property="resourceSize" column="resource_size" />
<result property="sort" column="sort" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectSourceProjectVo">
select project_id, slug, project_name, category, cover_text, summary, description_md, price, view_count,
tags, modules, scenes, resource_name, resource_url, resource_version, resource_size, sort, status,
del_flag, create_by, create_time, update_by, update_time, remark
from source_project
</sql>
<select id="selectSourceProjectByProjectId" parameterType="Long" resultMap="SourceProjectResult">
<include refid="selectSourceProjectVo"/>
where project_id = #{projectId} and del_flag = '0'
</select>
<select id="selectSourceProjectBySlug" parameterType="String" resultMap="SourceProjectResult">
<include refid="selectSourceProjectVo"/>
where slug = #{slug} and del_flag = '0'
</select>
<select id="selectSourceProjectList" parameterType="SourceProject" resultMap="SourceProjectResult">
<include refid="selectSourceProjectVo"/>
<where>
and del_flag = '0'
<if test="projectName != null and projectName != ''"> and project_name like concat('%', #{projectName}, '%')</if>
<if test="slug != null and slug != ''"> and slug like concat('%', #{slug}, '%')</if>
<if test="category != null and category != '' and category != 'all'"> and category = #{category}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="keyword != null and keyword != ''">
and (
project_name like concat('%', #{keyword}, '%')
or summary like concat('%', #{keyword}, '%')
or tags like concat('%', #{keyword}, '%')
or modules like concat('%', #{keyword}, '%')
or scenes like concat('%', #{keyword}, '%')
)
</if>
</where>
order by sort asc, update_time desc, create_time desc
</select>
<select id="selectPublicSourceProjectList" parameterType="SourceProject" resultMap="SourceProjectResult">
<include refid="selectSourceProjectVo"/>
<where>
and del_flag = '0'
and status = '0'
<if test="category != null and category != '' and category != 'all'"> and category = #{category}</if>
<if test="keyword != null and keyword != ''">
and (
project_name like concat('%', #{keyword}, '%')
or summary like concat('%', #{keyword}, '%')
or tags like concat('%', #{keyword}, '%')
or modules like concat('%', #{keyword}, '%')
or scenes like concat('%', #{keyword}, '%')
)
</if>
</where>
order by sort asc, update_time desc, create_time desc
</select>
<insert id="insertSourceProject" parameterType="SourceProject" useGeneratedKeys="true" keyProperty="projectId">
insert into source_project
<trim prefix="(" suffix=")" suffixOverrides=",">
slug,
project_name,
<if test="category != null">category,</if>
<if test="coverText != null">cover_text,</if>
<if test="summary != null">summary,</if>
<if test="descriptionMd != null">description_md,</if>
<if test="price != null">price,</if>
<if test="viewCount != null">view_count,</if>
<if test="tags != null">tags,</if>
<if test="modules != null">modules,</if>
<if test="scenes != null">scenes,</if>
<if test="resourceName != null">resource_name,</if>
<if test="resourceUrl != null">resource_url,</if>
<if test="resourceVersion != null">resource_version,</if>
<if test="resourceSize != null">resource_size,</if>
<if test="sort != null">sort,</if>
<if test="status != null">status,</if>
<if test="delFlag != null">del_flag,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
#{slug},
#{projectName},
<if test="category != null">#{category},</if>
<if test="coverText != null">#{coverText},</if>
<if test="summary != null">#{summary},</if>
<if test="descriptionMd != null">#{descriptionMd},</if>
<if test="price != null">#{price},</if>
<if test="viewCount != null">#{viewCount},</if>
<if test="tags != null">#{tags},</if>
<if test="modules != null">#{modules},</if>
<if test="scenes != null">#{scenes},</if>
<if test="resourceName != null">#{resourceName},</if>
<if test="resourceUrl != null">#{resourceUrl},</if>
<if test="resourceVersion != null">#{resourceVersion},</if>
<if test="resourceSize != null">#{resourceSize},</if>
<if test="sort != null">#{sort},</if>
<if test="status != null">#{status},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateSourceProject" parameterType="SourceProject">
update source_project
<trim prefix="SET" suffixOverrides=",">
<if test="slug != null">slug = #{slug},</if>
<if test="projectName != null">project_name = #{projectName},</if>
<if test="category != null">category = #{category},</if>
<if test="coverText != null">cover_text = #{coverText},</if>
<if test="summary != null">summary = #{summary},</if>
<if test="descriptionMd != null">description_md = #{descriptionMd},</if>
<if test="price != null">price = #{price},</if>
<if test="viewCount != null">view_count = #{viewCount},</if>
<if test="tags != null">tags = #{tags},</if>
<if test="modules != null">modules = #{modules},</if>
<if test="scenes != null">scenes = #{scenes},</if>
<if test="resourceName != null">resource_name = #{resourceName},</if>
<if test="resourceUrl != null">resource_url = #{resourceUrl},</if>
<if test="resourceVersion != null">resource_version = #{resourceVersion},</if>
<if test="resourceSize != null">resource_size = #{resourceSize},</if>
<if test="sort != null">sort = #{sort},</if>
<if test="status != null">status = #{status},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where project_id = #{projectId}
</update>
<update id="deleteSourceProjectByProjectIds" parameterType="Long">
update source_project set del_flag = '2', update_time = sysdate()
where project_id in
<foreach item="projectId" collection="array" open="(" separator="," close=")">
#{projectId}
</foreach>
</update>
<update id="changeSourceProjectStatus" parameterType="SourceProject">
update source_project
set status = #{status}, update_time = #{updateTime}
where project_id = #{projectId}
</update>
<update id="increaseViewCount" parameterType="Long">
update source_project set view_count = ifnull(view_count, 0) + 1 where project_id = #{projectId}
</update>
</mapper>

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.generator.mapper.SourcePurchaseMapper">
<resultMap type="SourcePurchase" id="SourcePurchaseResult">
<id property="purchaseId" column="purchase_id" />
<result property="projectId" column="project_id" />
<result property="userId" column="user_id" />
<result property="orderNo" column="order_no" />
<result property="amount" column="amount" />
<result property="payStatus" column="pay_status" />
<result property="resourceStatus" column="resource_status" />
<result property="purchaseTime" column="purchase_time" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
<result property="projectName" column="project_name" />
</resultMap>
<sql id="selectSourcePurchaseVo">
select p.purchase_id, p.project_id, p.user_id, p.order_no, p.amount, p.pay_status, p.resource_status,
p.purchase_time, p.create_by, p.create_time, p.update_by, p.update_time, p.remark,
sp.project_name
from source_purchase p
left join source_project sp on sp.project_id = p.project_id
</sql>
<select id="selectSourcePurchaseByPurchaseId" parameterType="Long" resultMap="SourcePurchaseResult">
<include refid="selectSourcePurchaseVo"/>
where p.purchase_id = #{purchaseId}
</select>
<select id="selectPaidPurchase" resultMap="SourcePurchaseResult">
<include refid="selectSourcePurchaseVo"/>
where p.user_id = #{userId} and p.project_id = #{projectId} and p.pay_status = '1'
order by p.purchase_time desc
limit 1
</select>
<select id="selectSourcePurchaseList" parameterType="SourcePurchase" resultMap="SourcePurchaseResult">
<include refid="selectSourcePurchaseVo"/>
<where>
<if test="projectId != null"> and p.project_id = #{projectId}</if>
<if test="userId != null"> and p.user_id = #{userId}</if>
<if test="orderNo != null and orderNo != ''"> and p.order_no like concat('%', #{orderNo}, '%')</if>
<if test="payStatus != null and payStatus != ''"> and p.pay_status = #{payStatus}</if>
<if test="resourceStatus != null and resourceStatus != ''"> and p.resource_status = #{resourceStatus}</if>
<if test="projectName != null and projectName != ''"> and sp.project_name like concat('%', #{projectName}, '%')</if>
</where>
order by p.purchase_time desc, p.create_time desc
</select>
<select id="selectSourcePurchasesByUserId" parameterType="Long" resultMap="SourcePurchaseResult">
<include refid="selectSourcePurchaseVo"/>
where p.user_id = #{userId}
order by p.purchase_time desc, p.create_time desc
</select>
<insert id="insertSourcePurchase" parameterType="SourcePurchase" useGeneratedKeys="true" keyProperty="purchaseId">
insert into source_purchase
<trim prefix="(" suffix=")" suffixOverrides=",">
project_id,
user_id,
order_no,
amount,
pay_status,
resource_status,
purchase_time,
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
#{projectId},
#{userId},
#{orderNo},
#{amount},
#{payStatus},
#{resourceStatus},
#{purchaseTime},
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateSourcePurchase" parameterType="SourcePurchase">
update source_purchase
<trim prefix="SET" suffixOverrides=",">
<if test="payStatus != null">pay_status = #{payStatus},</if>
<if test="resourceStatus != null">resource_status = #{resourceStatus},</if>
<if test="purchaseTime != null">purchase_time = #{purchaseTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where purchase_id = #{purchaseId}
</update>
</mapper>

View File

@@ -0,0 +1,150 @@
package com.ruoyi.generator.service;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.generator.domain.SourceProject;
import com.ruoyi.generator.domain.SourcePurchase;
import com.ruoyi.generator.domain.source.SourceProjectResponse;
import com.ruoyi.generator.mapper.SourceProjectMapper;
import com.ruoyi.generator.mapper.SourcePurchaseMapper;
public class SourceProjectServiceImplTest
{
private SourceProjectServiceImpl service;
@Mock
private SourceProjectMapper sourceProjectMapper;
@Mock
private SourcePurchaseMapper sourcePurchaseMapper;
@Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
service = new SourceProjectServiceImpl();
setField("sourceProjectMapper", sourceProjectMapper);
setField("sourcePurchaseMapper", sourcePurchaseMapper);
}
@Test
public void listPublicProjectsFormatsCatalogFieldsAndPurchaseState()
{
SourceProject query = new SourceProject();
SourceProject project = project("0");
project.setTags("[\"Spring Boot\",\"Vue2\"]");
project.setModules("[\"Dealer\",\"Repair\"]");
project.setScenes("[\"Aftermarket\"]");
when(sourceProjectMapper.selectPublicSourceProjectList(query)).thenReturn(Collections.singletonList(project));
when(sourcePurchaseMapper.selectPaidPurchase(7L, 10L)).thenReturn(purchase());
List<SourceProjectResponse> projects = service.listPublicProjects(7L, query);
assertEquals(1, projects.size());
SourceProjectResponse response = projects.get(0);
assertEquals(Long.valueOf(10L), response.getProjectId());
assertEquals("dms-platform", response.getSlug());
assertEquals("Commercial DMS", response.getProjectName());
assertEquals("¥399", response.getPriceLabel());
assertEquals(Long.valueOf(2860L), response.getViewCount());
assertEquals("Spring Boot", response.getTags().get(0));
assertEquals("Repair", response.getModules().get(1));
assertTrue(response.isPurchased());
}
@Test
public void purchaseCreatesPaidPurchaseForActiveProject()
{
SourceProject project = project("0");
project.setResourceUrl("https://example.com/dms.zip");
when(sourceProjectMapper.selectSourceProjectByProjectId(10L)).thenReturn(project);
SourcePurchase purchase = service.purchase(7L, 10L);
ArgumentCaptor<SourcePurchase> captor = ArgumentCaptor.forClass(SourcePurchase.class);
verify(sourcePurchaseMapper).insertSourcePurchase(captor.capture());
assertEquals(Long.valueOf(7L), captor.getValue().getUserId());
assertEquals(Long.valueOf(10L), captor.getValue().getProjectId());
assertEquals(Long.valueOf(39900L), captor.getValue().getAmount());
assertEquals("1", captor.getValue().getPayStatus());
assertEquals("1", captor.getValue().getResourceStatus());
assertTrue(captor.getValue().getOrderNo().startsWith("SRC"));
assertEquals(captor.getValue(), purchase);
}
@Test
public void purchaseRejectsOfflineProject()
{
when(sourceProjectMapper.selectSourceProjectByProjectId(10L)).thenReturn(project("1"));
ServiceException exception = expectServiceException(new ThrowingRunnable()
{
@Override
public void run()
{
service.purchase(7L, 10L);
}
});
assertEquals("源码项目未上架或不存在", exception.getMessage());
}
private SourceProject project(String status)
{
SourceProject project = new SourceProject();
project.setProjectId(10L);
project.setSlug("dms-platform");
project.setProjectName("Commercial DMS");
project.setCategory("enterprise");
project.setCoverText("DMS");
project.setSummary("DMS source");
project.setDescriptionMd("# DMS");
project.setPrice(39900L);
project.setViewCount(2860L);
project.setStatus(status);
return project;
}
private SourcePurchase purchase()
{
SourcePurchase purchase = new SourcePurchase();
purchase.setPurchaseId(20L);
return purchase;
}
private void setField(String name, Object value) throws Exception
{
Field field = SourceProjectServiceImpl.class.getDeclaredField(name);
field.setAccessible(true);
field.set(service, value);
}
private ServiceException expectServiceException(ThrowingRunnable runnable)
{
try
{
runnable.run();
}
catch (ServiceException e)
{
return e;
}
throw new AssertionError("Expected ServiceException");
}
private interface ThrowingRunnable
{
void run();
}
}

View File

@@ -86,6 +86,94 @@ public class AiGenerateServiceImplTest
assertEquals(2, columnCaptor.getAllValues().size());
assertEquals("1", generationCaptor.getValue().getSuccess());
assertTrue(response.getSql().contains("CREATE TABLE `car_info`"));
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
assertEquals("1", projectCaptor.getValue().getGenerateStatus());
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
}
@Test
public void generateDatabaseAddsIdPrimaryKeyWhenAiOmitsPrimaryKey()
{
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(deepSeekClient.chat(anyString())).thenReturn(responseWithoutPrimaryKey());
DatabaseDesignResponse response = service.generateDatabase(7L, 10L, request());
ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
FrontProjectColumn idColumn = columnCaptor.getAllValues().get(0);
assertEquals("id", idColumn.getColumnName());
assertEquals("1", idColumn.getIsPk());
assertEquals("1", idColumn.getIsIncrement());
assertTrue(response.getSql().contains("PRIMARY KEY (`id`)"));
}
@Test
public void generateDatabaseAcceptsPrimaryKeyAliasFromAi()
{
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(deepSeekClient.chat(anyString())).thenReturn(responseWithPrimaryKeyAlias());
service.generateDatabase(7L, 10L, request());
ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
FrontProjectColumn orderId = columnCaptor.getAllValues().get(0);
assertEquals("order_id", orderId.getColumnName());
assertEquals("1", orderId.getIsPk());
assertEquals("1", orderId.getIsIncrement());
}
@Test
public void generateDatabaseKeepsOnlyFirstPrimaryKeyWhenAiReturnsMultiple()
{
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(deepSeekClient.chat(anyString())).thenReturn(responseWithMultiplePrimaryKeys());
service.generateDatabase(7L, 10L, request());
ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
verify(frontProjectColumnMapper, org.mockito.Mockito.times(2)).insertFrontProjectColumn(columnCaptor.capture());
assertEquals("1", columnCaptor.getAllValues().get(0).getIsPk());
assertEquals("0", columnCaptor.getAllValues().get(1).getIsPk());
}
@Test
public void generateDatabaseNormalizesUnsafeAiColumnNamesBeforeValidation()
{
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(deepSeekClient.chat(anyString())).thenReturn(responseWithUnsafeColumnNames());
service.generateDatabase(7L, 10L, request());
ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
verify(frontProjectColumnMapper, org.mockito.Mockito.times(4)).insertFrontProjectColumn(columnCaptor.capture());
assertEquals("order_id", columnCaptor.getAllValues().get(0).getColumnName());
assertEquals("customer_name", columnCaptor.getAllValues().get(1).getColumnName());
assertEquals("field_3", columnCaptor.getAllValues().get(2).getColumnName());
assertEquals("customer_name_2", columnCaptor.getAllValues().get(3).getColumnName());
}
@Test
public void generateDatabaseNormalizesUnsupportedAiColumnTypesBeforeValidation()
{
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(deepSeekClient.chat(anyString())).thenReturn(responseWithUnsupportedColumnTypes());
service.generateDatabase(7L, 10L, request());
ArgumentCaptor<FrontProjectColumn> columnCaptor = ArgumentCaptor.forClass(FrontProjectColumn.class);
verify(frontProjectColumnMapper, org.mockito.Mockito.times(8)).insertFrontProjectColumn(columnCaptor.capture());
assertEquals("bigint(20)", columnCaptor.getAllValues().get(0).getColumnType());
assertEquals("int(11)", columnCaptor.getAllValues().get(1).getColumnType());
assertEquals("varchar(255)", columnCaptor.getAllValues().get(2).getColumnType());
assertEquals("int(11)", columnCaptor.getAllValues().get(3).getColumnType());
assertEquals("datetime", columnCaptor.getAllValues().get(4).getColumnType());
assertEquals("text", columnCaptor.getAllValues().get(5).getColumnType());
assertEquals("decimal(10,2)", columnCaptor.getAllValues().get(6).getColumnType());
assertEquals("varchar(1000)", columnCaptor.getAllValues().get(7).getColumnType());
}
@Test
@@ -200,4 +288,134 @@ public class AiGenerateServiceImplTest
+ " ]\n"
+ "}";
}
private String responseWithoutPrimaryKey()
{
return "{\n"
+ " \"tables\": [\n"
+ " {\n"
+ " \"tableName\": \"customer_info\",\n"
+ " \"tableComment\": \"Customer info\",\n"
+ " \"columns\": [\n"
+ " {\n"
+ " \"columnName\": \"customer_name\",\n"
+ " \"columnType\": \"varchar(64)\",\n"
+ " \"isRequired\": \"1\",\n"
+ " \"columnComment\": \"Customer name\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
}
private String responseWithPrimaryKeyAlias()
{
return "{\n"
+ " \"tables\": [\n"
+ " {\n"
+ " \"tableName\": \"order_info\",\n"
+ " \"tableComment\": \"Order info\",\n"
+ " \"columns\": [\n"
+ " {\n"
+ " \"columnName\": \"order_id\",\n"
+ " \"columnType\": \"bigint(20)\",\n"
+ " \"primaryKey\": true,\n"
+ " \"autoIncrement\": true,\n"
+ " \"columnComment\": \"Order ID\"\n"
+ " },\n"
+ " {\n"
+ " \"columnName\": \"order_no\",\n"
+ " \"columnType\": \"varchar(64)\",\n"
+ " \"columnComment\": \"Order no\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
}
private String responseWithMultiplePrimaryKeys()
{
return "{\n"
+ " \"tables\": [\n"
+ " {\n"
+ " \"tableName\": \"product_info\",\n"
+ " \"tableComment\": \"Product info\",\n"
+ " \"columns\": [\n"
+ " {\n"
+ " \"columnName\": \"product_id\",\n"
+ " \"columnType\": \"bigint(20)\",\n"
+ " \"isPk\": \"1\",\n"
+ " \"columnComment\": \"Product ID\"\n"
+ " },\n"
+ " {\n"
+ " \"columnName\": \"sku_id\",\n"
+ " \"columnType\": \"bigint(20)\",\n"
+ " \"isPk\": \"1\",\n"
+ " \"columnComment\": \"SKU ID\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
}
private String responseWithUnsafeColumnNames()
{
return "{\n"
+ " \"tables\": [\n"
+ " {\n"
+ " \"tableName\": \"order_info\",\n"
+ " \"tableComment\": \"Order info\",\n"
+ " \"columns\": [\n"
+ " {\n"
+ " \"columnName\": \"OrderID\",\n"
+ " \"columnType\": \"bigint(20)\",\n"
+ " \"primaryKey\": true,\n"
+ " \"autoIncrement\": true,\n"
+ " \"columnComment\": \"Order ID\"\n"
+ " },\n"
+ " {\n"
+ " \"columnName\": \"Customer Name\",\n"
+ " \"columnType\": \"varchar(64)\",\n"
+ " \"columnComment\": \"Customer name\"\n"
+ " },\n"
+ " {\n"
+ " \"columnName\": \"客户名称\",\n"
+ " \"columnType\": \"varchar(64)\",\n"
+ " \"columnComment\": \"Customer name cn\"\n"
+ " },\n"
+ " {\n"
+ " \"columnName\": \"customer-name\",\n"
+ " \"columnType\": \"varchar(64)\",\n"
+ " \"columnComment\": \"Customer name duplicate\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
}
private String responseWithUnsupportedColumnTypes()
{
return "{\n"
+ " \"tables\": [\n"
+ " {\n"
+ " \"tableName\": \"order_info\",\n"
+ " \"tableComment\": \"Order info\",\n"
+ " \"columns\": [\n"
+ " {\"columnName\": \"order_id\", \"columnType\": \"BIGINT unsigned\", \"primaryKey\": true, \"autoIncrement\": true, \"columnComment\": \"Order ID\"},\n"
+ " {\"columnName\": \"order_status\", \"columnType\": \"INTEGER\", \"columnComment\": \"Order status\"},\n"
+ " {\"columnName\": \"customer_name\", \"columnType\": \"string\", \"columnComment\": \"Customer name\"},\n"
+ " {\"columnName\": \"enabled\", \"columnType\": \"boolean\", \"columnComment\": \"Enabled\"},\n"
+ " {\"columnName\": \"created_at\", \"columnType\": \"timestamp\", \"columnComment\": \"Created time\"},\n"
+ " {\"columnName\": \"extra_data\", \"columnType\": \"json\", \"columnComment\": \"Extra data\"},\n"
+ " {\"columnName\": \"amount\", \"columnType\": \"float\", \"columnComment\": \"Amount\"},\n"
+ " {\"columnName\": \"remark\", \"columnType\": \"varchar(5000) not null\", \"columnComment\": \"Remark\"}\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
}
}

View File

@@ -10,13 +10,22 @@ import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@@ -51,6 +60,27 @@ public class FrontProjectPreviewServiceImplTest
assertEquals("backend", structure.get(0).get("label"));
}
@Test
public void downloadAllPackagesBackendFrontendAndAdminFrontend() throws Exception
{
FrontProject project = frontProjectWithOneTable();
project.setPreviewStatus("1");
when(frontProjectService.getProject(100L, 200L)).thenReturn(project);
when(genProjectService.downloadStructure(any(GenProject.class), eq("backend")))
.thenReturn(zipWithEntry("demo/backend.txt", "backend"));
when(genProjectService.downloadStructure(any(GenProject.class), eq("frontend")))
.thenReturn(zipWithEntry("demo/frontend.txt", "frontend"));
when(genProjectService.downloadStructure(any(GenProject.class), eq("admin_frontend")))
.thenReturn(zipWithEntry("demo/admin.txt", "admin"));
byte[] data = previewService.downloadAll(100L, 200L);
Set<String> entries = zipEntries(data);
assertTrue(entries.contains("backend/demo/backend.txt"));
assertTrue(entries.contains("front-web/demo/frontend.txt"));
assertTrue(entries.contains("admin-web/demo/admin.txt"));
}
private void setField(String name, Object value) throws Exception
{
Field field = FrontProjectPreviewServiceImpl.class.getDeclaredField(name);
@@ -89,4 +119,29 @@ public class FrontProjectPreviewServiceImplTest
column.setIsPk("1");
return column;
}
private byte[] zipWithEntry(String name, String content) throws IOException
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
zip.putNextEntry(new ZipEntry(name));
zip.write(content.getBytes("UTF-8"));
zip.closeEntry();
zip.close();
return outputStream.toByteArray();
}
private Set<String> zipEntries(byte[] data) throws IOException
{
Set<String> entries = new HashSet<String>();
ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(data));
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null)
{
entries.add(entry.getName());
zip.closeEntry();
}
zip.close();
return entries;
}
}

View File

@@ -10,6 +10,7 @@ import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -156,6 +157,7 @@ public class FrontProjectServiceImplTest
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project());
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.emptyList());
DatabaseDesignResponse request = database(table("sys_user", pkColumn(), column("user_name", "varchar(64)", "0")));
request.setErDiagram(Collections.singletonMap("relations", Collections.singletonList("student_department")));
service.saveDatabase(7L, 10L, request);
@@ -166,6 +168,27 @@ public class FrontProjectServiceImplTest
assertEquals(Long.valueOf(10L), tableCaptor.getValue().getProjectId());
verify(frontProjectColumnMapper).deleteColumnsByProjectId(10L);
verify(frontProjectTableMapper).deleteTablesByProjectId(10L);
ArgumentCaptor<FrontProject> projectCaptor = ArgumentCaptor.forClass(FrontProject.class);
verify(frontProjectMapper).updateFrontProject(projectCaptor.capture());
assertEquals(Long.valueOf(10L), projectCaptor.getValue().getProjectId());
assertEquals("1", projectCaptor.getValue().getGenerateStatus());
assertEquals("0", projectCaptor.getValue().getPreviewStatus());
assertTrue(projectCaptor.getValue().getErDiagram().contains("student_department"));
}
@Test
public void getDatabaseReturnsErDiagramDraft()
{
FrontProject project = project();
project.setErDiagram("{\"positions\":{\"department\":{\"x\":24,\"y\":48}}}");
when(frontProjectMapper.selectFrontProjectByUserAndId(7L, 10L)).thenReturn(project);
when(frontProjectTableMapper.selectTablesByProjectId(10L)).thenReturn(Collections.emptyList());
DatabaseDesignResponse response = service.getDatabase(7L, 10L);
@SuppressWarnings("unchecked")
Map<String, Object> erDiagram = (Map<String, Object>) response.getErDiagram();
assertTrue(erDiagram.containsKey("positions"));
}
private void setField(String name, Object value) throws Exception

View File

@@ -0,0 +1,69 @@
package com.ruoyi.generator.service.front;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field;
import org.junit.Test;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import com.ruoyi.generator.config.DeepSeekProperties;
public class HttpDeepSeekClientTest
{
@Test
public void requestFactoryUsesDefaultSslVerificationByDefault() throws Exception
{
HttpDeepSeekClient client = client(false);
SimpleClientHttpRequestFactory factory = client.requestFactory(1000);
assertSame(SimpleClientHttpRequestFactory.class, factory.getClass());
}
@Test
public void requestFactoryCanSkipSslVerificationWhenEnabled() throws Exception
{
HttpDeepSeekClient client = client(true);
SimpleClientHttpRequestFactory factory = client.requestFactory(1000);
assertNotEquals(SimpleClientHttpRequestFactory.class, factory.getClass());
}
@Test
public void skipSslVerifyDefaultsToFalse()
{
DeepSeekProperties properties = new DeepSeekProperties();
assertFalse(properties.isSkipSslVerify());
properties.setSkipSslVerify(true);
assertTrue(properties.isSkipSslVerify());
properties.setSkipSslVerify(null);
assertFalse(properties.isSkipSslVerify());
}
@Test
public void deepSeekTimeoutDefaultsToLongRunningGeneration()
{
assertEquals(Integer.valueOf(180000), new DeepSeekProperties().getTimeout());
}
private HttpDeepSeekClient client(boolean skipSslVerify) throws Exception
{
HttpDeepSeekClient client = new HttpDeepSeekClient();
DeepSeekProperties properties = new DeepSeekProperties();
properties.setSkipSslVerify(skipSslVerify);
setField(client, "deepSeekProperties", properties);
return client;
}
private void setField(Object target, String name, Object value) throws Exception
{
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
}