feat:搭建工程

This commit is contained in:
2025-06-24 20:01:04 +08:00
commit 532bf9633a
39 changed files with 1345 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip

259
mvnw vendored Normal file
View File

@@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

BIN
mvnw.cmd vendored Normal file

Binary file not shown.

41
pom.xml Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cxx</groupId>
<artifactId>starter-parent</artifactId>
<version>2.0.0</version>
<relativePath/>
</parent>
<artifactId>food-hub-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>food-hub-service</name>
<description>food-hub-service</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-web</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-jdbc</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.cxx</groupId>
<artifactId>starter-logging</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package com.cxx.food;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FoodHubServiceApplication {
public static void main(String[] args) {
SpringApplication.run(FoodHubServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,100 @@
package com.cxx.food.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.food.dto.*;
import com.cxx.food.service.FoodService;
import com.cxx.food.vo.FoodQueryVo;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/food")
@Tag(name = "美食记录")
public class FoodController {
@Resource
private FoodService foodService;
@Operation(summary = "新增美食菜谱")
@PostMapping("/recipe")
public Boolean addFoodRecipe(@RequestBody @JsonView(WriteView.class) @Validated FoodRecipeDto foodRecipeDto) {
return foodService.addFoodRecipe(foodRecipeDto);
}
@Operation(summary = "更新美食菜谱")
@PutMapping("/recipe/{id}")
public Boolean updateFoodRecipe(@PathVariable("id") long id,
@RequestBody @JsonView(WriteView.class) FoodRecipeDto foodRecipeDto) {
return foodService.updateFoodRecipe(id, foodRecipeDto);
}
@Operation(summary = "删除美食菜谱")
@DeleteMapping("/recipe/{id}")
public Boolean deleteFoodRecipe(@PathVariable("id") long id) {
return foodService.deleteFoodRecipe(id);
}
@Operation(summary = "分页查询美食菜谱")
@GetMapping("/recipe/page")
public IPage<FoodSummaryDto> queryFoodSummary(@RequestParam("currentPage") Integer currentPage,
@RequestParam("pageSize") Integer pageSize,
FoodQueryVo foodQueryVo) {
return foodService.queryFoodSummary(currentPage, pageSize, foodQueryVo);
}
@Operation(summary = "查询美食")
@GetMapping("/recipe/{id}")
public @JsonView(ReadView.class) FoodRecipeDto queryFoodRecipe(@PathVariable("id") long id) {
return foodService.queryFoodRecipe(id);
}
@Operation(summary = "查询所有美食名称")
@GetMapping("/recipe/name")
public List<String> queryFoodName() {
return foodService.queryFoodName();
}
@Operation(summary = "新增美食成果")
@PostMapping("/record")
public Boolean addFoodRecord(@RequestBody @JsonView(WriteView.class) @Validated FoodRecordDto foodRecordDto) {
return foodService.addFoodRecord(foodRecordDto);
}
@Operation(summary = "更新美食成果")
@PutMapping("/record/{id}")
public Boolean updateFoodRecord(@PathVariable("id") long id,
@RequestBody @JsonView(WriteView.class) FoodRecordDto foodRecordDto) {
return foodService.updateFoodRecord(id, foodRecordDto);
}
@Operation(summary = "删除美食成果")
@DeleteMapping("/record/{id}")
public Boolean deleteFoodRecord(@PathVariable("id") long id) {
return foodService.deleteFoodRecord(id);
}
@Operation(summary = "查询美食记录")
@GetMapping("/record")
public @JsonView(ReadView.class) List<FoodRecordDto> queryFoodRecord(@RequestParam("startDate") String startDate,
@RequestParam("endDate") String endDate) {
return foodService.queryFoodRecord(startDate, endDate);
}
@Operation(summary = "查询美食类别")
@GetMapping("/category")
public List<String> queryFoodCategory() {
return foodService.queryFoodCategory();
}
@Operation(summary = "查询美食统计")
@GetMapping("/stats")
public FoodStatsDto queryFoodStats() {
return foodService.queryFoodStats();
}
}

View File

@@ -0,0 +1,28 @@
package com.cxx.food.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import com.cxx.food.dto.SessionDto;
import com.cxx.food.service.SessionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/session")
@Tag(name = "会话管理")
public class SessionController {
@Resource
private SessionService sessionService;
@Operation(summary = "创建会话")
@PostMapping("")
@SaIgnore
public SessionDto createSession(@RequestParam("name") String name,
@RequestParam("password") String password) {
return sessionService.create(name, password);
}
}

View File

@@ -0,0 +1,33 @@
package com.cxx.food.dao;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.food.dto.*;
import com.cxx.food.vo.FoodQueryVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface FoodDao {
FoodRecipeDto queryFoodRecipe(@Param("id") Long id);
IPage<FoodSummaryDto> queryFoodSummary(IPage<FoodSummaryDto> page,
@Param("query") FoodQueryVo foodQueryVo);
List<FoodRecordDto> queryFoodRecordById(@Param("id") Long id);
List<FoodRecordDto> queryFoodRecord(@Param("startDate") String startDate,
@Param("endDate") String endDate);
FoodStatsDto queryFoodStats();
List<String> queryFoodCategory();
void insertFoodMaterial(@Param("foodId") Long foodId,
@Param("list") List<FoodMaterialDto> list);
void insertFoodStep(@Param("foodId") Long foodId,
@Param("list") List<FoodStepDto> list);
}

View File

@@ -0,0 +1,23 @@
package com.cxx.food.dto;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@JsonView(PlainView.class)
public class FoodMaterialDto {
@Schema(description = "id")
private Long id;
@Schema(description = "食材类型")
private String type;
@Schema(description = "食材名称")
private String name;
@Schema(description = "食材分量")
private String amount;
}

View File

@@ -0,0 +1,43 @@
package com.cxx.food.dto;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Range;
import java.util.List;
@Getter
@Setter
@JsonView(PlainView.class)
public class FoodRecipeDto {
@Schema(description = "id")
@JsonView(ReadView.class)
private Long id;
@Schema(description = "美食名称")
@NotBlank(message = "名称不能为空")
private String name;
@Schema(description = "美食菜系")
private String category;
@Schema(description = "推荐指数")
@Range(min = 0, max = 5, message = "推荐指数在0-5之间")
private Integer recommendRate;
@Schema(description = "美食食材")
private List<FoodMaterialDto> materialList;
@Schema(description = "美食步骤")
private List<FoodStepDto> stepList;
@Schema(description = "美食成果")
@JsonView(ReadView.class)
private List<FoodRecordDto> recordList;
@Schema(description = "备注")
private String remark;
}

View File

@@ -0,0 +1,32 @@
package com.cxx.food.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
@Getter
@Setter
@JsonView(PlainView.class)
public class FoodRecordDto {
@Schema(description = "id")
@JsonView(ReadView.class)
private Long id;
@Schema(description = "名称")
private String name;
@Schema(description = "分类")
@JsonView(ReadView.class)
private String category;
@Schema(description = "完成时间")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date date;
@Schema(description = "成果图片")
private String imageUrl;
}

View File

@@ -0,0 +1,19 @@
package com.cxx.food.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class FoodStatsDto {
@Schema(description = "菜谱总数")
private Integer recipeCount;
@Schema(description = "菜谱类别")
private Integer categoryCount;
@Schema(description = "做菜次数")
private Integer workCount;
}

View File

@@ -0,0 +1,23 @@
package com.cxx.food.dto;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@JsonView(PlainView.class)
public class FoodStepDto {
@Schema(description = "id")
private Long id;
@Schema(description = "顺序")
private Integer sort;
@Schema(description = "步骤内容")
private String content;
@Schema(description = "步骤图片")
private String imageUrl;
}

View File

@@ -0,0 +1,26 @@
package com.cxx.food.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class FoodSummaryDto {
@Schema(description = "id")
private Long id;
@Schema(description = "美食名称")
private String name;
@Schema(description = "美食类型")
private String category;
@Schema(description = "推荐指数")
private Integer recommendRate;
@Schema(description = "美食记录")
private List<FoodRecordDto> recordList;
}

View File

@@ -0,0 +1,4 @@
package com.cxx.food.dto;
public interface PlainView {
}

View File

@@ -0,0 +1,4 @@
package com.cxx.food.dto;
public interface ReadView extends PlainView {
}

View File

@@ -0,0 +1,14 @@
package com.cxx.food.dto;
import cn.dev33.satoken.stp.SaTokenInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SessionDto {
@Schema(description = "令牌")
private SaTokenInfo saToken;
}

View File

@@ -0,0 +1,4 @@
package com.cxx.food.dto;
public interface WriteView extends PlainView {
}

View File

@@ -0,0 +1,24 @@
package com.cxx.food.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxx.framework.data.AbstractIdEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("food_material")
public class FoodMaterial extends AbstractIdEntity {
@TableField("food_id")
private Long foodId;
@TableField("type")
private String type;
@TableField("name")
private String name;
@TableField("amount")
private String amount;
}

View File

@@ -0,0 +1,24 @@
package com.cxx.food.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxx.framework.data.AbstractEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("food_recipe")
public class FoodRecipe extends AbstractEntity {
@TableField("name")
private String name;
@TableField("category")
private String category;
@TableField("recommend_rate")
private Integer recommendRate;
@TableField("remark")
private String remark;
}

View File

@@ -0,0 +1,23 @@
package com.cxx.food.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxx.framework.data.AbstractIdEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("food_record")
public class FoodRecord extends AbstractIdEntity {
@TableField("food_id")
private Long foodId;
@TableField("date")
private Date date;
@TableField("image_url")
private String imageUrl;
}

View File

@@ -0,0 +1,24 @@
package com.cxx.food.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxx.framework.data.AbstractIdEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("food_step")
public class FoodStep extends AbstractIdEntity {
@TableField("food_id")
private Long foodId;
@TableField("sort")
private Integer sort;
@TableField("content")
private String content;
@TableField("image_url")
private String imageUrl;
}

View File

@@ -0,0 +1,9 @@
package com.cxx.food.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxx.food.entity.FoodMaterial;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FoodMaterialMapper extends BaseMapper<FoodMaterial> {
}

View File

@@ -0,0 +1,9 @@
package com.cxx.food.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxx.food.entity.FoodRecipe;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FoodRecipeMapper extends BaseMapper<FoodRecipe> {
}

View File

@@ -0,0 +1,9 @@
package com.cxx.food.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxx.food.entity.FoodRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FoodRecordMapper extends BaseMapper<FoodRecord> {
}

View File

@@ -0,0 +1,9 @@
package com.cxx.food.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxx.food.entity.FoodStep;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface FoodStepMapper extends BaseMapper<FoodStep> {
}

View File

@@ -0,0 +1,37 @@
package com.cxx.food.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cxx.food.dto.FoodRecipeDto;
import com.cxx.food.dto.FoodRecordDto;
import com.cxx.food.dto.FoodStatsDto;
import com.cxx.food.dto.FoodSummaryDto;
import com.cxx.food.vo.FoodQueryVo;
import java.util.List;
public interface FoodService {
Boolean addFoodRecipe(FoodRecipeDto foodRecipeDto);
Boolean updateFoodRecipe(Long id, FoodRecipeDto foodRecipeDto);
Boolean deleteFoodRecipe(Long id);
Boolean addFoodRecord(FoodRecordDto foodRecordDto);
Boolean updateFoodRecord(Long id, FoodRecordDto foodRecordDto);
Boolean deleteFoodRecord(Long id);
FoodRecipeDto queryFoodRecipe(Long id);
IPage<FoodSummaryDto> queryFoodSummary(Integer currentPage, Integer pageSize, FoodQueryVo foodQueryVo);
List<FoodRecordDto> queryFoodRecord(String startDate, String endDate);
List<String> queryFoodName();
List<String> queryFoodCategory();
FoodStatsDto queryFoodStats();
}

View File

@@ -0,0 +1,8 @@
package com.cxx.food.service;
import com.cxx.food.dto.SessionDto;
public interface SessionService {
SessionDto create(String name, String password);
}

View File

@@ -0,0 +1,200 @@
package com.cxx.food.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cxx.food.dao.FoodDao;
import com.cxx.food.dto.*;
import com.cxx.food.entity.FoodMaterial;
import com.cxx.food.entity.FoodRecipe;
import com.cxx.food.entity.FoodRecord;
import com.cxx.food.entity.FoodStep;
import com.cxx.food.mapper.FoodMaterialMapper;
import com.cxx.food.mapper.FoodRecipeMapper;
import com.cxx.food.mapper.FoodRecordMapper;
import com.cxx.food.mapper.FoodStepMapper;
import com.cxx.food.service.FoodService;
import com.cxx.food.vo.FoodQueryVo;
import com.cxx.framework.web.CustomException;
import com.github.yitter.idgen.YitIdHelper;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class FoodServiceImpl implements FoodService {
@Resource
private FoodDao foodDao;
@Resource
private FoodRecipeMapper foodRecipeMapper;
@Resource
private FoodMaterialMapper foodMaterialMapper;
@Resource
private FoodStepMapper foodStepMapper;
@Resource
private FoodRecordMapper foodRecordMapper;
@Override
@Transactional
public Boolean addFoodRecipe(FoodRecipeDto foodRecipeDto) {
LambdaQueryWrapper<FoodRecipe> queryWrapper = new LambdaQueryWrapper<>();
if (foodRecipeMapper.exists(queryWrapper.eq(FoodRecipe::getName, foodRecipeDto.getName()))) {
throw new CustomException("该菜谱已经存在");
}
FoodRecipe foodRecipe = new FoodRecipe();
BeanUtils.copyProperties(foodRecipeDto, foodRecipe);
foodRecipeMapper.insert(foodRecipe);
resetFoodRecipe(foodRecipe.getId(), foodRecipeDto);
return Boolean.TRUE;
}
@Override
@Transactional
public Boolean updateFoodRecipe(Long id, FoodRecipeDto foodRecipeDto) {
if (foodRecipeMapper.selectById(id) == null) {
throw new CustomException("该菜谱不存在");
}
FoodRecipe foodRecipe = new FoodRecipe();
BeanUtils.copyProperties(foodRecipeDto, foodRecipe);
foodRecipe.setId(id);
foodRecipeMapper.updateById(foodRecipe);
resetFoodRecipe(foodRecipe.getId(), foodRecipeDto);
return Boolean.TRUE;
}
@Override
@Transactional
public Boolean deleteFoodRecipe(Long id) {
foodRecipeMapper.deleteById(id);
foodMaterialMapper.delete(Wrappers.lambdaQuery(FoodMaterial.class).eq(FoodMaterial::getFoodId, id));
foodStepMapper.delete(Wrappers.lambdaQuery(FoodStep.class).eq(FoodStep::getFoodId, id));
foodRecordMapper.delete(Wrappers.lambdaQuery(FoodRecord.class).eq(FoodRecord::getFoodId, id));
return Boolean.TRUE;
}
@Override
public Boolean addFoodRecord(FoodRecordDto foodRecordDto) {
LambdaQueryWrapper<FoodRecipe> queryWrapper = new LambdaQueryWrapper<>();
FoodRecipe foodRecipe = foodRecipeMapper.selectOne(queryWrapper.eq(FoodRecipe::getName, foodRecordDto.getName()));
if (foodRecipe == null) {
throw new CustomException("该菜谱不存在");
}
FoodRecord foodRecord = new FoodRecord();
foodRecord.setFoodId(foodRecipe.getId());
BeanUtils.copyProperties(foodRecordDto, foodRecord);
foodRecordMapper.insert(foodRecord);
return Boolean.TRUE;
}
@Override
public Boolean updateFoodRecord(Long id, FoodRecordDto foodRecordDto) {
if (foodRecordMapper.selectById(id) == null) {
throw new CustomException("该成果不存在");
}
FoodRecord foodRecord = new FoodRecord();
BeanUtils.copyProperties(foodRecordDto, foodRecord);
foodRecord.setId(id);
foodRecordMapper.updateById(foodRecord);
return Boolean.TRUE;
}
@Override
public Boolean deleteFoodRecord(Long id) {
foodRecordMapper.deleteById(id);
return Boolean.TRUE;
}
private void resetFoodRecipe(Long id, FoodRecipeDto foodRecipeDto) {
foodMaterialMapper.delete(Wrappers.lambdaQuery(FoodMaterial.class).eq(FoodMaterial::getFoodId, id));
foodStepMapper.delete(Wrappers.lambdaQuery(FoodStep.class).eq(FoodStep::getFoodId, id));
insertFoodMaterial(id, foodRecipeDto.getMaterialList());
insertFoodStep(id, foodRecipeDto.getStepList());
}
private void insertFoodMaterial(Long foodId, List<FoodMaterialDto> foodMaterialList) {
if (foodMaterialList.isEmpty()) {
return;
}
foodMaterialList.forEach((item) -> {
item.setId(YitIdHelper.nextId());
});
foodDao.insertFoodMaterial(foodId, foodMaterialList);
}
private void insertFoodStep(Long foodId, List<FoodStepDto> foodStepList) {
if (foodStepList.isEmpty()) {
return;
}
foodStepList.forEach((item) -> {
item.setId(YitIdHelper.nextId());
});
foodDao.insertFoodStep(foodId, foodStepList);
}
@Override
public FoodRecipeDto queryFoodRecipe(Long id) {
if (foodRecipeMapper.selectById(id) == null) {
throw new CustomException("该美食不存在");
}
return foodDao.queryFoodRecipe(id);
}
@Override
public IPage<FoodSummaryDto> queryFoodSummary(Integer currentPage, Integer pageSize, FoodQueryVo foodQueryVo) {
// 这里只能先对主表进行分页 然后在查询子表信息
// 不能使用Left Join关联表查询因为这里是一对多数据关联查询会导致数据有重复
// 从而导致page计算错误会把重复的数据当成多条记录实际上只有1条记录
IPage<FoodSummaryDto> foodSummaryPage = foodDao.queryFoodSummary(new Page<>(currentPage, pageSize), foodQueryVo);
List<FoodSummaryDto> foodSummaryList = foodSummaryPage.getRecords();
for (FoodSummaryDto foodSummary : foodSummaryList) {
foodSummary.setRecordList(foodDao.queryFoodRecordById(foodSummary.getId()));
}
foodSummaryPage.setRecords(foodSummaryList);
return foodSummaryPage;
}
@Override
public List<FoodRecordDto> queryFoodRecord(String startDate, String endDate) {
return foodDao.queryFoodRecord(startDate, endDate);
}
@Override
public List<String> queryFoodName() {
return foodRecipeMapper.selectList(null)
.stream().map(FoodRecipe::getName).collect(Collectors.toList());
}
@Override
public List<String> queryFoodCategory() {
return foodDao.queryFoodCategory();
}
@Override
public FoodStatsDto queryFoodStats() {
return foodDao.queryFoodStats();
}
}

View File

@@ -0,0 +1,27 @@
package com.cxx.food.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cxx.food.dto.SessionDto;
import com.cxx.food.service.SessionService;
import com.cxx.framework.web.CustomException;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Service
public class SessionServiceImpl implements SessionService {
@Override
public SessionDto create(String name, String password) {
SessionDto sessionDto = new SessionDto();
StpUtil.login(name);
sessionDto.setSaToken(StpUtil.getTokenInfo());
return sessionDto;
}
}

View File

@@ -0,0 +1,13 @@
package com.cxx.food.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class FoodQueryVo {
@Schema(description = "美食类别")
private String category;
}

View File

@@ -0,0 +1,10 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: cxx
password: 123456
file:
path: D:\\temp\\
log-path: D:\\temp\\

View File

@@ -0,0 +1,9 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: docker
password: 19940822Cxx
file:
path: /upload-file/

View File

@@ -0,0 +1,10 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/sweet_hut?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: cxx
password: 19940822Cxx@1213
file:
path: /root/docker/upload-file/
log-path: /root/service/logs/

View File

@@ -0,0 +1,23 @@
server:
port: 8090
spring:
application:
name: @project.artifactId@
version: @project.version@
profiles:
active: dev
# mybatis plus mapper路径
mybatis-plus:
# mybatis plus
mapper-locations: classpath*:/mapper/**/*.xml
configuration:
log-impl:
org.apache.ibatis.logging.stdout.StdOutImpl
web-starter:
base-package: com.cxx.food
aes:
secret-key: "sG5p7t9wBdKnPqRsUvYx2D5F8H9JmQ=2"

View File

@@ -0,0 +1,144 @@
<?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.cxx.food.dao.FoodDao">
<resultMap id="foodRecipeResultMap" type="com.cxx.food.dto.FoodRecipeDto">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="category" column="category"/>
<result property="recommendRate" column="recommendRate"/>
<result property="remark" column="remark"/>
<collection property="materialList" ofType="com.cxx.food.dto.FoodMaterialDto"
column="id" select="queryFoodMaterial">
</collection>
<collection property="stepList" ofType="com.cxx.food.dto.FoodStepDto"
column="id" select="queryFoodStep">
</collection>
<collection property="recordList" ofType="com.cxx.food.dto.FoodRecordDto"
column="id" select="queryFoodRecordById">
</collection>
</resultMap>
<resultMap id="foodMaterialResultMap" type="com.cxx.food.dto.FoodMaterialDto">
<id property="id" column="id"/>
<result property="type" column="type"/>
<result property="name" column="name"/>
<result property="amount" column="amount"/>
</resultMap>
<resultMap id="foodStepResultMap" type="com.cxx.food.dto.FoodStepDto">
<id property="id" column="id"/>
<result property="sort" column="sort"/>
<result property="content" column="content"/>
<result property="imageUrl" column="imageUrl"/>
</resultMap>
<resultMap id="foodRecordResultMap" type="com.cxx.food.dto.FoodRecordDto">
<id property="id" column="id"/>
<result property="date" column="date"/>
<result property="imageUrl" column="imageUrl"/>
</resultMap>
<resultMap id="foodSummaryResultMap" type="com.cxx.food.dto.FoodSummaryDto">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="category" column="category"/>
<result property="recommendRate" column="recommendRate"/>
<collection property="recordList" ofType="com.cxx.food.dto.FoodRecordDto">
<id property="id" column="workId"/>
<result property="date" column="recordDate"/>
<result property="person" column="recordPerson"/>
<result property="imageUrl" column="workImageUrl"/>
</collection>
</resultMap>
<select id="queryFoodMaterial" resultMap="foodMaterialResultMap">
SELECT id AS id,
type AS type,
name AS name,
amount AS amount
FROM food_material
WHERE food_id = #{id}
</select>
<select id="queryFoodStep" resultMap="foodStepResultMap">
SELECT id AS id,
sort AS sort,
content AS content,
image_url AS imageUrl
FROM food_step
WHERE food_id = #{id}
ORDER BY sort
</select>
<select id="queryFoodRecordById" resultMap="foodRecordResultMap">
SELECT id AS id,
date AS date,
image_url AS imageUrl
FROM food_record
WHERE food_id = #{id}
ORDER BY date DESC
</select>
<select id="queryFoodRecord" resultType="com.cxx.food.dto.FoodRecordDto">
SELECT a.id AS id,
b.name AS name,
b.category AS category,
a.date AS date,
a.image_url AS imageUrl
FROM food_record AS a
LEFT JOIN food_recipe AS b ON a.food_id = b.id
WHERE a.date <![CDATA[ >=]]> STR_TO_DATE(#{startDate}, '%Y-%m-%d')
AND a.date <![CDATA[ <=]]> STR_TO_DATE(#{endDate}, '%Y-%m-%d')
ORDER BY date DESC
</select>
<select id="queryFoodRecipe" resultMap="foodRecipeResultMap">
SELECT a.id AS id,
a.name AS name,
a.category AS category,
a.recommend_rate AS recommendRate,
a.remark AS remake
FROM food_recipe a
WHERE a.id = #{id}
</select>
<select id="queryFoodSummary" resultMap="foodSummaryResultMap">
SELECT a.id AS id,
a.name AS name,
a.category AS category,
a.recommend_rate AS recommendRate
FROM food_recipe a
<where>
<if test="query.category != null and query.category != ''">
a.category = #{query.category}
</if>
</where>
</select>
<select id="queryFoodStats" resultType="com.cxx.food.dto.FoodStatsDto">
SELECT COUNT(DISTINCT a.name) AS recipeCount,
COUNT(DISTINCT a.category) AS categoryCount,
COUNT(b.id) AS workCount
FROM food_recipe a
LEFT JOIN food_record b ON a.id = b.food_id
</select>
<select id="queryFoodCategory" resultType="string">
SELECT DISTINCT a.category AS category
FROM food_recipe a
</select>
<insert id="insertFoodMaterial">
INSERT INTO food_material (id, food_id, type, name, amount) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{foodId}, #{item.type}, #{item.name}, #{item.amount})
</foreach>
</insert>
<insert id="insertFoodStep">
INSERT INTO food_step (id, food_id, sort, content, image_url) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.id}, #{foodId}, #{item.sort}, #{item.content}, #{item.imageUrl})
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,13 @@
package com.cxx.food;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class FoodHubServiceApplicationTests {
@Test
void contextLoads() {
}
}