From 98d1455efaa4cd3d5d3213e047ccc0ac187ba05c Mon Sep 17 00:00:00 2001
From: shinnosuke <tantao@1hoper.com>
Date: 星期一, 03 八月 2026 17:30:53 +0800
Subject: [PATCH] feat(admin): 添加政策文章管理和商户功能增强
---
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java | 121 ++++++++++++++++++++++++++++++++++++---
1 files changed, 110 insertions(+), 11 deletions(-)
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
index d8ab5a6..374f605 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
@@ -10,6 +10,7 @@
import com.talentcard.server.business.BusinessIdGenerator;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.PageResult;
+import com.talentcard.server.business.merchant.auth.MerchantPasswordHasher;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
import org.jsoup.Jsoup;
@@ -155,6 +156,52 @@
}
@Transactional(readOnly = true)
+ public PageResult<Map<String, Object>> policies(String title, String author, Integer status, String publishStart, String publishEnd, int page, int size) {
+ StringBuilder sql = new StringBuilder("select id, policy_no, title, subtitle, cover_url, author, source, view_count, is_top, sort_no, publish_time, status, created_at, updated_at from policy_article where deleted = 0");
+ StringBuilder countSql = new StringBuilder("select count(*) from policy_article where deleted = 0");
+ List<Object> args = new ArrayList<>();
+ if (title != null && !title.isBlank()) { sql.append(" and title like ?"); countSql.append(" and title like ?"); args.add("%" + title.trim() + "%"); }
+ if (author != null && !author.isBlank()) {
+ sql.append(" and (author like ? or source like ?)"); countSql.append(" and (author like ? or source like ?)");
+ String like = "%" + author.trim() + "%"; args.add(like); args.add(like);
+ }
+ if (status != null) { sql.append(" and status = ?"); countSql.append(" and status = ?"); args.add(status); }
+ if (publishStart != null && !publishStart.isBlank()) { sql.append(" and date(publish_time) >= ?"); countSql.append(" and date(publish_time) >= ?"); args.add(publishStart); }
+ if (publishEnd != null && !publishEnd.isBlank()) { sql.append(" and date(publish_time) <= ?"); countSql.append(" and date(publish_time) <= ?"); args.add(publishEnd); }
+ sql.append(" order by is_top desc, sort_no desc, publish_time desc, id desc");
+ return page(sql.toString(), countSql.toString(), page, size, args.toArray());
+ }
+
+ @Transactional(readOnly = true)
+ public Map<String, Object> policyDetail(Long id) {
+ return one("select id, policy_no, title, subtitle, cover_url, author, source, content, view_count, is_top, sort_no, publish_time, status, created_at, updated_at from policy_article where deleted = 0 and id = ?", id);
+ }
+
+ @Transactional
+ public Map<String, Object> createPolicy(PolicyArticleRequest request) {
+ int status = defaultNewsStatus(request.status());
+ jdbcTemplate.update("insert into policy_article(policy_no, title, subtitle, cover_url, author, source, content, is_top, sort_no, publish_time, status) values (?, ?, ?, ?, ?, ?, ?, ?, ?, case when ? = 1 then now() else null end, ?)",
+ BusinessIdGenerator.next("POLICY"), request.title().trim(), blankToNull(request.subtitle()), blankToNull(request.coverUrl()), blankToNull(request.author()), blankToNull(request.source()), sanitizeNewsContent(request.content()), Boolean.TRUE.equals(request.isTop()) ? 1 : 0, request.sortNo() == null ? 0 : request.sortNo(), status, status);
+ return policyDetail(jdbcTemplate.queryForObject("select last_insert_id()", Long.class));
+ }
+
+ @Transactional
+ public Map<String, Object> updatePolicy(Long id, PolicyArticleRequest request) {
+ int status = defaultNewsStatus(request.status());
+ int updated = jdbcTemplate.update("update policy_article set title = ?, subtitle = ?, cover_url = ?, author = ?, source = ?, content = ?, is_top = ?, sort_no = ?, status = ?, publish_time = case when ? = 1 then coalesce(publish_time, now()) else publish_time end where deleted = 0 and id = ?",
+ request.title().trim(), blankToNull(request.subtitle()), blankToNull(request.coverUrl()), blankToNull(request.author()), blankToNull(request.source()), sanitizeNewsContent(request.content()), Boolean.TRUE.equals(request.isTop()) ? 1 : 0, request.sortNo() == null ? 0 : request.sortNo(), status, status, id);
+ ensureUpdated(updated);
+ return policyDetail(id);
+ }
+
+ @Transactional
+ public Map<String, Object> updatePolicyStatus(Long id, Byte status) {
+ int updated = jdbcTemplate.update("update policy_article set status = ?, publish_time = case when ? = 1 then coalesce(publish_time, now()) else publish_time end where deleted = 0 and id = ?", status, status, id);
+ ensureUpdated(updated);
+ return policyDetail(id);
+ }
+
+ @Transactional(readOnly = true)
public PageResult<Map<String, Object>> merchants(
String keyword,
String contactMobile,
@@ -169,7 +216,21 @@
StringBuilder sql = new StringBuilder("""
select m.id, m.merchant_name, m.merchant_type, m.contact_name, m.contact_mobile,
m.logo_url, m.cover_url, m.address, m.is_recommend, m.sort_no, m.status,
- m.business_status, m.created_at,
+ case
+ when m.status <> 1 then '休息中'
+ when lower(coalesce(m.business_status, 'open')) in ('closed', '休息中') then '休息中'
+ when exists (
+ select 1
+ from merchant_business_hours h
+ join merchant_store s on s.id = h.store_id and s.status = 1 and s.deleted = 0
+ where h.merchant_id = m.id
+ and (
+ (h.start_time <= h.end_time and current_time() >= h.start_time and current_time() < h.end_time)
+ or (h.start_time > h.end_time and (current_time() >= h.start_time or current_time() < h.end_time))
+ )
+ ) then '营业中'
+ else '休息中'
+ end as business_status, m.created_at,
(select count(*) from merchant_product p where p.merchant_id = m.id and p.deleted = 0) as product_count,
(select count(*) from benefit_order o where o.merchant_id = m.id and o.deleted = 0) as order_count,
0 as reservation_count
@@ -224,20 +285,34 @@
public Map<String, Object> merchant(Long id) {
return one("""
select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url,
- address, business_status, longitude, latitude, group_buying_enabled, usage_instructions,
- rights_text, service_label, is_recommend, sort_no, status, created_at, updated_at
+ address, business_status, longitude, latitude, can_login_merchant_app, group_buying_enabled, usage_instructions,
+ rights_text, merchant_rights_config, service_label, is_recommend, sort_no, status, created_at, updated_at,
+ (select o.account from merchant_operator o
+ where o.merchant_id = merchant.id and o.role_code = 'owner' and o.deleted = 0
+ order by o.id asc limit 1) as login_account
from merchant
where deleted = 0 and id = ?
""", id);
+ }
+
+ @Transactional(readOnly = true)
+ public Map<String, Object> merchantLoginAccountAvailable(String account) {
+ String normalizedAccount = account == null ? "" : account.trim();
+ if (!normalizedAccount.matches("^[A-Za-z0-9]{1,32}$")) {
+ return Map.of("available", false);
+ }
+ Long count = jdbcTemplate.queryForObject(
+ "select count(*) from merchant_operator where account = ?", Long.class, normalizedAccount);
+ return Map.of("available", count == null || count == 0);
}
@Transactional
public Map<String, Object> createMerchant(MerchantCreateRequest request) {
jdbcTemplate.update("""
insert into merchant(merchant_name, merchant_type, contact_name, contact_mobile, address,
- logo_url, cover_url, business_status, longitude, latitude, group_buying_enabled,
- usage_instructions, rights_text, service_label, is_recommend, status, sort_no)
- values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)
+ logo_url, cover_url, business_status, longitude, latitude, can_login_merchant_app, group_buying_enabled,
+ usage_instructions, rights_text, merchant_rights_config, service_label, is_recommend, status, sort_no)
+ values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0)
""",
request.merchantName(),
request.merchantType(),
@@ -249,13 +324,36 @@
request.businessStatus(),
request.longitude(),
request.latitude(),
+ request.canLoginMerchantApp() == null ? 1 : request.canLoginMerchantApp(),
request.groupBuyingEnabled() == null ? 1 : request.groupBuyingEnabled(),
request.usageInstructions(),
request.rightsText(),
+ toJson(request.merchantRightsConfig()),
request.serviceLabel(),
request.recommend() == null ? 0 : request.recommend());
Long id = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
+ if (isFoodMerchantWithAppAccess(request)) {
+ String account = request.loginAccount().trim();
+ if (!account.matches("^[A-Za-z0-9]{1,32}$")) {
+ throw new BusinessException(ErrorCode.BAD_REQUEST, "商户登录账号只能包含英文字母和数字");
+ }
+ if (request.loginPassword() == null || request.loginPassword().length() < 6 || request.loginPassword().length() > 64) {
+ throw new BusinessException(ErrorCode.BAD_REQUEST, "商户登录密码长度须为6至64位");
+ }
+ Integer occupied = jdbcTemplate.queryForObject("select count(*) from merchant_operator where account = ?", Integer.class, account);
+ if (occupied != null && occupied > 0) throw new BusinessException(ErrorCode.CONFLICT, "商户登录账号已存在");
+ jdbcTemplate.update("insert into merchant_store(merchant_id, store_name, address, status) values (?, ?, ?, 1)", id, request.merchantName().trim(), request.address());
+ Long storeId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
+ jdbcTemplate.update("insert into merchant_operator(merchant_id, account, password_hash, display_name, role_code, status) values (?, ?, ?, ?, 'owner', 1)",
+ id, account, MerchantPasswordHasher.encode(request.loginPassword()), request.merchantName().trim());
+ Long operatorId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
+ jdbcTemplate.update("insert into merchant_operator_store(operator_id, store_id) values (?, ?)", operatorId, storeId);
+ }
return one("select * from merchant where id = ?", id);
+ }
+
+ private boolean isFoodMerchantWithAppAccess(MerchantCreateRequest request) {
+ return "餐饮服务".equals(request.merchantType()) && (request.canLoginMerchantApp() == null || request.canLoginMerchantApp() == 1);
}
@Transactional
@@ -263,8 +361,8 @@
int updated = jdbcTemplate.update("""
update merchant
set merchant_name = ?, merchant_type = ?, contact_name = ?, contact_mobile = ?,
- address = ?, logo_url = ?, cover_url = ?, business_status = ?, longitude = ?, latitude = ?,
- group_buying_enabled = ?, usage_instructions = ?, rights_text = ?, service_label = ?,
+ address = ?, logo_url = ?, cover_url = ?, business_status = ?, longitude = ?, latitude = ?, can_login_merchant_app = ?,
+ group_buying_enabled = ?, usage_instructions = ?, rights_text = ?, merchant_rights_config = ?, service_label = ?,
is_recommend = ?, sort_no = ?, status = ?
where deleted = 0 and id = ?
""",
@@ -278,9 +376,11 @@
request.businessStatus(),
request.longitude(),
request.latitude(),
+ request.canLoginMerchantApp() == null ? 1 : request.canLoginMerchantApp(),
request.groupBuyingEnabled() == null ? 1 : request.groupBuyingEnabled(),
request.usageInstructions(),
request.rightsText(),
+ toJson(request.merchantRightsConfig()),
request.serviceLabel(),
request.recommend() == null ? 0 : request.recommend(),
request.sortNo() == null ? 0 : request.sortNo(),
@@ -367,10 +467,9 @@
args.add("%" + contactMobile.trim() + "%");
}
if (keyword != null && !keyword.isBlank()) {
- sql.append(" and (p.product_name like ? or p.product_no like ?)");
- countSql.append(" and (p.product_name like ? or p.product_no like ?)");
+ sql.append(" and p.product_name like ?");
+ countSql.append(" and p.product_name like ?");
String like = "%" + keyword.trim() + "%";
- args.add(like);
args.add(like);
}
if (type != null && !type.isBlank()) {
--
Gitblit v1.7.1