刘帅
2026-09-29 b55cd9aad0835a96a790e61f9f89edc2fae1be9f
注释优化、格式优化
19个文件已修改
542 ■■■■■ 已修改文件
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/privacy/SensitiveDataMaskingAdvice.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/privacy/UnmaskedSensitiveData.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/cn/mb/cloud/common/sentinel/fallback/DefaultFeignFallback.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/feignClient/account/AppUserClient.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/AppGuideManagementController.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/util/ExcelUtil.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/privacy/SensitiveDataMaskingAdvice.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/appGuide/appGuide.js 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/controller/AppGuideController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/entity/AppGuideBanner.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/entity/AppGuideConfig.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/entity/QrLink.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/feignclient/account/AppUserClient.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/AppGuideBannerMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/AppGuideConfigMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/QrLinkMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/SysAccountAuditLogMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/service/SysAccountAuditLogService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java
@@ -247,10 +247,9 @@
    }
    /**
     * Internal-only data source for the management sensitive-data query.  The
     * caller is authenticated and authorised by the management service; its
     * Feign interceptor adds the header that permits the response to remain
     * unmasked for that single request.
     * 仅供管理端敏感信息查询使用的内部数据接口。
     * 调用方由管理服务完成认证和授权;其 Feign 拦截器会添加请求头,
     * 以允许本次响应不进行脱敏。
     */
    @PostMapping("/internal/sensitive/app-user/{id}")
    public Map<String, Object> sensitiveAppUser(@PathVariable("id") Integer id,
@@ -269,9 +268,8 @@
    }
    /**
     * Internal data source used when an APP booking submits its displayed,
     * masked phone number.  Limit the response to the single field needed for
     * that booking; callers must use the internal sensitive-data header.
     * APP 订场提交页面展示的脱敏手机号时使用的内部数据接口。
     * 仅返回该预约所需的手机号字段;调用方必须携带内部敏感数据访问请求头。
     */
    @PostMapping("/internal/sensitive/site-booking/app-user/{id}/phone")
    public Map<String, Object> siteBookingPhone(@PathVariable("id") Integer id,
@@ -883,8 +881,8 @@
    public void updateAppUser(@RequestBody TAppUser appUser) {
        try {
            TAppUser appUser1 = appUserService.getById(appUser.getId());
            // A management edit form may contain the displayed masked value.
            // Never persist that display value over the original identifier.
            // 管理端编辑表单可能携带页面展示的脱敏值。
            // 不得使用该展示值覆盖原始证件或联系方式。
            if (appUser.getPhone() != null && appUser.getPhone().contains("*")) appUser.setPhone(appUser1.getPhone());
            if (appUser.getIdCard() != null && appUser.getIdCard().contains("*")) appUser.setIdCard(appUser1.getIdCard());
            appUserService.updateById(appUser);
cloud-server-account/src/main/java/com/dsh/privacy/SensitiveDataMaskingAdvice.java
@@ -57,8 +57,8 @@
    private void mask(Object value, IdentityHashMap<Object, Boolean> visited) {
        if (value == null || value instanceof String || value.getClass().isEnum() || visited.put(value, Boolean.TRUE) != null) return;
        // Path 的实现类(如 sun.nio.fs.UnixPath)本身是 Iterable;遍历其子路径会不断创建新对象,
        // IdentityHashMap 无法识别该循环,因而会导致 StackOverflowError。
        // java.nio.file.Path 的实现类(如 sun.nio.fs.UnixPath)实现了 Iterable 接口;
        // 遍历其子路径会不断创建新对象,IdentityHashMap 无法识别该循环,因而会导致 StackOverflowError。
        if (value instanceof java.nio.file.Path || isJdkType(value.getClass())) return;
        if (value instanceof Map) {
            for (Object entryObject : ((Map<?, ?>) value).entrySet()) {
cloud-server-account/src/main/java/com/dsh/privacy/UnmaskedSensitiveData.java
@@ -6,8 +6,8 @@
import java.lang.annotation.Target;
/**
 * Allows a single response field to bypass sensitive-data masking.
 * Use only for endpoints that are authorised to return the original value.
 * 允许单个响应字段跳过敏感数据脱敏。
 * 仅限已获授权返回原始数据的接口使用。
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
cloud-server-management/src/main/java/cn/mb/cloud/common/sentinel/fallback/DefaultFeignFallback.java
@@ -18,12 +18,11 @@
import java.util.Set;
/**
 * Safe replacement for the common Sentinel Feign fallback.
 * 公共 Sentinel Feign 降级处理的安全替代实现。
 *
 * <p>The dependency version assumes every generic return type is a Class. That
 * is false for declarations such as {@code Map<String, Object>}, whose runtime
 * type is {@link ParameterizedType}; the failed cast used to hide the original
 * downstream exception behind an AssertionError.</p>
 * <p>依赖版本假定所有泛型返回类型均为 {@link Class}。对于
 * {@code Map<String, Object>} 等声明并不成立,其运行时类型为
 * {@link ParameterizedType};此前转换失败会以 AssertionError 掩盖原始下游异常。</p>
 */
public class DefaultFeignFallback<T> implements MethodInterceptor {
    private static final Logger log = LoggerFactory.getLogger(DefaultFeignFallback.class);
@@ -47,9 +46,8 @@
        Class<?> returnType = rawType(declaredReturnType);
        Object data = defaultValue(returnType);
        // Only methods that explicitly declare ResponseData may receive a
        // ResponseData fallback. Returning it from Map/List methods causes a
        // second ClassCastException in the Feign proxy.
        // 仅显式声明返回 ResponseData 的方法可以返回 ResponseData 降级结果。
        // 在 Map/List 方法中返回该对象会导致 Feign 代理再次抛出 ClassCastException。
        if (ResponseData.class.equals(returnType)) {
            Type dataType = firstTypeArgument(declaredReturnType);
            return ResponseData.builder()
cloud-server-management/src/main/java/com/dsh/course/feignClient/account/AppUserClient.java
@@ -28,11 +28,11 @@
@FeignClient("mb-cloud-account")
public interface AppUserClient {
    /** Only invoked by the super-administrator sensitive data query. */
    /** 仅供超级管理员敏感信息查询接口调用。 */
    @PostMapping("/internal/sensitive/app-user/{id}")
    Map<String, Object> sensitiveAppUser(@PathVariable("id") Integer id);
    /** Only invoked by the super-administrator sensitive student-data query. */
    /** 仅供超级管理员学员敏感信息查询接口调用。 */
    @PostMapping("/internal/sensitive/student/{id}")
    Map<String, Object> sensitiveStudent(@PathVariable("id") Integer id);
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/AppGuideManagementController.java
@@ -39,17 +39,32 @@
    @Autowired
    private ITSiteService siteService;
    /**
     * 打开 APP 下载引导管理页面。
     *
     * @return APP 下载引导管理页面路径
     */
    @GetMapping("")
    public String index() {
        return "/system/appGuide/appGuide.html";
    }
    /**
     * 查询可用于生成订场二维码的场地列表。
     *
     * @return 按 ID 倒序排列的场地列表
     */
    @ResponseBody
    @GetMapping("/sites")
    public List<TSite> queryAvailableSites() {
        return siteService.list(new QueryWrapper<TSite>().orderByDesc("id"));
    }
    /**
     * 查询 APP 下载引导页面所需的配置、宣传图和订场二维码数据。
     *
     * @return 下载引导页面的聚合数据
     */
    @ResponseBody
    @GetMapping("/data")
    public Map<String, Object> data() {
@@ -69,6 +84,12 @@
        return r;
    }
    /**
     * 保存 APP 下载引导配置。
     *
     * @param config 下载引导配置
     * @return 保存结果
     */
    @ResponseBody
    @PostMapping("/config")
    public Map<String, Object> saveConfig(AppGuideConfig config) {
@@ -80,6 +101,12 @@
        return ok();
    }
    /**
     * 保存 APP 下载引导宣传图。
     *
     * @param banner 宣传图配置
     * @return 保存结果
     */
    @ResponseBody
    @PostMapping("/banner")
    public Map<String, Object> saveBanner(AppGuideBanner banner) {
@@ -89,6 +116,12 @@
        return ok();
    }
    /**
     * 删除 APP 下载引导宣传图。
     *
     * @param id 宣传图 ID
     * @return 删除结果
     */
    @ResponseBody
    @PostMapping("/banner/delete")
    public Map<String, Object> deleteBanner(Long id) {
@@ -96,6 +129,16 @@
        return ok();
    }
    /**
     * 为指定场地创建订场二维码。
     *
     * @param storeId 门店 ID
     * @param siteId 场地 ID
     * @param name 二维码名称
     * @param channel 投放渠道
     * @param pageName 引导页面名称
     * @return 创建结果及二维码引导链接
     */
    @ResponseBody
    @PostMapping("/siteQr")
    public Map<String, Object> createSiteQr(Integer storeId, Integer siteId, String name, String channel, String pageName) {
@@ -118,6 +161,13 @@
        return r;
    }
    /**
     * 修改场地订场二维码的启用状态。
     *
     * @param id 二维码 ID
     * @param enabled 启用状态(0:停用;1:启用)
     * @return 修改结果
     */
    @ResponseBody
    @PostMapping("/siteQr/status")
    public Map<String, Object> changeStatus(Long id, Integer enabled) {
@@ -128,7 +178,12 @@
        return ok();
    }
    /** 逻辑删除场地订场二维码。 */
    /**
     * 逻辑删除场地订场二维码。
     *
     * @param id 二维码 ID
     * @return 删除结果
     */
    @ResponseBody
    @PostMapping("/siteQr/delete")
    public Map<String, Object> deleteSiteQr(Long id) {
@@ -136,6 +191,13 @@
        return ok();
    }
    /**
     * 生成并输出场地订场二维码图片。
     *
     * @param id 二维码 ID
     * @param response HTTP 响应
     * @throws IOException 图片输出失败时抛出
     */
    @GetMapping("/siteQr/{id}.png")
    public void qrImage(@PathVariable Long id, HttpServletResponse response) throws IOException {
        QrLink link = qrLinkMapper.selectById(id);
cloud-server-management/src/main/java/com/dsh/guns/modular/system/util/ExcelUtil.java
@@ -40,7 +40,13 @@
        return wb;
    }
    /** Export files are an external disclosure channel and always remain masked. */
    /**
     * 导出文件属于对外数据出口,敏感字段必须始终保持脱敏。
     *
     * @param columnName 列名,用于识别手机号和身份证号等敏感字段
     * @param value 原始单元格值
     * @return 脱敏后的单元格值
     */
    private static String maskSensitiveValue(String columnName, String value) {
        if (value == null) return null;
        String column = columnName == null ? "" : columnName.toLowerCase();
cloud-server-management/src/main/java/com/dsh/privacy/SensitiveDataMaskingAdvice.java
@@ -15,14 +15,14 @@
import java.util.Iterator;
import java.util.Map;
/** Ensures every normal management JSON response uses the same privacy rules. */
/** 确保所有常规管理端 JSON 响应均遵循统一的隐私保护规则。 */
@ControllerAdvice
public class SensitiveDataMaskingAdvice implements ResponseBodyAdvice<Object> {
    private final HttpServletRequest request;
    public SensitiveDataMaskingAdvice(HttpServletRequest request) { this.request = request; }
    @Override public boolean supports(MethodParameter type, Class<? extends HttpMessageConverter<?>> converter) {
        // Only the explicit, admin-only and audited endpoint returns the source value.
        // 仅明确限定、仅管理员可访问且已审计的接口返回原始数据。
        return !request.getRequestURI().startsWith("/system/sensitive/");
    }
    @Override public Object beforeBodyWrite(Object body, MethodParameter type, MediaType mediaType,
@@ -32,13 +32,12 @@
    }
    private void mask(Object value, IdentityHashMap<Object, Boolean> seen) {
        if (value == null || value instanceof String || value.getClass().isEnum() || seen.put(value, Boolean.TRUE) != null) return;
        // Reactive streams and their internal queues are runtime infrastructure, not
        // response DTOs. In particular, Reactor's MpscLinkedQueue implements
        // Iterable but deliberately throws UnsupportedOperationException from
        // iterator(). Do not introspect it (or any other reactive infrastructure).
        // 响应式流及其内部队列属于运行时基础设施,并非响应 DTO。特别是 Reactor 的
        // MpscLinkedQueue 虽实现了 Iterable,却会在 iterator() 中主动抛出
        // UnsupportedOperationException;不得反射遍历它或其他响应式基础设施。
        if (isReactiveInfrastructure(value.getClass())) return;
        // UnixPath implements Iterable.  Its iterator creates a new Path for every
        // segment, so identity-based cycle detection cannot stop the recursion.
        // UnixPath 实现了 Iterable,其迭代器会为每个路径段创建新的 Path 对象,
        // 因此基于对象标识的循环检测无法终止递归。
        if (value instanceof Path) return;
        if (value instanceof Map) {
            for (Object item : ((Map<?, ?>) value).entrySet()) {
@@ -62,16 +61,15 @@
    }
    /**
     * Some third-party containers expose Iterable only for API compatibility and
     * intentionally do not support iteration. A response privacy filter must never
     * turn such an implementation detail into a 500 response.
     * 部分第三方容器仅为 API 兼容而实现 Iterable,并不支持实际迭代。
     * 响应隐私过滤器不得因这类实现细节导致接口返回 500 错误。
     */
    private void maskIterable(Iterable<?> values, IdentityHashMap<Object, Boolean> seen) {
        try {
            Iterator<?> iterator = values.iterator();
            while (iterator.hasNext()) mask(iterator.next(), seen);
        } catch (UnsupportedOperationException ignored) {
            // There is no safely accessible element to mask.
            // 没有可安全访问并进行脱敏的元素。
        }
    }
cloud-server-management/src/main/webapp/static/modular/system/appGuide/appGuide.js
@@ -1,11 +1,110 @@
(function(){
  function esc(v){return $('<div>').text(v==null?'':v).html()} function val(n,v){$('#configForm [name='+n+']').val(v==null?'':v)}
  window.reloadAppGuide=function(){ $.get(Feng.ctxPath+'/appGuide/data',function(d){var c=d.config||{};['id','apkUrl','iosStoreUrl'].forEach(function(k){val(k,c[k])});$('#bannerRows').html((d.banners||[]).map(function(x){return '<tr><td><img class="qr" src="'+esc(x.imageUrl)+'"></td><td>'+esc(x.title)+'</td><td>'+esc(x.sort)+'</td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-danger btn-xs" onclick="deleteBanner(\''+x.id+'\')">删除</button></td></tr>'}).join(''));$('#linkRows').html((d.links||[]).map(function(x){var id=String(x.id),q=Feng.ctxPath+'/appGuide/siteQr/'+encodeURIComponent(id)+'.png';return '<tr><td>'+esc(x.name)+'</td><td>'+esc(x.pageName)+'</td><td>'+x.storeId+' / '+x.siteId+'</td><td><a href="'+q+'" target="_blank"><img class="qr" src="'+q+'"></a></td><td><code>'+esc(x.guideUrl)+'</code></td><td>'+((x.enabled===1)?'启用':'停用')+'</td><td><button class="btn btn-warning btn-xs" onclick="changeQr(\''+id+'\','+(x.enabled===1?0:1)+')">'+(x.enabled===1?'停用':'启用')+'</button> <button class="btn btn-danger btn-xs" onclick="deleteQr(\''+id+'\')">删除</button></td></tr>'}).join(''))})};
  window.saveConfig=function(){$.post(Feng.ctxPath+'/appGuide/config',$('#configForm').serialize(),function(){Feng.success('保存成功');reloadAppGuide()})};
  window.uploadBanner=function(){var f=$('#bannerFile')[0].files[0];if(!f){Feng.info('请选择图片');return}var d=new FormData();d.append('file',f);$.ajax({url:Feng.ctxPath+'/mgr/uploadFile',data:d,type:'POST',contentType:false,processData:false,success:function(url){$('#bannerUrl').val(url);$('#uploadTip').text('上传成功')}})};
  window.addBanner=function(){if(!$('#bannerUrl').val()){Feng.info('请先上传图片');return}$.post(Feng.ctxPath+'/appGuide/banner',{imageUrl:$('#bannerUrl').val(),title:$('#bannerTitle').val(),sort:$('#bannerSort').val()||0,enabled:1},function(){reloadAppGuide();$('#bannerUrl,#bannerTitle,#bannerSort,#bannerFile').val('')})};
  window.deleteBanner=function(id){$.post(Feng.ctxPath+'/appGuide/banner/delete',{id:id},reloadAppGuide)};
  window.createQr=function(){var o=$('#siteSelect option:selected'),pageName='com.sinata.wepark.ui.home.PlaceDetailActivity';if(!o.val()){Feng.info('请选择场地');return}$.post(Feng.ctxPath+'/appGuide/siteQr',{storeId:o.data('store'),siteId:o.val(),name:o.text(),pageName:pageName},function(r){if(!r.success){Feng.error(r.message);return}Feng.success('已生成');reloadAppGuide()})};
  window.changeQr=function(id,enabled){$.post(Feng.ctxPath+'/appGuide/siteQr/status',{id:id,enabled:enabled},reloadAppGuide)};$.get(Feng.ctxPath+'/appGuide/sites',function(s){$('#siteSelect').append((s||[]).map(function(x){return '<option value="'+x.id+'" data-store="'+x.storeId+'">['+x.storeId+'] '+esc(x.name)+'</option>'}).join(''))});$(reloadAppGuide);
  window.deleteQr=function(id){if(!confirm('确认删除该场地订场二维码?'))return;$.post(Feng.ctxPath+'/appGuide/siteQr/delete',{id:id},reloadAppGuide)};
})();
(function() {
  function esc(v) {
    return $('<div>').text(v == null ? '' : v).html()
  }
  function val(n, v) {
    $('#configForm [name=' + n + ']').val(v == null ? '' : v)
  }
  window.reloadAppGuide = function() {
    $.get(Feng.ctxPath + '/appGuide/data', function(d) {
      var c = d.config || {};
      ['id', 'apkUrl', 'iosStoreUrl'].forEach(function(k) {
        val(k, c[k])
      });
      $('#bannerRows').html((d.banners || []).map(function(x) {
        return '<tr><td><img class="qr" src="' + esc(x.imageUrl) + '"></td><td>' + esc(x.title) + '</td><td>' + esc(x.sort) + '</td><td>' + ((x.enabled === 1) ? '启用' : '停用') + '</td><td><button class="btn btn-danger btn-xs" onclick="deleteBanner(\'' + x.id + '\')">删除</button></td></tr>'
      }).join(''));
      $('#linkRows').html((d.links || []).map(function(x) {
        var id = String(x.id),
          q = Feng.ctxPath + '/appGuide/siteQr/' + encodeURIComponent(id) + '.png';
        return '<tr><td>' + esc(x.name) + '</td><td>' + esc(x.pageName) + '</td><td>' + x.storeId + ' / ' + x.siteId + '</td><td><a href="' + q + '" target="_blank"><img class="qr" src="' + q + '"></a></td><td><code>' + esc(x.guideUrl) + '</code></td><td>' + ((x.enabled === 1) ? '启用' : '停用') + '</td><td><button class="btn btn-warning btn-xs" onclick="changeQr(\'' + id + '\',' + (x.enabled === 1 ? 0 : 1) + ')">' + (x.enabled === 1 ? '停用' : '启用') + '</button> <button class="btn btn-danger btn-xs" onclick="deleteQr(\'' + id + '\')">删除</button></td></tr>'
      }).join(''))
    })
  };
  window.saveConfig = function() {
    $.post(Feng.ctxPath + '/appGuide/config', $('#configForm').serialize(), function() {
      Feng.success('保存成功');
      reloadAppGuide()
    })
  };
  window.uploadBanner = function() {
    var f = $('#bannerFile')[0].files[0];
    if (!f) {
      Feng.info('请选择图片');
      return
    }
    var d = new FormData();
    d.append('file', f);
    $.ajax({
      url: Feng.ctxPath + '/mgr/uploadFile',
      data: d,
      type: 'POST',
      contentType: false,
      processData: false,
      success: function(url) {
        $('#bannerUrl').val(url);
        $('#uploadTip').text('上传成功')
      }
    })
  };
  window.addBanner = function() {
    if (!$('#bannerUrl').val()) {
      Feng.info('请先上传图片');
      return
    }
    $.post(Feng.ctxPath + '/appGuide/banner', {
      imageUrl: $('#bannerUrl').val(),
      title: $('#bannerTitle').val(),
      sort: $('#bannerSort').val() || 0,
      enabled: 1
    }, function() {
      reloadAppGuide();
      $('#bannerUrl,#bannerTitle,#bannerSort,#bannerFile').val('')
    })
  };
  window.deleteBanner = function(id) {
    $.post(Feng.ctxPath + '/appGuide/banner/delete', {
      id: id
    }, reloadAppGuide)
  };
  window.createQr = function() {
    var o = $('#siteSelect option:selected'),
      pageName = 'com.sinata.wepark.ui.home.PlaceDetailActivity';
    if (!o.val()) {
      Feng.info('请选择场地');
      return
    }
    $.post(Feng.ctxPath + '/appGuide/siteQr', {
      storeId: o.data('store'),
      siteId: o.val(),
      name: o.text(),
      pageName: pageName
    }, function(r) {
      if (!r.success) {
        Feng.error(r.message);
        return
      }
      Feng.success('已生成');
      reloadAppGuide()
    })
  };
  window.changeQr = function(id, enabled) {
    $.post(Feng.ctxPath + '/appGuide/siteQr/status', {
      id: id,
      enabled: enabled
    }, reloadAppGuide)
  };
  $.get(Feng.ctxPath + '/appGuide/sites', function(s) {
    $('#siteSelect').append((s || []).map(function(x) {
      return '<option value="' + x.id + '" data-store="' + x.storeId + '">[' + x.storeId + '] ' + esc(x.name) + '</option>'
    }).join(''))
  });
  $(reloadAppGuide);
  window.deleteQr = function(id) {
    if (!confirm('确认删除该场地订场二维码?')) return;
    $.post(Feng.ctxPath + '/appGuide/siteQr/delete', {
      id: id
    }, reloadAppGuide)
  };
})();
cloud-server-other/src/main/java/com/dsh/other/controller/AppGuideController.java
@@ -15,26 +15,36 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.annotation.Resource;
import java.util.*;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/** 对外下载引导页与 App 扫码解析接口。 */
@RestController
@Api(tags = "APP下载引导与场地二维码")
public class AppGuideController {
    @Autowired private AppGuideConfigMapper appGuideConfigMapper;
    @Autowired private AppGuideBannerMapper appGuideBannerMapper;
    @Autowired private QrLinkMapper qrLinkMapper;
    @Autowired private TokenUtil tokenUtil;
    @Autowired
    private AppGuideConfigMapper appGuideConfigMapper;
    @Autowired
    private AppGuideBannerMapper appGuideBannerMapper;
    @Autowired
    private QrLinkMapper qrLinkMapper;
    @Autowired
    private TokenUtil tokenUtil;
//    @GetMapping("/q/{shortCode}")
//    public void guidePage(@PathVariable String shortCode, HttpServletRequest request,
@@ -48,10 +58,16 @@
    @ApiOperation("获取下载引导页配置(无需登录)")
    public ResultUtil<Map<String, Object>> guideConfig(String code) {
        AppGuideConfig config = appGuideConfigMapper.selectOne(new QueryWrapper<AppGuideConfig>()
                .eq("enabled", EnableStatusEnum.ENABLED.getCode()).orderByDesc("updateTime").last("limit 1"));
        if (config == null) return ResultUtil.error("下载引导页尚未配置");
                .eq("enabled", EnableStatusEnum.ENABLED.getCode())
                .orderByDesc("updateTime")
                .last("limit 1"));
        if (config == null) {
            return ResultUtil.error("下载引导页尚未配置");
        }
        QrLink link = validLink(code);
        if (code != null && !code.isEmpty() && link == null) return ResultUtil.error("二维码无效或已失效");
        if (code != null && !code.isEmpty() && link == null) {
            return ResultUtil.error("二维码无效或已失效");
        }
        Map<String, Object> data = new HashMap<>();
        data.put("appName", StringUtils.isNotBlank(config.getAppName()) ? config.getAppName() : "玩湃生活");
        data.put("androidPackage", StringUtils.isNotBlank(config.getAndroidPackage()) ? config.getAndroidPackage() : "com.sinata.wepark");
@@ -61,7 +77,9 @@
        data.put("androidDeepLink", config.getAndroidDeepLink() + (StringUtils.isNotBlank(code) ? "?route=" + code : ""));
        data.put("iosDeepLink", "weparklife://app" + (StringUtils.isNotBlank(code) ? "?code=" + code : ""));
        data.put("banners", appGuideBannerMapper.selectList(new QueryWrapper<AppGuideBanner>()
                .eq("enabled", EnableStatusEnum.ENABLED.getCode()).orderByAsc("sort").orderByDesc("id")));
                .eq("enabled", EnableStatusEnum.ENABLED.getCode())
                .orderByAsc("sort")
                .orderByDesc("id")));
//        if (link != null) data.put("target", target(link));
        return ResultUtil.success(data);
    }
@@ -70,15 +88,23 @@
    @ApiOperation("App扫码解析二维码")
    public ResultUtil<Map<String, Object>> resolve(String code) {
        QrLink link = validLink(code);
        if (link == null) return ResultUtil.error("二维码无效或已失效");
        if (link == null) {
            return ResultUtil.error("二维码无效或已失效");
        }
        return ResultUtil.success(target(link));
    }
    private QrLink validLink(String code) {
        if (code == null || !code.matches("[A-Za-z0-9]{6,32}")) return null;
        QrLink link = qrLinkMapper.selectOne(new QueryWrapper<QrLink>().eq("shortCode", code).last("limit 1"));
        if (code == null || !code.matches("[A-Za-z0-9]{6,32}")) {
            return null;
        }
        QrLink link = qrLinkMapper.selectOne(new QueryWrapper<QrLink>()
                .eq("shortCode", code)
                .last("limit 1"));
        if (link == null || !Integer.valueOf(1).equals(link.getEnabled())
                || (link.getExpiresAt() != null && link.getExpiresAt().before(new Date()))) return null;
                || (link.getExpiresAt() != null && link.getExpiresAt().before(new Date()))) {
            return null;
        }
        return link;
    }
@@ -92,7 +118,13 @@
    }
    private Object parseJson(String text) {
        if (text == null || text.trim().isEmpty()) return Collections.emptyMap();
        try { return JSON.parseObject(text); } catch (Exception ignored) { return Collections.emptyMap(); }
        if (text == null || text.trim().isEmpty()) {
            return Collections.emptyMap();
        }
        try {
            return JSON.parseObject(text);
        } catch (Exception ignored) {
            return Collections.emptyMap();
        }
    }
}
cloud-server-other/src/main/java/com/dsh/other/entity/AppGuideBanner.java
@@ -1,28 +1,48 @@
package com.dsh.other.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/** APP 下载引导宣传图实体,对应表 t_app_guide_banner。 */
@Getter
@Setter
@TableName("t_app_guide_banner")
@ApiModel(description = "APP 下载引导宣传图")
public class AppGuideBanner {
    @TableId(type = IdType.ID_WORKER)
    @ApiModelProperty("主键 ID")
    private Long id;
    @TableField("imageUrl") private String imageUrl;
    @TableField("imageUrl")
    @ApiModelProperty("宣传图地址")
    private String imageUrl;
    @ApiModelProperty("宣传图标题")
    private String title;
    @ApiModelProperty("排序值")
    private Integer sort;
    @ApiModelProperty("启用状态:0-停用,1-启用")
    private Integer enabled;
    /** 逻辑删除标识:0-正常,1-删除。 */
    @TableLogic
    @TableField("isDeleted")
    @ApiModelProperty("逻辑删除标识:0-正常,1-删除")
    private Integer isDeleted;
    /** 创建时间。 */ @TableField("createTime") private Date createTime;
    /** 创建时间。 */
    @TableField("createTime")
    @ApiModelProperty("创建时间")
    private Date createTime;
}
cloud-server-other/src/main/java/com/dsh/other/entity/AppGuideConfig.java
@@ -1,10 +1,12 @@
package com.dsh.other.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
@@ -14,24 +16,67 @@
@Getter
@Setter
@TableName("t_app_guide_config")
@ApiModel(description = "APP 下载引导配置")
public class AppGuideConfig {
    /** 主键ID(雪花算法生成)。 */ @TableId(type = IdType.ID_WORKER)
    /** 主键ID(雪花算法生成)。 */
    @TableId(type = IdType.ID_WORKER)
    @ApiModelProperty("主键 ID")
    private Long id;
    @TableField("appName") private String appName;
    @TableField("appScheme") private String appScheme;
    @TableField("androidPackage") private String androidPackage;
    @TableField("iosPackage") private String iosPackage;
    @TableField("iosStoreUrl") private String iosStoreUrl;
    @TableField("androidDefaultUrl") private String androidDefaultUrl;
    @TableField("androidMarketUrls") private String androidMarketUrls;
    @TableField("apkUrl") private String apkUrl;
    @TableField("androidDeepLink") private String androidDeepLink;
    @TableField("iosDeepLink") private String iosDeepLink;
    @TableField("guideDomain") private String guideDomain;
    @TableField("appName")
    @ApiModelProperty("APP 名称")
    private String appName;
    @TableField("appScheme")
    @ApiModelProperty("APP 自定义 Scheme")
    private String appScheme;
    @TableField("androidPackage")
    @ApiModelProperty("Android 应用包名")
    private String androidPackage;
    @TableField("iosPackage")
    @ApiModelProperty("iOS 应用包名")
    private String iosPackage;
    @TableField("iosStoreUrl")
    @ApiModelProperty("iOS App Store 地址")
    private String iosStoreUrl;
    @TableField("androidDefaultUrl")
    @ApiModelProperty("Android 默认应用市场地址")
    private String androidDefaultUrl;
    @TableField("androidMarketUrls")
    @ApiModelProperty("Android 品牌应用市场地址 JSON")
    private String androidMarketUrls;
    @TableField("apkUrl")
    @ApiModelProperty("APK 下载地址")
    private String apkUrl;
    @TableField("androidDeepLink")
    @ApiModelProperty("Android 深度链接")
    private String androidDeepLink;
    @TableField("iosDeepLink")
    @ApiModelProperty("iOS 深度链接")
    private String iosDeepLink;
    @TableField("guideDomain")
    @ApiModelProperty("二维码引导域名")
    private String guideDomain;
    @ApiModelProperty("启用状态:0-停用,1-启用")
    private Integer enabled;
    /** 逻辑删除标识:0-正常,1-删除。 */
    @TableLogic
    @TableField("isDeleted")
    @ApiModelProperty("逻辑删除标识:0-正常,1-删除")
    private Integer isDeleted;
    @TableField("updateTime") private Date updateTime;
    @TableField("updateTime")
    @ApiModelProperty("更新时间")
    private Date updateTime;
}
cloud-server-other/src/main/java/com/dsh/other/entity/QrLink.java
@@ -1,35 +1,75 @@
package com.dsh.other.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/** 二维码短链接实体,对应表 t_qr_link。 */
@Getter
@Setter
@TableName("t_qr_link")
@ApiModel(description = "二维码短链接")
public class QrLink {
    @TableId(type = IdType.ID_WORKER)
    @ApiModelProperty("主键 ID")
    private Long id;
    @TableField("shortCode") private String shortCode;
    @TableField("shortCode")
    @ApiModelProperty("二维码短码")
    private String shortCode;
    @ApiModelProperty("二维码类型")
    private String type;
    @TableField("storeId") private Integer storeId;
    @TableField("siteId") private Integer siteId;
    @TableField("bookingId") private Integer bookingId;
    @TableField("storeId")
    @ApiModelProperty("关联门店 ID")
    private Integer storeId;
    @TableField("siteId")
    @ApiModelProperty("关联场地 ID")
    private Integer siteId;
    @TableField("bookingId")
    @ApiModelProperty("关联预约订单 ID")
    private Integer bookingId;
    @ApiModelProperty("二维码名称")
    private String name;
    @ApiModelProperty("投放渠道")
    private String channel;
    @TableField("pageName") private String pageName;
    @TableField("expiresAt") private Date expiresAt;
    @TableField("pageName")
    @ApiModelProperty("跳转页面名称")
    private String pageName;
    @TableField("expiresAt")
    @ApiModelProperty("失效时间")
    private Date expiresAt;
    @ApiModelProperty("启用状态:0-停用,1-启用")
    private Integer enabled;
    /** 逻辑删除标识:0-正常,1-删除。 */
    @TableLogic
    @TableField("isDeleted")
    @ApiModelProperty("逻辑删除标识:0-正常,1-删除")
    private Integer isDeleted;
    /** 创建时间。 */ @TableField("createTime") private Date createTime;
    @TableField("updateTime") private Date updateTime;
    /** 创建时间。 */
    @TableField("createTime")
    @ApiModelProperty("创建时间")
    private Date createTime;
    @TableField("updateTime")
    @ApiModelProperty("更新时间")
    private Date updateTime;
}
cloud-server-other/src/main/java/com/dsh/other/feignclient/account/AppUserClient.java
@@ -33,7 +33,7 @@
    @PostMapping("/base/appUser/updateAppUser")
    void updateAppUser(AppUser appUser);
    /** Gets only the original phone for the authenticated user's site booking. */
    /** 获取已认证用户订场所需的原始手机号。 */
    @PostMapping("/internal/sensitive/site-booking/app-user/{id}/phone")
    Map<String, Object> siteBookingPhone(@PathVariable("id") Integer appUserId,
                                         @RequestHeader("X-Internal-Sensitive-Access") String sensitiveAccess);
cloud-server-other/src/main/java/com/dsh/other/mapper/AppGuideBannerMapper.java
@@ -1,4 +1,14 @@
package com.dsh.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dsh.other.entity.AppGuideBanner;
public interface AppGuideBannerMapper extends BaseMapper<AppGuideBanner> {}
import org.apache.ibatis.annotations.Mapper;
/**
 * APP 下载引导宣传图数据访问层。
 *
 * <p>继承 {@link BaseMapper},提供 {@link AppGuideBanner} 的基础增删改查能力。</p>
 */
@Mapper
public interface AppGuideBannerMapper extends BaseMapper<AppGuideBanner> {
}
cloud-server-other/src/main/java/com/dsh/other/mapper/AppGuideConfigMapper.java
@@ -1,4 +1,14 @@
package com.dsh.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dsh.other.entity.AppGuideConfig;
public interface AppGuideConfigMapper extends BaseMapper<AppGuideConfig> {}
import org.apache.ibatis.annotations.Mapper;
/**
 * APP 下载引导配置数据访问层。
 *
 * <p>继承 {@link BaseMapper},提供 {@link AppGuideConfig} 的基础增删改查能力。</p>
 */
@Mapper
public interface AppGuideConfigMapper extends BaseMapper<AppGuideConfig> {
}
cloud-server-other/src/main/java/com/dsh/other/mapper/QrLinkMapper.java
@@ -1,4 +1,14 @@
package com.dsh.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dsh.other.entity.QrLink;
public interface QrLinkMapper extends BaseMapper<QrLink> {}
import org.apache.ibatis.annotations.Mapper;
/**
 * 二维码短链接数据访问层。
 *
 * <p>继承 {@link BaseMapper},提供 {@link QrLink} 的基础增删改查能力。</p>
 */
@Mapper
public interface QrLinkMapper extends BaseMapper<QrLink> {
}
cloud-server-other/src/main/java/com/dsh/other/mapper/SysAccountAuditLogMapper.java
@@ -4,6 +4,11 @@
import com.dsh.other.entity.SysAccountAuditLog;
import org.apache.ibatis.annotations.Mapper;
/**
 * 账户敏感信息访问审计日志数据访问层。
 *
 * <p>继承 {@link BaseMapper},提供 {@link SysAccountAuditLog} 的基础增删改查能力。</p>
 */
@Mapper
public interface SysAccountAuditLogMapper extends BaseMapper<SysAccountAuditLog> {
}
cloud-server-other/src/main/java/com/dsh/other/service/SysAccountAuditLogService.java
@@ -3,6 +3,17 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.dsh.other.entity.SysAccountAuditLog;
/**
 * 账户敏感信息访问审计日志服务。
 *
 * <p>继承 {@link IService},提供审计日志的基础服务能力。</p>
 */
public interface SysAccountAuditLogService extends IService<SysAccountAuditLog> {
    /**
     * 追加一条审计日志。
     *
     * @param auditLog 待写入的审计日志
     */
    void append(SysAccountAuditLog auditLog);
}