1.0.2原版
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
|
||||
/**
|
||||
* @author ryanwang
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@GVK(group = "equipment.kunkunyu.com", version = "v1alpha1", kind = "Equipment", plural = "equipments",
|
||||
singular = "equipment")
|
||||
public class Equipment extends AbstractExtension {
|
||||
|
||||
private EquipmentSpec spec;
|
||||
|
||||
@Data
|
||||
public static class EquipmentSpec {
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String displayName;
|
||||
|
||||
private String specification;
|
||||
|
||||
private String description;
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String cover;
|
||||
|
||||
private String url;
|
||||
|
||||
private Integer priority;
|
||||
|
||||
@Schema(requiredMode = REQUIRED, pattern = "^\\S+$")
|
||||
private String groupName;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isDeleted() {
|
||||
return Objects.equals(true,
|
||||
getMetadata().getDeletionTimestamp() != null
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import com.kunkunyu.equipment.service.EquipmentService;
|
||||
|
||||
/**
|
||||
* A custom endpoint for {@link Equipment}.
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class EquipmentEndpoint implements CustomEndpoint {
|
||||
|
||||
private final EquipmentService equipmentService;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
final var tag = "console.api.equipment.kunkunyu.com/v1alpha1/Equipment";
|
||||
return route()
|
||||
.GET("equipments", this::listEquipment,
|
||||
builder -> {
|
||||
builder.operationId("ListEquipments")
|
||||
.description("List equipment.")
|
||||
.tag(tag)
|
||||
.response(responseBuilder().implementation(
|
||||
ListResult.generateGenericClass(Equipment.class)));
|
||||
|
||||
EquipmentQuery.buildParameters(builder);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return GroupVersion.parseAPIVersion("console.api.equipment.kunkunyu.com/v1alpha1");
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listEquipment(ServerRequest serverRequest) {
|
||||
EquipmentQuery query = new EquipmentQuery(serverRequest.exchange());
|
||||
return equipmentService.listEquipment(query)
|
||||
.flatMap(equipments -> ServerResponse.ok().bodyValue(equipments));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@GVK(group = "equipment.kunkunyu.com", version = "v1alpha1", kind = "EquipmentGroup",
|
||||
plural = "equipmentgroups", singular = "equipmentgroup")
|
||||
public class EquipmentGroup extends AbstractExtension {
|
||||
|
||||
@Schema(required = true)
|
||||
private EquipmentGroupSpec spec;
|
||||
|
||||
@Schema
|
||||
private EquipmentGroupStatus status;
|
||||
|
||||
@Data
|
||||
public static class EquipmentGroupSpec {
|
||||
@Schema(required = true)
|
||||
private String displayName;
|
||||
|
||||
private String description;
|
||||
|
||||
private Integer priority;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public EquipmentGroupStatus getStatusOrDefault() {
|
||||
if (this.status == null) {
|
||||
this.status = new EquipmentGroupStatus();
|
||||
}
|
||||
return this.status;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class EquipmentGroupStatus {
|
||||
|
||||
public Integer equipmentCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.extension.GroupVersion;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import com.kunkunyu.equipment.service.EquipmentGroupService;
|
||||
|
||||
/**
|
||||
* A custom endpoint for {@link Equipment}.
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class EquipmentGroupEndpoint implements CustomEndpoint {
|
||||
|
||||
private final EquipmentGroupService equipmentGroupService;
|
||||
|
||||
@Override
|
||||
public RouterFunction<ServerResponse> endpoint() {
|
||||
return route()
|
||||
.GET("equipmentgroups", this::listEquipmentGroup,
|
||||
builder -> {
|
||||
builder.operationId("ListEquipments")
|
||||
.description("List equipment.")
|
||||
.response(responseBuilder().implementation(
|
||||
ListResult.generateGenericClass(EquipmentGroup.class))
|
||||
);
|
||||
EquipmentQuery.buildParameters(builder);
|
||||
}
|
||||
)
|
||||
.DELETE("equipmentgroups/{name}", this::deleteEquipmentGroup,
|
||||
builder -> builder.operationId("DeleteEquipmentGroup")
|
||||
.description("Delete equipment group.")
|
||||
.parameter(parameterBuilder()
|
||||
.name("name")
|
||||
.in(ParameterIn.PATH)
|
||||
.description("Equipment group name")
|
||||
.implementation(String.class)
|
||||
.required(true)
|
||||
)
|
||||
.response(responseBuilder().implementation(EquipmentGroup.class))
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupVersion groupVersion() {
|
||||
return GroupVersion.parseAPIVersion("console.api.equipment.kunkunyu.com/v1alpha1");
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> deleteEquipmentGroup(ServerRequest serverRequest) {
|
||||
String name = serverRequest.pathVariable("name");
|
||||
if (StringUtils.isBlank(name)) {
|
||||
throw new ServerWebInputException("Equipment group name must not be blank.");
|
||||
}
|
||||
return equipmentGroupService.deleteEquipmentGroup(name)
|
||||
.flatMap(equipmentGroup -> ServerResponse.ok().bodyValue(equipmentGroup));
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> listEquipmentGroup(ServerRequest serverRequest) {
|
||||
var request = new EquipmentQuery(serverRequest.exchange());
|
||||
return equipmentGroupService.listEquipmentGroup(request)
|
||||
.flatMap(equipmentGroups -> ServerResponse.ok().bodyValue(equipmentGroups));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.SchemeManager;
|
||||
import run.halo.app.extension.index.IndexSpec;
|
||||
import run.halo.app.plugin.BasePlugin;
|
||||
import run.halo.app.plugin.PluginContext;
|
||||
|
||||
@Component
|
||||
public class EquipmentPlugin extends BasePlugin {
|
||||
private final SchemeManager schemeManager;
|
||||
|
||||
public EquipmentPlugin(PluginContext pluginContext, SchemeManager schemeManager) {
|
||||
super(pluginContext);
|
||||
this.schemeManager = schemeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
schemeManager.register(Equipment.class, indexSpecs -> {
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName("spec.groupName")
|
||||
.setIndexFunc(simpleAttribute(Equipment.class, equipment ->
|
||||
equipment.getSpec() == null ? "" : equipment.getSpec().getGroupName()
|
||||
))
|
||||
);
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName("spec.displayName")
|
||||
.setIndexFunc(simpleAttribute(Equipment.class, equipment ->
|
||||
equipment.getSpec() == null ? "" : equipment.getSpec().getDisplayName()
|
||||
))
|
||||
);
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName("spec.priority")
|
||||
.setIndexFunc(simpleAttribute(Equipment.class, equipment ->
|
||||
equipment.getSpec() == null || equipment.getSpec().getPriority() == null
|
||||
? String.valueOf(0) : equipment.getSpec().getPriority().toString()
|
||||
))
|
||||
);
|
||||
});
|
||||
schemeManager.register(EquipmentGroup.class, indexSpecs -> {
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName("spec.priority")
|
||||
.setIndexFunc(simpleAttribute(EquipmentGroup.class, group ->
|
||||
group.getSpec() == null || group.getSpec().getPriority() == null
|
||||
? String.valueOf(0) : group.getSpec().getPriority().toString()
|
||||
))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
schemeManager.unregister(Scheme.buildFromType(Equipment.class));
|
||||
schemeManager.unregister(Scheme.buildFromType(EquipmentGroup.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springdoc.core.fn.builders.operation.Builder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import run.halo.app.extension.router.IListRequest;
|
||||
import run.halo.app.extension.router.SortableRequest;
|
||||
|
||||
/**
|
||||
* A query object for {@link Equipment} list.
|
||||
*
|
||||
*/
|
||||
public class EquipmentQuery extends SortableRequest {
|
||||
|
||||
public EquipmentQuery(ServerWebExchange exchange) {
|
||||
super(exchange);
|
||||
}
|
||||
|
||||
@Schema(description = "Equipments filtered by group.")
|
||||
public String getGroup() {
|
||||
return queryParams.getFirst("group");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Schema(description = "Equipments filtered by keyword.")
|
||||
public String getKeyword() {
|
||||
return queryParams.getFirst("keyword");
|
||||
}
|
||||
|
||||
public static void buildParameters(Builder builder) {
|
||||
IListRequest.buildParameters(builder);
|
||||
builder.parameter(sortParameter())
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.QUERY)
|
||||
.name("keyword")
|
||||
.description("Equipments filtered by keyword.")
|
||||
.implementation(String.class)
|
||||
.required(false))
|
||||
.parameter(parameterBuilder()
|
||||
.in(ParameterIn.QUERY)
|
||||
.name("group")
|
||||
.description("equipment group name")
|
||||
.implementation(String.class)
|
||||
.required(false))
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static run.halo.app.theme.router.PageUrlUtils.totalPage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.plugin.ReactiveSettingFetcher;
|
||||
import run.halo.app.theme.TemplateNameResolver;
|
||||
import run.halo.app.theme.router.PageUrlUtils;
|
||||
import run.halo.app.theme.router.UrlContextListResult;
|
||||
import com.kunkunyu.equipment.finders.EquipmentFinder;
|
||||
import com.kunkunyu.equipment.vo.EquipmentGroupVo;
|
||||
import com.kunkunyu.equipment.vo.EquipmentVo;
|
||||
|
||||
/**
|
||||
* Provides a <code>/equipments</code> route for the topic end to handle routing.
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class EquipmentRouter {
|
||||
|
||||
private static final String GROUP_PARAM = "group";
|
||||
|
||||
private EquipmentFinder equipmentFinder;
|
||||
|
||||
private final ReactiveSettingFetcher settingFetcher;
|
||||
|
||||
private final TemplateNameResolver templateNameResolver;
|
||||
|
||||
/**
|
||||
* Provides a <code>/equipments</code> route for the topic end to handle routing.
|
||||
*
|
||||
* @return a {@link RouterFunction} instance
|
||||
*/
|
||||
@Bean
|
||||
RouterFunction<ServerResponse> equipmentRouter() {
|
||||
return route(GET("/equipments").or(GET("/equipments/page/{page:\\d+}")),
|
||||
handlerFunction()
|
||||
);
|
||||
}
|
||||
|
||||
private HandlerFunction<ServerResponse> handlerFunction() {
|
||||
return request -> templateNameResolver.resolveTemplateNameOrDefault(request.exchange(), "equipments")
|
||||
.flatMap(templateName -> ServerResponse.ok().render(templateName,
|
||||
Map.of("groups", equipmentGroups(),
|
||||
"equipments", equipmentList(request),
|
||||
ModelConst.TEMPLATE_ID, templateName,
|
||||
"title", getEquipmentTitle()
|
||||
))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private Mono<UrlContextListResult<EquipmentVo>> equipmentList(ServerRequest request) {
|
||||
String path = request.path();
|
||||
int pageNum = pageNumInPathVariable(request);
|
||||
String group = groupPathQueryParam(request);
|
||||
return this.settingFetcher.get("base")
|
||||
.map(item -> item.get("pageSize").asInt(10))
|
||||
.defaultIfEmpty(10)
|
||||
.flatMap(pageSize -> equipmentFinder.list(pageNum, pageSize, group)
|
||||
.map(list -> new UrlContextListResult.Builder<EquipmentVo>()
|
||||
.listResult(list)
|
||||
.nextUrl(appendGroupParam(
|
||||
PageUrlUtils.nextPageUrl(path, totalPage(list)), group)
|
||||
)
|
||||
.prevUrl(appendGroupParam(PageUrlUtils.prevPageUrl(path), group))
|
||||
.build()
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private static String appendGroupParam(String path, String group) {
|
||||
return UriComponentsBuilder.fromPath(path)
|
||||
.queryParamIfPresent(GROUP_PARAM, Optional.ofNullable(group))
|
||||
.build()
|
||||
.toString();
|
||||
}
|
||||
|
||||
private int pageNumInPathVariable(ServerRequest request) {
|
||||
String page = request.pathVariables().get("page");
|
||||
return NumberUtils.toInt(page, 1);
|
||||
}
|
||||
|
||||
private String groupPathQueryParam(ServerRequest request) {
|
||||
return request.queryParam(GROUP_PARAM)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
Mono<String> getEquipmentTitle() {
|
||||
return this.settingFetcher.get("base").map(
|
||||
setting -> setting.get("title").asText("装备")).defaultIfEmpty(
|
||||
"装备");
|
||||
}
|
||||
|
||||
private Mono<List<EquipmentGroupVo>> equipmentGroups() {
|
||||
return equipmentFinder.groupBy().collectList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kunkunyu.equipment;
|
||||
|
||||
/**
|
||||
* Static variable keys for view model.
|
||||
*
|
||||
*/
|
||||
public enum ModelConst {
|
||||
;
|
||||
public static final String TEMPLATE_ID = "_templateId";
|
||||
public static final Integer DEFAULT_PAGE_SIZE = 10;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.kunkunyu.equipment.finders;
|
||||
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import com.kunkunyu.equipment.vo.EquipmentGroupVo;
|
||||
import com.kunkunyu.equipment.vo.EquipmentVo;
|
||||
|
||||
|
||||
/**
|
||||
* A finder for {@link Equipment}.
|
||||
*
|
||||
*/
|
||||
public interface EquipmentFinder {
|
||||
|
||||
/**
|
||||
* List all equipment.
|
||||
*
|
||||
* @return a flux of equipment vo
|
||||
*/
|
||||
Flux<EquipmentVo> listAll();
|
||||
|
||||
/**
|
||||
* List equipment by page.
|
||||
*
|
||||
* @param page page number
|
||||
* @param size page size
|
||||
* @return a mono of list result
|
||||
*/
|
||||
Mono<ListResult<EquipmentVo>> list(Integer page, Integer size);
|
||||
|
||||
/**
|
||||
* List equipment by page and group.
|
||||
*
|
||||
* @param page page number
|
||||
* @param size page size
|
||||
* @param group group name
|
||||
* @return a mono of list result
|
||||
*/
|
||||
Mono<ListResult<EquipmentVo>> list(Integer page, Integer size, String group);
|
||||
|
||||
/**
|
||||
* List equipment by group.
|
||||
*
|
||||
* @param group group name
|
||||
* @return a flux of equipment vo
|
||||
*/
|
||||
Flux<EquipmentVo> listBy(String group);
|
||||
|
||||
/**
|
||||
* List all groups.
|
||||
*
|
||||
* @return a flux of equipment group vo
|
||||
*/
|
||||
Flux<EquipmentGroupVo> groupBy();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.kunkunyu.equipment.finders.impl;
|
||||
|
||||
import static org.springframework.data.domain.Sort.Order.asc;
|
||||
import static org.springframework.data.domain.Sort.Order.desc;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.index.query.QueryFactory;
|
||||
import run.halo.app.theme.finders.Finder;
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
import com.kunkunyu.equipment.EquipmentGroup;
|
||||
import com.kunkunyu.equipment.finders.EquipmentFinder;
|
||||
import com.kunkunyu.equipment.vo.EquipmentGroupVo;
|
||||
import com.kunkunyu.equipment.vo.EquipmentVo;
|
||||
|
||||
@Finder("equipmentFinder")
|
||||
public class EquipmentFInderImpl implements EquipmentFinder {
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public EquipmentFInderImpl(ReactiveExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<EquipmentVo> listAll() {
|
||||
return this.client.listAll(Equipment.class, ListOptions.builder().build(), defaultSort())
|
||||
.map(EquipmentVo::from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<EquipmentVo>> list(Integer page, Integer size) {
|
||||
return list(page, size, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<EquipmentVo>> list(Integer page, Integer size, String group) {
|
||||
return pageEquipment(page, size, group);
|
||||
}
|
||||
|
||||
private Mono<ListResult<EquipmentVo>> pageEquipment(Integer page, Integer size, String group) {
|
||||
var builder = ListOptions.builder();
|
||||
if (StringUtils.isNotEmpty(group)) {
|
||||
builder.andQuery(QueryFactory.equal("spec.groupName", group));
|
||||
}
|
||||
return client.listBy(Equipment.class, builder.build(),
|
||||
PageRequestImpl.of(page, size, defaultSort()))
|
||||
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||
.map(EquipmentVo::from)
|
||||
.collectList()
|
||||
.map(list -> new ListResult<>(
|
||||
listResult.getPage(), listResult.getSize(), listResult.getTotal(), list
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<EquipmentVo> listBy(String groupName) {
|
||||
var options = ListOptions.builder()
|
||||
.andQuery(QueryFactory.equal("spec.groupName", groupName))
|
||||
.build();
|
||||
return client.listAll(Equipment.class, options, defaultSort()).map(EquipmentVo::from);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<EquipmentGroupVo> groupBy() {
|
||||
return this.client.listAll(EquipmentGroup.class, ListOptions.builder().build(), defaultSort())
|
||||
.concatMap(group -> {
|
||||
var builder = EquipmentGroupVo.from(group);
|
||||
return this.listBy(group.getMetadata().getName())
|
||||
.collectList()
|
||||
.doOnNext(equipments -> {
|
||||
EquipmentGroup.EquipmentGroupStatus status = group.getStatus();
|
||||
status.setEquipmentCount(equipments.size());
|
||||
builder.status(status);
|
||||
builder.equipments(equipments);
|
||||
})
|
||||
.then(Mono.fromSupplier(builder::build));
|
||||
});
|
||||
}
|
||||
|
||||
static Sort defaultSort() {
|
||||
return Sort.by(
|
||||
asc("spec.priority"),
|
||||
desc("metadata.creationTimestamp"),
|
||||
asc("metadata.name")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.kunkunyu.equipment.service;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||
import com.kunkunyu.equipment.EquipmentGroup;
|
||||
|
||||
/**
|
||||
* A service for {@link EquipmentGroup}.
|
||||
*
|
||||
*/
|
||||
public interface EquipmentGroupService {
|
||||
|
||||
/**
|
||||
* List equipment groups.
|
||||
*
|
||||
* @param request request
|
||||
* @return a mono of list result
|
||||
*/
|
||||
Mono<ListResult<EquipmentGroup>> listEquipmentGroup(QueryListRequest request);
|
||||
|
||||
/**
|
||||
* Create a equipment group.
|
||||
*
|
||||
* @param name name
|
||||
* @return a mono of equipment group
|
||||
*/
|
||||
Mono<EquipmentGroup> deleteEquipmentGroup(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.kunkunyu.equipment.service;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
import com.kunkunyu.equipment.EquipmentQuery;
|
||||
|
||||
/**
|
||||
* A service for {@link Equipment}.
|
||||
*
|
||||
*/
|
||||
public interface EquipmentService {
|
||||
|
||||
/**
|
||||
* List equipment.
|
||||
*
|
||||
* @param query query
|
||||
* @return a mono of list result
|
||||
*/
|
||||
Mono<ListResult<Equipment>> listEquipment(EquipmentQuery query);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.kunkunyu.equipment.service.impl;
|
||||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.index.query.QueryFactory;
|
||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
import com.kunkunyu.equipment.EquipmentGroup;
|
||||
import com.kunkunyu.equipment.service.EquipmentGroupService;
|
||||
|
||||
/**
|
||||
* Service implementation for {@link Equipment}.
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
class EquipmentGroupServiceImpl implements EquipmentGroupService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public EquipmentGroupServiceImpl(ReactiveExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<EquipmentGroup>> listEquipmentGroup(QueryListRequest query) {
|
||||
return this.client.listBy(
|
||||
EquipmentGroup.class,
|
||||
toListOptions(query),
|
||||
PageRequestImpl.of(query.getPage(), query.getSize())
|
||||
)
|
||||
.flatMap(listResult -> Flux.fromStream(listResult.get())
|
||||
.flatMap(this::populateEquipments)
|
||||
.collectList()
|
||||
.map(groups -> new ListResult<>(
|
||||
listResult.getPage(),
|
||||
listResult.getSize(),
|
||||
listResult.getTotal(),
|
||||
groups
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<EquipmentGroup> deleteEquipmentGroup(String name) {
|
||||
return this.client.fetch(EquipmentGroup.class, name)
|
||||
.flatMap(this.client::delete)
|
||||
.flatMap(deleted -> {
|
||||
var listOptions = ListOptions.builder()
|
||||
.andQuery(QueryFactory.equal("spec.groupName", name))
|
||||
.build();
|
||||
return this.client.listAll(Equipment.class, listOptions, Sort.unsorted())
|
||||
.flatMap(this.client::delete)
|
||||
.then()
|
||||
.thenReturn(deleted);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Mono<EquipmentGroup> populateEquipments(EquipmentGroup equipmentGroup) {
|
||||
return fetchEquipmentCount(equipmentGroup)
|
||||
.doOnNext(count -> equipmentGroup.getStatusOrDefault().setEquipmentCount(count))
|
||||
.thenReturn(equipmentGroup);
|
||||
}
|
||||
|
||||
Mono<Integer> fetchEquipmentCount(EquipmentGroup equipmentGroup) {
|
||||
Assert.notNull(equipmentGroup, "The equipmentGroup must not be null.");
|
||||
String name = equipmentGroup.getMetadata().getName();
|
||||
return client.list(
|
||||
Equipment.class,
|
||||
equipment -> !equipment.isDeleted() && equipment.getSpec().getGroupName().equals(name),
|
||||
null
|
||||
)
|
||||
.count()
|
||||
.defaultIfEmpty(0L)
|
||||
.map(Long::intValue);
|
||||
}
|
||||
|
||||
ListOptions toListOptions(QueryListRequest query) {
|
||||
return labelAndFieldSelectorToListOptions(
|
||||
query.getLabelSelector(), query.getFieldSelector()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.kunkunyu.equipment.service.impl;
|
||||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.index.query.QueryFactory;
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
import com.kunkunyu.equipment.EquipmentQuery;
|
||||
import com.kunkunyu.equipment.service.EquipmentService;
|
||||
|
||||
/**
|
||||
* Service implementation for {@link Equipment}.
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
class EquipmentServiceImpl implements EquipmentService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public EquipmentServiceImpl(ReactiveExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ListResult<Equipment>> listEquipment(EquipmentQuery query) {
|
||||
return this.client.listBy(
|
||||
Equipment.class,
|
||||
toListOptions(query),
|
||||
PageRequestImpl.of(query.getPage(), query.getSize(), query.getSort())
|
||||
);
|
||||
}
|
||||
|
||||
ListOptions toListOptions(EquipmentQuery query) {
|
||||
var builder = ListOptions.builder(labelAndFieldSelectorToListOptions(
|
||||
query.getLabelSelector(), query.getFieldSelector())
|
||||
);
|
||||
|
||||
if (StringUtils.isNotBlank(query.getKeyword())) {
|
||||
builder.andQuery(QueryFactory.contains("spec.displayName", query.getKeyword()));
|
||||
}
|
||||
if (StringUtils.isNotBlank(query.getGroup())) {
|
||||
builder.andQuery(QueryFactory.equal("spec.groupName", query.getGroup()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.kunkunyu.equipment.vo;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.theme.finders.vo.ExtensionVoOperator;
|
||||
import com.kunkunyu.equipment.EquipmentGroup;
|
||||
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class EquipmentGroupVo implements ExtensionVoOperator {
|
||||
MetadataOperator metadata;
|
||||
|
||||
EquipmentGroup.EquipmentGroupSpec spec;
|
||||
|
||||
EquipmentGroup.EquipmentGroupStatus status;
|
||||
|
||||
List<EquipmentVo> equipments;
|
||||
|
||||
public static EquipmentGroupVoBuilder from(EquipmentGroup EquipmentGroup) {
|
||||
return EquipmentGroupVo.builder()
|
||||
.metadata(EquipmentGroup.getMetadata())
|
||||
.spec(EquipmentGroup.getSpec())
|
||||
.status(EquipmentGroup.getStatusOrDefault())
|
||||
.equipments(List.of());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.kunkunyu.equipment.vo;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.theme.finders.vo.ExtensionVoOperator;
|
||||
import com.kunkunyu.equipment.Equipment;
|
||||
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public class EquipmentVo implements ExtensionVoOperator {
|
||||
|
||||
MetadataOperator metadata;
|
||||
|
||||
Equipment.EquipmentSpec spec;
|
||||
|
||||
public static EquipmentVo from(Equipment equipment) {
|
||||
return EquipmentVo.builder()
|
||||
.metadata(equipment.getMetadata())
|
||||
.spec(equipment.getSpec())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1alpha1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: role-template-equipment-view
|
||||
labels:
|
||||
halo.run/role-template: "true"
|
||||
annotations:
|
||||
rbac.authorization.halo.run/module: "装备"
|
||||
rbac.authorization.halo.run/ui-permissions: '["plugin:equipment:view"]'
|
||||
rbac.authorization.halo.run/display-name: "装备查看"
|
||||
rules:
|
||||
- apiGroups: [ "equipment.kunkunyu.com", "console.api.equipment.kunkunyu.com"]
|
||||
resources: [ "equipments", "equipmentgroups" ]
|
||||
verbs: [ "get", "list" ]
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: role-template-equipment-manage
|
||||
labels:
|
||||
halo.run/role-template: "true"
|
||||
annotations:
|
||||
rbac.authorization.halo.run/dependencies: '["role-template-equipment-view"]'
|
||||
rbac.authorization.halo.run/ui-permissions: '["plugin:equipment:manage"]'
|
||||
rbac.authorization.halo.run/module: "装备"
|
||||
rbac.authorization.halo.run/display-name: "装备管理"
|
||||
rules:
|
||||
- apiGroups: [ "equipment.kunkunyu.com", "console.api.equipment.kunkunyu.com"]
|
||||
resources: [ "equipments", "equipmentgroups" ]
|
||||
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1alpha1
|
||||
kind: Setting
|
||||
metadata:
|
||||
name: equipment-settings
|
||||
spec:
|
||||
forms:
|
||||
- group: base
|
||||
label: 基本设置
|
||||
formSchema:
|
||||
- $formkit: text
|
||||
label: 页面标题
|
||||
name: title
|
||||
validation: required
|
||||
value: "装备"
|
||||
- $formkit: text
|
||||
label: 装备列表显示条数
|
||||
name: pageSize
|
||||
validation: required|Number
|
||||
value: 20
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12L6 9H4a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h2zm6 0l3-3h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-2zm-3 3l-3 3v2a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-2zm0-6L9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2z"/></svg>
|
||||
|
After Width: | Height: | Size: 378 B |
@@ -0,0 +1,24 @@
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: equipment
|
||||
annotations:
|
||||
"store.halo.run/app-id": "app-ytygyqml"
|
||||
spec:
|
||||
enabled: true
|
||||
version: 1.0.2
|
||||
requires: ">=2.21.0"
|
||||
author:
|
||||
name: 困困鱼
|
||||
website: https://github.com/chengzhongxue
|
||||
logo: logo.svg
|
||||
homepage: https://www.halo.run/store/apps/app-ytygyqml
|
||||
repo: https://github.com/chengzhongxue/plugin-equipment
|
||||
issues: https://github.com/chengzhongxue/plugin-equipment/issues
|
||||
configMapName: equipment-configmap
|
||||
settingName: equipment-settings
|
||||
displayName: "装备管理"
|
||||
description: "装备管理模块"
|
||||
license:
|
||||
- name: "GPL-3.0"
|
||||
url: "https://github.com/chengzhongxue/plugin-equipment/blob/main/LICENSE"
|
||||
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title th:text="${site.title+'-'+title}"></title>
|
||||
<link data-n-head="true" rel="icon" type="image/x-icon" th:href="${site.favicon}">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#4A5568',
|
||||
secondary: '#718096'
|
||||
},
|
||||
borderRadius: {
|
||||
'none': '0px',
|
||||
'sm': '2px',
|
||||
DEFAULT: '4px',
|
||||
'md': '8px',
|
||||
'lg': '12px',
|
||||
'xl': '16px',
|
||||
'2xl': '20px',
|
||||
'3xl': '24px',
|
||||
'full': '9999px',
|
||||
'button': '4px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
min-height: 100vh;
|
||||
background-color: #f7fafc;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
min-height: 1024px;
|
||||
}
|
||||
}
|
||||
|
||||
.product-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.group-block {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.group-block:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="text-gray-800">
|
||||
<div class="max-w-[1440px] mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-8 lg:py-12">
|
||||
<th:block th:each="group : ${equipmentFinder.groupBy()}">
|
||||
<div class="mb-6 group-block">
|
||||
<h2 class="text-2xl sm:text-3xl font-bold mb-2 sm:mb-4" th:text="${group.spec.displayName}"></h2>
|
||||
<p class="text-gray-600 text-base sm:text-lg" th:text="${group.spec.description}"></p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 mb-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-4 lg:gap-5 ">
|
||||
|
||||
<div th:each="equipment : ${group.equipments}"
|
||||
class="bg-white rounded-lg p-6 shadow-sm product-card flex flex-col h-full">
|
||||
<div class="aspect-[4/3] mb-6 overflow-hidden -mx-6 -mt-6 rounded-t-lg">
|
||||
<img th:src="${equipment.spec.cover}"
|
||||
class="w-full h-full object-cover object-center" th:alt="${equipment.spec.displayName}">
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-lg sm:text-xl font-bold mb-2" th:text="${equipment.spec.displayName}"></h3>
|
||||
<p class="text-gray-600 mb-2" th:text="${equipment.spec.specification}"></p>
|
||||
<p class="text-gray-700" th:text="${equipment.spec.description}"></p>
|
||||
</div>
|
||||
<div th:if="${equipment.spec.url}" class="flex justify-start items-center mt-4">
|
||||
<a th:href="${equipment.spec.url}" target="_blank" rel="noopener" class="px-6 py-1 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors whitespace-nowrap">
|
||||
详情
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user