132 lines
6.6 KiB
XML
132 lines
6.6 KiB
XML
<?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.fastbee.system.mapper.SysUserSettingMapper">
|
||
|
||
<resultMap type="com.fastbee.system.domain.SysUserSetting" id="SysUserSettingResult">
|
||
<result property="settingId" column="setting_id" />
|
||
<result property="userId" column="user_id" />
|
||
<result property="loginRedirect" column="login_redirect" />
|
||
<result property="pinnedTags" column="pinned_tags" />
|
||
<result property="queryGatewayChild" column="query_gateway_child" />
|
||
<result property="showChild" column="show_child" />
|
||
<result property="createBy" column="create_by" />
|
||
<result property="createTime" column="create_time" />
|
||
<result property="updateBy" column="update_by" />
|
||
<result property="updateTime" column="update_time" />
|
||
<result property="remark" column="remark" />
|
||
</resultMap>
|
||
|
||
<resultMap type="com.fastbee.system.domain.LoginRedirectOption" id="LoginRedirectOptionResult">
|
||
<result property="path" column="path" />
|
||
<result property="menuName" column="menu_name" />
|
||
</resultMap>
|
||
|
||
<sql id="selectUserSettingVo">
|
||
select setting_id, user_id, login_redirect, pinned_tags, query_gateway_child, show_child,
|
||
create_by, create_time, update_by, update_time, remark
|
||
from sys_user_setting
|
||
</sql>
|
||
|
||
<select id="selectByUserId" parameterType="Long" resultMap="SysUserSettingResult">
|
||
<include refid="selectUserSettingVo"/>
|
||
where user_id = #{userId}
|
||
</select>
|
||
|
||
<!--
|
||
部分更新:只更新**显式传入**(非 null)的列。
|
||
设置页每次只提交用户真正改动过的那几项,因此保存 A 配置不可能把 B 配置冲成默认值——
|
||
这是把写入从「整体覆盖」拆成「单量提交」的关键:
|
||
· Boolean 为 null = 本次不改这一项(显式传 false 与不传是两回事,包装类型能区分)
|
||
· String 为 null = 本次不改这一项
|
||
刻意不碰 pinned_tags:它由 tags-view 的独立写入路径维护,混进来会互相覆盖。
|
||
调用方必须先 insertDefaultIfAbsent 保证行存在,否则 UPDATE 0 行会静默丢改动。
|
||
-->
|
||
<update id="updateSetting" parameterType="com.fastbee.system.domain.SysUserSetting">
|
||
update sys_user_setting
|
||
<set>
|
||
<if test="loginRedirect != null">login_redirect = #{loginRedirect},</if>
|
||
<if test="queryGatewayChild != null">query_gateway_child = #{queryGatewayChild},</if>
|
||
<if test="showChild != null">show_child = #{showChild},</if>
|
||
update_by = #{updateBy},
|
||
update_time = #{updateTime}
|
||
</set>
|
||
where user_id = #{userId}
|
||
</update>
|
||
|
||
<!--
|
||
确保设置行存在:不存在则插入默认行,已存在则**什么都不改**。
|
||
两处调用:① 固定标签的增量写入要在一行上做「锁定读 → 改 → 写」,行必须先存在;
|
||
② 设置页的部分更新前先兜一行,否则 UPDATE 0 行会静默丢改动。
|
||
刻意不用 ON DUPLICATE 写其它列:那会覆盖 login_redirect / query_gateway_child / show_child,
|
||
把用户已保存的设置冲回默认值;这里用 user_id = user_id 作为 no-op 更新。
|
||
-->
|
||
<insert id="insertDefaultIfAbsent">
|
||
insert into sys_user_setting
|
||
(user_id, login_redirect, pinned_tags, query_gateway_child, show_child, create_by, create_time, update_by, update_time)
|
||
values
|
||
(#{userId}, #{loginRedirect}, '[]', #{queryGatewayChild}, #{showChild}, #{operator}, #{now}, #{operator}, #{now})
|
||
on duplicate key update
|
||
user_id = user_id
|
||
</insert>
|
||
|
||
<!--
|
||
锁定读固定标签。固定标签的写入是「读-改-写」,必须靠行锁把并发请求串行化:
|
||
没有 FOR UPDATE 时两个并发请求各自读到同一份旧值,后写的会把先写的覆盖掉——
|
||
这正是原先「前端全量 PUT」丢标签的服务端成因。
|
||
-->
|
||
<select id="selectPinnedTagsForUpdate" resultType="java.lang.String">
|
||
select pinned_tags
|
||
from sys_user_setting
|
||
where user_id = #{userId}
|
||
for update
|
||
</select>
|
||
|
||
<!--
|
||
整列覆盖写固定标签。与设置页的部分更新是两条独立写入路径:
|
||
若把 pinned_tags 并进 updateSetting,保存登录跳转等配置时会把固定标签一并覆盖掉。
|
||
**只在 addPinnedTag / removePinnedTag 的事务内调用**,且在 SELECT ... FOR UPDATE
|
||
之后落下,入参是锁内读出并改好的完整数组;对外不再有全量覆盖写的入口。
|
||
-->
|
||
<update id="updatePinnedTags" parameterType="com.fastbee.system.domain.SysUserSetting">
|
||
update sys_user_setting
|
||
set pinned_tags = #{pinnedTags},
|
||
update_by = #{updateBy},
|
||
update_time = #{updateTime}
|
||
where user_id = #{userId}
|
||
</update>
|
||
|
||
<!--
|
||
登录落地页候选项:取可作为页签的菜单,拼出完整路由 path。
|
||
过滤条件缺一不可:
|
||
menu_type='C' 只要页签,排除按钮(F)与目录(M)
|
||
status='0' 排除停用
|
||
visible='0' 排除菜单树里隐藏的
|
||
is_frame=1 排除外链(is_frame=0,component 为 http 地址),防止开放重定向
|
||
component 非空 路由必须有对应组件
|
||
p.menu_type='M' 只取一级页签。父级是页签(C)的是二级嵌套操作页
|
||
(如设备管理下的「设备分享列表」),不适合作为登录落地页。
|
||
路径拼接:父菜单 path 为空(顶级菜单 parent_id=0)时用 IFNULL 兜住,
|
||
再用 TRIM(TRAILING '/') 清掉尾部斜杠,避免 CONCAT 产出 '//system' 或 '/xxx/'。
|
||
排序按父目录 order_num 再按自身 order_num,与左侧菜单树顺序一致。
|
||
-->
|
||
<select id="selectLoginRedirectOptions" resultMap="LoginRedirectOptionResult">
|
||
select concat('/', trim(trailing '/' from concat(ifnull(p.path, ''), '/', m.path))) as path,
|
||
m.menu_name
|
||
from sys_menu m
|
||
left join sys_menu p on m.parent_id = p.menu_id
|
||
where m.menu_type = 'C'
|
||
and m.status = '0'
|
||
and m.visible = '0'
|
||
and m.is_frame = '1'
|
||
and m.component is not null
|
||
and m.component != ''
|
||
and m.path is not null
|
||
and m.path != ''
|
||
and p.menu_type = 'M'
|
||
order by p.order_num, m.order_num
|
||
</select>
|
||
|
||
</mapper>
|